@harperfast/agent 0.13.7-ink → 0.13.8-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 (3) hide show
  1. package/README.md +8 -8
  2. package/dist/agent.js +1371 -828
  3. package/package.json +2 -1
package/dist/agent.js CHANGED
@@ -1,11 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // agent.ts
4
- import chalk6 from "chalk";
5
-
6
- // agent/AgentManager.ts
7
- import { Agent as Agent3 } from "@openai/agents";
8
-
9
3
  // ink/emitters/listener.ts
10
4
  import React, { useCallback, useEffect } from "react";
11
5
  var listenersMap = {};
@@ -68,29 +62,215 @@ function curryEmitToListeners(name, value, trigger) {
68
62
  return (e) => emitToListeners(name, value, trigger ?? e);
69
63
  }
70
64
 
65
+ // utils/sessions/rateLimits.ts
66
+ var RateLimitTracker = class {
67
+ status = {
68
+ limitRequests: null,
69
+ limitTokens: null,
70
+ remainingRequests: null,
71
+ remainingTokens: null,
72
+ resetRequests: null,
73
+ resetTokens: null,
74
+ retryAfter: null
75
+ };
76
+ updateFromHeaders(headers) {
77
+ const normalizedHeaders = {};
78
+ for (const key of Object.keys(headers)) {
79
+ normalizedHeaders[key.toLowerCase()] = headers[key];
80
+ }
81
+ const getHeader = (name) => {
82
+ const value = normalizedHeaders[name.toLowerCase()];
83
+ return Array.isArray(value) ? value[0] : value;
84
+ };
85
+ const limitRequests = getHeader("x-ratelimit-limit-requests") || getHeader("anthropic-ratelimit-requests-limit") || getHeader("x-ratelimit-limit") || getHeader("ratelimit-limit") || getHeader("x-request-limit");
86
+ const limitTokens = getHeader("x-ratelimit-limit-tokens") || getHeader("anthropic-ratelimit-tokens-limit") || getHeader("x-token-limit");
87
+ const remainingRequests = getHeader("x-ratelimit-remaining-requests") || getHeader("anthropic-ratelimit-requests-remaining") || getHeader("x-ratelimit-remaining") || getHeader("ratelimit-remaining") || getHeader("x-request-remaining");
88
+ const remainingTokens = getHeader("x-ratelimit-remaining-tokens") || getHeader("anthropic-ratelimit-tokens-remaining") || getHeader("x-token-remaining");
89
+ const resetRequests = getHeader("x-ratelimit-reset-requests") || getHeader("anthropic-ratelimit-requests-reset") || getHeader("x-ratelimit-reset") || getHeader("ratelimit-reset") || getHeader("x-request-reset");
90
+ const resetTokens = getHeader("x-ratelimit-reset-tokens") || getHeader("anthropic-ratelimit-tokens-reset") || getHeader("x-token-reset");
91
+ const retryAfter = getHeader("retry-after");
92
+ if (limitRequests) {
93
+ this.status.limitRequests = parseInt(limitRequests, 10);
94
+ }
95
+ if (limitTokens) {
96
+ this.status.limitTokens = parseInt(limitTokens, 10);
97
+ }
98
+ if (remainingRequests) {
99
+ this.status.remainingRequests = parseInt(remainingRequests, 10);
100
+ }
101
+ if (remainingTokens) {
102
+ this.status.remainingTokens = parseInt(remainingTokens, 10);
103
+ }
104
+ if (resetRequests) {
105
+ this.status.resetRequests = resetRequests;
106
+ }
107
+ if (resetTokens) {
108
+ this.status.resetTokens = resetTokens;
109
+ }
110
+ if (retryAfter) {
111
+ this.status.retryAfter = parseInt(retryAfter, 10);
112
+ }
113
+ }
114
+ isApproachingLimit(threshold) {
115
+ const usage = this.getUsagePercentage();
116
+ return {
117
+ requests: usage.requests >= threshold,
118
+ tokens: usage.tokens >= threshold
119
+ };
120
+ }
121
+ getStatus() {
122
+ return { ...this.status };
123
+ }
124
+ getUsagePercentage() {
125
+ const requests = this.status.limitRequests && this.status.remainingRequests !== null ? 100 * (1 - this.status.remainingRequests / this.status.limitRequests) : 0;
126
+ const tokens = this.status.limitTokens && this.status.remainingTokens !== null ? 100 * (1 - this.status.remainingTokens / this.status.limitTokens) : 0;
127
+ return { requests, tokens };
128
+ }
129
+ reset() {
130
+ this.status = {
131
+ limitRequests: null,
132
+ limitTokens: null,
133
+ remainingRequests: null,
134
+ remainingTokens: null,
135
+ resetRequests: null,
136
+ resetTokens: null,
137
+ retryAfter: null
138
+ };
139
+ }
140
+ };
141
+ var rateLimitTracker = new RateLimitTracker();
142
+
143
+ // lifecycle/patchFetch.ts
144
+ var originalFetch = globalThis.fetch;
145
+ if (originalFetch) {
146
+ globalThis.fetch = async (...args) => {
147
+ const response = await originalFetch(...args);
148
+ const headers = {};
149
+ response.headers.forEach((value, key) => {
150
+ headers[key] = value;
151
+ });
152
+ const commonHeaders = [
153
+ "x-ratelimit-limit-requests",
154
+ "x-ratelimit-limit-tokens",
155
+ "x-ratelimit-remaining-requests",
156
+ "x-ratelimit-remaining-tokens",
157
+ "x-ratelimit-reset-requests",
158
+ "x-ratelimit-reset-tokens",
159
+ "anthropic-ratelimit-requests-limit",
160
+ "anthropic-ratelimit-requests-remaining",
161
+ "anthropic-ratelimit-requests-reset",
162
+ "anthropic-ratelimit-tokens-limit",
163
+ "anthropic-ratelimit-tokens-remaining",
164
+ "anthropic-ratelimit-tokens-reset",
165
+ "retry-after"
166
+ ];
167
+ for (const key of commonHeaders) {
168
+ if (!headers[key]) {
169
+ const val = response.headers.get(key);
170
+ if (val) {
171
+ headers[key] = val;
172
+ }
173
+ }
174
+ }
175
+ rateLimitTracker.updateFromHeaders(headers);
176
+ emitToListeners("SettingsUpdated", void 0);
177
+ const retryAfter = response.headers.get("retry-after");
178
+ if (retryAfter) {
179
+ const seconds = parseInt(retryAfter, 10);
180
+ if (!isNaN(seconds) && seconds > 0) {
181
+ emitToListeners("PushNewMessages", [{
182
+ type: "interrupted",
183
+ text: `Rate limit reached. Sleeping for ${seconds} seconds (Retry-After)...`,
184
+ version: 1
185
+ }]);
186
+ await new Promise((resolve2) => setTimeout(resolve2, seconds * 1e3));
187
+ }
188
+ }
189
+ return response;
190
+ };
191
+ }
192
+
193
+ // agent.ts
194
+ import chalk7 from "chalk";
195
+
196
+ // agent/AgentManager.ts
197
+ import { Agent as Agent3 } from "@openai/agents";
198
+
199
+ // ink/contexts/globalPlanContext.ts
200
+ var globalPlanContext = {
201
+ planDescription: "",
202
+ planItems: [],
203
+ progress: 0
204
+ };
205
+
71
206
  // lifecycle/defaultInstructions.ts
207
+ import { existsSync as existsSync2 } from "fs";
208
+ import { join as join2 } from "path";
209
+
210
+ // utils/files/harperApp.ts
72
211
  import { existsSync } from "fs";
73
- import { join } from "path";
212
+ import path, { dirname, join } from "path";
213
+ function findNearestHarperConfigDir(startDir) {
214
+ let dir = path.resolve(startDir);
215
+ let maxDepth = 10;
216
+ while (true) {
217
+ if (maxDepth-- <= 0) {
218
+ return null;
219
+ }
220
+ const candidate = join(dir, "config.yaml");
221
+ if (existsSync(candidate)) {
222
+ return dir;
223
+ }
224
+ const parent = dirname(dir);
225
+ if (parent === dir) {
226
+ return null;
227
+ }
228
+ dir = parent;
229
+ }
230
+ }
231
+ function resolveSessionPathConsideringHarper(raw, cwd, originalCwd) {
232
+ if (!raw) {
233
+ return null;
234
+ }
235
+ if (raw.startsWith("~") || path.isAbsolute(raw)) {
236
+ return raw;
237
+ }
238
+ const harperRoot = findNearestHarperConfigDir(cwd);
239
+ if (harperRoot) {
240
+ return path.resolve(harperRoot, raw);
241
+ }
242
+ return path.resolve(originalCwd, raw);
243
+ }
74
244
 
75
245
  // lifecycle/trackedState.ts
76
246
  var trackedState = {
247
+ originalCwd: process.cwd(),
77
248
  cwd: process.cwd(),
78
249
  model: "",
79
250
  compactionModel: "",
80
- sessionPath: null,
251
+ originalSessionPath: null,
252
+ get sessionPath() {
253
+ return resolveSessionPathConsideringHarper(trackedState.originalSessionPath, this.cwd, this.originalCwd);
254
+ },
255
+ set sessionPath(value) {
256
+ trackedState.originalSessionPath = value;
257
+ },
81
258
  useFlexTier: false,
259
+ currentTurn: 0,
82
260
  maxTurns: 30,
83
261
  maxCost: null,
84
262
  autoApproveCodeInterpreter: false,
85
263
  autoApprovePatches: false,
86
264
  autoApproveShell: false,
87
265
  monitorRateLimits: true,
88
- rateLimitThreshold: 80
266
+ rateLimitThreshold: 80,
267
+ autonomous: false,
268
+ prompt: null
89
269
  };
90
270
 
91
271
  // lifecycle/defaultInstructions.ts
92
272
  function defaultInstructions() {
93
- const harperAppExists = existsSync(join(trackedState.cwd, "config.yaml"));
273
+ const harperAppExists = existsSync2(join2(trackedState.cwd, "config.yaml"));
94
274
  const vibing = harperAppExists ? "updating" : "creating";
95
275
  return `You are working on ${vibing} a harper app with the user.`;
96
276
  }
@@ -102,8 +282,33 @@ import { openai } from "@ai-sdk/openai";
102
282
  import { aisdk } from "@openai/agents-extensions";
103
283
  import "@openai/agents-extensions/ai-sdk";
104
284
  import { createOllama, ollama } from "ollama-ai-provider-v2";
285
+
286
+ // agent/defaults.ts
287
+ var defaultModelToken = "default";
288
+ var defaultOpenAIModel = "gpt-5.2";
289
+ var defaultOpenAICompactionModel = "gpt-5-nano";
290
+ var defaultAnthropicModel = "claude-4-6-opus-latest";
291
+ var defaultAnthropicCompactionModel = "claude-4-5-haiku-latest";
292
+ var defaultGoogleModel = "gemini-3-pro";
293
+ var defaultGoogleCompactionModel = "gemini-2.5-flash-lite";
294
+ var defaultOllamaModel = "ollama-qwen3-coder:30b";
295
+ var defaultOllamaCompactionModel = "ollama-qwen2.5-coder";
296
+ var defaultModels = [
297
+ defaultOpenAIModel,
298
+ defaultAnthropicModel,
299
+ defaultGoogleModel,
300
+ defaultOllamaModel
301
+ ];
302
+ var defaultCompactionModels = [
303
+ defaultOpenAICompactionModel,
304
+ defaultAnthropicCompactionModel,
305
+ defaultGoogleCompactionModel,
306
+ defaultOllamaCompactionModel
307
+ ];
308
+
309
+ // lifecycle/getModel.ts
105
310
  function isOpenAIModel(modelName) {
106
- if (!modelName || modelName === "gpt-5.2") {
311
+ if (!modelName || modelName === defaultOpenAIModel) {
107
312
  return true;
108
313
  }
109
314
  return !modelName.startsWith("claude-") && !modelName.startsWith("gemini-") && !modelName.startsWith("ollama-");
@@ -154,13 +359,136 @@ function normalizeOllamaBaseUrl(baseUrl) {
154
359
  return urlObj.toString().replace(/\/$/, "");
155
360
  }
156
361
 
362
+ // lifecycle/handleExit.ts
363
+ import { getGlobalTraceProvider } from "@openai/agents";
364
+
365
+ // utils/shell/harperProcess.ts
366
+ import spawn from "cross-spawn";
367
+ import { execSync } from "child_process";
368
+ import { homedir } from "os";
369
+ import { join as join3 } from "path";
370
+ var HarperProcess = class {
371
+ childProcess = null;
372
+ externalPid = null;
373
+ logTailProcess = null;
374
+ logs = [];
375
+ httpPort = 9926;
376
+ get running() {
377
+ if (this.childProcess !== null) {
378
+ return true;
379
+ }
380
+ this.updateExternalStatus();
381
+ return this.externalPid !== null;
382
+ }
383
+ get startedInternally() {
384
+ return this.childProcess !== null;
385
+ }
386
+ updateExternalStatus() {
387
+ try {
388
+ const status = execSync("harper status", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
389
+ if (status.includes("status: running")) {
390
+ const pidMatch = status.match(/pid: (\d+)/);
391
+ if (pidMatch?.[1]) {
392
+ this.externalPid = parseInt(pidMatch[1], 10);
393
+ if (!this.logTailProcess) {
394
+ this.startTailingLogs();
395
+ }
396
+ return;
397
+ }
398
+ }
399
+ } catch {
400
+ }
401
+ this.externalPid = null;
402
+ this.stopTailingLogs();
403
+ }
404
+ startTailingLogs() {
405
+ const logPath = join3(homedir(), "hdb", "log", "hdb.log");
406
+ this.logTailProcess = spawn("tail", ["-f", logPath], {
407
+ stdio: ["ignore", "pipe", "pipe"]
408
+ });
409
+ this.logTailProcess.stdout?.on("data", (data) => {
410
+ const string = data.toString();
411
+ this.updatePortFromLogs(string);
412
+ this.logs.push(string);
413
+ });
414
+ this.logTailProcess.stderr?.on("data", (data) => {
415
+ this.logs.push(data.toString());
416
+ });
417
+ this.logTailProcess.on("exit", () => {
418
+ this.logTailProcess = null;
419
+ });
420
+ }
421
+ stopTailingLogs() {
422
+ if (this.logTailProcess) {
423
+ this.logTailProcess.kill();
424
+ this.logTailProcess = null;
425
+ }
426
+ }
427
+ updatePortFromLogs(string) {
428
+ if (string.includes("REST:") && string.includes("HTTP:")) {
429
+ this.httpPort = parseInt(string.split("HTTP:").pop().split(",")[0], 10);
430
+ }
431
+ }
432
+ start(directoryName) {
433
+ if (this.running) {
434
+ throw new Error("Harper process is already running.");
435
+ }
436
+ this.logs = [];
437
+ this.childProcess = spawn("harperdb", ["dev", "."], {
438
+ cwd: directoryName,
439
+ stdio: ["ignore", "pipe", "pipe"]
440
+ });
441
+ this.childProcess.stdout?.on("data", (data) => {
442
+ const string = data.toString();
443
+ this.updatePortFromLogs(string);
444
+ this.logs.push(string);
445
+ });
446
+ this.childProcess.stderr?.on("data", (data) => {
447
+ this.logs.push(data.toString());
448
+ });
449
+ this.childProcess.on("exit", () => {
450
+ this.childProcess = null;
451
+ });
452
+ }
453
+ stop() {
454
+ if (this.childProcess) {
455
+ this.childProcess.kill();
456
+ this.childProcess = null;
457
+ }
458
+ if (this.externalPid) {
459
+ try {
460
+ process.kill(this.externalPid);
461
+ } catch {
462
+ }
463
+ this.externalPid = null;
464
+ }
465
+ this.stopTailingLogs();
466
+ }
467
+ getAndClearLogs() {
468
+ const logs2 = this.logs;
469
+ this.logs = [];
470
+ return logs2.join("");
471
+ }
472
+ };
473
+ var harperProcess = new HarperProcess();
474
+
475
+ // lifecycle/handleExit.ts
476
+ async function handleExit() {
477
+ if (harperProcess.startedInternally) {
478
+ harperProcess.stop();
479
+ }
480
+ await getGlobalTraceProvider().forceFlush();
481
+ emitToListeners("ExitUI", void 0);
482
+ process.exit(0);
483
+ }
484
+
157
485
  // lifecycle/readAgentsMD.ts
158
- import { existsSync as existsSync2, readFileSync } from "fs";
159
- import { join as join2 } from "path";
486
+ import { existsSync as existsSync3, readFileSync } from "fs";
487
+ import { join as join4 } from "path";
160
488
  function readAgentsMD() {
161
- const agentsMdExists = existsSync2(join2(trackedState.cwd, "AGENTS.md"));
489
+ const agentsMdExists = existsSync3(join4(trackedState.cwd, "AGENTS.md"));
162
490
  if (agentsMdExists) {
163
- return readFileSync(join2(trackedState.cwd, "AGENTS.md"), "utf8");
491
+ return readFileSync(join4(trackedState.cwd, "AGENTS.md"), "utf8");
164
492
  }
165
493
  }
166
494
 
@@ -460,14 +788,14 @@ function getEnv(newKey, oldKey) {
460
788
  import { tool as tool10 } from "@openai/agents";
461
789
  import { readdirSync, readFileSync as readFileSync2 } from "fs";
462
790
  import { createRequire } from "module";
463
- import { dirname, join as join3 } from "path";
791
+ import { dirname as dirname2, join as join5 } from "path";
464
792
  import { z as z10 } from "zod";
465
- var createHarper = dirname(createRequire(import.meta.url).resolve("create-harper"));
466
- var agentsMarkdown = join3(
793
+ var createHarper = dirname2(createRequire(import.meta.url).resolve("create-harper"));
794
+ var agentsMarkdown = join5(
467
795
  createHarper,
468
796
  "AGENTS.md"
469
797
  );
470
- var skillsDir = join3(
798
+ var skillsDir = join5(
471
799
  createHarper,
472
800
  "template-vanilla",
473
801
  "skills"
@@ -504,7 +832,7 @@ async function execute10({ skill }) {
504
832
  return "No skills found.";
505
833
  }
506
834
  try {
507
- const filePath = join3(skillsDir, `${skill}.md`);
835
+ const filePath = join5(skillsDir, `${skill}.md`);
508
836
  const content = readFileSync2(filePath, "utf8");
509
837
  agentManager.session?.addSkillRead?.(skill);
510
838
  return content;
@@ -515,9 +843,9 @@ async function execute10({ skill }) {
515
843
 
516
844
  // tools/files/workspaceEditor.ts
517
845
  import { applyDiff } from "@openai/agents";
518
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
846
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
519
847
  import { mkdir, rm, writeFile } from "fs/promises";
520
- import path3 from "path";
848
+ import path4 from "path";
521
849
 
522
850
  // utils/files/normalizeDiff.ts
523
851
  function normalizeDiff(diff) {
@@ -534,17 +862,17 @@ function normalizeDiff(diff) {
534
862
  }
535
863
 
536
864
  // utils/files/paths.ts
537
- import path2 from "path";
865
+ import path3 from "path";
538
866
 
539
867
  // utils/files/aiignore.ts
540
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
541
- import path from "path";
868
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
869
+ import path2 from "path";
542
870
 
543
871
  // 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");
872
+ import { appendFileSync, existsSync as existsSync4, mkdirSync } from "fs";
873
+ import { homedir as homedir2 } from "os";
874
+ import { dirname as dirname3, join as join6 } from "path";
875
+ var ERROR_LOG_PATH = join6(homedir2(), ".harper", "harper-agent-errors");
548
876
  function logError(error) {
549
877
  const message = error instanceof Error ? error.stack || error.message : String(error);
550
878
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -552,8 +880,8 @@ function logError(error) {
552
880
 
553
881
  `;
554
882
  try {
555
- const dir = dirname2(ERROR_LOG_PATH);
556
- if (!existsSync3(dir)) {
883
+ const dir = dirname3(ERROR_LOG_PATH);
884
+ if (!existsSync4(dir)) {
557
885
  mkdirSync(dir, { recursive: true });
558
886
  }
559
887
  appendFileSync(ERROR_LOG_PATH, logEntry, "utf8");
@@ -577,8 +905,8 @@ function setupGlobalErrorHandlers() {
577
905
  // utils/files/aiignore.ts
578
906
  var ignorePatterns = [];
579
907
  function loadAiIgnore() {
580
- const ignorePath = path.join(trackedState.cwd, ".aiignore");
581
- if (existsSync4(ignorePath)) {
908
+ const ignorePath = path2.join(trackedState.cwd, ".aiignore");
909
+ if (existsSync5(ignorePath)) {
582
910
  try {
583
911
  const content = readFileSync3(ignorePath, "utf8");
584
912
  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);
@@ -592,39 +920,39 @@ function loadAiIgnore() {
592
920
  }
593
921
  }
594
922
  function isIgnored(filePath) {
595
- const directParts = path.normalize(filePath).split(path.sep);
923
+ const directParts = path2.normalize(filePath).split(path2.sep);
596
924
  if (directParts.includes(".aiignore")) {
597
925
  return true;
598
926
  }
599
- const absolutePath = path.resolve(trackedState.cwd, filePath);
600
- const relativePath = path.relative(trackedState.cwd, absolutePath);
601
- if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
927
+ const absolutePath = path2.resolve(trackedState.cwd, filePath);
928
+ const relativePath = path2.relative(trackedState.cwd, absolutePath);
929
+ if (relativePath.startsWith("..") || path2.isAbsolute(relativePath)) {
602
930
  return false;
603
931
  }
604
- const normalizedPath2 = path.normalize(relativePath);
605
- const parts = normalizedPath2.split(path.sep);
932
+ const normalizedPath2 = path2.normalize(relativePath);
933
+ const parts = normalizedPath2.split(path2.sep);
606
934
  loadAiIgnore();
607
935
  if (ignorePatterns.length === 0) {
608
936
  return false;
609
937
  }
610
938
  return ignorePatterns.some((pattern) => {
611
939
  if (pattern.startsWith("/")) {
612
- const normalizedPattern = path.normalize(pattern.substring(1));
613
- return normalizedPath2 === normalizedPattern || normalizedPath2.startsWith(normalizedPattern + path.sep);
940
+ const normalizedPattern = path2.normalize(pattern.substring(1));
941
+ return normalizedPath2 === normalizedPattern || normalizedPath2.startsWith(normalizedPattern + path2.sep);
614
942
  }
615
943
  return parts.some((part, index) => {
616
- const subPath = parts.slice(index).join(path.sep);
617
- const normalizedPattern = path.normalize(pattern);
618
- return subPath === normalizedPattern || subPath.startsWith(normalizedPattern + path.sep);
944
+ const subPath = parts.slice(index).join(path2.sep);
945
+ const normalizedPattern = path2.normalize(pattern);
946
+ return subPath === normalizedPattern || subPath.startsWith(normalizedPattern + path2.sep);
619
947
  });
620
948
  });
621
949
  }
622
950
 
623
951
  // utils/files/paths.ts
624
952
  function resolvePath(root, relativePath) {
625
- const resolved = path2.resolve(root, relativePath);
626
- const relative = path2.relative(root, resolved);
627
- if (relative.startsWith("..") || path2.isAbsolute(relative)) {
953
+ const resolved = path3.resolve(root, relativePath);
954
+ const relative = path3.relative(root, resolved);
955
+ if (relative.startsWith("..") || path3.isAbsolute(relative)) {
628
956
  throw new Error(`Operation outside workspace: ${relativePath}`);
629
957
  }
630
958
  if (isIgnored(resolved)) {
@@ -642,7 +970,7 @@ var WorkspaceEditor = class {
642
970
  async createFile(operation) {
643
971
  try {
644
972
  const targetPath = resolvePath(this.root(), operation.path);
645
- await mkdir(path3.dirname(targetPath), { recursive: true });
973
+ await mkdir(path4.dirname(targetPath), { recursive: true });
646
974
  const normalizedDiff = normalizeDiff(operation.diff);
647
975
  const content = applyDiff("", normalizedDiff, "create");
648
976
  await writeFile(targetPath, content, "utf8");
@@ -654,7 +982,7 @@ var WorkspaceEditor = class {
654
982
  async updateFile(operation) {
655
983
  try {
656
984
  const targetPath = resolvePath(this.root(), operation.path);
657
- if (!existsSync5(targetPath)) {
985
+ if (!existsSync6(targetPath)) {
658
986
  return { status: "failed", output: "Error: file not found at path " + targetPath };
659
987
  }
660
988
  const original = readFileSync4(targetPath, "utf8");
@@ -669,7 +997,7 @@ var WorkspaceEditor = class {
669
997
  async deleteFile(operation) {
670
998
  try {
671
999
  const targetPath = resolvePath(this.root(), operation.path);
672
- if (!existsSync5(targetPath)) {
1000
+ if (!existsSync6(targetPath)) {
673
1001
  return { status: "failed", output: "Error: file not found at path " + targetPath };
674
1002
  }
675
1003
  await rm(targetPath, { force: true });
@@ -910,7 +1238,7 @@ var findTool = tool14({
910
1238
  // tools/files/readDirTool.ts
911
1239
  import { tool as tool15 } from "@openai/agents";
912
1240
  import { readdir } from "fs/promises";
913
- import path4 from "path";
1241
+ import path5 from "path";
914
1242
  import { z as z15 } from "zod";
915
1243
  var ToolParameters14 = z15.object({
916
1244
  directoryName: z15.string().describe("The name of the directory to read.")
@@ -923,7 +1251,7 @@ var readDirTool = tool15({
923
1251
  try {
924
1252
  const resolvedPath = resolvePath(trackedState.cwd, directoryName);
925
1253
  const files = await readdir(resolvedPath, "utf8");
926
- return files.filter((file) => !isIgnored(path4.join(resolvedPath, file)));
1254
+ return files.filter((file) => !isIgnored(path5.join(resolvedPath, file)));
927
1255
  } catch (error) {
928
1256
  return `Error reading directory: ${error}`;
929
1257
  }
@@ -979,7 +1307,7 @@ var readFileTool = tool16({
979
1307
  import { tool as tool17 } from "@openai/agents";
980
1308
  import { exec } from "child_process";
981
1309
  import { unlink, writeFile as writeFile2 } from "fs/promises";
982
- import path5 from "path";
1310
+ import path6 from "path";
983
1311
  import { promisify as promisify3 } from "util";
984
1312
  import { z as z17 } from "zod";
985
1313
  var execAsync = promisify3(exec);
@@ -1022,7 +1350,7 @@ async function needsApproval2(runContext, parameters, callId) {
1022
1350
  async function execute14({ code, language }) {
1023
1351
  const extension = language === "javascript" ? "js" : "py";
1024
1352
  const interpreter = language === "javascript" ? "node" : "python3";
1025
- const tempFile = path5.join(trackedState.cwd, `.temp_code_${Date.now()}.${extension}`);
1353
+ const tempFile = path6.join(trackedState.cwd, `.temp_code_${Date.now()}.${extension}`);
1026
1354
  try {
1027
1355
  await writeFile2(tempFile, code, "utf8");
1028
1356
  const { stdout, stderr } = await execAsync(`${interpreter} ${tempFile}`);
@@ -1040,45 +1368,82 @@ STDERR: ${error.stderr || ""}`;
1040
1368
  }
1041
1369
  }
1042
1370
 
1043
- // tools/general/setInterpreterAutoApproveTool.ts
1371
+ // tools/general/collectFeedbackTool.ts
1044
1372
  import { tool as tool18 } from "@openai/agents";
1373
+ import { exec as exec2 } from "child_process";
1374
+ import { promisify as promisify4 } from "util";
1045
1375
  import { z as z18 } from "zod";
1376
+ var execAsync2 = promisify4(exec2);
1377
+ var ToolParameters16 = z18.object({
1378
+ feedbackSummary: z18.string().describe("A brief summary of the feedback, used as the discussion title."),
1379
+ feedbackDetails: z18.string().describe("Detailed feedback from the user or agent observation."),
1380
+ recap: z18.string().describe(
1381
+ "A sanitized recap of what the agent and user did together and if it was successful. No sensitive information."
1382
+ )
1383
+ });
1384
+ async function execute15({ feedbackSummary, feedbackDetails, recap }) {
1385
+ const title = feedbackSummary;
1386
+ const body = `${feedbackDetails}
1387
+
1388
+ ${recap}`;
1389
+ const encodedTitle = encodeURIComponent(title);
1390
+ const encodedBody = encodeURIComponent(body);
1391
+ const url = `https://github.com/HarperFast/harper-agent/discussions/new?category=usage-feedback&title=${encodedTitle}&body=${encodedBody}`;
1392
+ try {
1393
+ const command = process.platform === "win32" ? `start "" "${url.replace(/&/g, "^&")}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
1394
+ await execAsync2(command);
1395
+ return `Successfully opened feedback URL: ${url}`;
1396
+ } catch (error) {
1397
+ return `Error opening feedback URL: ${error}`;
1398
+ }
1399
+ }
1400
+ var collectFeedbackTool = tool18({
1401
+ name: "collect_feedback",
1402
+ description: "Collects feedback from the user by opening a pre-populated GitHub Discussion. Use this if the user lets us know we did something well, or if the user seems frustrated, or wants to report a bug. You MUST ask the user if they are willing to share these results before calling this tool.",
1403
+ parameters: ToolParameters16,
1404
+ execute: execute15
1405
+ });
1406
+
1407
+ // tools/general/setInterpreterAutoApproveTool.ts
1408
+ import { tool as tool19 } from "@openai/agents";
1409
+ import { z as z19 } from "zod";
1046
1410
 
1047
1411
  // utils/files/updateEnv.ts
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";
1412
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync } from "fs";
1413
+ import { homedir as homedir3 } from "os";
1414
+ import { dirname as dirname4, join as join7 } from "path";
1051
1415
  function updateEnv(key, value) {
1052
- process.env[key] = value;
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)) {
1416
+ const normalizedValue = key === "HARPER_AGENT_MODEL" && defaultModels.includes(value) ? defaultModelToken : key === "HARPER_AGENT_COMPACTION_MODEL" && defaultCompactionModels.includes(value) ? defaultModelToken : value;
1417
+ process.env[key] = normalizedValue;
1418
+ const topLevelEnvPath = join7(homedir3(), ".harper", "harper-agent-env");
1419
+ const localEnvPath = join7(trackedState.cwd, ".env");
1420
+ const envPath = existsSync7(topLevelEnvPath) || !existsSync7(localEnvPath) ? topLevelEnvPath : localEnvPath;
1421
+ const dir = dirname4(envPath);
1422
+ if (!existsSync7(dir)) {
1058
1423
  mkdirSync2(dir, { recursive: true });
1059
1424
  }
1060
1425
  let envContent = "";
1061
- if (existsSync6(envPath)) {
1426
+ if (existsSync7(envPath)) {
1062
1427
  envContent = readFileSync5(envPath, "utf8");
1063
1428
  }
1064
1429
  const regex = new RegExp(`^${key}=.*`, "m");
1065
1430
  if (regex.test(envContent)) {
1066
- envContent = envContent.replace(regex, `${key}=${value}`);
1431
+ envContent = envContent.replace(regex, `${key}=${normalizedValue}`);
1067
1432
  } else {
1068
1433
  if (envContent && !envContent.endsWith("\n")) {
1069
1434
  envContent += "\n";
1070
1435
  }
1071
- envContent += `${key}=${value}
1436
+ envContent += `${key}=${normalizedValue}
1072
1437
  `;
1073
1438
  }
1074
1439
  writeFileSync(envPath, envContent);
1075
1440
  }
1076
1441
 
1077
1442
  // tools/general/setInterpreterAutoApproveTool.ts
1078
- var SetInterpreterAutoApproveParameters = z18.object({
1079
- autoApprove: z18.boolean()
1443
+ var SetInterpreterAutoApproveParameters = z19.object({
1444
+ autoApprove: z19.boolean()
1080
1445
  });
1081
- var setInterpreterAutoApproveTool = tool18({
1446
+ var setInterpreterAutoApproveTool = tool19({
1082
1447
  name: "set_interpreter_auto_approve",
1083
1448
  description: "Enable or disable automatic approval for code interpreter by setting HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER=1 or 0 in .env and current process.",
1084
1449
  parameters: SetInterpreterAutoApproveParameters,
@@ -1103,12 +1468,12 @@ var setInterpreterAutoApproveTool = tool18({
1103
1468
  });
1104
1469
 
1105
1470
  // tools/general/setPatchAutoApproveTool.ts
1106
- import { tool as tool19 } from "@openai/agents";
1107
- import { z as z19 } from "zod";
1108
- var SetPatchAutoApproveParameters = z19.object({
1109
- autoApprove: z19.boolean()
1471
+ import { tool as tool20 } from "@openai/agents";
1472
+ import { z as z20 } from "zod";
1473
+ var SetPatchAutoApproveParameters = z20.object({
1474
+ autoApprove: z20.boolean()
1110
1475
  });
1111
- var setPatchAutoApproveTool = tool19({
1476
+ var setPatchAutoApproveTool = tool20({
1112
1477
  name: "set_patch_auto_approve",
1113
1478
  description: "Enable or disable automatic approval for patch commands by setting HARPER_AGENT_AUTO_APPROVE_PATCHES=1 or 0 in .env and current process.",
1114
1479
  parameters: SetPatchAutoApproveParameters,
@@ -1133,12 +1498,12 @@ var setPatchAutoApproveTool = tool19({
1133
1498
  });
1134
1499
 
1135
1500
  // tools/general/setShellAutoApproveTool.ts
1136
- import { tool as tool20 } from "@openai/agents";
1137
- import { z as z20 } from "zod";
1138
- var SetShellAutoApproveParameters = z20.object({
1139
- autoApprove: z20.boolean()
1501
+ import { tool as tool21 } from "@openai/agents";
1502
+ import { z as z21 } from "zod";
1503
+ var SetShellAutoApproveParameters = z21.object({
1504
+ autoApprove: z21.boolean()
1140
1505
  });
1141
- var setShellAutoApproveTool = tool20({
1506
+ var setShellAutoApproveTool = tool21({
1142
1507
  name: "set_shell_auto_approve",
1143
1508
  description: "Enable or disable automatic approval for shell commands by setting HARPER_AGENT_AUTO_APPROVE_SHELL=1 or 0 in .env and current process.",
1144
1509
  parameters: SetShellAutoApproveParameters,
@@ -1163,8 +1528,8 @@ var setShellAutoApproveTool = tool20({
1163
1528
  });
1164
1529
 
1165
1530
  // tools/general/shellTool.ts
1166
- import { tool as tool21 } from "@openai/agents";
1167
- import { z as z21 } from "zod";
1531
+ import { tool as tool22 } from "@openai/agents";
1532
+ import { z as z22 } from "zod";
1168
1533
 
1169
1534
  // utils/files/mentionsIgnoredPath.ts
1170
1535
  function mentionsIgnoredPath(command) {
@@ -1218,8 +1583,8 @@ function isRiskyCommand(command) {
1218
1583
  }
1219
1584
 
1220
1585
  // utils/shell/LocalShell.ts
1221
- import { exec as exec2 } from "child_process";
1222
- import { promisify as promisify4 } from "util";
1586
+ import { exec as exec3 } from "child_process";
1587
+ import { promisify as promisify5 } from "util";
1223
1588
 
1224
1589
  // ink/contexts/ShellContext.tsx
1225
1590
  import { createContext, useContext, useMemo, useState } from "react";
@@ -1228,7 +1593,7 @@ var ShellContext = createContext(void 0);
1228
1593
  var commandId = 0;
1229
1594
 
1230
1595
  // utils/shell/LocalShell.ts
1231
- var execAsync2 = promisify4(exec2);
1596
+ var execAsync3 = promisify5(exec3);
1232
1597
  var LocalShell = class {
1233
1598
  defaultTimeoutMs;
1234
1599
  constructor(options) {
@@ -1263,7 +1628,7 @@ var LocalShell = class {
1263
1628
  running: true
1264
1629
  });
1265
1630
  try {
1266
- const { stdout: localStdout, stderr: localStderr } = await execAsync2(
1631
+ const { stdout: localStdout, stderr: localStderr } = await execAsync3(
1267
1632
  command,
1268
1633
  {
1269
1634
  cwd: trackedState.cwd,
@@ -1311,11 +1676,11 @@ var LocalShell = class {
1311
1676
  };
1312
1677
 
1313
1678
  // tools/general/shellTool.ts
1314
- var ShellParameters = z21.object({
1315
- commands: z21.array(z21.string()).describe("The commands to execute.")
1679
+ var ShellParameters = z22.object({
1680
+ commands: z22.array(z22.string()).describe("The commands to execute.")
1316
1681
  });
1317
1682
  var shell = new LocalShell();
1318
- var shellTool = tool21({
1683
+ var shellTool = tool22({
1319
1684
  name: "shell",
1320
1685
  description: "Executes shell commands. Only use when we do not have a better tool.",
1321
1686
  parameters: ShellParameters,
@@ -1364,15 +1729,15 @@ TIMEOUT`;
1364
1729
  });
1365
1730
 
1366
1731
  // tools/git/gitAddTool.ts
1367
- import { tool as tool22 } from "@openai/agents";
1732
+ import { tool as tool23 } from "@openai/agents";
1368
1733
  import { execFile as execFile3 } from "child_process";
1369
- import { promisify as promisify5 } from "util";
1370
- import { z as z22 } from "zod";
1371
- var execFileAsync3 = promisify5(execFile3);
1372
- var GitAddParameters = z22.object({
1373
- files: z22.array(z22.string()).describe("The files to add. If not provided, all changes will be added.")
1734
+ import { promisify as promisify6 } from "util";
1735
+ import { z as z23 } from "zod";
1736
+ var execFileAsync3 = promisify6(execFile3);
1737
+ var GitAddParameters = z23.object({
1738
+ files: z23.array(z23.string()).describe("The files to add. If not provided, all changes will be added.")
1374
1739
  });
1375
- var gitAddTool = tool22({
1740
+ var gitAddTool = tool23({
1376
1741
  name: "git_add",
1377
1742
  description: "Add file contents to the index.",
1378
1743
  parameters: GitAddParameters,
@@ -1393,16 +1758,16 @@ var gitAddTool = tool22({
1393
1758
  });
1394
1759
 
1395
1760
  // tools/git/gitBranchTool.ts
1396
- import { tool as tool23 } from "@openai/agents";
1761
+ import { tool as tool24 } from "@openai/agents";
1397
1762
  import { execFile as execFile4 } from "child_process";
1398
- import { promisify as promisify6 } from "util";
1399
- import { z as z23 } from "zod";
1400
- var execFileAsync4 = promisify6(execFile4);
1401
- var GitBranchParameters = z23.object({
1402
- branchName: z23.string().describe("The name of the branch to create or switch to."),
1403
- create: z23.boolean().optional().default(false).describe("Whether to create a new branch.")
1763
+ import { promisify as promisify7 } from "util";
1764
+ import { z as z24 } from "zod";
1765
+ var execFileAsync4 = promisify7(execFile4);
1766
+ var GitBranchParameters = z24.object({
1767
+ branchName: z24.string().describe("The name of the branch to create or switch to."),
1768
+ create: z24.boolean().optional().default(false).describe("Whether to create a new branch.")
1404
1769
  });
1405
- var gitBranchTool = tool23({
1770
+ var gitBranchTool = tool24({
1406
1771
  name: "git_branch",
1407
1772
  description: "Create or switch to a git branch.",
1408
1773
  parameters: GitBranchParameters,
@@ -1419,18 +1784,18 @@ var gitBranchTool = tool23({
1419
1784
  });
1420
1785
 
1421
1786
  // tools/git/gitCommitTool.ts
1422
- import { tool as tool24 } from "@openai/agents";
1787
+ import { tool as tool25 } from "@openai/agents";
1423
1788
  import { execFile as execFile5 } from "child_process";
1424
- import { promisify as promisify7 } from "util";
1425
- import { z as z24 } from "zod";
1426
- var execFileAsync5 = promisify7(execFile5);
1427
- var GitCommitParameters = z24.object({
1428
- message: z24.string().describe("The commit message."),
1429
- addAll: z24.boolean().optional().default(false).describe(
1789
+ import { promisify as promisify8 } from "util";
1790
+ import { z as z25 } from "zod";
1791
+ var execFileAsync5 = promisify8(execFile5);
1792
+ var GitCommitParameters = z25.object({
1793
+ message: z25.string().describe("The commit message."),
1794
+ addAll: z25.boolean().optional().default(false).describe(
1430
1795
  "Whether to add all changes before committing (git commit -am)."
1431
1796
  )
1432
1797
  });
1433
- var gitCommitTool = tool24({
1798
+ var gitCommitTool = tool25({
1434
1799
  name: "git_commit",
1435
1800
  description: "Commit changes to the repository.",
1436
1801
  parameters: GitCommitParameters,
@@ -1446,16 +1811,16 @@ var gitCommitTool = tool24({
1446
1811
  });
1447
1812
 
1448
1813
  // tools/git/gitLogTool.ts
1449
- import { tool as tool25 } from "@openai/agents";
1814
+ import { tool as tool26 } from "@openai/agents";
1450
1815
  import { execFile as execFile6 } from "child_process";
1451
- import { promisify as promisify8 } from "util";
1452
- import { z as z25 } from "zod";
1453
- var execFileAsync6 = promisify8(execFile6);
1454
- var GitLogParameters = z25.object({
1455
- count: z25.number().optional().default(10).describe("Number of commits to show."),
1456
- oneline: z25.boolean().optional().default(true).describe("Whether to show log in oneline format.")
1816
+ import { promisify as promisify9 } from "util";
1817
+ import { z as z26 } from "zod";
1818
+ var execFileAsync6 = promisify9(execFile6);
1819
+ var GitLogParameters = z26.object({
1820
+ count: z26.number().optional().default(10).describe("Number of commits to show."),
1821
+ oneline: z26.boolean().optional().default(true).describe("Whether to show log in oneline format.")
1457
1822
  });
1458
- var gitLogTool = tool25({
1823
+ var gitLogTool = tool26({
1459
1824
  name: "git_log",
1460
1825
  description: "Show commit logs.",
1461
1826
  parameters: GitLogParameters,
@@ -1474,17 +1839,17 @@ var gitLogTool = tool25({
1474
1839
  });
1475
1840
 
1476
1841
  // tools/git/gitStashTool.ts
1477
- import { tool as tool26 } from "@openai/agents";
1842
+ import { tool as tool27 } from "@openai/agents";
1478
1843
  import { execFile as execFile7 } from "child_process";
1479
- import { promisify as promisify9 } from "util";
1480
- import { z as z26 } from "zod";
1481
- var execFileAsync7 = promisify9(execFile7);
1844
+ import { promisify as promisify10 } from "util";
1845
+ import { z as z27 } from "zod";
1846
+ var execFileAsync7 = promisify10(execFile7);
1482
1847
  var allowedActions = ["push", "pop", "apply", "list"];
1483
- var GitStashParameters = z26.object({
1484
- action: z26.string().describe("The stash action to perform: " + allowedActions.join(", ")),
1485
- message: z26.string().describe("A message for the stash change.")
1848
+ var GitStashParameters = z27.object({
1849
+ action: z27.string().describe("The stash action to perform: " + allowedActions.join(", ")),
1850
+ message: z27.string().describe("A message for the stash change.")
1486
1851
  });
1487
- var gitStashTool = tool26({
1852
+ var gitStashTool = tool27({
1488
1853
  name: "git_stash",
1489
1854
  description: "Stash changes or apply a stash.",
1490
1855
  parameters: GitStashParameters,
@@ -1506,15 +1871,15 @@ var gitStashTool = tool26({
1506
1871
  });
1507
1872
 
1508
1873
  // tools/git/gitStatusTool.ts
1509
- import { tool as tool27 } from "@openai/agents";
1874
+ import { tool as tool28 } from "@openai/agents";
1510
1875
  import { execFile as execFile8 } from "child_process";
1511
- import { promisify as promisify10 } from "util";
1512
- import { z as z27 } from "zod";
1513
- var execFileAsync8 = promisify10(execFile8);
1514
- var GitStatusParameters = z27.object({
1515
- short: z27.boolean().optional().default(false).describe("Whether to show the status in short format.")
1876
+ import { promisify as promisify11 } from "util";
1877
+ import { z as z28 } from "zod";
1878
+ var execFileAsync8 = promisify11(execFile8);
1879
+ var GitStatusParameters = z28.object({
1880
+ short: z28.boolean().optional().default(false).describe("Whether to show the status in short format.")
1516
1881
  });
1517
- var gitStatusTool = tool27({
1882
+ var gitStatusTool = tool28({
1518
1883
  name: "git_status",
1519
1884
  description: "Show the working tree status.",
1520
1885
  parameters: GitStatusParameters,
@@ -1533,17 +1898,17 @@ var gitStatusTool = tool27({
1533
1898
  });
1534
1899
 
1535
1900
  // tools/git/gitWorkspaceTool.ts
1536
- import { tool as tool28 } from "@openai/agents";
1901
+ import { tool as tool29 } from "@openai/agents";
1537
1902
  import { execFile as execFile9 } from "child_process";
1538
- import { promisify as promisify11 } from "util";
1539
- import { z as z28 } from "zod";
1540
- var execFileAsync9 = promisify11(execFile9);
1541
- var GitWorkspaceParameters = z28.object({
1542
- path: z28.string().describe("The path where the new workspace (worktree) should be created."),
1543
- branchName: z28.string().describe("The name of the branch to use in the new workspace."),
1544
- createBranch: z28.boolean().optional().default(false).describe("Whether to create a new branch for this workspace.")
1903
+ import { promisify as promisify12 } from "util";
1904
+ import { z as z29 } from "zod";
1905
+ var execFileAsync9 = promisify12(execFile9);
1906
+ var GitWorkspaceParameters = z29.object({
1907
+ path: z29.string().describe("The path where the new workspace (worktree) should be created."),
1908
+ branchName: z29.string().describe("The name of the branch to use in the new workspace."),
1909
+ createBranch: z29.boolean().optional().default(false).describe("Whether to create a new branch for this workspace.")
1545
1910
  });
1546
- var gitWorkspaceTool = tool28({
1911
+ var gitWorkspaceTool = tool29({
1547
1912
  name: "git_workspace",
1548
1913
  description: "Create a new workspace (git worktree) for parallel work.",
1549
1914
  parameters: GitWorkspaceParameters,
@@ -1560,125 +1925,13 @@ var gitWorkspaceTool = tool28({
1560
1925
  });
1561
1926
 
1562
1927
  // tools/harper/checkHarperStatusTool.ts
1563
- import { tool as tool29 } from "@openai/agents";
1564
- import { z as z29 } from "zod";
1565
-
1566
- // utils/shell/harperProcess.ts
1567
- import spawn from "cross-spawn";
1568
- import { execSync } from "child_process";
1569
- import { homedir as homedir3 } from "os";
1570
- import { join as join6 } from "path";
1571
- var HarperProcess = class {
1572
- childProcess = null;
1573
- externalPid = null;
1574
- logTailProcess = null;
1575
- logs = [];
1576
- httpPort = 9926;
1577
- get running() {
1578
- if (this.childProcess !== null) {
1579
- return true;
1580
- }
1581
- this.updateExternalStatus();
1582
- return this.externalPid !== null;
1583
- }
1584
- get startedInternally() {
1585
- return this.childProcess !== null;
1586
- }
1587
- updateExternalStatus() {
1588
- try {
1589
- const status = execSync("harper status", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
1590
- if (status.includes("status: running")) {
1591
- const pidMatch = status.match(/pid: (\d+)/);
1592
- if (pidMatch?.[1]) {
1593
- this.externalPid = parseInt(pidMatch[1], 10);
1594
- if (!this.logTailProcess) {
1595
- this.startTailingLogs();
1596
- }
1597
- return;
1598
- }
1599
- }
1600
- } catch {
1601
- }
1602
- this.externalPid = null;
1603
- this.stopTailingLogs();
1604
- }
1605
- startTailingLogs() {
1606
- const logPath = join6(homedir3(), "hdb", "log", "hdb.log");
1607
- this.logTailProcess = spawn("tail", ["-f", logPath], {
1608
- stdio: ["ignore", "pipe", "pipe"]
1609
- });
1610
- this.logTailProcess.stdout?.on("data", (data) => {
1611
- const string = data.toString();
1612
- this.updatePortFromLogs(string);
1613
- this.logs.push(string);
1614
- });
1615
- this.logTailProcess.stderr?.on("data", (data) => {
1616
- this.logs.push(data.toString());
1617
- });
1618
- this.logTailProcess.on("exit", () => {
1619
- this.logTailProcess = null;
1620
- });
1621
- }
1622
- stopTailingLogs() {
1623
- if (this.logTailProcess) {
1624
- this.logTailProcess.kill();
1625
- this.logTailProcess = null;
1626
- }
1627
- }
1628
- updatePortFromLogs(string) {
1629
- if (string.includes("REST:") && string.includes("HTTP:")) {
1630
- this.httpPort = parseInt(string.split("HTTP:").pop().split(",")[0], 10);
1631
- }
1632
- }
1633
- start(directoryName) {
1634
- if (this.running) {
1635
- throw new Error("Harper process is already running.");
1636
- }
1637
- this.logs = [];
1638
- this.childProcess = spawn("harperdb", ["dev", "."], {
1639
- cwd: directoryName,
1640
- stdio: ["ignore", "pipe", "pipe"]
1641
- });
1642
- this.childProcess.stdout?.on("data", (data) => {
1643
- const string = data.toString();
1644
- this.updatePortFromLogs(string);
1645
- this.logs.push(string);
1646
- });
1647
- this.childProcess.stderr?.on("data", (data) => {
1648
- this.logs.push(data.toString());
1649
- });
1650
- this.childProcess.on("exit", () => {
1651
- this.childProcess = null;
1652
- });
1653
- }
1654
- stop() {
1655
- if (this.childProcess) {
1656
- this.childProcess.kill();
1657
- this.childProcess = null;
1658
- }
1659
- if (this.externalPid) {
1660
- try {
1661
- process.kill(this.externalPid);
1662
- } catch {
1663
- }
1664
- this.externalPid = null;
1665
- }
1666
- this.stopTailingLogs();
1667
- }
1668
- getAndClearLogs() {
1669
- const logs2 = this.logs;
1670
- this.logs = [];
1671
- return logs2.join("");
1672
- }
1673
- };
1674
- var harperProcess = new HarperProcess();
1675
-
1676
- // tools/harper/checkHarperStatusTool.ts
1677
- var ToolParameters16 = z29.object({});
1678
- var checkHarperStatusTool = tool29({
1928
+ import { tool as tool30 } from "@openai/agents";
1929
+ import { z as z30 } from "zod";
1930
+ var ToolParameters17 = z30.object({});
1931
+ var checkHarperStatusTool = tool30({
1679
1932
  name: "check_harper_status",
1680
1933
  description: "Checks if a Harper application is currently running.",
1681
- parameters: ToolParameters16,
1934
+ parameters: ToolParameters17,
1682
1935
  async execute() {
1683
1936
  if (harperProcess.running) {
1684
1937
  return "A Harper application is currently running.";
@@ -1689,10 +1942,10 @@ var checkHarperStatusTool = tool29({
1689
1942
  });
1690
1943
 
1691
1944
  // tools/harper/createNewHarperApplicationTool.ts
1692
- import { tool as tool30 } from "@openai/agents";
1945
+ import { tool as tool31 } from "@openai/agents";
1693
1946
  import { execSync as execSync3 } from "child_process";
1694
- import path6 from "path";
1695
- import { z as z30 } from "zod";
1947
+ import path7 from "path";
1948
+ import { z as z31 } from "zod";
1696
1949
 
1697
1950
  // utils/package/buildHarperCreateCommand.ts
1698
1951
  function buildCreateCommand(pm, appName, template) {
@@ -1742,16 +1995,16 @@ var PM_DISPLAY = {
1742
1995
  };
1743
1996
 
1744
1997
  // tools/harper/createNewHarperApplicationTool.ts
1745
- var ToolParameters17 = z30.object({
1746
- directoryName: z30.string().describe("The name of the directory to create the application in."),
1747
- template: z30.enum(["vanilla-ts", "vanilla", "react-ts", "react"]).optional().describe("The template to use for the new application. Defaults to vanilla-ts.").default("vanilla-ts")
1998
+ var ToolParameters18 = z31.object({
1999
+ directoryName: z31.string().describe("The name of the directory to create the application in."),
2000
+ template: z31.enum(["vanilla-ts", "vanilla", "react-ts", "react"]).optional().describe("The template to use for the new application. Defaults to vanilla-ts.").default("vanilla-ts")
1748
2001
  });
1749
- async function execute15({ directoryName, template }) {
2002
+ async function execute16({ directoryName, template }) {
1750
2003
  const currentCwd = trackedState.cwd;
1751
2004
  const resolvedPath = resolvePath(currentCwd, directoryName);
1752
2005
  const isCurrentDir = resolvedPath === currentCwd;
1753
- const executionCwd = isCurrentDir ? resolvedPath : path6.dirname(resolvedPath);
1754
- const appName = isCurrentDir ? "." : path6.basename(resolvedPath);
2006
+ const executionCwd = isCurrentDir ? resolvedPath : path7.dirname(resolvedPath);
2007
+ const appName = isCurrentDir ? "." : path7.basename(resolvedPath);
1755
2008
  try {
1756
2009
  const pm = pickPreferredPackageManager();
1757
2010
  const { cmd, label } = buildCreateCommand(pm, appName, template);
@@ -1783,33 +2036,33 @@ ${error.stdout}`;
1783
2036
  return errorMsg;
1784
2037
  }
1785
2038
  }
1786
- var createNewHarperApplicationTool = tool30({
2039
+ var createNewHarperApplicationTool = tool31({
1787
2040
  name: "create_new_harper_application",
1788
2041
  description: "Creates a new Harper application using the best available package manager (yarn/pnpm/bun/deno, falling back to npm).",
1789
- parameters: ToolParameters17,
1790
- execute: execute15
2042
+ parameters: ToolParameters18,
2043
+ execute: execute16
1791
2044
  });
1792
2045
 
1793
2046
  // tools/harper/getHarperConfigSchemaTool.ts
1794
- import { tool as tool31 } from "@openai/agents";
2047
+ import { tool as tool32 } from "@openai/agents";
1795
2048
  import { readFile as readFile2 } from "fs/promises";
1796
2049
  import { createRequire as createRequire2 } from "module";
1797
- import { dirname as dirname4, join as join7 } from "path";
1798
- import { z as z31 } from "zod";
1799
- var ToolParameters18 = z31.object({
1800
- schemaType: z31.enum(["app", "root"]).describe(
2050
+ import { dirname as dirname5, join as join8 } from "path";
2051
+ import { z as z32 } from "zod";
2052
+ var ToolParameters19 = z32.object({
2053
+ schemaType: z32.enum(["app", "root"]).describe(
1801
2054
  'The type of configuration schema to retrieve: "app" for application configuration or "root" for root Harper configuration.'
1802
2055
  )
1803
2056
  });
1804
- var getHarperConfigSchemaTool = tool31({
2057
+ var getHarperConfigSchemaTool = tool32({
1805
2058
  name: "get_harper_config_schema",
1806
2059
  description: "Returns the JSON schema for HarperDB configuration files (either app or root), which describes the config.yaml or harperdb-config.yaml files.",
1807
- parameters: ToolParameters18,
2060
+ parameters: ToolParameters19,
1808
2061
  async execute({ schemaType }) {
1809
2062
  try {
1810
2063
  return await readFile2(
1811
- join7(
1812
- dirname4(createRequire2(import.meta.url).resolve("harperdb")),
2064
+ join8(
2065
+ dirname5(createRequire2(import.meta.url).resolve("harperdb")),
1813
2066
  `config-${schemaType}.schema.json`
1814
2067
  ),
1815
2068
  "utf8"
@@ -1821,13 +2074,13 @@ var getHarperConfigSchemaTool = tool31({
1821
2074
  });
1822
2075
 
1823
2076
  // tools/harper/getHarperResourceInterfaceTool.ts
1824
- import { tool as tool32 } from "@openai/agents";
2077
+ import { tool as tool33 } from "@openai/agents";
1825
2078
  import { readFile as readFile3 } from "fs/promises";
1826
2079
  import { createRequire as createRequire3 } from "module";
1827
- import { dirname as dirname5, join as join8 } from "path";
1828
- import { z as z32 } from "zod";
1829
- var ToolParameters19 = z32.object({
1830
- resourceFile: z32.enum([
2080
+ import { dirname as dirname6, join as join9 } from "path";
2081
+ import { z as z33 } from "zod";
2082
+ var ToolParameters20 = z33.object({
2083
+ resourceFile: z33.enum([
1831
2084
  "ResourceInterfaceV2",
1832
2085
  "ResourceInterface",
1833
2086
  "Table",
@@ -1837,15 +2090,15 @@ var ToolParameters19 = z32.object({
1837
2090
  "The resource-related definition file to read. Defaults to ResourceInterfaceV2."
1838
2091
  ).default("ResourceInterfaceV2")
1839
2092
  });
1840
- var getHarperResourceInterfaceTool = tool32({
2093
+ var getHarperResourceInterfaceTool = tool33({
1841
2094
  name: "get_harper_resource_interface",
1842
2095
  description: "Reads HarperDB resource interface and class definitions (like ResourceInterfaceV2.d.ts) to understand how resources and tables are structured.",
1843
- parameters: ToolParameters19,
2096
+ parameters: ToolParameters20,
1844
2097
  async execute({ resourceFile }) {
1845
2098
  try {
1846
2099
  return await readFile3(
1847
- join8(
1848
- dirname5(createRequire3(import.meta.url).resolve("harperdb")),
2100
+ join9(
2101
+ dirname6(createRequire3(import.meta.url).resolve("harperdb")),
1849
2102
  "resources",
1850
2103
  `${resourceFile}.d.ts`
1851
2104
  ),
@@ -1858,21 +2111,21 @@ var getHarperResourceInterfaceTool = tool32({
1858
2111
  });
1859
2112
 
1860
2113
  // tools/harper/getHarperSchemaGraphQLTool.ts
1861
- import { tool as tool33 } from "@openai/agents";
2114
+ import { tool as tool34 } from "@openai/agents";
1862
2115
  import { readFile as readFile4 } from "fs/promises";
1863
2116
  import { createRequire as createRequire4 } from "module";
1864
- import { dirname as dirname6, join as join9 } from "path";
1865
- import { z as z33 } from "zod";
1866
- var ToolParameters20 = z33.object({});
1867
- var getHarperSchemaGraphQLTool = tool33({
2117
+ import { dirname as dirname7, join as join10 } from "path";
2118
+ import { z as z34 } from "zod";
2119
+ var ToolParameters21 = z34.object({});
2120
+ var getHarperSchemaGraphQLTool = tool34({
1868
2121
  name: "get_harper_schema_graphql",
1869
2122
  description: "Returns the GraphQL schema for HarperDB schema files, which define the structure of HarperDB database tables.",
1870
- parameters: ToolParameters20,
2123
+ parameters: ToolParameters21,
1871
2124
  async execute() {
1872
2125
  try {
1873
2126
  return await readFile4(
1874
- join9(
1875
- dirname6(createRequire4(import.meta.url).resolve("harperdb")),
2127
+ join10(
2128
+ dirname7(createRequire4(import.meta.url).resolve("harperdb")),
1876
2129
  `schema.graphql`
1877
2130
  ),
1878
2131
  "utf8"
@@ -1884,22 +2137,22 @@ var getHarperSchemaGraphQLTool = tool33({
1884
2137
  });
1885
2138
 
1886
2139
  // tools/harper/hitHarperAPITool.ts
1887
- import { tool as tool34 } from "@openai/agents";
1888
- import { z as z34 } from "zod";
1889
- var ToolParameters21 = z34.object({
1890
- method: z34.enum(["POST", "GET", "PUT", "DELETE"]).optional().default("GET").describe(
2140
+ import { tool as tool35 } from "@openai/agents";
2141
+ import { z as z35 } from "zod";
2142
+ var ToolParameters22 = z35.object({
2143
+ method: z35.enum(["POST", "GET", "PUT", "DELETE"]).optional().default("GET").describe(
1891
2144
  'The HTTP method to use, defaults to "get". Notably, POST and PUT require the full body be sent.'
1892
2145
  ),
1893
- path: z34.string().optional().default("/openapi").describe("The path to fetch from localhost (defaults to /openapi)."),
1894
- port: z34.number().optional().default(harperProcess.httpPort).describe(
2146
+ path: z35.string().optional().default("/openapi").describe("The path to fetch from localhost (defaults to /openapi)."),
2147
+ port: z35.number().optional().default(harperProcess.httpPort).describe(
1895
2148
  "The port to fetch from localhost (defaults to the running Harper port)."
1896
2149
  ),
1897
- body: z34.string().optional().default("").describe("An optional JSON string body to send along with the request.")
2150
+ body: z35.string().optional().default("").describe("An optional JSON string body to send along with the request.")
1898
2151
  });
1899
- var hitHarperAPITool = tool34({
2152
+ var hitHarperAPITool = tool35({
1900
2153
  name: "hit_harper_api",
1901
2154
  description: "Performs a request against the running Harper API. Use /openapi to look up Harper APIs.",
1902
- parameters: ToolParameters21,
2155
+ parameters: ToolParameters22,
1903
2156
  needsApproval: async (runContext, input, callId) => {
1904
2157
  if (callId && runContext.isToolApproved({ toolName: "hit_harper_api", callId })) {
1905
2158
  return false;
@@ -1937,13 +2190,13 @@ var hitHarperAPITool = tool34({
1937
2190
  });
1938
2191
 
1939
2192
  // tools/harper/readHarperLogsTool.ts
1940
- import { tool as tool35 } from "@openai/agents";
1941
- import { z as z35 } from "zod";
1942
- var ToolParameters22 = z35.object({});
1943
- var readHarperLogsTool = tool35({
2193
+ import { tool as tool36 } from "@openai/agents";
2194
+ import { z as z36 } from "zod";
2195
+ var ToolParameters23 = z36.object({});
2196
+ var readHarperLogsTool = tool36({
1944
2197
  name: "read_harper_logs",
1945
2198
  description: "Reads the most recent console logs of a started Harper app and clears them so that subsequent reads will only show new logs.",
1946
- parameters: ToolParameters22,
2199
+ parameters: ToolParameters23,
1947
2200
  async execute() {
1948
2201
  if (!harperProcess.running) {
1949
2202
  return `Error: No Harper application is currently running.`;
@@ -1958,10 +2211,10 @@ var readHarperLogsTool = tool35({
1958
2211
  });
1959
2212
 
1960
2213
  // tools/harper/startHarperTool.ts
1961
- import { tool as tool36 } from "@openai/agents";
1962
- import { existsSync as existsSync7 } from "fs";
2214
+ import { tool as tool37 } from "@openai/agents";
2215
+ import { existsSync as existsSync8 } from "fs";
1963
2216
  import { basename, resolve } from "path";
1964
- import { z as z36 } from "zod";
2217
+ import { z as z37 } from "zod";
1965
2218
 
1966
2219
  // utils/promises/sleep.ts
1967
2220
  function sleep(ms) {
@@ -1969,13 +2222,13 @@ function sleep(ms) {
1969
2222
  }
1970
2223
 
1971
2224
  // tools/harper/startHarperTool.ts
1972
- var ToolParameters23 = z36.object({
1973
- directoryName: z36.string().describe("The name of the directory that the Harper app is in.")
2225
+ var ToolParameters24 = z37.object({
2226
+ directoryName: z37.string().describe("The name of the directory that the Harper app is in.")
1974
2227
  });
1975
- var startHarperTool = tool36({
2228
+ var startHarperTool = tool37({
1976
2229
  name: "start_harper",
1977
2230
  description: "Starts a Harper app background process, allowing you to observe the app in action (by readHarperLogsTool, hitHarperAPITool, etc).",
1978
- parameters: ToolParameters23,
2231
+ parameters: ToolParameters24,
1979
2232
  async execute({ directoryName }) {
1980
2233
  if (isIgnored(directoryName)) {
1981
2234
  return `Error: Target directory ${directoryName} is restricted by .aiignore`;
@@ -1986,7 +2239,7 @@ var startHarperTool = tool36({
1986
2239
  try {
1987
2240
  let effectiveDirectory = directoryName;
1988
2241
  const candidatePath = resolve(process.cwd(), directoryName);
1989
- if (!existsSync7(candidatePath)) {
2242
+ if (!existsSync8(candidatePath)) {
1990
2243
  const cwd = process.cwd();
1991
2244
  if (basename(cwd) === directoryName) {
1992
2245
  effectiveDirectory = cwd;
@@ -2004,13 +2257,13 @@ ${logs2}`;
2004
2257
  });
2005
2258
 
2006
2259
  // tools/harper/stopHarperTool.ts
2007
- import { tool as tool37 } from "@openai/agents";
2008
- import { z as z37 } from "zod";
2009
- var ToolParameters24 = z37.object({});
2010
- var stopHarperTool = tool37({
2260
+ import { tool as tool38 } from "@openai/agents";
2261
+ import { z as z38 } from "zod";
2262
+ var ToolParameters25 = z38.object({});
2263
+ var stopHarperTool = tool38({
2011
2264
  name: "stop_harper",
2012
2265
  description: "Stops all previously started Harper app background process.",
2013
- parameters: ToolParameters24,
2266
+ parameters: ToolParameters25,
2014
2267
  async execute() {
2015
2268
  if (!harperProcess.running) {
2016
2269
  return `Error: No Harper application is currently running.`;
@@ -2025,21 +2278,12 @@ var stopHarperTool = tool37({
2025
2278
  });
2026
2279
 
2027
2280
  // 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.")
2281
+ import { tool as tool39 } from "@openai/agents";
2282
+ import { z as z39 } from "zod";
2283
+ var AddPlanItemParameters = z39.object({
2284
+ text: z39.string().describe("The description of the task or milestone to add to the plan.")
2041
2285
  });
2042
- var addPlanItemTool = tool38({
2286
+ var addPlanItemTool = tool39({
2043
2287
  name: "add_plan_item",
2044
2288
  description: "Add a new item to the plan.",
2045
2289
  parameters: AddPlanItemParameters,
@@ -2058,12 +2302,12 @@ var addPlanItemTool = tool38({
2058
2302
  });
2059
2303
 
2060
2304
  // 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.")
2305
+ import { tool as tool40 } from "@openai/agents";
2306
+ import { z as z40 } from "zod";
2307
+ var SetPlanDescriptionParameters = z40.object({
2308
+ description: z40.string().describe("A high-level description of the overall plan and goals.")
2065
2309
  });
2066
- var setPlanDescriptionTool = tool39({
2310
+ var setPlanDescriptionTool = tool40({
2067
2311
  name: "set_plan_description",
2068
2312
  description: "Set the high-level description for the current plan.",
2069
2313
  parameters: SetPlanDescriptionParameters,
@@ -2074,12 +2318,12 @@ var setPlanDescriptionTool = tool39({
2074
2318
  });
2075
2319
 
2076
2320
  // 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.")
2321
+ import { tool as tool41 } from "@openai/agents";
2322
+ import { z as z41 } from "zod";
2323
+ var SetPlanItemsParameters = z41.object({
2324
+ items: z41.array(z41.string()).describe("An array of task descriptions to set as the plan items.")
2081
2325
  });
2082
- var setPlanItemsTool = tool40({
2326
+ var setPlanItemsTool = tool41({
2083
2327
  name: "set_plan_items",
2084
2328
  description: "Set multiple plan items at once, replacing any existing items.",
2085
2329
  parameters: SetPlanItemsParameters,
@@ -2095,16 +2339,16 @@ var setPlanItemsTool = tool40({
2095
2339
  });
2096
2340
 
2097
2341
  // 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(
2342
+ import { tool as tool42 } from "@openai/agents";
2343
+ import { z as z42 } from "zod";
2344
+ var UpdatePlanItemParameters = z42.object({
2345
+ id: z42.number().describe("The ID of the plan item to update."),
2346
+ text: z42.string().describe("The new description of the task."),
2347
+ status: z42.enum(["unchanged", "todo", "in-progress", "done", "not-needed"]).describe(
2104
2348
  "The new status of the task."
2105
2349
  )
2106
2350
  });
2107
- var updatePlanItemTool = tool41({
2351
+ var updatePlanItemTool = tool42({
2108
2352
  name: "update_plan_item",
2109
2353
  description: "Update an existing plan item.",
2110
2354
  parameters: UpdatePlanItemParameters,
@@ -2143,6 +2387,7 @@ function createTools() {
2143
2387
  addPlanItemTool,
2144
2388
  changeCwdTool,
2145
2389
  checkHarperStatusTool,
2390
+ collectFeedbackTool,
2146
2391
  codeInterpreterTool,
2147
2392
  createApplyPatchTool(),
2148
2393
  createNewHarperApplicationTool,
@@ -2185,6 +2430,7 @@ function trackCompaction(session) {
2185
2430
  try {
2186
2431
  return await originalRunCompaction(args);
2187
2432
  } catch (error) {
2433
+ logError(error);
2188
2434
  const err = error ?? {};
2189
2435
  const name = err.name || "Error";
2190
2436
  const message = err.message || String(err);
@@ -2255,16 +2501,15 @@ Stack: ${String(err.stack).split("\n").slice(0, 8).join("\n")}` : "";
2255
2501
  text: `${name}:${code}${statusStr} ${message}${hint}${compactionCtx}${argsSnippet}${responseDataSnippet}${stack}`,
2256
2502
  version: 1
2257
2503
  }]);
2258
- return void 0;
2259
2504
  }
2260
2505
  };
2261
2506
  }
2262
2507
 
2263
2508
  // utils/sessions/DiskSession.ts
2264
2509
  import { MemorySession } from "@openai/agents";
2265
- import { existsSync as existsSync8 } from "fs";
2510
+ import { existsSync as existsSync9 } from "fs";
2266
2511
  import { mkdir as mkdir2, readFile as readFile5, rename, writeFile as writeFile3 } from "fs/promises";
2267
- import { dirname as dirname7 } from "path";
2512
+ import { dirname as dirname8 } from "path";
2268
2513
  var DiskSession = class extends MemorySession {
2269
2514
  filePath;
2270
2515
  ready;
@@ -2277,9 +2522,23 @@ var DiskSession = class extends MemorySession {
2277
2522
  const storage = await this.loadStorage();
2278
2523
  let sessionId = this.sessionId;
2279
2524
  if (!options?.sessionId) {
2280
- const sessionIds = Object.keys(storage.sessions);
2525
+ let candidate;
2526
+ const sessionIds = Object.keys(storage.sessions ?? {});
2281
2527
  if (sessionIds.length > 0) {
2282
- sessionId = sessionIds[0];
2528
+ candidate = sessionIds[0];
2529
+ } else {
2530
+ const planIds = Object.keys(storage.plan ?? {});
2531
+ if (planIds.length > 0) {
2532
+ candidate = planIds[0];
2533
+ } else {
2534
+ const skillsIds = Object.keys(storage.skillsRead ?? {});
2535
+ if (skillsIds.length > 0) {
2536
+ candidate = skillsIds[0];
2537
+ }
2538
+ }
2539
+ }
2540
+ if (candidate) {
2541
+ sessionId = candidate;
2283
2542
  this.sessionId = sessionId;
2284
2543
  }
2285
2544
  }
@@ -2299,24 +2558,25 @@ var DiskSession = class extends MemorySession {
2299
2558
  }
2300
2559
  }
2301
2560
  async loadStorage() {
2302
- if (existsSync8(this.filePath)) {
2561
+ if (existsSync9(this.filePath)) {
2303
2562
  try {
2304
2563
  const data = await readFile5(this.filePath, "utf-8");
2305
2564
  const parsed = JSON.parse(data);
2306
2565
  parsed.sessions = parsed.sessions || {};
2307
2566
  parsed.skillsRead = parsed.skillsRead || {};
2567
+ parsed.plan = parsed.plan || {};
2308
2568
  return parsed;
2309
2569
  } catch (e) {
2310
2570
  console.error(`Failed to read session file ${this.filePath}:`, e);
2311
2571
  }
2312
2572
  }
2313
- return { sessions: {}, skillsRead: {} };
2573
+ return { sessions: {}, skillsRead: {}, plan: {} };
2314
2574
  }
2315
2575
  async updateStorage(update) {
2316
2576
  const storage = await this.loadStorage();
2317
2577
  update(storage);
2318
- const dir = dirname7(this.filePath);
2319
- if (!existsSync8(dir)) {
2578
+ const dir = dirname8(this.filePath);
2579
+ if (!existsSync9(dir)) {
2320
2580
  await mkdir2(dir, { recursive: true });
2321
2581
  }
2322
2582
  const data = JSON.stringify(storage, null, 2);
@@ -2365,6 +2625,9 @@ var DiskSession = class extends MemorySession {
2365
2625
  if (storage.skillsRead) {
2366
2626
  delete storage.skillsRead[sessionId];
2367
2627
  }
2628
+ if (storage.plan) {
2629
+ delete storage.plan[sessionId];
2630
+ }
2368
2631
  });
2369
2632
  }
2370
2633
  async addSkillRead(skill) {
@@ -2386,6 +2649,28 @@ var DiskSession = class extends MemorySession {
2386
2649
  const storage = await this.loadStorage();
2387
2650
  return storage.skillsRead?.[sessionId] ?? [];
2388
2651
  }
2652
+ async getPlanState() {
2653
+ await this.ready;
2654
+ const sessionId = await this.getSessionId();
2655
+ const storage = await this.loadStorage();
2656
+ return storage.plan?.[sessionId] ?? null;
2657
+ }
2658
+ async setPlanState(state) {
2659
+ await this.ready;
2660
+ const sessionId = await this.getSessionId();
2661
+ await this.updateStorage((storage) => {
2662
+ if (!storage.plan) {
2663
+ storage.plan = {};
2664
+ }
2665
+ const existing = storage.plan[sessionId] ?? { planDescription: "", planItems: [], progress: 0 };
2666
+ storage.plan[sessionId] = {
2667
+ ...existing,
2668
+ ...state,
2669
+ // Ensure arrays/objects are properly merged for planItems
2670
+ planItems: Array.isArray(state.planItems) ? state.planItems : existing.planItems
2671
+ };
2672
+ });
2673
+ }
2389
2674
  };
2390
2675
 
2391
2676
  // utils/sessions/MemoryCompactionSession.ts
@@ -2421,8 +2706,17 @@ function getModelSettings(modelName) {
2421
2706
 
2422
2707
  // utils/sessions/compactConversation.ts
2423
2708
  async function compactConversation(items) {
2424
- const recentItems = items.slice(-3);
2425
- const itemsToCompact = items.slice(0, -3);
2709
+ let splitIndex = Math.max(0, items.length - 3);
2710
+ while (splitIndex > 0 && splitIndex < items.length) {
2711
+ const itemAtSplit = items[splitIndex];
2712
+ if (itemAtSplit.type === "function_call_result") {
2713
+ splitIndex--;
2714
+ } else {
2715
+ break;
2716
+ }
2717
+ }
2718
+ const recentItems = items.slice(splitIndex);
2719
+ const itemsToCompact = items.slice(0, splitIndex);
2426
2720
  let noticeContent = "... conversation history compacted ...";
2427
2721
  if (trackedState.compactionModel && itemsToCompact.length > 0) {
2428
2722
  try {
@@ -2430,17 +2724,17 @@ async function compactConversation(items) {
2430
2724
  name: "History Compactor",
2431
2725
  model: isOpenAIModel(trackedState.compactionModel) ? trackedState.compactionModel : getModel(trackedState.compactionModel),
2432
2726
  modelSettings: getModelSettings(trackedState.compactionModel),
2433
- instructions: "Compact the provided conversation history into key observations. Focus on what seems likely to be needed later. Be concise and avoid repeating information."
2727
+ instructions: "Compact the provided conversation history.\n- Focus on what is NOT completed and needs to be remembered for later.\n- Do NOT include file content or patches, it is available on the filesystem already. \n- Be concise."
2434
2728
  });
2729
+ emitToListeners("SetCompacting", true);
2435
2730
  const result = await run(
2436
2731
  agent,
2437
2732
  itemsToCompact
2438
2733
  );
2439
2734
  const summary = result.finalOutput;
2440
2735
  if (summary && summary.trim().length > 0) {
2441
- const s = summary.replace(/\s+/g, " ").trim();
2442
2736
  noticeContent = `Key observations from earlier:
2443
- ${s}`;
2737
+ ${summary.trim()}`;
2444
2738
  }
2445
2739
  } catch (err) {
2446
2740
  const msg = String(err?.message || err || "");
@@ -2448,6 +2742,8 @@ ${s}`;
2448
2742
  if (!isNoTrace) {
2449
2743
  console.warn("Compaction summarization failed:", msg);
2450
2744
  }
2745
+ } finally {
2746
+ emitToListeners("SetCompacting", false);
2451
2747
  }
2452
2748
  }
2453
2749
  const itemsToAdd = [system(noticeContent), ...recentItems].filter(excludeFalsy);
@@ -2469,7 +2765,10 @@ function getModelContextLimit(modelName) {
2469
2765
  if (name.startsWith("claude-3.5") || name.startsWith("claude-3")) {
2470
2766
  return 2e5;
2471
2767
  }
2472
- if (name.startsWith("gemini-1.5")) {
2768
+ if (name.startsWith("claude-4.6") || name.startsWith("claude-4.5")) {
2769
+ return 1e6;
2770
+ }
2771
+ if (name.startsWith("gemini-1.5") || name.startsWith("gemini-3")) {
2473
2772
  return 1e6;
2474
2773
  }
2475
2774
  if (name.startsWith("gemini-")) {
@@ -2493,6 +2792,7 @@ var MemoryCompactionSession = class {
2493
2792
  triggerTokens;
2494
2793
  itemsAddedSinceLastCompaction = 0;
2495
2794
  skillsReadLocal = /* @__PURE__ */ new Set();
2795
+ planStateLocal;
2496
2796
  constructor(options) {
2497
2797
  this.underlyingSession = options.underlyingSession ?? new MemorySession2();
2498
2798
  if (trackedState.compactionModel) {
@@ -2522,11 +2822,68 @@ var MemoryCompactionSession = class {
2522
2822
  const merged = /* @__PURE__ */ new Set([...base, ...this.skillsReadLocal]);
2523
2823
  return Array.from(merged);
2524
2824
  }
2825
+ async getPlanState() {
2826
+ const u = this.underlyingSession;
2827
+ let base = null;
2828
+ if (u && typeof u.getPlanState === "function") {
2829
+ try {
2830
+ base = await Promise.resolve(u.getPlanState());
2831
+ } catch {
2832
+ }
2833
+ }
2834
+ if (this.planStateLocal) {
2835
+ const existing = base ?? { planDescription: "", planItems: [], progress: 0 };
2836
+ const merged = {
2837
+ ...existing,
2838
+ ...this.planStateLocal,
2839
+ planItems: Array.isArray(this.planStateLocal.planItems) ? this.planStateLocal.planItems : existing.planItems
2840
+ };
2841
+ return merged;
2842
+ }
2843
+ return base;
2844
+ }
2845
+ async setPlanState(state) {
2846
+ const u = this.underlyingSession;
2847
+ if (u && typeof u.setPlanState === "function") {
2848
+ try {
2849
+ return await Promise.resolve(u.setPlanState(state));
2850
+ } catch {
2851
+ }
2852
+ }
2853
+ const existing = this.planStateLocal ?? { planDescription: "", planItems: [], progress: 0 };
2854
+ this.planStateLocal = {
2855
+ ...existing,
2856
+ ...state,
2857
+ planItems: Array.isArray(state.planItems) ? state.planItems : existing.planItems
2858
+ };
2859
+ }
2525
2860
  async getItems(limit) {
2526
- return this.underlyingSession.getItems(limit);
2861
+ const items = await this.underlyingSession.getItems(limit);
2862
+ return items.map((it) => sanitizeItem(it));
2863
+ }
2864
+ /**
2865
+ * Returns the provider-stamped timestamp of the latest-added item, or null when unavailable.
2866
+ * Uses the underlying session directly to access our providerData without sanitation.
2867
+ */
2868
+ async getLatestAddedTimestamp() {
2869
+ const items = await this.underlyingSession.getItems();
2870
+ if (!Array.isArray(items) || items.length === 0) {
2871
+ return null;
2872
+ }
2873
+ const last = items[items.length - 1];
2874
+ const ts = last?.providerData?.harper?.addedAtMs;
2875
+ return typeof ts === "number" && Number.isFinite(ts) ? ts : null;
2527
2876
  }
2528
2877
  async addItems(items) {
2529
- await this.underlyingSession.addItems(items);
2878
+ const now = Date.now();
2879
+ const stamped = items.map((it) => {
2880
+ const pd = { ...it?.providerData };
2881
+ const ours = { ...pd["harper"] };
2882
+ ours.addedAtMs = now;
2883
+ pd["harper"] = ours;
2884
+ return { ...it, providerData: pd };
2885
+ });
2886
+ await this.underlyingSession.addItems(stamped);
2530
2887
  this.itemsAddedSinceLastCompaction += items.length;
2531
2888
  await this.runCompaction({ reason: "post-add-check", mode: "auto" });
2532
2889
  }
@@ -2552,7 +2909,7 @@ var MemoryCompactionSession = class {
2552
2909
  * summarized by the model), and retains the last 3 recent items.
2553
2910
  */
2554
2911
  async runCompaction(args) {
2555
- const items = await this.underlyingSession.getItems();
2912
+ const items = await this.getItems();
2556
2913
  if (items.length <= 1) {
2557
2914
  return null;
2558
2915
  }
@@ -2573,6 +2930,37 @@ var MemoryCompactionSession = class {
2573
2930
  return null;
2574
2931
  }
2575
2932
  };
2933
+ function sanitizeItem(it) {
2934
+ if (!it || typeof it !== "object") {
2935
+ return it;
2936
+ }
2937
+ const out = { ...it };
2938
+ if ("harper" in out) {
2939
+ try {
2940
+ delete out.harper;
2941
+ } catch {
2942
+ }
2943
+ }
2944
+ const pd = out.providerData && typeof out.providerData === "object" ? { ...out.providerData } : void 0;
2945
+ if (pd) {
2946
+ if ("harper" in pd) {
2947
+ try {
2948
+ delete pd.harper;
2949
+ } catch {
2950
+ }
2951
+ }
2952
+ if (Object.keys(pd).length === 0) {
2953
+ try {
2954
+ delete out.providerData;
2955
+ } catch {
2956
+ out.providerData = void 0;
2957
+ }
2958
+ } else {
2959
+ out.providerData = pd;
2960
+ }
2961
+ }
2962
+ return out;
2963
+ }
2576
2964
  function estimateTokens(items) {
2577
2965
  let chars = 0;
2578
2966
  for (const it of items) {
@@ -2657,54 +3045,48 @@ var ActionsProvider = ({ children }) => {
2657
3045
  return /* @__PURE__ */ jsx2(ActionsContext.Provider, { value, children });
2658
3046
  };
2659
3047
 
2660
- // lifecycle/handleExit.ts
2661
- import { getGlobalTraceProvider } from "@openai/agents";
2662
- async function handleExit() {
2663
- if (harperProcess.startedInternally) {
2664
- harperProcess.stop();
2665
- }
2666
- await getGlobalTraceProvider().forceFlush();
2667
- emitToListeners("ExitUI", void 0);
2668
- process.exit(0);
2669
- }
2670
-
2671
3048
  // utils/sessions/cost.ts
2672
3049
  import chalk2 from "chalk";
2673
3050
  var FLEX_PRICES = {
2674
- "gpt-5.2": { input: 0.875 / 1e6, cachedInput: 0.0875 / 1e6, output: 7 / 1e6 },
2675
- "gpt-5.1": { input: 0.625 / 1e6, cachedInput: 0.0625 / 1e6, output: 5 / 1e6 },
2676
3051
  "gpt-5": { input: 0.625 / 1e6, cachedInput: 0.0625 / 1e6, output: 5 / 1e6 },
2677
3052
  "gpt-5-mini": { input: 0.125 / 1e6, cachedInput: 0.0125 / 1e6, output: 1 / 1e6 },
2678
3053
  "gpt-5-nano": { input: 0.025 / 1e6, cachedInput: 25e-4 / 1e6, output: 0.2 / 1e6 },
3054
+ "gpt-5.1": { input: 0.625 / 1e6, cachedInput: 0.0625 / 1e6, output: 5 / 1e6 },
3055
+ "gpt-5.2": { input: 0.875 / 1e6, cachedInput: 0.0875 / 1e6, output: 7 / 1e6 },
2679
3056
  "o3": { input: 1 / 1e6, cachedInput: 0.25 / 1e6, output: 4 / 1e6 },
2680
3057
  "o4-mini": { input: 0.55 / 1e6, cachedInput: 0.138 / 1e6, output: 2.2 / 1e6 }
2681
3058
  };
2682
3059
  var STANDARD_PRICES = {
2683
- "gpt-5.2": { input: 1.75 / 1e6, cachedInput: 0.175 / 1e6, output: 14 / 1e6 },
2684
- "gpt-5.1": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
3060
+ "gpt-4o": { input: 2.5 / 1e6, cachedInput: 1.25 / 1e6, output: 10 / 1e6 },
3061
+ "gpt-4o-2024-05-13": { input: 5 / 1e6, cachedInput: 5 / 1e6, output: 15 / 1e6 },
3062
+ "gpt-4o-mini": { input: 0.15 / 1e6, cachedInput: 0.075 / 1e6, output: 0.6 / 1e6 },
2685
3063
  "gpt-5": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
3064
+ "gpt-5-chat-latest": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
3065
+ "gpt-5-codex": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
2686
3066
  "gpt-5-mini": { input: 0.25 / 1e6, cachedInput: 0.025 / 1e6, output: 2 / 1e6 },
2687
3067
  "gpt-5-nano": { input: 0.05 / 1e6, cachedInput: 5e-3 / 1e6, output: 0.4 / 1e6 },
2688
- "gpt-5.2-chat-latest": { input: 1.75 / 1e6, cachedInput: 0.175 / 1e6, output: 14 / 1e6 },
2689
- "gpt-5.1-chat-latest": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
2690
- "gpt-5-chat-latest": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
2691
- "gpt-5.2-pro": { input: 21 / 1e6, cachedInput: 21 / 1e6, output: 168 / 1e6 },
2692
3068
  "gpt-5-pro": { input: 15 / 1e6, cachedInput: 15 / 1e6, output: 120 / 1e6 },
2693
3069
  "gpt-4.1": { input: 2 / 1e6, cachedInput: 0.5 / 1e6, output: 8 / 1e6 },
2694
3070
  "gpt-4.1-mini": { input: 0.4 / 1e6, cachedInput: 0.1 / 1e6, output: 1.6 / 1e6 },
2695
3071
  "gpt-4.1-nano": { input: 0.1 / 1e6, cachedInput: 0.025 / 1e6, output: 0.4 / 1e6 },
2696
- "gpt-4o": { input: 2.5 / 1e6, cachedInput: 1.25 / 1e6, output: 10 / 1e6 },
2697
- "gpt-4o-2024-05-13": { input: 5 / 1e6, cachedInput: 5 / 1e6, output: 15 / 1e6 },
2698
- "gpt-4o-mini": { input: 0.15 / 1e6, cachedInput: 0.075 / 1e6, output: 0.6 / 1e6 },
3072
+ "gpt-5.1": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
3073
+ "gpt-5.1-chat-latest": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
3074
+ "gpt-5.1-codex": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
3075
+ "gpt-5.1-codex-max": { input: 1.25 / 1e6, cachedInput: 0.125 / 1e6, output: 10 / 1e6 },
3076
+ "gpt-5.2": { input: 1.75 / 1e6, cachedInput: 0.175 / 1e6, output: 14 / 1e6 },
3077
+ "gpt-5.2-chat-latest": { input: 1.75 / 1e6, cachedInput: 0.175 / 1e6, output: 14 / 1e6 },
3078
+ "gpt-5.2-codex": { input: 1.75 / 1e6, cachedInput: 0.175 / 1e6, output: 14 / 1e6 },
3079
+ "gpt-5.2-pro": { input: 21 / 1e6, cachedInput: 21 / 1e6, output: 168 / 1e6 },
3080
+ "gpt-5.3-codex": { input: 1.75 / 1e6, cachedInput: 0.175 / 1e6, output: 14 / 1e6 },
2699
3081
  "o1": { input: 15 / 1e6, cachedInput: 7.5 / 1e6, output: 60 / 1e6 },
3082
+ "o1-mini": { input: 1.1 / 1e6, cachedInput: 0.55 / 1e6, output: 4.4 / 1e6 },
2700
3083
  "o1-pro": { input: 150 / 1e6, cachedInput: 150 / 1e6, output: 600 / 1e6 },
2701
- "o3-pro": { input: 20 / 1e6, cachedInput: 20 / 1e6, output: 80 / 1e6 },
2702
3084
  "o3": { input: 2 / 1e6, cachedInput: 0.5 / 1e6, output: 8 / 1e6 },
2703
3085
  "o3-deep-research": { input: 10 / 1e6, cachedInput: 2.5 / 1e6, output: 40 / 1e6 },
2704
- "o4-mini": { input: 1.1 / 1e6, cachedInput: 0.275 / 1e6, output: 4.4 / 1e6 },
2705
- "o4-mini-deep-research": { input: 2 / 1e6, cachedInput: 0.5 / 1e6, output: 8 / 1e6 },
2706
3086
  "o3-mini": { input: 1.1 / 1e6, cachedInput: 0.55 / 1e6, output: 4.4 / 1e6 },
2707
- "o1-mini": { input: 1.1 / 1e6, cachedInput: 0.55 / 1e6, output: 4.4 / 1e6 }
3087
+ "o3-pro": { input: 20 / 1e6, cachedInput: 20 / 1e6, output: 80 / 1e6 },
3088
+ "o4-mini": { input: 1.1 / 1e6, cachedInput: 0.275 / 1e6, output: 4.4 / 1e6 },
3089
+ "o4-mini-deep-research": { input: 2 / 1e6, cachedInput: 0.5 / 1e6, output: 8 / 1e6 }
2708
3090
  };
2709
3091
  var MODEL_PRICES = {
2710
3092
  flex: FLEX_PRICES,
@@ -2842,64 +3224,6 @@ var CostTracker = class {
2842
3224
  };
2843
3225
  var costTracker = new CostTracker();
2844
3226
 
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
-
2903
3227
  // utils/strings/isTrue.ts
2904
3228
  function isTrue(v) {
2905
3229
  if (v === void 0) {
@@ -2948,7 +3272,7 @@ Last tool call: ${lastToolCallInfo}` : "";
2948
3272
  }
2949
3273
 
2950
3274
  // agent/runAgentForOnePass.ts
2951
- async function runAgentForOnePass(agent, session, input, controller) {
3275
+ async function runAgentForOnePass(agent, session, input, controller, isPrompt) {
2952
3276
  let lastToolCallInfo = null;
2953
3277
  const toolInfoMap = /* @__PURE__ */ new Map();
2954
3278
  const removeToolListener = addListener("RegisterToolInfo", (info) => {
@@ -2958,9 +3282,9 @@ async function runAgentForOnePass(agent, session, input, controller) {
2958
3282
  let hasStartedResponse = false;
2959
3283
  let adjustedInput = input;
2960
3284
  const noPlanYet = globalPlanContext.planItems.length === 0 && (!globalPlanContext.planDescription || globalPlanContext.planDescription.trim().length === 0);
2961
- if (noPlanYet && (typeof input === "string" || Array.isArray(input))) {
3285
+ if (noPlanYet && typeof input === "string") {
2962
3286
  const planningInstruction = [
2963
- "If there is no current plan, first establish one and keep it updated:",
3287
+ isPrompt ? "The following request is from a non-interactive prompt. You MUST establish a comprehensive plan first, then execute it autonomously until completion." : "If there is no current plan, first establish one and keep it updated:",
2964
3288
  "- Use the tools to manage the plan:",
2965
3289
  " \u2022 set_plan_description(description)",
2966
3290
  " \u2022 set_plan_items(items: string[])",
@@ -2969,14 +3293,10 @@ async function runAgentForOnePass(agent, session, input, controller) {
2969
3293
  "- After setting the plan, as you progress, mark items as in-progress, done, or not-needed.",
2970
3294
  "- Keep the plan concise and actionable. Update statuses as you move forward."
2971
3295
  ].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
- }
3296
+ adjustedInput = [
3297
+ system2(planningInstruction),
3298
+ { type: "message", role: "user", content: input }
3299
+ ];
2980
3300
  }
2981
3301
  const stream = await run2(agent, adjustedInput, {
2982
3302
  session,
@@ -2984,6 +3304,49 @@ async function runAgentForOnePass(agent, session, input, controller) {
2984
3304
  signal: controller.signal,
2985
3305
  maxTurns: trackedState.maxTurns
2986
3306
  });
3307
+ const pushNewItemsIntoSession = async () => {
3308
+ const newItems = stream.newItems.filter((item) => {
3309
+ if ("status" in item) {
3310
+ return item.status === "completed";
3311
+ }
3312
+ return true;
3313
+ }).map((item) => {
3314
+ const json = typeof item.toJSON === "function" ? item.toJSON() : item;
3315
+ if (json && typeof json === "object" && "rawItem" in json) {
3316
+ return json.rawItem;
3317
+ }
3318
+ return json;
3319
+ });
3320
+ const itemsToPush = [];
3321
+ const callIds = /* @__PURE__ */ new Set();
3322
+ const resultIds = /* @__PURE__ */ new Set();
3323
+ for (const item of newItems) {
3324
+ if (item.type === "function_call") {
3325
+ callIds.add(item.callId);
3326
+ } else if (item.type === "function_call_result") {
3327
+ resultIds.add(item.callId);
3328
+ }
3329
+ }
3330
+ const existingItems = await session.getItems();
3331
+ for (const item of existingItems) {
3332
+ if (item.type === "function_call") {
3333
+ callIds.add(item.callId);
3334
+ } else if (item.type === "function_call_result") {
3335
+ resultIds.add(item.callId);
3336
+ }
3337
+ }
3338
+ for (const item of newItems) {
3339
+ if (item.type === "function_call" || item.type === "function_call_result") {
3340
+ if (!callIds.has(item.callId) || !resultIds.has(item.callId)) {
3341
+ continue;
3342
+ }
3343
+ }
3344
+ itemsToPush.push(item);
3345
+ }
3346
+ if (itemsToPush.length > 0) {
3347
+ await session.addItems(itemsToPush);
3348
+ }
3349
+ };
2987
3350
  for await (const event of stream) {
2988
3351
  if (trackedState.monitorRateLimits) {
2989
3352
  const { requests, tokens } = rateLimitTracker.isApproachingLimit(trackedState.rateLimitThreshold);
@@ -3015,9 +3378,8 @@ async function runAgentForOnePass(agent, session, input, controller) {
3015
3378
  text: "Operation canceled due to rate limits.",
3016
3379
  version: 1
3017
3380
  }]);
3018
- if (controller) {
3019
- controller.abort();
3020
- }
3381
+ await pushNewItemsIntoSession();
3382
+ controller.abort();
3021
3383
  process.exitCode = 1;
3022
3384
  await handleExit();
3023
3385
  }
@@ -3089,6 +3451,21 @@ async function runAgentForOnePass(agent, session, input, controller) {
3089
3451
  cachedInputTokens: sessionStats2.cachedInputTokens + costTracker.extractCachedTokens(stream.state.usage.inputTokensDetails),
3090
3452
  hasUnknownPrices: sessionStats2.hasUnknownPrices
3091
3453
  });
3454
+ if (trackedState.currentTurn !== stream.state._currentTurn || trackedState.maxTurns !== stream.state._maxTurns) {
3455
+ trackedState.currentTurn = stream.state._currentTurn;
3456
+ trackedState.maxTurns = stream.state._maxTurns;
3457
+ emitToListeners("SettingsUpdated", void 0);
3458
+ if (trackedState.currentTurn + 1 >= trackedState.maxTurns) {
3459
+ await pushNewItemsIntoSession();
3460
+ emitToListeners("PushNewMessages", [{
3461
+ type: "interrupted",
3462
+ text: `- max turns reached${trackedState.autonomous ? ", thinking for a moment" : ""} -`,
3463
+ version: 1
3464
+ }]);
3465
+ controller.abort();
3466
+ return null;
3467
+ }
3468
+ }
3092
3469
  break;
3093
3470
  case "run_item_stream_event":
3094
3471
  if (event.name === "tool_called") {
@@ -3124,8 +3501,8 @@ async function runAgentForOnePass(agent, session, input, controller) {
3124
3501
  if (trackedState.maxCost !== null) {
3125
3502
  const estimatedTotalCost2 = costTracker.getEstimatedTotalCost(
3126
3503
  stream.state.usage,
3127
- trackedState.model || "gpt-5.2",
3128
- trackedState.compactionModel || "gpt-4o-mini"
3504
+ trackedState.model,
3505
+ trackedState.compactionModel
3129
3506
  );
3130
3507
  if (estimatedTotalCost2 > trackedState.maxCost) {
3131
3508
  emitToListeners("SetInputMode", "denied");
@@ -3136,9 +3513,8 @@ async function runAgentForOnePass(agent, session, input, controller) {
3136
3513
  text: `Cost limit exceeded: $${estimatedTotalCost2.toFixed(4)} > $${trackedState.maxCost.toFixed(4)}`,
3137
3514
  version: 1
3138
3515
  }]);
3139
- if (controller) {
3140
- controller.abort();
3141
- }
3516
+ await pushNewItemsIntoSession();
3517
+ controller.abort();
3142
3518
  process.exitCode = 1;
3143
3519
  await handleExit();
3144
3520
  }
@@ -3157,6 +3533,9 @@ async function runAgentForOnePass(agent, session, input, controller) {
3157
3533
  cachedInputTokens: sessionStats.cachedInputTokens + costTracker.extractCachedTokens(stream.state.usage.inputTokensDetails),
3158
3534
  hasUnknownPrices: sessionStats.hasUnknownPrices
3159
3535
  });
3536
+ trackedState.currentTurn = stream.state._currentTurn;
3537
+ trackedState.maxTurns = stream.state._maxTurns;
3538
+ emitToListeners("SettingsUpdated", void 0);
3160
3539
  if (stream.interruptions?.length) {
3161
3540
  emitToListeners("SetThinking", false);
3162
3541
  emitToListeners("SetInputMode", "approving");
@@ -3237,6 +3616,22 @@ async function runAgentForOnePass(agent, session, input, controller) {
3237
3616
  removeToolListener();
3238
3617
  return null;
3239
3618
  } catch (error) {
3619
+ if (error?.name?.includes("MaxTurnsExceededError")) {
3620
+ emitToListeners("PushNewMessages", [{
3621
+ type: "interrupted",
3622
+ text: `Max turns exceeded. Please ask me to continue or pivot.`,
3623
+ version: 1
3624
+ }]);
3625
+ return null;
3626
+ }
3627
+ if (error?.name?.includes("rate_limit_exceeded")) {
3628
+ emitToListeners("PushNewMessages", [{
3629
+ type: "interrupted",
3630
+ text: `Rate limit reached. Please ask me to continue in a few minutes.`,
3631
+ version: 1
3632
+ }]);
3633
+ return null;
3634
+ }
3240
3635
  showErrorToUser(error, lastToolCallInfo);
3241
3636
  return null;
3242
3637
  }
@@ -3247,6 +3642,7 @@ var AgentManager = class {
3247
3642
  isInitialized = false;
3248
3643
  controller = null;
3249
3644
  queuedUserInputs = [];
3645
+ resumeState = null;
3250
3646
  agent = null;
3251
3647
  session = null;
3252
3648
  initialMessages = [];
@@ -3254,14 +3650,55 @@ var AgentManager = class {
3254
3650
  if (this.isInitialized) {
3255
3651
  return;
3256
3652
  }
3257
- this.agent = new Agent3({
3258
- name: "Harper Agent",
3259
- model: isOpenAIModel(trackedState.model) ? trackedState.model : getModel(trackedState.model),
3260
- modelSettings: getModelSettings(trackedState.model),
3261
- instructions: readAgentsMD() || defaultInstructions(),
3262
- tools: createTools()
3263
- });
3264
- this.session = createSession(trackedState.sessionPath);
3653
+ this.agent = new Agent3({
3654
+ name: "Harper Agent",
3655
+ model: isOpenAIModel(trackedState.model) ? trackedState.model : getModel(trackedState.model),
3656
+ modelSettings: getModelSettings(trackedState.model),
3657
+ instructions: readAgentsMD() || defaultInstructions(),
3658
+ tools: createTools()
3659
+ });
3660
+ this.session = createSession(trackedState.sessionPath);
3661
+ try {
3662
+ const plan = await this.session?.getPlanState?.();
3663
+ if (plan && typeof plan === "object") {
3664
+ if (typeof plan.planDescription === "string") {
3665
+ globalPlanContext.planDescription = plan.planDescription;
3666
+ emitToListeners("SetPlanDescription", plan.planDescription);
3667
+ }
3668
+ if (Array.isArray(plan.planItems)) {
3669
+ globalPlanContext.planItems = plan.planItems;
3670
+ const completedCount = plan.planItems.filter(
3671
+ (it) => it?.status === "done" || it?.status === "not-needed"
3672
+ ).length;
3673
+ const progress = plan.planItems.length === 0 ? 0 : Math.round(completedCount / plan.planItems.length * 100);
3674
+ globalPlanContext.progress = progress;
3675
+ emitToListeners("SetPlanItems", plan.planItems);
3676
+ }
3677
+ }
3678
+ } catch {
3679
+ }
3680
+ try {
3681
+ addListener("SetPlanDescription", async (desc) => {
3682
+ try {
3683
+ await this.session?.setPlanState?.({ planDescription: desc });
3684
+ } catch {
3685
+ }
3686
+ });
3687
+ addListener("SetPlanItems", async (items) => {
3688
+ if (Array.isArray(items)) {
3689
+ globalPlanContext.planItems = items;
3690
+ const completedCount = items.filter((it) => it?.status === "done" || it?.status === "not-needed").length;
3691
+ globalPlanContext.progress = items.length > 0 ? Math.round(completedCount / items.length * 100) : 0;
3692
+ }
3693
+ try {
3694
+ const completedCount = Array.isArray(items) ? items.filter((it) => it?.status === "done" || it?.status === "not-needed").length : 0;
3695
+ const progress = Array.isArray(items) && items.length > 0 ? Math.round(completedCount / items.length * 100) : 0;
3696
+ await this.session?.setPlanState?.({ planItems: items, progress });
3697
+ } catch {
3698
+ }
3699
+ });
3700
+ } catch {
3701
+ }
3265
3702
  if (trackedState.sessionPath) {
3266
3703
  const items = await this.session.getItems();
3267
3704
  if (items.length > 0) {
@@ -3329,12 +3766,54 @@ var AgentManager = class {
3329
3766
  this.queuedUserInputs.push(text);
3330
3767
  }
3331
3768
  }
3332
- async runTask(task) {
3769
+ async runTask(task, isPrompt) {
3333
3770
  this.controller = new AbortController();
3771
+ await this.runCompactionIfWeWereIdle();
3334
3772
  emitToListeners("SetThinking", true);
3335
3773
  let taskOrState = task;
3774
+ const lowerTask = task.toLowerCase();
3775
+ if (this.resumeState && (lowerTask.includes("continue") || lowerTask.includes("keep going") || lowerTask.includes("more") || lowerTask === "y" || lowerTask === "yes")) {
3776
+ taskOrState = this.resumeState;
3777
+ this.resumeState = null;
3778
+ } else {
3779
+ this.resumeState = null;
3780
+ }
3336
3781
  while (taskOrState) {
3337
- taskOrState = await runAgentForOnePass(this.agent, this.session, taskOrState, this.controller);
3782
+ taskOrState = await runAgentForOnePass(this.agent, this.session, taskOrState, this.controller, isPrompt);
3783
+ if (taskOrState && !trackedState.autonomous) {
3784
+ if (taskOrState.getInterruptions().length === 0) {
3785
+ this.resumeState = taskOrState;
3786
+ emitToListeners("SetThinking", false);
3787
+ emitToListeners("PushNewMessages", [{
3788
+ type: "interrupted",
3789
+ text: `Would you like me to continue?`,
3790
+ version: 1
3791
+ }]);
3792
+ break;
3793
+ }
3794
+ }
3795
+ if (trackedState.autonomous && !this.resumeState) {
3796
+ const planItems = globalPlanContext.planItems;
3797
+ const hasPlan = planItems.length > 0;
3798
+ const allDone = hasPlan && planItems.every((item) => item.status === "done" || item.status === "not-needed");
3799
+ if (allDone) {
3800
+ emitToListeners("SetThinking", false);
3801
+ emitToListeners("PushNewMessages", [{
3802
+ type: "agent",
3803
+ text: "Plan 100% accomplished. Exiting autonomous mode.",
3804
+ version: 1
3805
+ }]);
3806
+ await sleep(1e3);
3807
+ await handleExit();
3808
+ } else {
3809
+ await sleep(1e3);
3810
+ await this.session?.runCompaction({ force: true });
3811
+ trackedState.currentTurn = 0;
3812
+ this.resumeState = null;
3813
+ this.controller = new AbortController();
3814
+ taskOrState = "Keep going";
3815
+ }
3816
+ }
3338
3817
  }
3339
3818
  emitToListeners("SetThinking", false);
3340
3819
  if (this.queuedUserInputs.length > 0) {
@@ -3342,6 +3821,22 @@ var AgentManager = class {
3342
3821
  void this.runTask(batched);
3343
3822
  }
3344
3823
  }
3824
+ async runCompactionIfWeWereIdle() {
3825
+ if (this.session) {
3826
+ const lastTs = await this.session.getLatestAddedTimestamp();
3827
+ if (typeof lastTs === "number" && Number.isFinite(lastTs)) {
3828
+ const ONE_HOUR_MS = 60 * 60 * 1e3;
3829
+ const idleMs = Date.now() - lastTs;
3830
+ if (idleMs > ONE_HOUR_MS) {
3831
+ try {
3832
+ await this.session.runCompaction({ force: true });
3833
+ } catch (err) {
3834
+ logError(err);
3835
+ }
3836
+ }
3837
+ }
3838
+ }
3839
+ }
3345
3840
  interrupt() {
3346
3841
  if (this.controller) {
3347
3842
  this.controller.abort();
@@ -3357,7 +3852,7 @@ import "react";
3357
3852
  // ink/components/ChatContent.tsx
3358
3853
  import { Spinner as Spinner2 } from "@inkjs/ui";
3359
3854
  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";
3855
+ import React11, { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMemo11, useRef as useRef2, useState as useState14 } from "react";
3361
3856
 
3362
3857
  // ink/contexts/ChatContext.tsx
3363
3858
  import { createContext as createContext3, useContext as useContext3, useMemo as useMemo3, useState as useState3 } from "react";
@@ -3392,6 +3887,7 @@ var ChatProvider = ({
3392
3887
  const [messages, setMessages] = useState3(getInitialMessages());
3393
3888
  const [userInputMode, setUserInputMode] = useState3("waiting");
3394
3889
  const [isThinking, setIsThinking] = useState3(false);
3890
+ const [isCompacting, setIsCompacting] = useState3(false);
3395
3891
  const [focusedArea, setFocusedArea] = useState3("input");
3396
3892
  useListener("PushNewMessages", (messages2) => {
3397
3893
  setMessages((prev) => {
@@ -3406,6 +3902,9 @@ var ChatProvider = ({
3406
3902
  useListener("SetThinking", (value2) => {
3407
3903
  setIsThinking(Boolean(value2));
3408
3904
  }, []);
3905
+ useListener("SetCompacting", (value2) => {
3906
+ setIsCompacting(Boolean(value2));
3907
+ }, []);
3409
3908
  useListener("InterruptThought", () => {
3410
3909
  setIsThinking(false);
3411
3910
  }, []);
@@ -3433,9 +3932,10 @@ var ChatProvider = ({
3433
3932
  messages,
3434
3933
  userInputMode,
3435
3934
  isThinking,
3935
+ isCompacting,
3436
3936
  focusedArea,
3437
3937
  setFocusedArea
3438
- }), [messages, userInputMode, isThinking, focusedArea]);
3938
+ }), [messages, userInputMode, isThinking, isCompacting, focusedArea]);
3439
3939
  return /* @__PURE__ */ jsx3(ChatContext.Provider, { value, children });
3440
3940
  };
3441
3941
 
@@ -3447,7 +3947,6 @@ function useMessageListener() {
3447
3947
  agentManager.interrupt();
3448
3948
  emitToListeners("PushNewMessages", [
3449
3949
  {
3450
- id: Date.now(),
3451
3950
  type: "interrupted",
3452
3951
  text: "- thought interrupted -",
3453
3952
  version: 1
@@ -3456,7 +3955,7 @@ function useMessageListener() {
3456
3955
  }, []);
3457
3956
  useListener("PushNewMessages", async (messages) => {
3458
3957
  for (const message of messages) {
3459
- if (message.type === "user" && message.text) {
3958
+ if ((message.type === "user" || message.type === "prompt") && message.text) {
3460
3959
  const lowerText = message.text.toLowerCase();
3461
3960
  if (lowerText === "exit" || lowerText === "bye") {
3462
3961
  await handleExit();
@@ -3473,7 +3972,7 @@ function useMessageListener() {
3473
3972
  agentManager.enqueueUserInput(message.text);
3474
3973
  message.handled = true;
3475
3974
  } else if (!message.handled) {
3476
- void agentManager.runTask(message.text);
3975
+ void agentManager.runTask(message.text, message.type === "prompt");
3477
3976
  }
3478
3977
  }
3479
3978
  }
@@ -3483,12 +3982,102 @@ function useMessageListener() {
3483
3982
  // ink/constants/footerHeight.ts
3484
3983
  var footerHeight = 2;
3485
3984
 
3985
+ // ink/contexts/ApprovalContext.tsx
3986
+ import { createContext as createContext4, useCallback as useCallback2, useContext as useContext4, useMemo as useMemo4, useState as useState4 } from "react";
3987
+ import { jsx as jsx4 } from "react/jsx-runtime";
3988
+ var ApprovalContext = createContext4(void 0);
3989
+ var useApproval = () => {
3990
+ const context = useContext4(ApprovalContext);
3991
+ if (!context) {
3992
+ throw new Error("useApproval must be used within an ApprovalProvider");
3993
+ }
3994
+ return context;
3995
+ };
3996
+ var ApprovalProvider = ({ children }) => {
3997
+ const [payload, setPayload] = useState4(null);
3998
+ const [toolInfos] = useState4(/* @__PURE__ */ new Map());
3999
+ const registerToolInfo = useCallback2(
4000
+ (info) => {
4001
+ toolInfos.set(info.callId, info);
4002
+ },
4003
+ [toolInfos]
4004
+ );
4005
+ useListener("OpenApprovalViewer", (p) => {
4006
+ let finalPayload = { ...p, openedAt: Date.now() };
4007
+ if (p.mode === "info" && p.callId) {
4008
+ const info = toolInfos.get(p.callId);
4009
+ if (info) {
4010
+ finalPayload = { ...finalPayload, ...info };
4011
+ }
4012
+ } else if (p.callId) {
4013
+ registerToolInfo({
4014
+ callId: p.callId,
4015
+ type: p.type,
4016
+ path: p.path,
4017
+ diff: p.diff,
4018
+ code: p.code,
4019
+ commands: p.commands
4020
+ });
4021
+ }
4022
+ setPayload(finalPayload);
4023
+ }, [registerToolInfo, toolInfos]);
4024
+ useListener("RegisterToolInfo", (info) => {
4025
+ registerToolInfo(info);
4026
+ }, [registerToolInfo]);
4027
+ useListener("CloseApprovalViewer", () => {
4028
+ setPayload(null);
4029
+ }, []);
4030
+ const value = useMemo4(() => ({
4031
+ payload,
4032
+ setPayload,
4033
+ registerToolInfo
4034
+ }), [payload, registerToolInfo]);
4035
+ return /* @__PURE__ */ jsx4(ApprovalContext.Provider, { value, children });
4036
+ };
4037
+
4038
+ // ink/contexts/PlanContext.tsx
4039
+ import { createContext as createContext5, useContext as useContext5, useMemo as useMemo5, useState as useState5 } from "react";
4040
+ import { jsx as jsx5 } from "react/jsx-runtime";
4041
+ var PlanContext = createContext5(globalPlanContext);
4042
+ var usePlan = () => {
4043
+ const context = useContext5(PlanContext);
4044
+ if (!context) {
4045
+ throw new Error("usePlan must be used within a PlanProvider");
4046
+ }
4047
+ return context;
4048
+ };
4049
+ var PlanProvider = ({
4050
+ children
4051
+ }) => {
4052
+ const [planDescription, setPlanDescription] = useState5(globalPlanContext.planDescription);
4053
+ const [planItems, setPlanItems] = useState5(globalPlanContext.planItems);
4054
+ const [progress, setProgress] = useState5(globalPlanContext.progress);
4055
+ useListener("SetPlanDescription", (newGoal) => {
4056
+ globalPlanContext.planDescription = newGoal;
4057
+ setPlanDescription(newGoal);
4058
+ }, []);
4059
+ useListener("SetPlanItems", (planItems2) => {
4060
+ globalPlanContext.planItems = planItems2;
4061
+ const completedCount = planItems2.filter((item) => item.status === "done" || item.status === "not-needed").length;
4062
+ const progress2 = planItems2.length === 0 ? 0 : Math.round(completedCount / planItems2.length * 100);
4063
+ globalPlanContext.progress = progress2;
4064
+ setPlanItems(planItems2);
4065
+ setProgress(progress2);
4066
+ }, []);
4067
+ const value = useMemo5(() => ({
4068
+ progress,
4069
+ planDescription,
4070
+ planItems
4071
+ }), [progress, planDescription, planItems]);
4072
+ return /* @__PURE__ */ jsx5(PlanContext.Provider, { value, children });
4073
+ };
4074
+
3486
4075
  // ink/library/useTerminalSize.ts
3487
4076
  import { useStdout } from "ink";
3488
- import { useEffect as useEffect3, useState as useState4 } from "react";
4077
+ import { useEffect as useEffect3, useState as useState6 } from "react";
3489
4078
  function useTerminalSize() {
3490
4079
  const { stdout } = useStdout();
3491
- const [size, setSize] = useState4({
4080
+ const [size, setSize] = useState6({
3492
4081
  rows: stdout?.rows ?? 24,
3493
4082
  columns: stdout?.columns ?? 80
3494
4083
  });
@@ -3559,18 +4148,18 @@ function wrapText(text, width) {
3559
4148
 
3560
4149
  // ink/components/ActionsView.tsx
3561
4150
  import { Box as Box3, Text as Text3, useInput } from "ink";
3562
- import { useCallback as useCallback2, useEffect as useEffect5, useState as useState6 } from "react";
4151
+ import { useCallback as useCallback3, useEffect as useEffect5, useState as useState8 } from "react";
3563
4152
 
3564
4153
  // ink/components/ActionItemRow.tsx
3565
4154
  import { Spinner } from "@inkjs/ui";
3566
4155
  import { Box, Text } from "ink";
3567
4156
  import { memo } from "react";
3568
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
4157
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
3569
4158
  var ActionItemRow = memo(
3570
4159
  ({ item, isSelected, isFocused, width }) => {
3571
4160
  const statusColor = item.running ? "yellow" : item.status === "approved" || item.status === "succeeded" || typeof item.exitCode === "number" && item.exitCode === 0 ? "green" : item.status === "denied" || item.status === "failed" || typeof item.exitCode === "number" && item.exitCode !== 0 ? "red" : "cyan";
3572
4161
  const selectionColor = isFocused ? "cyan" : "gray";
3573
- const pipe = /* @__PURE__ */ jsx4(Text, { color: isSelected ? selectionColor : "gray", bold: isSelected, children: isSelected ? "\u2503 " : "\u2502 " });
4162
+ const pipe = /* @__PURE__ */ jsx6(Text, { color: isSelected ? selectionColor : "gray", bold: isSelected, children: isSelected ? "\u2503 " : "\u2502 " });
3574
4163
  const statusLabel = item.running ? " " : item.kind === "approval" ? item.status === "approved" ? " approved" : item.status === "denied" ? " denied" : "" : typeof item.exitCode === "number" ? ` ${item.exitCode}` : "";
3575
4164
  const statusLabelWidth = item.running ? 2 : statusLabel.length;
3576
4165
  const pipeWidth = 3;
@@ -3588,24 +4177,24 @@ var ActionItemRow = memo(
3588
4177
  return /* @__PURE__ */ jsxs(Box, { children: [
3589
4178
  pipe,
3590
4179
  /* @__PURE__ */ jsxs(Box, { flexGrow: 1, children: [
3591
- /* @__PURE__ */ jsx4(Text, { color: statusColor, bold: true, children: item.title }),
4180
+ /* @__PURE__ */ jsx6(Text, { color: statusColor, bold: true, children: item.title }),
3592
4181
  /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3593
4182
  " ",
3594
4183
  displayedDetail
3595
4184
  ] })
3596
4185
  ] }),
3597
- /* @__PURE__ */ jsx4(Box, { children: item.running ? /* @__PURE__ */ jsxs(Box, { children: [
3598
- /* @__PURE__ */ jsx4(Text, { color: "yellow" }),
3599
- /* @__PURE__ */ jsx4(Spinner, { type: "dots" })
3600
- ] }) : /* @__PURE__ */ jsx4(Text, { color: statusColor, bold: true, children: statusLabel }) })
4186
+ /* @__PURE__ */ jsx6(Box, { children: item.running ? /* @__PURE__ */ jsxs(Box, { children: [
4187
+ /* @__PURE__ */ jsx6(Text, { color: "yellow" }),
4188
+ /* @__PURE__ */ jsx6(Spinner, { type: "dots" })
4189
+ ] }) : /* @__PURE__ */ jsx6(Text, { color: statusColor, bold: true, children: statusLabel }) })
3601
4190
  ] });
3602
4191
  }
3603
4192
  );
3604
4193
 
3605
4194
  // ink/components/VirtualList.tsx
3606
4195
  import { Box as Box2, Text as Text2 } from "ink";
3607
- import { forwardRef, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo4, useRef, useState as useState5 } from "react";
3608
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
4196
+ import { forwardRef, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo6, useRef, useState as useState7 } from "react";
4197
+ import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
3609
4198
  function getDefaultKey(item, index) {
3610
4199
  if (item && typeof item === "object") {
3611
4200
  if ("id" in item && (typeof item.id === "string" || typeof item.id === "number")) {
@@ -3635,7 +4224,7 @@ var VirtualListInner = (props, ref) => {
3635
4224
  onViewportChange
3636
4225
  } = props;
3637
4226
  const { rows: terminalRows } = useTerminalSize();
3638
- const resolvedHeight = useMemo4(() => {
4227
+ const resolvedHeight = useMemo6(() => {
3639
4228
  if (typeof propHeight === "number") {
3640
4229
  return propHeight;
3641
4230
  }
@@ -3645,7 +4234,7 @@ var VirtualListInner = (props, ref) => {
3645
4234
  }
3646
4235
  return Math.max(1, terminalRows - reservedLines);
3647
4236
  }, [propHeight, terminalRows, reservedLines]);
3648
- const getItemHeight = useMemo4(() => {
4237
+ const getItemHeight = useMemo6(() => {
3649
4238
  if (getItemHeightProp) {
3650
4239
  return getItemHeightProp;
3651
4240
  }
@@ -3656,14 +4245,14 @@ var VirtualListInner = (props, ref) => {
3656
4245
  }, [getItemHeightProp, fixedItemHeight]);
3657
4246
  const indicatorLines = showOverflowIndicators ? 2 : 0;
3658
4247
  const availableHeight = Math.max(0, resolvedHeight - indicatorLines);
3659
- const [viewportOffset, setViewportOffset] = useState5(0);
4248
+ const [viewportOffset, setViewportOffset] = useState7(0);
3660
4249
  const lastItemsLength = useRef(items.length);
3661
4250
  const viewportOffsetRef = useRef(viewportOffset);
3662
4251
  useEffect4(() => {
3663
4252
  viewportOffsetRef.current = viewportOffset;
3664
4253
  }, [viewportOffset]);
3665
4254
  const clampedSelectedIndex = Math.max(0, Math.min(selectedIndex, items.length - 1));
3666
- const { visibleCount, visibleItems } = useMemo4(() => {
4255
+ const { visibleCount, visibleItems } = useMemo6(() => {
3667
4256
  if (availableHeight <= 0) {
3668
4257
  return { visibleCount: 0, visibleItems: [] };
3669
4258
  }
@@ -3728,7 +4317,7 @@ var VirtualListInner = (props, ref) => {
3728
4317
  }
3729
4318
  }
3730
4319
  }, [clampedSelectedIndex, items, availableHeight, getItemHeight, visibleCount]);
3731
- const viewport = useMemo4(
4320
+ const viewport = useMemo6(
3732
4321
  () => ({
3733
4322
  offset: viewportOffset,
3734
4323
  visibleCount,
@@ -3860,7 +4449,7 @@ var VirtualListInner = (props, ref) => {
3860
4449
  if (count < overflowIndicatorThreshold) {
3861
4450
  return null;
3862
4451
  }
3863
- return /* @__PURE__ */ jsx5(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
4452
+ return /* @__PURE__ */ jsx7(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3864
4453
  "\u25B2 ",
3865
4454
  count,
3866
4455
  " more"
@@ -3870,7 +4459,7 @@ var VirtualListInner = (props, ref) => {
3870
4459
  if (count < overflowIndicatorThreshold) {
3871
4460
  return null;
3872
4461
  }
3873
- return /* @__PURE__ */ jsx5(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
4462
+ return /* @__PURE__ */ jsx7(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3874
4463
  "\u25BC ",
3875
4464
  count,
3876
4465
  " more"
@@ -3879,7 +4468,7 @@ var VirtualListInner = (props, ref) => {
3879
4468
  const topIndicator = renderOverflowTop ?? defaultOverflowTop;
3880
4469
  const bottomIndicator = renderOverflowBottom ?? defaultOverflowBottom;
3881
4470
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: resolvedHeight, children: [
3882
- showOverflowIndicators && /* @__PURE__ */ jsx5(Box2, { height: 1, children: topIndicator(overflowTop) }),
4471
+ showOverflowIndicators && /* @__PURE__ */ jsx7(Box2, { height: 1, children: topIndicator(overflowTop) }),
3883
4472
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, children: [
3884
4473
  visibleItems.map((item, idx) => {
3885
4474
  const actualIndex = viewportOffset + idx;
@@ -3889,20 +4478,20 @@ var VirtualListInner = (props, ref) => {
3889
4478
  index: actualIndex,
3890
4479
  isSelected: actualIndex === clampedSelectedIndex
3891
4480
  };
3892
- return /* @__PURE__ */ jsx5(Box2, { height: getItemHeight(item, actualIndex), overflow: "hidden", children: renderItem(itemProps) }, key);
4481
+ return /* @__PURE__ */ jsx7(Box2, { height: getItemHeight(item, actualIndex), overflow: "hidden", children: renderItem(itemProps) }, key);
3893
4482
  }),
3894
- renderFiller && hSum < availableHeight && /* @__PURE__ */ jsx5(Box2, { flexDirection: "column", flexGrow: 1, children: renderFiller(availableHeight - hSum) })
4483
+ renderFiller && hSum < availableHeight && /* @__PURE__ */ jsx7(Box2, { flexDirection: "column", flexGrow: 1, children: renderFiller(availableHeight - hSum) })
3895
4484
  ] }),
3896
- showOverflowIndicators && /* @__PURE__ */ jsx5(Box2, { height: 1, children: bottomIndicator(overflowBottom) })
4485
+ showOverflowIndicators && /* @__PURE__ */ jsx7(Box2, { height: 1, children: bottomIndicator(overflowBottom) })
3897
4486
  ] });
3898
4487
  };
3899
4488
  var VirtualList = forwardRef(VirtualListInner);
3900
4489
 
3901
4490
  // ink/components/ActionsView.tsx
3902
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
4491
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
3903
4492
  function ActionsView({ height, isFocused }) {
3904
4493
  const { actions } = useActions();
3905
- const [selectedIndex, setSelectedIndex] = useState6(0);
4494
+ const [selectedIndex, setSelectedIndex] = useState8(0);
3906
4495
  const size = useTerminalSize();
3907
4496
  const timelineWidth = Math.floor(size.columns * 0.65);
3908
4497
  const statusWidth = size.columns - timelineWidth;
@@ -3935,8 +4524,8 @@ function ActionsView({ height, isFocused }) {
3935
4524
  }
3936
4525
  }
3937
4526
  });
3938
- const renderOverflowTop = useCallback2((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
3939
- /* @__PURE__ */ jsx6(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
4527
+ const renderOverflowTop = useCallback3((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
4528
+ /* @__PURE__ */ jsx8(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
3940
4529
  count > 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
3941
4530
  " ",
3942
4531
  "\u25B2 ",
@@ -3944,8 +4533,8 @@ function ActionsView({ height, isFocused }) {
3944
4533
  " more"
3945
4534
  ] })
3946
4535
  ] }), []);
3947
- const renderOverflowBottom = useCallback2((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
3948
- /* @__PURE__ */ jsx6(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
4536
+ const renderOverflowBottom = useCallback3((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
4537
+ /* @__PURE__ */ jsx8(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
3949
4538
  count > 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
3950
4539
  " ",
3951
4540
  "\u25BC ",
@@ -3953,15 +4542,15 @@ function ActionsView({ height, isFocused }) {
3953
4542
  " more"
3954
4543
  ] })
3955
4544
  ] }), []);
3956
- const renderFiller = useCallback2((h) => /* @__PURE__ */ jsx6(Box3, { flexDirection: "column", children: Array.from({ length: h }).map((_, i) => /* @__PURE__ */ jsx6(Box3, { children: /* @__PURE__ */ jsx6(Text3, { color: "gray", dimColor: true, children: "\u2502" }) }, i)) }), []);
3957
- const getItemHeight = useCallback2((_item) => 1, []);
4545
+ const renderFiller = useCallback3((h) => /* @__PURE__ */ jsx8(Box3, { flexDirection: "column", children: Array.from({ length: h }).map((_, i) => /* @__PURE__ */ jsx8(Box3, { children: /* @__PURE__ */ jsx8(Text3, { color: "gray", dimColor: true, children: "\u2502" }) }, i)) }), []);
4546
+ const getItemHeight = useCallback3((_item) => 1, []);
3958
4547
  if (actions.length === 0) {
3959
4548
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexGrow: 1, height, children: [
3960
- /* @__PURE__ */ jsx6(Box3, { children: /* @__PURE__ */ jsx6(Text3, { italic: true, color: "gray", children: "No actions yet." }) }),
3961
- Array.from({ length: height - 1 }).map((_, i) => /* @__PURE__ */ jsx6(Box3, { children: /* @__PURE__ */ jsx6(Text3, {}) }, i))
4549
+ /* @__PURE__ */ jsx8(Box3, { children: /* @__PURE__ */ jsx8(Text3, { italic: true, color: "gray", children: "No actions yet." }) }),
4550
+ Array.from({ length: height - 1 }).map((_, i) => /* @__PURE__ */ jsx8(Box3, { children: /* @__PURE__ */ jsx8(Text3, {}) }, i))
3962
4551
  ] });
3963
4552
  }
3964
- return /* @__PURE__ */ jsx6(Box3, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx6(
4553
+ return /* @__PURE__ */ jsx8(Box3, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx8(
3965
4554
  VirtualList,
3966
4555
  {
3967
4556
  items: actions,
@@ -3971,7 +4560,7 @@ function ActionsView({ height, isFocused }) {
3971
4560
  renderOverflowTop,
3972
4561
  renderOverflowBottom,
3973
4562
  renderFiller,
3974
- renderItem: ({ item, isSelected }) => /* @__PURE__ */ jsx6(ActionItemRow, { item, isSelected, isFocused, width: statusWidth - 2 })
4563
+ renderItem: ({ item, isSelected }) => /* @__PURE__ */ jsx8(ActionItemRow, { item, isSelected, isFocused, width: statusWidth - 2 })
3975
4564
  }
3976
4565
  ) });
3977
4566
  }
@@ -3981,11 +4570,11 @@ import { Box as Box4, Text as Text4 } from "ink";
3981
4570
  import "react";
3982
4571
 
3983
4572
  // ink/contexts/CostContext.tsx
3984
- import { createContext as createContext4, useContext as useContext4, useMemo as useMemo5, useState as useState7 } from "react";
3985
- import { jsx as jsx7 } from "react/jsx-runtime";
3986
- var CostContext = createContext4(void 0);
4573
+ import { createContext as createContext6, useContext as useContext6, useMemo as useMemo7, useState as useState9 } from "react";
4574
+ import { jsx as jsx9 } from "react/jsx-runtime";
4575
+ var CostContext = createContext6(void 0);
3987
4576
  var useCost = () => {
3988
- const context = useContext4(CostContext);
4577
+ const context = useContext6(CostContext);
3989
4578
  if (!context) {
3990
4579
  throw new Error("useCost must be used within a CostProvider");
3991
4580
  }
@@ -3994,7 +4583,7 @@ var useCost = () => {
3994
4583
  var CostProvider = ({
3995
4584
  children
3996
4585
  }) => {
3997
- const [cost, setCost] = useState7({
4586
+ const [cost, setCost] = useState9({
3998
4587
  totalCost: 0,
3999
4588
  inputTokens: 0,
4000
4589
  outputTokens: 0,
@@ -4004,37 +4593,37 @@ var CostProvider = ({
4004
4593
  useListener("UpdateCost", (newCost) => {
4005
4594
  setCost(newCost);
4006
4595
  }, []);
4007
- const value = useMemo5(() => ({
4596
+ const value = useMemo7(() => ({
4008
4597
  cost
4009
4598
  }), [cost]);
4010
- return /* @__PURE__ */ jsx7(CostContext.Provider, { value, children });
4599
+ return /* @__PURE__ */ jsx9(CostContext.Provider, { value, children });
4011
4600
  };
4012
4601
 
4013
4602
  // ink/components/CostView.tsx
4014
- import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
4603
+ import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
4015
4604
  function CostView({ isDense = false }) {
4016
4605
  const { cost } = useCost();
4017
4606
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 1, paddingTop: isDense ? 0 : 1, children: [
4018
- /* @__PURE__ */ jsx8(Box4, { marginBottom: isDense ? 0 : 1, children: /* @__PURE__ */ jsx8(Text4, { bold: true, underline: true, color: "cyan", children: "Session Cost Breakdown" }) }),
4607
+ /* @__PURE__ */ jsx10(Box4, { marginBottom: isDense ? 0 : 1, children: /* @__PURE__ */ jsx10(Text4, { bold: true, underline: true, color: "cyan", children: "Session Cost Breakdown" }) }),
4019
4608
  /* @__PURE__ */ jsxs4(Box4, { children: [
4020
- /* @__PURE__ */ jsx8(Box4, { width: 20, children: /* @__PURE__ */ jsx8(Text4, { children: "Total Cost:" }) }),
4609
+ /* @__PURE__ */ jsx10(Box4, { width: 20, children: /* @__PURE__ */ jsx10(Text4, { children: "Total Cost:" }) }),
4021
4610
  /* @__PURE__ */ jsxs4(Text4, { color: "green", children: [
4022
4611
  "$",
4023
4612
  cost.totalCost.toFixed(4)
4024
4613
  ] }),
4025
- cost.hasUnknownPrices && /* @__PURE__ */ jsx8(Text4, { color: "yellow", children: "(est.)" })
4614
+ cost.hasUnknownPrices && /* @__PURE__ */ jsx10(Text4, { color: "yellow", children: "(est.)" })
4026
4615
  ] }),
4027
4616
  /* @__PURE__ */ jsxs4(Box4, { children: [
4028
- /* @__PURE__ */ jsx8(Box4, { width: 20, children: /* @__PURE__ */ jsx8(Text4, { children: "Input Tokens:" }) }),
4029
- /* @__PURE__ */ jsx8(Text4, { children: cost.inputTokens.toLocaleString() })
4617
+ /* @__PURE__ */ jsx10(Box4, { width: 20, children: /* @__PURE__ */ jsx10(Text4, { children: "Input Tokens:" }) }),
4618
+ /* @__PURE__ */ jsx10(Text4, { children: cost.inputTokens.toLocaleString() })
4030
4619
  ] }),
4031
4620
  /* @__PURE__ */ jsxs4(Box4, { children: [
4032
- /* @__PURE__ */ jsx8(Box4, { width: 20, children: /* @__PURE__ */ jsx8(Text4, { children: "Cached Tokens:" }) }),
4033
- /* @__PURE__ */ jsx8(Text4, { color: "gray", children: cost.cachedInputTokens.toLocaleString() })
4621
+ /* @__PURE__ */ jsx10(Box4, { width: 20, children: /* @__PURE__ */ jsx10(Text4, { children: "Cached Tokens:" }) }),
4622
+ /* @__PURE__ */ jsx10(Text4, { color: "gray", children: cost.cachedInputTokens.toLocaleString() })
4034
4623
  ] }),
4035
4624
  /* @__PURE__ */ jsxs4(Box4, { children: [
4036
- /* @__PURE__ */ jsx8(Box4, { width: 20, children: /* @__PURE__ */ jsx8(Text4, { children: "Output Tokens:" }) }),
4037
- /* @__PURE__ */ jsx8(Text4, { children: cost.outputTokens.toLocaleString() })
4625
+ /* @__PURE__ */ jsx10(Box4, { width: 20, children: /* @__PURE__ */ jsx10(Text4, { children: "Output Tokens:" }) }),
4626
+ /* @__PURE__ */ jsx10(Text4, { children: cost.outputTokens.toLocaleString() })
4038
4627
  ] })
4039
4628
  ] });
4040
4629
  }
@@ -4042,7 +4631,7 @@ function CostView({ isDense = false }) {
4042
4631
  // ink/components/MessageLineItem.tsx
4043
4632
  import { Box as Box5, Text as Text5 } from "ink";
4044
4633
  import { memo as memo2 } from "react";
4045
- import { Fragment, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
4634
+ import { Fragment, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
4046
4635
  var MessageLineItem = memo2(
4047
4636
  ({
4048
4637
  item,
@@ -4054,22 +4643,23 @@ var MessageLineItem = memo2(
4054
4643
  const label = item.type === "interrupted" ? "" : item.type.toUpperCase();
4055
4644
  const selectionColor = isFocused ? "cyan" : "gray";
4056
4645
  return /* @__PURE__ */ jsxs5(Box5, { children: [
4057
- /* @__PURE__ */ jsx9(Text5, { color: isSelected ? selectionColor : "gray", dimColor: !isSelected, bold: isSelected, children: isSelected ? "\u2503 " : "\u2502 " }),
4058
- /* @__PURE__ */ jsx9(Box5, { paddingLeft: indent, children: item.isFirstLine ? /* @__PURE__ */ jsxs5(Text5, { children: [
4646
+ /* @__PURE__ */ jsx11(Text5, { color: isSelected ? selectionColor : "gray", dimColor: !isSelected, bold: isSelected, children: isSelected ? "\u2503 " : "\u2502 " }),
4647
+ /* @__PURE__ */ jsx11(Box5, { paddingLeft: indent, children: item.isFirstLine ? /* @__PURE__ */ jsxs5(Text5, { children: [
4059
4648
  label && /* @__PURE__ */ jsxs5(Text5, { bold: true, color, children: [
4060
4649
  label,
4061
4650
  item.type === "tool" ? ": " : item.isArgsLine ? " args: " : ": "
4062
4651
  ] }),
4063
- item.type === "tool" && item.toolName ? /* @__PURE__ */ jsx9(Text5, { children: item.text.startsWith(item.toolName) ? /* @__PURE__ */ jsxs5(Fragment, { children: [
4064
- /* @__PURE__ */ jsx9(Text5, { color: "white", bold: true, children: item.toolName }),
4065
- /* @__PURE__ */ jsx9(Text5, { dimColor: true, italic: true, children: item.text.slice(item.toolName.length) })
4066
- ] }) : /* @__PURE__ */ jsx9(Text5, { dimColor: true, italic: true, children: item.text.startsWith(" ") ? item.text : ` ${item.text}` }) }) : /* @__PURE__ */ jsx9(Text5, { dimColor: !!item.isArgsLine, italic: !!item.isArgsLine, children: item.text })
4067
- ] }) : /* @__PURE__ */ jsx9(Text5, { children: item.type === "tool" && item.toolName ? /* @__PURE__ */ jsx9(Text5, { dimColor: true, italic: true, children: item.text }) : /* @__PURE__ */ jsx9(Text5, { dimColor: !!item.isArgsLine, italic: !!item.isArgsLine, children: item.text }) }) })
4652
+ item.type === "tool" && item.toolName ? /* @__PURE__ */ jsx11(Text5, { children: item.text.startsWith(item.toolName) ? /* @__PURE__ */ jsxs5(Fragment, { children: [
4653
+ /* @__PURE__ */ jsx11(Text5, { color: "white", bold: true, children: item.toolName }),
4654
+ /* @__PURE__ */ jsx11(Text5, { dimColor: true, italic: true, children: item.text.slice(item.toolName.length) })
4655
+ ] }) : /* @__PURE__ */ jsx11(Text5, { dimColor: true, italic: true, children: item.text.startsWith(" ") ? item.text : ` ${item.text}` }) }) : /* @__PURE__ */ jsx11(Text5, { dimColor: !!item.isArgsLine, italic: !!item.isArgsLine, children: item.text })
4656
+ ] }) : /* @__PURE__ */ jsx11(Text5, { children: item.type === "tool" && item.toolName ? /* @__PURE__ */ jsx11(Text5, { dimColor: true, italic: true, children: item.text }) : /* @__PURE__ */ jsx11(Text5, { dimColor: !!item.isArgsLine, italic: !!item.isArgsLine, children: item.text }) }) })
4068
4657
  ] });
4069
4658
  }
4070
4659
  );
4071
4660
  function messageTypeToColor(type) {
4072
4661
  switch (type) {
4662
+ case "prompt":
4073
4663
  case "user":
4074
4664
  return "green";
4075
4665
  case "agent":
@@ -4085,51 +4675,12 @@ function messageTypeToColor(type) {
4085
4675
  import { ProgressBar } from "@inkjs/ui";
4086
4676
  import { Box as Box6, Text as Text6 } from "ink";
4087
4677
  import "react";
4088
-
4089
- // ink/contexts/PlanContext.tsx
4090
- import { createContext as createContext5, useContext as useContext5, useMemo as useMemo6, useState as useState8 } from "react";
4091
- import { jsx as jsx10 } from "react/jsx-runtime";
4092
- var PlanContext = createContext5(globalPlanContext);
4093
- var usePlan = () => {
4094
- const context = useContext5(PlanContext);
4095
- if (!context) {
4096
- throw new Error("usePlan must be used within a PlanProvider");
4097
- }
4098
- return context;
4099
- };
4100
- var PlanProvider = ({
4101
- children
4102
- }) => {
4103
- const [planDescription, setPlanDescription] = useState8(globalPlanContext.planDescription);
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
- }, []);
4110
- useListener("SetPlanItems", (planItems2) => {
4111
- globalPlanContext.planItems = planItems2;
4112
- const completedCount = planItems2.filter((item) => item.status === "done" || item.status === "not-needed").length;
4113
- const progress2 = planItems2.length === 0 ? 0 : Math.round(completedCount / planItems2.length * 100);
4114
- globalPlanContext.progress = progress2;
4115
- setPlanItems(planItems2);
4116
- setProgress(progress2);
4117
- }, []);
4118
- const value = useMemo6(() => ({
4119
- progress,
4120
- planDescription,
4121
- planItems
4122
- }), [progress, planDescription, planItems]);
4123
- return /* @__PURE__ */ jsx10(PlanContext.Provider, { value, children });
4124
- };
4125
-
4126
- // ink/components/PlanView.tsx
4127
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
4678
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
4128
4679
  function PlanView() {
4129
4680
  const { planDescription, planItems, progress } = usePlan();
4130
4681
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
4131
- /* @__PURE__ */ jsx11(Box6, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx11(Text6, { italic: true, children: planDescription }) }),
4132
- /* @__PURE__ */ jsx11(Box6, { flexDirection: "column", flexGrow: 1, children: planItems.map((planItem) => /* @__PURE__ */ jsx11(Box6, { children: /* @__PURE__ */ jsxs6(
4682
+ /* @__PURE__ */ jsx12(Box6, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx12(Text6, { italic: true, children: planDescription }) }),
4683
+ /* @__PURE__ */ jsx12(Box6, { flexDirection: "column", flexGrow: 1, children: planItems.map((planItem) => /* @__PURE__ */ jsx12(Box6, { children: /* @__PURE__ */ jsxs6(
4133
4684
  Text6,
4134
4685
  {
4135
4686
  color: planItem.status === "done" ? "green" : planItem.status === "in-progress" ? "yellow" : planItem.status === "not-needed" ? "gray" : "white",
@@ -4146,22 +4697,22 @@ function PlanView() {
4146
4697
  progress,
4147
4698
  "%"
4148
4699
  ] }),
4149
- /* @__PURE__ */ jsx11(ProgressBar, { value: progress })
4700
+ /* @__PURE__ */ jsx12(ProgressBar, { value: progress })
4150
4701
  ] })
4151
4702
  ] });
4152
4703
  }
4153
4704
 
4154
4705
  // ink/components/SettingsView.tsx
4155
4706
  import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
4156
- import path7 from "path";
4157
- import { useEffect as useEffect6, useMemo as useMemo8, useState as useState10 } from "react";
4707
+ import path8 from "path";
4708
+ import { useEffect as useEffect6, useMemo as useMemo9, useState as useState11 } from "react";
4158
4709
 
4159
4710
  // ink/contexts/SettingsContext.tsx
4160
- import { createContext as createContext6, useContext as useContext6, useMemo as useMemo7, useState as useState9 } from "react";
4161
- import { jsx as jsx12 } from "react/jsx-runtime";
4162
- var SettingsContext = createContext6(void 0);
4711
+ import { createContext as createContext7, useContext as useContext7, useMemo as useMemo8, useState as useState10 } from "react";
4712
+ import { jsx as jsx13 } from "react/jsx-runtime";
4713
+ var SettingsContext = createContext7(void 0);
4163
4714
  var useSettings = () => {
4164
- const context = useContext6(SettingsContext);
4715
+ const context = useContext7(SettingsContext);
4165
4716
  if (!context) {
4166
4717
  throw new Error("useSettings must be used within a SettingsProvider");
4167
4718
  }
@@ -4170,17 +4721,18 @@ var useSettings = () => {
4170
4721
  var SettingsProvider = ({
4171
4722
  children
4172
4723
  }) => {
4173
- const [version, setVersion] = useState9(0);
4724
+ const [version, setVersion] = useState10(0);
4174
4725
  useListener("SettingsUpdated", () => {
4175
4726
  setVersion((v) => v + 1);
4176
4727
  }, []);
4177
- const value = useMemo7(() => ({
4728
+ const value = useMemo8(() => ({
4178
4729
  version,
4179
4730
  model: trackedState.model,
4180
4731
  compactionModel: trackedState.compactionModel,
4181
4732
  sessionPath: trackedState.sessionPath,
4182
4733
  cwd: trackedState.cwd,
4183
4734
  useFlexTier: trackedState.useFlexTier,
4735
+ currentTurn: trackedState.currentTurn,
4184
4736
  maxTurns: trackedState.maxTurns,
4185
4737
  maxCost: trackedState.maxCost,
4186
4738
  autoApproveCodeInterpreter: trackedState.autoApproveCodeInterpreter,
@@ -4190,11 +4742,11 @@ var SettingsProvider = ({
4190
4742
  rateLimitThreshold: trackedState.rateLimitThreshold,
4191
4743
  rateLimitStatus: rateLimitTracker.getStatus()
4192
4744
  }), [version]);
4193
- return /* @__PURE__ */ jsx12(SettingsContext.Provider, { value, children });
4745
+ return /* @__PURE__ */ jsx13(SettingsContext.Provider, { value, children });
4194
4746
  };
4195
4747
 
4196
4748
  // ink/components/SettingsView.tsx
4197
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4749
+ import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
4198
4750
  function SettingsView({ isDense = false }) {
4199
4751
  const {
4200
4752
  model,
@@ -4202,6 +4754,7 @@ function SettingsView({ isDense = false }) {
4202
4754
  sessionPath,
4203
4755
  cwd,
4204
4756
  useFlexTier,
4757
+ currentTurn,
4205
4758
  maxTurns,
4206
4759
  maxCost,
4207
4760
  autoApproveCodeInterpreter: initialAutoApproveCodeInterpreter,
@@ -4212,10 +4765,10 @@ function SettingsView({ isDense = false }) {
4212
4765
  rateLimitStatus
4213
4766
  } = useSettings();
4214
4767
  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);
4768
+ const [autoApproveCodeInterpreter, setAutoApproveCodeInterpreter] = useState11(initialAutoApproveCodeInterpreter);
4769
+ const [autoApprovePatches, setAutoApprovePatches] = useState11(initialAutoApprovePatches);
4770
+ const [autoApproveShell, setAutoApproveShell] = useState11(initialAutoApproveShell);
4771
+ const [monitorRateLimits, setMonitorRateLimits] = useState11(initialMonitorRateLimits);
4219
4772
  useEffect6(() => {
4220
4773
  setAutoApproveCodeInterpreter(initialAutoApproveCodeInterpreter);
4221
4774
  }, [initialAutoApproveCodeInterpreter]);
@@ -4228,8 +4781,8 @@ function SettingsView({ isDense = false }) {
4228
4781
  useEffect6(() => {
4229
4782
  setMonitorRateLimits(initialMonitorRateLimits);
4230
4783
  }, [initialMonitorRateLimits]);
4231
- const [selectedIndex, setSelectedIndex] = useState10(0);
4232
- const selectableOptions = useMemo8(() => [
4784
+ const [selectedIndex, setSelectedIndex] = useState11(0);
4785
+ const selectableOptions = useMemo9(() => [
4233
4786
  {
4234
4787
  label: "Code Interpreter",
4235
4788
  value: autoApproveCodeInterpreter,
@@ -4292,83 +4845,95 @@ function SettingsView({ isDense = false }) {
4292
4845
  }
4293
4846
  }
4294
4847
  });
4295
- const displayPath = useMemo8(() => {
4848
+ const displayPath = useMemo9(() => {
4296
4849
  if (!sessionPath) {
4297
4850
  return null;
4298
4851
  }
4299
- const relative = path7.relative(cwd, sessionPath);
4300
- if (!relative.startsWith("..") && !path7.isAbsolute(relative)) {
4852
+ const relative = path8.relative(cwd, sessionPath);
4853
+ if (!relative.startsWith("..") && !path8.isAbsolute(relative)) {
4301
4854
  return `./${relative}`;
4302
4855
  }
4303
4856
  return sessionPath;
4304
4857
  }, [cwd, sessionPath]);
4305
4858
  const marginBottom = isDense ? 0 : 1;
4306
4859
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", flexGrow: 1, paddingLeft: 1, children: [
4307
- /* @__PURE__ */ jsx13(Box7, { marginBottom, children: /* @__PURE__ */ jsx13(Text7, { bold: true, underline: true, color: "cyan", children: "Configuration" }) }),
4860
+ /* @__PURE__ */ jsx14(Box7, { marginBottom, children: /* @__PURE__ */ jsx14(Text7, { bold: true, underline: true, color: "cyan", children: "Configuration" }) }),
4308
4861
  /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
4309
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Model:" }) }),
4310
- /* @__PURE__ */ jsx13(Text7, { children: model })
4862
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "Model:" }) }),
4863
+ /* @__PURE__ */ jsx14(Text7, { children: model })
4311
4864
  ] }),
4312
4865
  /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
4313
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Compaction Model:" }) }),
4314
- /* @__PURE__ */ jsx13(Text7, { children: compactionModel })
4866
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "Compaction Model:" }) }),
4867
+ /* @__PURE__ */ jsx14(Text7, { children: compactionModel })
4315
4868
  ] }),
4316
4869
  displayPath && /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
4317
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Session Path:" }) }),
4318
- /* @__PURE__ */ jsx13(Text7, { children: displayPath })
4870
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "Session Path:" }) }),
4871
+ /* @__PURE__ */ jsx14(Text7, { children: displayPath })
4319
4872
  ] }),
4320
4873
  /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
4321
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Flex Tier:" }) }),
4322
- /* @__PURE__ */ jsx13(Text7, { children: useFlexTier ? "Yes" : "No" })
4874
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "Flex Tier:" }) }),
4875
+ /* @__PURE__ */ jsx14(Text7, { children: useFlexTier ? "Yes" : "No" })
4323
4876
  ] }),
4324
4877
  /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
4325
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Max Turns:" }) }),
4326
- /* @__PURE__ */ jsx13(Text7, { children: maxTurns })
4878
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "Current Turn:" }) }),
4879
+ /* @__PURE__ */ jsxs7(Text7, { children: [
4880
+ currentTurn,
4881
+ " / ",
4882
+ maxTurns
4883
+ ] })
4327
4884
  ] }),
4328
4885
  /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
4329
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Rate Limit Threshold:" }) }),
4886
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "Rate Limit @:" }) }),
4330
4887
  /* @__PURE__ */ jsxs7(Text7, { children: [
4331
4888
  initialRateLimitThreshold,
4332
4889
  "%"
4333
4890
  ] })
4334
4891
  ] }),
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:" }) }),
4892
+ rateLimitStatus && /* @__PURE__ */ jsxs7(Box7, { marginBottom, flexDirection: "column", children: [
4893
+ rateLimitStatus.retryAfter !== null && /* @__PURE__ */ jsxs7(Box7, { children: [
4894
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { color: "red", children: "Retry After:" }) }),
4895
+ /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
4896
+ rateLimitStatus.retryAfter,
4897
+ "s"
4898
+ ] })
4899
+ ] }),
4900
+ (rateLimitStatus.limitRequests !== null || rateLimitStatus.remainingRequests !== null) && /* @__PURE__ */ jsxs7(Box7, { children: [
4901
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "RPM Limit:" }) }),
4338
4902
  /* @__PURE__ */ jsxs7(Text7, { children: [
4339
- rateLimitStatus.remainingRequests,
4903
+ rateLimitStatus.remainingRequests ?? "?",
4340
4904
  " / ",
4341
- rateLimitStatus.limitRequests,
4905
+ rateLimitStatus.limitRequests ?? "?",
4342
4906
  " (Reset:",
4343
4907
  " ",
4344
- rateLimitStatus.resetRequests,
4908
+ rateLimitStatus.resetRequests ?? "?",
4345
4909
  ")"
4346
4910
  ] })
4347
4911
  ] }),
4348
- /* @__PURE__ */ jsxs7(Box7, { children: [
4349
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "TPM Limit:" }) }),
4912
+ rateLimitStatus.remainingTokens !== null && /* @__PURE__ */ jsxs7(Box7, { children: [
4913
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "TPM Limit:" }) }),
4350
4914
  /* @__PURE__ */ jsxs7(Text7, { children: [
4351
- rateLimitStatus.remainingTokens,
4915
+ rateLimitStatus.remainingTokens ?? "?",
4352
4916
  " / ",
4353
- rateLimitStatus.limitTokens,
4354
- " (Reset: ",
4355
- rateLimitStatus.resetTokens,
4917
+ rateLimitStatus.limitTokens ?? "?",
4918
+ " (Reset:",
4919
+ " ",
4920
+ rateLimitStatus.resetTokens ?? "?",
4356
4921
  ")"
4357
4922
  ] })
4358
4923
  ] })
4359
4924
  ] }),
4360
4925
  maxCost !== null && /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
4361
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Max Cost:" }) }),
4926
+ /* @__PURE__ */ jsx14(Box7, { width: 20, children: /* @__PURE__ */ jsx14(Text7, { children: "Max Cost:" }) }),
4362
4927
  /* @__PURE__ */ jsxs7(Text7, { children: [
4363
4928
  "$",
4364
4929
  maxCost.toFixed(2)
4365
4930
  ] })
4366
4931
  ] }),
4367
4932
  /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
4368
- /* @__PURE__ */ jsx13(Text7, { bold: true, children: "Auto-approvals (up/down & space to toggle):" }),
4933
+ /* @__PURE__ */ jsx14(Text7, { bold: true, children: "Auto-approvals (up/down & space to toggle):" }),
4369
4934
  selectableOptions.map((option, index) => {
4370
4935
  const isSelected = index === selectedIndex && focusedArea === "status";
4371
- return /* @__PURE__ */ jsx13(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "cyan" : "white", children: [
4936
+ return /* @__PURE__ */ jsx14(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "cyan" : "white", children: [
4372
4937
  isSelected ? "> " : " ",
4373
4938
  option.label,
4374
4939
  "isAction" in option ? "" : `: ${option.value ? "ON" : "OFF"}`
@@ -4380,13 +4945,13 @@ function SettingsView({ isDense = false }) {
4380
4945
 
4381
4946
  // ink/components/UserInput.tsx
4382
4947
  import { Box as Box9, Text as Text9 } from "ink";
4383
- import { useCallback as useCallback3, useState as useState12 } from "react";
4948
+ import { useCallback as useCallback4, useState as useState13 } from "react";
4384
4949
 
4385
4950
  // ink/components/BlinkingTextInput.tsx
4386
4951
  import chalk3 from "chalk";
4387
4952
  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";
4389
- import { jsx as jsx14 } from "react/jsx-runtime";
4953
+ import { useEffect as useEffect7, useMemo as useMemo10, useReducer, useState as useState12 } from "react";
4954
+ import { jsx as jsx15 } from "react/jsx-runtime";
4390
4955
  var reducer = (state, action) => {
4391
4956
  switch (action.type) {
4392
4957
  case "move-cursor-left":
@@ -4439,7 +5004,7 @@ function BlinkingTextInput({
4439
5004
  previousValue: defaultValue,
4440
5005
  cursorOffset: defaultValue.length
4441
5006
  });
4442
- const [isCursorVisible, setIsCursorVisible] = useState11(true);
5007
+ const [isCursorVisible, setIsCursorVisible] = useState12(true);
4443
5008
  useEffect7(() => {
4444
5009
  if (isDisabled) {
4445
5010
  setIsCursorVisible(false);
@@ -4455,7 +5020,7 @@ function BlinkingTextInput({
4455
5020
  onChange?.(state.value);
4456
5021
  }
4457
5022
  }, [state.value, state.previousValue, onChange]);
4458
- const suggestion = useMemo9(() => {
5023
+ const suggestion = useMemo10(() => {
4459
5024
  if (state.value.length === 0) {
4460
5025
  return "";
4461
5026
  }
@@ -4491,10 +5056,10 @@ function BlinkingTextInput({
4491
5056
  },
4492
5057
  { isActive: !isDisabled }
4493
5058
  );
4494
- const renderedValue = useMemo9(() => {
5059
+ const renderedValue = useMemo10(() => {
4495
5060
  if (isDisabled) {
4496
5061
  const lines2 = (state.value || (placeholder ? chalk3.dim(placeholder) : "")).split("\n");
4497
- return /* @__PURE__ */ jsx14(Box8, { flexDirection: "column", children: lines2.map((line, i) => /* @__PURE__ */ jsx14(Box8, { children: /* @__PURE__ */ jsx14(Text8, { children: line }) }, i)) });
5062
+ return /* @__PURE__ */ jsx15(Box8, { flexDirection: "column", children: lines2.map((line, i) => /* @__PURE__ */ jsx15(Box8, { children: /* @__PURE__ */ jsx15(Text8, { children: line }) }, i)) });
4498
5063
  }
4499
5064
  if (state.value.length === 0) {
4500
5065
  let displayContent = "";
@@ -4503,7 +5068,7 @@ function BlinkingTextInput({
4503
5068
  } else {
4504
5069
  displayContent = isCursorVisible ? chalk3.inverse(" ") : " ";
4505
5070
  }
4506
- return /* @__PURE__ */ jsx14(Box8, { flexGrow: 1, minWidth: 1, children: /* @__PURE__ */ jsx14(Text8, { wrap: "end", children: displayContent }) });
5071
+ return /* @__PURE__ */ jsx15(Box8, { flexGrow: 1, minWidth: 1, children: /* @__PURE__ */ jsx15(Text8, { wrap: "end", children: displayContent }) });
4507
5072
  }
4508
5073
  const lines = state.value.split("\n");
4509
5074
  const result = [];
@@ -4529,19 +5094,19 @@ function BlinkingTextInput({
4529
5094
  lineContent += chalk3.dim(suggestion);
4530
5095
  }
4531
5096
  result.push(
4532
- /* @__PURE__ */ jsx14(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx14(Text8, { wrap: "end", children: lineContent }) }, lineIndex)
5097
+ /* @__PURE__ */ jsx15(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx15(Text8, { wrap: "end", children: lineContent }) }, lineIndex)
4533
5098
  );
4534
5099
  if (lineIndex < lines.length - 1) {
4535
5100
  totalOffset++;
4536
5101
  }
4537
5102
  });
4538
- return /* @__PURE__ */ jsx14(Box8, { flexDirection: "column", flexGrow: 1, children: result });
5103
+ return /* @__PURE__ */ jsx15(Box8, { flexDirection: "column", flexGrow: 1, children: result });
4539
5104
  }, [state.value, state.cursorOffset, suggestion, isCursorVisible, isDisabled, placeholder]);
4540
- return /* @__PURE__ */ jsx14(Box8, { flexGrow: 1, minWidth: 1, children: renderedValue });
5105
+ return /* @__PURE__ */ jsx15(Box8, { flexGrow: 1, minWidth: 1, children: renderedValue });
4541
5106
  }
4542
5107
 
4543
5108
  // ink/components/UserInput.tsx
4544
- import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
5109
+ import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
4545
5110
  var modeSuggestion = {
4546
5111
  approved: [],
4547
5112
  approving: ["yes", "approve", "no", "deny"],
@@ -4550,14 +5115,14 @@ var modeSuggestion = {
4550
5115
  };
4551
5116
  function UserInput() {
4552
5117
  const { userInputMode, focusedArea } = useChat();
4553
- const [resetKey, setResetKey] = useState12(0);
4554
- const [, setBlankLines] = useState12(0);
5118
+ const [resetKey, setResetKey] = useState13(0);
5119
+ const [, setBlankLines] = useState13(0);
4555
5120
  useListener("ClearUserInput", () => {
4556
5121
  setResetKey((prev) => prev + 1);
4557
5122
  }, []);
4558
5123
  const borderColor = focusedArea === "input" ? "cyan" : "gray";
4559
5124
  const placeholder = calculatePlaceholder(userInputMode);
4560
- const onSubmitResetKey = useCallback3((value) => {
5125
+ const onSubmitResetKey = useCallback4((value) => {
4561
5126
  if (value.length) {
4562
5127
  setResetKey((prev) => prev + 1);
4563
5128
  emitToListeners("PushNewMessages", [{ type: "user", text: value.trim(), version: 1 }]);
@@ -4583,8 +5148,8 @@ function UserInput() {
4583
5148
  alignItems: "flex-start",
4584
5149
  gap: 1,
4585
5150
  children: [
4586
- /* @__PURE__ */ jsx15(Box9, { marginLeft: 1, height: 1, children: /* @__PURE__ */ jsx15(Text9, { bold: true, color: borderColor, children: "\u276F" }) }),
4587
- /* @__PURE__ */ jsx15(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx15(
5151
+ /* @__PURE__ */ jsx16(Box9, { marginLeft: 1, height: 1, children: /* @__PURE__ */ jsx16(Text9, { bold: true, color: borderColor, children: "\u276F" }) }),
5152
+ /* @__PURE__ */ jsx16(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx16(
4588
5153
  BlinkingTextInput,
4589
5154
  {
4590
5155
  placeholder,
@@ -4613,13 +5178,24 @@ function calculatePlaceholder(mode) {
4613
5178
  }
4614
5179
 
4615
5180
  // ink/components/ChatContent.tsx
4616
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
5181
+ import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
4617
5182
  function ChatContent() {
4618
- const { messages, isThinking, focusedArea, setFocusedArea } = useChat();
5183
+ const { messages, isThinking, isCompacting, focusedArea, setFocusedArea } = useChat();
5184
+ const { payload } = useApproval();
4619
5185
  const size = useTerminalSize();
4620
5186
  useMessageListener();
4621
- const [activeTab, setActiveTab] = useState13("settings");
4622
- const [selectedIndex, setSelectedIndex] = useState13(0);
5187
+ const [activeTab, setActiveTab] = useState14("settings");
5188
+ const [userHasSwitchedTab, setUserHasSwitchedTab] = useState14(false);
5189
+ const { planDescription, planItems } = usePlan();
5190
+ useEffect8(() => {
5191
+ if (!userHasSwitchedTab && activeTab === "settings") {
5192
+ const hasPlan = planDescription.trim().length > 0 || planItems.length > 0;
5193
+ if (hasPlan) {
5194
+ setActiveTab("planDescription");
5195
+ }
5196
+ }
5197
+ }, [planDescription, planItems, userHasSwitchedTab, activeTab]);
5198
+ const [selectedIndex, setSelectedIndex] = useState14(0);
4623
5199
  const wrapCache = useRef2(
4624
5200
  /* @__PURE__ */ new Map()
4625
5201
  );
@@ -4663,27 +5239,34 @@ function ChatContent() {
4663
5239
  const currentIndex = tabNames.indexOf(activeTab);
4664
5240
  if (key.leftArrow) {
4665
5241
  const nextIndex = (currentIndex - 1 + tabNames.length) % tabNames.length;
4666
- setActiveTab(tabNames[nextIndex]);
5242
+ const newTab = tabNames[nextIndex];
5243
+ setActiveTab(newTab);
5244
+ setUserHasSwitchedTab(true);
4667
5245
  } else {
4668
5246
  const nextIndex = (currentIndex + 1) % tabNames.length;
4669
- setActiveTab(tabNames[nextIndex]);
5247
+ const newTab = tabNames[nextIndex];
5248
+ setActiveTab(newTab);
5249
+ setUserHasSwitchedTab(true);
4670
5250
  }
4671
5251
  }
4672
5252
  }
4673
5253
  if (key.ctrl && input === "x") {
4674
5254
  void handleExit();
4675
5255
  }
4676
- if (key.escape && isThinking) {
5256
+ if (key.escape && isThinking && focusedArea === "input" && !payload) {
4677
5257
  emitToListeners("InterruptThought", void 0);
4678
5258
  }
4679
5259
  });
4680
5260
  const contentHeight = size.rows - footerHeight;
4681
5261
  const timelineWidth = Math.floor(size.columns * 0.65);
4682
5262
  const statusWidth = size.columns - timelineWidth;
4683
- const labelWidthFor = useCallback4((type) => {
5263
+ const labelWidthFor = useCallback5((type) => {
4684
5264
  if (type === "agent") {
4685
5265
  return 7;
4686
5266
  }
5267
+ if (type === "prompt") {
5268
+ return 7;
5269
+ }
4687
5270
  if (type === "user") {
4688
5271
  return 6;
4689
5272
  }
@@ -4693,7 +5276,7 @@ function ChatContent() {
4693
5276
  return 6;
4694
5277
  }, []);
4695
5278
  const availableTextWidth = timelineWidth - 4;
4696
- const lineItems = useMemo10(() => {
5279
+ const lineItems = useMemo11(() => {
4697
5280
  const acc = [];
4698
5281
  for (const msg of messages) {
4699
5282
  const labelWidth = labelWidthFor(msg.type);
@@ -4772,14 +5355,15 @@ function ChatContent() {
4772
5355
  setSelectedIndex(lineItems.length - 1);
4773
5356
  }
4774
5357
  }, [size.rows, size.columns]);
4775
- const tabs = useMemo10(() => [
5358
+ const tabs = useMemo11(() => [
4776
5359
  { name: "settings", label: "SETTINGS" },
4777
5360
  { name: "planDescription", label: "PLAN" },
4778
5361
  { name: "actions", label: "ACTIONS" }
4779
5362
  ], []);
4780
5363
  const timelineTitle = "TIMELINE:";
4781
5364
  const timelineHeaderWidth = timelineWidth - 1;
4782
- const timelineDashes = timelineHeaderWidth - timelineTitle.length - (isThinking ? 5 : 0);
5365
+ const showSpinner = isCompacting || isThinking;
5366
+ const timelineDashes = timelineHeaderWidth - timelineTitle.length - (showSpinner ? 5 : 0);
4783
5367
  const tabsTotalWidth = tabs.reduce((acc, t) => acc + t.label.length + 2, 0) + (tabs.length - 1);
4784
5368
  const statusDashes = Math.max(0, statusWidth - tabsTotalWidth - 2);
4785
5369
  const timelineColor = focusedArea === "timeline" ? "cyan" : "gray";
@@ -4790,16 +5374,16 @@ function ChatContent() {
4790
5374
  const junctionLeftColor = focusedArea === "timeline" || focusedArea === "input" ? "cyan" : "gray";
4791
5375
  const junctionMiddleColor = focusedArea === "timeline" || focusedArea === "status" || focusedArea === "input" ? "cyan" : "gray";
4792
5376
  const junctionRightColor = focusedArea === "status" || focusedArea === "input" ? "cyan" : "gray";
4793
- const timelinePipeFiller = useCallback4((count) => /* @__PURE__ */ jsx16(Box10, { flexDirection: "column", children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx16(Box10, { children: /* @__PURE__ */ jsx16(Text10, { color: "gray", dimColor: true, children: "\u2502" }) }, i)) }), []);
5377
+ const timelinePipeFiller = useCallback5((count) => /* @__PURE__ */ jsx17(Box10, { flexDirection: "column", children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx17(Box10, { children: /* @__PURE__ */ jsx17(Text10, { color: "gray", dimColor: true, children: "\u2502" }) }, i)) }), []);
4794
5378
  return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", height: size.rows, padding: 0, children: [
4795
5379
  /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: 1, children: [
4796
- /* @__PURE__ */ jsx16(Text10, { color: timelineColor, children: "\u256D" }),
4797
- /* @__PURE__ */ jsx16(Text10, { bold: true, color: timelineColor, children: timelineTitle }),
4798
- isThinking && /* @__PURE__ */ jsx16(Box10, { paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx16(Spinner2, { type: "clock" }) }),
4799
- /* @__PURE__ */ jsx16(Text10, { color: timelineColor, children: "\u2500".repeat(Math.max(0, timelineDashes)) }),
4800
- /* @__PURE__ */ jsx16(Text10, { color: dividerColor, children: "\u252C" }),
5380
+ /* @__PURE__ */ jsx17(Text10, { color: timelineColor, children: "\u256D" }),
5381
+ /* @__PURE__ */ jsx17(Text10, { bold: true, color: timelineColor, children: timelineTitle }),
5382
+ showSpinner && /* @__PURE__ */ jsx17(Box10, { paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx17(Spinner2, { type: "clock" }) }),
5383
+ /* @__PURE__ */ jsx17(Text10, { color: timelineColor, children: "\u2500".repeat(Math.max(0, timelineDashes)) }),
5384
+ /* @__PURE__ */ jsx17(Text10, { color: dividerColor, children: "\u252C" }),
4801
5385
  tabs.map((tab, i) => /* @__PURE__ */ jsxs9(React11.Fragment, { children: [
4802
- /* @__PURE__ */ jsx16(
5386
+ /* @__PURE__ */ jsx17(
4803
5387
  Text10,
4804
5388
  {
4805
5389
  color: activeTab === tab.name ? "black" : statusColor,
@@ -4808,7 +5392,7 @@ function ChatContent() {
4808
5392
  children: ` ${tab.label} `
4809
5393
  }
4810
5394
  ),
4811
- i < tabs.length - 1 && /* @__PURE__ */ jsx16(Text10, { color: statusColor, children: "|" })
5395
+ i < tabs.length - 1 && /* @__PURE__ */ jsx17(Text10, { color: statusColor, children: "|" })
4812
5396
  ] }, tab.name)),
4813
5397
  /* @__PURE__ */ jsxs9(Text10, { color: statusColor, children: [
4814
5398
  "\u2500".repeat(statusDashes),
@@ -4816,22 +5400,22 @@ function ChatContent() {
4816
5400
  ] })
4817
5401
  ] }),
4818
5402
  /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: contentHeight - 2, children: [
4819
- /* @__PURE__ */ jsx16(
5403
+ /* @__PURE__ */ jsx17(
4820
5404
  Box10,
4821
5405
  {
4822
5406
  flexDirection: "column",
4823
5407
  width: timelineWidth,
4824
5408
  paddingLeft: 0,
4825
5409
  paddingRight: 1,
4826
- children: /* @__PURE__ */ jsx16(Box10, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx16(
5410
+ children: /* @__PURE__ */ jsx17(Box10, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx17(
4827
5411
  VirtualList,
4828
5412
  {
4829
5413
  items: lineItems,
4830
5414
  itemHeight: 1,
4831
5415
  height: contentHeight - 2,
4832
5416
  selectedIndex,
4833
- renderOverflowTop: useCallback4((count) => /* @__PURE__ */ jsxs9(Box10, { children: [
4834
- /* @__PURE__ */ jsx16(Text10, { color: "gray", dimColor: true, children: "\u2502" }),
5417
+ renderOverflowTop: useCallback5((count) => /* @__PURE__ */ jsxs9(Box10, { children: [
5418
+ /* @__PURE__ */ jsx17(Text10, { color: "gray", dimColor: true, children: "\u2502" }),
4835
5419
  count > 0 && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
4836
5420
  " ",
4837
5421
  "\u25B2 ",
@@ -4839,8 +5423,8 @@ function ChatContent() {
4839
5423
  " more"
4840
5424
  ] })
4841
5425
  ] }), []),
4842
- renderOverflowBottom: useCallback4((count) => /* @__PURE__ */ jsxs9(Box10, { children: [
4843
- /* @__PURE__ */ jsx16(Text10, { color: "gray", dimColor: true, children: "\u2502" }),
5426
+ renderOverflowBottom: useCallback5((count) => /* @__PURE__ */ jsxs9(Box10, { children: [
5427
+ /* @__PURE__ */ jsx17(Text10, { color: "gray", dimColor: true, children: "\u2502" }),
4844
5428
  count > 0 && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
4845
5429
  " ",
4846
5430
  "\u25BC ",
@@ -4850,8 +5434,8 @@ function ChatContent() {
4850
5434
  ] }), []),
4851
5435
  renderFiller: timelinePipeFiller,
4852
5436
  keyExtractor: (it) => it.key,
4853
- renderItem: useCallback4(
4854
- ({ item, isSelected }) => /* @__PURE__ */ jsx16(
5437
+ renderItem: useCallback5(
5438
+ ({ item, isSelected }) => /* @__PURE__ */ jsx17(
4855
5439
  MessageLineItem,
4856
5440
  {
4857
5441
  item,
@@ -4866,7 +5450,7 @@ function ChatContent() {
4866
5450
  ) })
4867
5451
  }
4868
5452
  ),
4869
- activeTab !== "shell" && activeTab !== "actions" && /* @__PURE__ */ jsx16(
5453
+ activeTab !== "shell" && activeTab !== "actions" && /* @__PURE__ */ jsx17(
4870
5454
  Box10,
4871
5455
  {
4872
5456
  flexDirection: "column",
@@ -4878,7 +5462,7 @@ function ChatContent() {
4878
5462
  borderRight: false
4879
5463
  }
4880
5464
  ),
4881
- /* @__PURE__ */ jsx16(
5465
+ /* @__PURE__ */ jsx17(
4882
5466
  Box10,
4883
5467
  {
4884
5468
  flexDirection: "column",
@@ -4892,84 +5476,29 @@ function ChatContent() {
4892
5476
  paddingRight: 1,
4893
5477
  children: /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", flexGrow: 1, marginTop: 0, children: [
4894
5478
  activeTab === "settings" && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
4895
- /* @__PURE__ */ jsx16(CostView, { isDense: true }),
4896
- /* @__PURE__ */ jsx16(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx16(SettingsView, { isDense: true }) })
5479
+ /* @__PURE__ */ jsx17(CostView, { isDense: true }),
5480
+ /* @__PURE__ */ jsx17(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx17(SettingsView, { isDense: true }) })
4897
5481
  ] }),
4898
- activeTab === "planDescription" && /* @__PURE__ */ jsx16(PlanView, {}),
4899
- activeTab === "actions" && /* @__PURE__ */ jsx16(ActionsView, { height: contentHeight - 2, isFocused: focusedArea === "status" })
5482
+ activeTab === "planDescription" && /* @__PURE__ */ jsx17(PlanView, {}),
5483
+ activeTab === "actions" && /* @__PURE__ */ jsx17(ActionsView, { height: contentHeight - 2, isFocused: focusedArea === "status" })
4900
5484
  ] })
4901
5485
  }
4902
5486
  )
4903
5487
  ] }),
4904
5488
  /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: 1, children: [
4905
- /* @__PURE__ */ jsx16(Text10, { color: junctionLeftColor, children: "\u2522" }),
4906
- /* @__PURE__ */ jsx16(Text10, { color: timelineBottomColor, children: "\u2501".repeat(timelineWidth - 1) }),
4907
- /* @__PURE__ */ jsx16(Text10, { color: junctionMiddleColor, children: "\u2537" }),
4908
- /* @__PURE__ */ jsx16(Text10, { color: statusBottomColor, children: "\u2501".repeat(Math.max(0, statusWidth - 2)) }),
4909
- /* @__PURE__ */ jsx16(Text10, { color: junctionRightColor, children: "\u252A" })
5489
+ /* @__PURE__ */ jsx17(Text10, { color: junctionLeftColor, children: "\u2522" }),
5490
+ /* @__PURE__ */ jsx17(Text10, { color: timelineBottomColor, children: "\u2501".repeat(timelineWidth - 1) }),
5491
+ /* @__PURE__ */ jsx17(Text10, { color: junctionMiddleColor, children: "\u2537" }),
5492
+ /* @__PURE__ */ jsx17(Text10, { color: statusBottomColor, children: "\u2501".repeat(Math.max(0, statusWidth - 2)) }),
5493
+ /* @__PURE__ */ jsx17(Text10, { color: junctionRightColor, children: "\u252A" })
4910
5494
  ] }),
4911
- /* @__PURE__ */ jsx16(UserInput, {})
5495
+ /* @__PURE__ */ jsx17(UserInput, {})
4912
5496
  ] });
4913
5497
  }
4914
5498
 
4915
5499
  // ink/components/DiffApprovalView.tsx
4916
5500
  import { Box as Box11, Text as Text11, useInput as useInput5 } from "ink";
4917
5501
  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
5502
  import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
4974
5503
  function DiffApprovalView() {
4975
5504
  const { payload } = useApproval();
@@ -5343,10 +5872,10 @@ function EnvironmentSettingsStep({ onConfirm, onBack }) {
5343
5872
 
5344
5873
  // ink/configurationWizard/modelsByProvider.ts
5345
5874
  var modelsByProvider = {
5346
- OpenAI: ["gpt-5.2", "gpt-5.0", "gpt-4o-mini"],
5347
- Anthropic: ["claude-4-6-opus-latest", "claude-4-5-sonnet-latest", "claude-4-5-haiku-latest"],
5348
- Google: ["gemini-3-pro", "gemini-3-flash", "gemini-2.5-flash", "gemini-2.5-flash-lite"],
5349
- Ollama: ["qwen3-coder:30b", "mistral", "codellama"]
5875
+ OpenAI: [defaultOpenAIModel, "gpt-5.0", defaultOpenAICompactionModel],
5876
+ Anthropic: [defaultAnthropicModel, "claude-4-5-sonnet-latest", defaultAnthropicCompactionModel],
5877
+ Google: [defaultGoogleModel, "gemini-3-flash", "gemini-2.5-flash", defaultGoogleCompactionModel],
5878
+ Ollama: [defaultOllamaModel, "mistral", defaultOllamaCompactionModel]
5350
5879
  };
5351
5880
  var compactorModelsByProvider = {
5352
5881
  OpenAI: modelsByProvider.OpenAI.slice().reverse(),
@@ -5605,20 +6134,48 @@ function MainChat() {
5605
6134
  ] }) }) }) }) }) });
5606
6135
  }
5607
6136
 
5608
- // lifecycle/parseArgs.ts
5609
- import path8 from "path";
6137
+ // utils/models/deprecations.ts
6138
+ import chalk4 from "chalk";
6139
+ var DEPRECATION_RULES = [
6140
+ // Redirect any gpt-4o variants (including dated and -mini) to gpt-5-nano
6141
+ {
6142
+ match: (name) => name.toLowerCase().startsWith("gpt-4o"),
6143
+ replacement: "gpt-5-nano",
6144
+ reason: "OpenAI gpt-4o family is deprecated in this agent"
6145
+ }
6146
+ ];
6147
+ function getDeprecatedReplacement(modelName) {
6148
+ if (!modelName) {
6149
+ return null;
6150
+ }
6151
+ for (const rule of DEPRECATION_RULES) {
6152
+ if (rule.match(modelName)) {
6153
+ return { replacement: rule.replacement, rule };
6154
+ }
6155
+ }
6156
+ return null;
6157
+ }
6158
+ function warnAndPersistRedirect(original, envKey, replacement, reason) {
6159
+ const reasonSuffix = reason ? ` Reason: ${reason}.` : "";
6160
+ console.warn(
6161
+ chalk4.yellow(
6162
+ `Warning: model "${original}" is deprecated and will be redirected to "${replacement}". Your environment setting (${envKey}) has been updated.${reasonSuffix}`
6163
+ )
6164
+ );
6165
+ updateEnv(envKey, replacement);
6166
+ }
5610
6167
 
5611
6168
  // utils/shell/cli.ts
5612
- import chalk4 from "chalk";
6169
+ import chalk5 from "chalk";
5613
6170
 
5614
6171
  // utils/package/getOwnPackageJson.ts
5615
6172
  import { readFileSync as readFileSync6 } from "fs";
5616
- import { join as join10 } from "path";
6173
+ import { join as join11 } from "path";
5617
6174
  import { fileURLToPath } from "url";
5618
6175
  var __dirname = fileURLToPath(new URL(".", import.meta.url));
5619
6176
  function getOwnPackageJson() {
5620
6177
  try {
5621
- const packageContents = readFileSync6(join10(__dirname, "../package.json"), "utf8");
6178
+ const packageContents = readFileSync6(join11(__dirname, "../package.json"), "utf8");
5622
6179
  return JSON.parse(packageContents);
5623
6180
  } catch {
5624
6181
  return { name: "@harperfast/agent", version: "0.0.0" };
@@ -5636,24 +6193,23 @@ function isVersionRequest(args) {
5636
6193
  }
5637
6194
  function handleHelp() {
5638
6195
  console.log(`
5639
- ${chalk4.bold("harper-agent")} - AI to help you with Harper app management
6196
+ ${chalk5.bold("harper-agent")} - AI to help you with Harper app creation and modification
5640
6197
 
5641
- ${chalk4.bold("USAGE")}
6198
+ ${chalk5.bold("USAGE")}
5642
6199
  $ harper-agent [options]
5643
6200
  $ harper-agent [command]
5644
6201
 
5645
- ${chalk4.bold("OPTIONS")}
6202
+ ${chalk5.bold("OPTIONS")}
5646
6203
  -h, --help Show help information
5647
6204
  -v, --version Show version information
5648
- -m, --model Specify the model to use (e.g., gpt-4o, claude-3-5-sonnet, ollama-llama3)
6205
+ -m, --model Specify the model to use (e.g., ${defaultOpenAIModel}, ${defaultAnthropicModel}, ${defaultOllamaModel})
5649
6206
  Can also be set via HARPER_AGENT_MODEL environment variable.
5650
- For Ollama, use the ollama- prefix (e.g., ollama-llama3).
5651
- -c, --compaction-model Specify the compaction model to use (defaults to gpt-4o-mini).
6207
+ For Ollama, use the ollama- prefix (e.g., ${defaultOllamaCompactionModel}).
6208
+ -c, --compaction-model Specify the compaction model to use (defaults to ${defaultOpenAICompactionModel}).
5652
6209
  Can also be set via HARPER_AGENT_COMPACTION_MODEL environment variable.
5653
6210
  -s, --session Specify a path to a SQLite database file to persist the chat session.
5654
6211
  Can also be set via HARPER_AGENT_SESSION environment variable.
5655
6212
  --max-turns Specify the maximum number of turns for the agent run.
5656
- In task-driven mode, this defaults to unlimited.
5657
6213
  Can also be set via HARPER_AGENT_MAX_TURNS environment variable.
5658
6214
  --max-cost Specify the maximum cost (in USD) for the agent run.
5659
6215
  If exceeded, the agent will exit with a non-zero code.
@@ -5661,12 +6217,13 @@ ${chalk4.bold("OPTIONS")}
5661
6217
  --flex-tier Force the use of the flex service tier for lower costs but potentially
5662
6218
  more errors under high system load.
5663
6219
  Can also be set via HARPER_AGENT_FLEX_TIER=true environment variable.
6220
+ -p, --prompt Specify a prompt to be executed autonomously until completion.
5664
6221
 
5665
- ${chalk4.bold("COMMANDS")}
6222
+ ${chalk5.bold("COMMANDS")}
5666
6223
  --help Show help information
5667
6224
  --version Show version information
5668
6225
 
5669
- ${chalk4.bold("EXAMPLES")}
6226
+ ${chalk5.bold("EXAMPLES")}
5670
6227
  $ harper-agent --help
5671
6228
  $ harper-agent --version
5672
6229
  $ harper-agent
@@ -5702,7 +6259,8 @@ function parseArgs() {
5702
6259
  ["sessionPath", ["--session", "-s", "session"]],
5703
6260
  ["maxTurns", ["--max-turns"]],
5704
6261
  ["maxCost", ["--max-cost"]],
5705
- ["rateLimitThreshold", ["--rate-limit-threshold"]]
6262
+ ["rateLimitThreshold", ["--rate-limit-threshold"]],
6263
+ ["prompt", ["--prompt", "-p"]]
5706
6264
  ];
5707
6265
  let handled = false;
5708
6266
  for (const [key, prefixes] of flagPairs) {
@@ -5740,6 +6298,8 @@ function parseArgs() {
5740
6298
  trackedState.useFlexTier = true;
5741
6299
  } else if (arg === "--no-monitor-rate-limits") {
5742
6300
  trackedState.monitorRateLimits = false;
6301
+ } else if (arg === "--autonomous" || arg === "-a") {
6302
+ trackedState.autonomous = true;
5743
6303
  }
5744
6304
  }
5745
6305
  if (!trackedState.model && process.env.HARPER_AGENT_MODEL) {
@@ -5763,10 +6323,6 @@ function parseArgs() {
5763
6323
  if (process.env.HARPER_AGENT_MONITOR_RATE_LIMITS === "false") {
5764
6324
  trackedState.monitorRateLimits = false;
5765
6325
  }
5766
- const sp = trackedState.sessionPath;
5767
- if (sp) {
5768
- trackedState.sessionPath = sp && !sp.startsWith("~") && !path8.isAbsolute(sp) ? path8.resolve(process.cwd(), sp) : sp;
5769
- }
5770
6326
  if (!trackedState.useFlexTier && isTrue(process.env.HARPER_AGENT_FLEX_TIER)) {
5771
6327
  trackedState.useFlexTier = true;
5772
6328
  }
@@ -5779,55 +6335,67 @@ function parseArgs() {
5779
6335
  if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_SHELL)) {
5780
6336
  trackedState.autoApproveShell = true;
5781
6337
  }
5782
- if (!trackedState.model) {
6338
+ if (isTrue(process.env.HARPER_AGENT_AUTONOMOUS)) {
6339
+ trackedState.autonomous = true;
6340
+ }
6341
+ if (!trackedState.model || trackedState.model === defaultModelToken) {
5783
6342
  if (process.env.ANTHROPIC_API_KEY) {
5784
- trackedState.model = "claude-3-7-sonnet-latest";
6343
+ trackedState.model = defaultAnthropicModel;
5785
6344
  } else if (process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
5786
- trackedState.model = "gemini-2.0-flash";
5787
- } else if (process.env.OPENAI_API_KEY) {
5788
- trackedState.model = "gpt-5.2";
6345
+ trackedState.model = defaultGoogleModel;
5789
6346
  } else if (process.env.OLLAMA_BASE_URL) {
5790
- trackedState.model = "ollama-qwen3-coder:30b";
6347
+ trackedState.model = defaultOllamaModel;
5791
6348
  } else {
5792
- trackedState.model = "gpt-5.2";
6349
+ trackedState.model = defaultOpenAIModel;
5793
6350
  }
5794
6351
  }
5795
- if (!trackedState.compactionModel) {
6352
+ if (!trackedState.compactionModel || trackedState.compactionModel === defaultModelToken) {
5796
6353
  const m = trackedState.model;
5797
6354
  if (m.startsWith("claude-")) {
5798
- trackedState.compactionModel = "claude-3-5-haiku-latest";
6355
+ trackedState.compactionModel = defaultAnthropicCompactionModel;
5799
6356
  } else if (m.startsWith("gemini-")) {
5800
- trackedState.compactionModel = "gemini-1.5-flash";
6357
+ trackedState.compactionModel = defaultGoogleCompactionModel;
5801
6358
  } else if (m.startsWith("ollama-")) {
5802
- trackedState.compactionModel = "ollama-qwen2.5-coder";
6359
+ trackedState.compactionModel = defaultOllamaCompactionModel;
5803
6360
  } else {
5804
- trackedState.compactionModel = "gpt-4o-mini";
6361
+ trackedState.compactionModel = defaultOpenAICompactionModel;
5805
6362
  }
5806
6363
  }
5807
6364
  if (!isOpenAIModel(trackedState.model)) {
5808
6365
  process.env.OPENAI_AGENTS_DISABLE_TRACING = process.env.OPENAI_AGENTS_DISABLE_TRACING || "1";
5809
6366
  }
6367
+ const maybeRedirect = (current, envKey) => {
6368
+ const hit = getDeprecatedReplacement(current);
6369
+ if (hit) {
6370
+ const { replacement, rule } = hit;
6371
+ warnAndPersistRedirect(current, envKey, replacement, rule.reason);
6372
+ return replacement;
6373
+ }
6374
+ return current;
6375
+ };
6376
+ trackedState.model = maybeRedirect(trackedState.model, "HARPER_AGENT_MODEL");
6377
+ trackedState.compactionModel = maybeRedirect(trackedState.compactionModel, "HARPER_AGENT_COMPACTION_MODEL");
5810
6378
  }
5811
6379
 
5812
6380
  // utils/envLoader.ts
5813
6381
  import dotenv from "dotenv";
5814
- import { existsSync as existsSync9 } from "fs";
6382
+ import { existsSync as existsSync10 } from "fs";
5815
6383
  import { homedir as homedir4 } from "os";
5816
- import { join as join11 } from "path";
6384
+ import { join as join12 } from "path";
5817
6385
  function loadEnv() {
5818
- const topLevelEnvPath = join11(homedir4(), ".harper", "harper-agent-env");
5819
- const localEnvPath = join11(process.cwd(), ".env");
5820
- if (existsSync9(topLevelEnvPath)) {
6386
+ const topLevelEnvPath = join12(homedir4(), ".harper", "harper-agent-env");
6387
+ const localEnvPath = join12(process.cwd(), ".env");
6388
+ if (existsSync10(topLevelEnvPath)) {
5821
6389
  dotenv.config({ path: topLevelEnvPath, quiet: true });
5822
6390
  }
5823
- if (existsSync9(localEnvPath)) {
6391
+ if (existsSync10(localEnvPath)) {
5824
6392
  dotenv.config({ path: localEnvPath, override: true, quiet: true });
5825
6393
  }
5826
6394
  }
5827
6395
 
5828
6396
  // utils/package/checkForUpdate.ts
5829
6397
  import { Select as Select3 } from "@inkjs/ui";
5830
- import chalk5 from "chalk";
6398
+ import chalk6 from "chalk";
5831
6399
  import spawn2 from "cross-spawn";
5832
6400
  import { Box as Box19, render as render2, Text as Text18 } from "ink";
5833
6401
  import React21 from "react";
@@ -5949,7 +6517,7 @@ function UpdatePrompt({ packageName, currentVersion, latestVersion, onSelect })
5949
6517
  React21.createElement(
5950
6518
  Text18,
5951
6519
  null,
5952
- `${chalk5.yellow("Update available:")} ${chalk5.bold(packageName)} ${chalk5.dim(`v${currentVersion}`)} \u2192 ${chalk5.green(`v${latestVersion}`)}`
6520
+ `${chalk6.yellow("Update available:")} ${chalk6.bold(packageName)} ${chalk6.dim(`v${currentVersion}`)} \u2192 ${chalk6.green(`v${latestVersion}`)}`
5953
6521
  ),
5954
6522
  React21.createElement(
5955
6523
  Box19,
@@ -5987,34 +6555,10 @@ function ensureApiKey() {
5987
6555
  return true;
5988
6556
  }
5989
6557
 
5990
- // utils/shell/getStdin.ts
5991
- async function getStdin() {
5992
- if (process.stdin.isTTY) {
5993
- return "";
5994
- }
5995
- let result = "";
5996
- process.stdin.setEncoding("utf8");
5997
- for await (const chunk of process.stdin) {
5998
- result += chunk;
5999
- }
6000
- return result.trim();
6001
- }
6002
-
6003
6558
  // agent.ts
6004
6559
  (async function() {
6005
6560
  setupGlobalErrorHandlers();
6006
6561
  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
- };
6018
6562
  process.on("SIGINT", handleExit);
6019
6563
  process.on("SIGTERM", handleExit);
6020
6564
  await checkForUpdate();
@@ -6026,17 +6570,16 @@ async function getStdin() {
6026
6570
  emitToListeners("ExitUI", void 0);
6027
6571
  parseArgs();
6028
6572
  if (!ensureApiKey()) {
6029
- console.log(chalk6.red("No key provided. Exiting."));
6573
+ console.log(chalk7.red("No key provided. Exiting."));
6030
6574
  process.exit(1);
6031
6575
  }
6032
6576
  }
6033
6577
  await agentManager.initialize();
6034
6578
  bootstrapMain();
6035
- getStdin().then((stdinPrompt) => {
6036
- if (stdinPrompt?.trim?.()?.length) {
6037
- emitToListeners("PushNewMessages", [
6038
- { type: "user", text: stdinPrompt.trim(), version: 1 }
6039
- ]);
6040
- }
6041
- });
6579
+ if (trackedState.prompt?.trim?.()?.length) {
6580
+ trackedState.autonomous = true;
6581
+ emitToListeners("PushNewMessages", [
6582
+ { type: "prompt", text: trackedState.prompt.trim(), version: 1 }
6583
+ ]);
6584
+ }
6042
6585
  })();