@hasna/oldpal 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20450,12 +20450,12 @@ var exports_anthropic = {};
20450
20450
  __export(exports_anthropic, {
20451
20451
  AnthropicClient: () => AnthropicClient
20452
20452
  });
20453
- import { readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
20454
- import { homedir as homedir5 } from "os";
20455
- import { join as join6 } from "path";
20453
+ import { readFileSync as readFileSync2, existsSync as existsSync5 } from "fs";
20454
+ import { homedir as homedir6 } from "os";
20455
+ import { join as join7 } from "path";
20456
20456
  function loadApiKeyFromSecrets() {
20457
- const secretsPath = join6(homedir5(), ".secrets");
20458
- if (existsSync4(secretsPath)) {
20457
+ const secretsPath = join7(homedir6(), ".secrets");
20458
+ if (existsSync5(secretsPath)) {
20459
20459
  try {
20460
20460
  const content = readFileSync2(secretsPath, "utf-8");
20461
20461
  const match = content.match(/export\s+ANTHROPIC_API_KEY\s*=\s*["']?([^"'\n]+)["']?/);
@@ -20543,6 +20543,7 @@ class AnthropicClient {
20543
20543
  }
20544
20544
  convertMessages(messages) {
20545
20545
  const result = [];
20546
+ const pendingToolUseIds = new Set;
20546
20547
  for (const msg of messages) {
20547
20548
  if (msg.role === "system")
20548
20549
  continue;
@@ -20558,16 +20559,20 @@ class AnthropicClient {
20558
20559
  name: toolCall.name,
20559
20560
  input: toolCall.input
20560
20561
  });
20562
+ pendingToolUseIds.add(toolCall.id);
20561
20563
  }
20562
20564
  }
20563
20565
  if (msg.toolResults) {
20564
20566
  for (const toolResult of msg.toolResults) {
20565
- content.push({
20566
- type: "tool_result",
20567
- tool_use_id: toolResult.toolCallId,
20568
- content: toolResult.content,
20569
- is_error: toolResult.isError
20570
- });
20567
+ if (pendingToolUseIds.has(toolResult.toolCallId)) {
20568
+ content.push({
20569
+ type: "tool_result",
20570
+ tool_use_id: toolResult.toolCallId,
20571
+ content: toolResult.content,
20572
+ is_error: toolResult.isError
20573
+ });
20574
+ pendingToolUseIds.delete(toolResult.toolCallId);
20575
+ }
20571
20576
  }
20572
20577
  }
20573
20578
  if (content.length > 0) {
@@ -28589,7 +28594,7 @@ var import_react20 = __toESM(require_react(), 1);
28589
28594
  // node_modules/.pnpm/ink@5.2.1_@types+react@18.3.27_react-devtools-core@4.28.5_react@18.3.1/node_modules/ink/build/hooks/use-focus-manager.js
28590
28595
  var import_react21 = __toESM(require_react(), 1);
28591
28596
  // packages/terminal/src/components/App.tsx
28592
- var import_react25 = __toESM(require_react(), 1);
28597
+ var import_react27 = __toESM(require_react(), 1);
28593
28598
  // packages/shared/src/utils.ts
28594
28599
  import { randomUUID } from "crypto";
28595
28600
  function generateId() {
@@ -28702,11 +28707,25 @@ class AgentContext {
28702
28707
  this.messages = [];
28703
28708
  }
28704
28709
  prune() {
28705
- if (this.messages.length > this.maxMessages) {
28706
- const systemMessages = this.messages.filter((m) => m.role === "system");
28707
- const recentMessages = this.messages.filter((m) => m.role !== "system").slice(-(this.maxMessages - systemMessages.length));
28708
- this.messages = [...systemMessages, ...recentMessages];
28710
+ if (this.messages.length <= this.maxMessages) {
28711
+ return;
28709
28712
  }
28713
+ const systemMessages = this.messages.filter((m) => m.role === "system");
28714
+ const nonSystemMessages = this.messages.filter((m) => m.role !== "system");
28715
+ const targetCount = this.maxMessages - systemMessages.length;
28716
+ let recentMessages = nonSystemMessages.slice(-targetCount);
28717
+ while (recentMessages.length > 0 && recentMessages[0].toolResults) {
28718
+ const firstIndex = nonSystemMessages.indexOf(recentMessages[0]);
28719
+ if (firstIndex > 0) {
28720
+ recentMessages = nonSystemMessages.slice(firstIndex - 1);
28721
+ if (recentMessages.length > targetCount + 1) {
28722
+ recentMessages = recentMessages.slice(-(targetCount + 1));
28723
+ }
28724
+ } else {
28725
+ recentMessages = recentMessages.slice(1);
28726
+ }
28727
+ }
28728
+ this.messages = [...systemMessages, ...recentMessages];
28710
28729
  }
28711
28730
  export() {
28712
28731
  return this.messages;
@@ -28770,7 +28789,6 @@ import { join } from "path";
28770
28789
  class ConnectorBridge {
28771
28790
  connectors = new Map;
28772
28791
  async discover(connectorNames) {
28773
- const discovered = [];
28774
28792
  const names = connectorNames || [
28775
28793
  "notion",
28776
28794
  "googledrive",
@@ -28787,19 +28805,22 @@ class ConnectorBridge {
28787
28805
  "openai",
28788
28806
  "elevenlabs"
28789
28807
  ];
28790
- for (const name of names) {
28808
+ const results = await Promise.all(names.map(async (name) => {
28791
28809
  const cli = `connect-${name}`;
28792
28810
  try {
28793
- const result = await Bun.$`which ${cli}`.quiet();
28811
+ const result = await Bun.$`which ${cli}`.quiet().nothrow();
28794
28812
  if (result.exitCode !== 0)
28795
- continue;
28813
+ return null;
28796
28814
  } catch {
28797
- continue;
28815
+ return null;
28798
28816
  }
28799
- const connector = await this.discoverConnector(name, cli);
28817
+ return this.discoverConnector(name, cli);
28818
+ }));
28819
+ const discovered = [];
28820
+ for (const connector of results) {
28800
28821
  if (connector) {
28801
28822
  discovered.push(connector);
28802
- this.connectors.set(name, connector);
28823
+ this.connectors.set(connector.name, connector);
28803
28824
  }
28804
28825
  }
28805
28826
  return discovered;
@@ -29466,28 +29487,162 @@ class WebTools {
29466
29487
  }
29467
29488
  }
29468
29489
 
29469
- // packages/core/src/skills/loader.ts
29490
+ // packages/core/src/tools/image.ts
29491
+ import { existsSync as existsSync2, writeFileSync, unlinkSync } from "fs";
29492
+ import { tmpdir } from "os";
29470
29493
  import { join as join3 } from "path";
29471
29494
  import { homedir as homedir2 } from "os";
29495
+ async function getViuPath() {
29496
+ const locations = [
29497
+ "viu",
29498
+ join3(homedir2(), ".cargo", "bin", "viu"),
29499
+ "/usr/local/bin/viu",
29500
+ "/opt/homebrew/bin/viu"
29501
+ ];
29502
+ for (const path of locations) {
29503
+ try {
29504
+ const result = await Bun.$`${path} --version`.quiet().nothrow();
29505
+ if (result.exitCode === 0) {
29506
+ return path;
29507
+ }
29508
+ } catch {
29509
+ continue;
29510
+ }
29511
+ }
29512
+ return null;
29513
+ }
29514
+
29515
+ class ImageDisplayTool {
29516
+ static tool = {
29517
+ name: "display_image",
29518
+ description: "Display an image in the terminal. Works with local files and URLs. Supports PNG, JPG, GIF, BMP, and other common formats.",
29519
+ parameters: {
29520
+ type: "object",
29521
+ properties: {
29522
+ path: {
29523
+ type: "string",
29524
+ description: "Path to the image file or URL to fetch"
29525
+ },
29526
+ width: {
29527
+ type: "number",
29528
+ description: "Width in characters (optional, defaults to terminal width)"
29529
+ },
29530
+ height: {
29531
+ type: "number",
29532
+ description: "Height in characters (optional)"
29533
+ }
29534
+ },
29535
+ required: ["path"]
29536
+ }
29537
+ };
29538
+ static executor = async (input) => {
29539
+ const imagePath = input.path;
29540
+ const width = input.width;
29541
+ const height = input.height;
29542
+ const viuPath = await getViuPath();
29543
+ if (!viuPath) {
29544
+ return "Error: viu is not installed. Install with: cargo install viu";
29545
+ }
29546
+ let localPath = imagePath;
29547
+ let tempFile = null;
29548
+ if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
29549
+ try {
29550
+ const response = await fetch(imagePath);
29551
+ if (!response.ok) {
29552
+ return `Error: Failed to fetch image: HTTP ${response.status}`;
29553
+ }
29554
+ const contentType = response.headers.get("content-type") || "";
29555
+ if (!contentType.startsWith("image/")) {
29556
+ return `Error: URL does not point to an image (content-type: ${contentType})`;
29557
+ }
29558
+ const buffer = await response.arrayBuffer();
29559
+ const ext = contentType.split("/")[1]?.split(";")[0] || "png";
29560
+ tempFile = join3(tmpdir(), `oldpal-image-${generateId()}.${ext}`);
29561
+ writeFileSync(tempFile, Buffer.from(buffer));
29562
+ localPath = tempFile;
29563
+ } catch (error) {
29564
+ return `Error: Failed to fetch image: ${error instanceof Error ? error.message : String(error)}`;
29565
+ }
29566
+ }
29567
+ if (!existsSync2(localPath)) {
29568
+ return `Error: Image file not found: ${localPath}`;
29569
+ }
29570
+ try {
29571
+ const args = [];
29572
+ if (width) {
29573
+ args.push("-w", String(width));
29574
+ }
29575
+ if (height) {
29576
+ args.push("-h", String(height));
29577
+ }
29578
+ args.push(localPath);
29579
+ const result = await Bun.$`${viuPath} ${args}`.quiet().nothrow();
29580
+ if (result.exitCode !== 0) {
29581
+ const stderr = result.stderr.toString().trim();
29582
+ return `Error displaying image: ${stderr || "Unknown error"}`;
29583
+ }
29584
+ const output = result.stdout.toString();
29585
+ if (output) {
29586
+ process.stdout.write(output);
29587
+ }
29588
+ return `Image displayed: ${imagePath}${width ? ` (width: ${width})` : ""}${height ? ` (height: ${height})` : ""}`;
29589
+ } catch (error) {
29590
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
29591
+ } finally {
29592
+ if (tempFile && existsSync2(tempFile)) {
29593
+ try {
29594
+ unlinkSync(tempFile);
29595
+ } catch {}
29596
+ }
29597
+ }
29598
+ };
29599
+ }
29600
+
29601
+ class ImageTools {
29602
+ static registerAll(registry) {
29603
+ registry.register(ImageDisplayTool.tool, ImageDisplayTool.executor);
29604
+ }
29605
+ }
29606
+
29607
+ // packages/core/src/skills/loader.ts
29608
+ import { join as join4 } from "path";
29609
+ import { homedir as homedir3 } from "os";
29472
29610
  var {Glob: Glob2 } = globalThis.Bun;
29473
29611
  class SkillLoader {
29474
29612
  skills = new Map;
29475
29613
  async loadAll(projectDir = process.cwd()) {
29476
- const userSkillsDir = join3(homedir2(), ".oldpal", "skills");
29614
+ const userSkillsDir = join4(homedir3(), ".oldpal", "skills");
29477
29615
  await this.loadFromDirectory(userSkillsDir);
29478
- const projectSkillsDir = join3(projectDir, ".oldpal", "skills");
29616
+ const projectSkillsDir = join4(projectDir, ".oldpal", "skills");
29479
29617
  await this.loadFromDirectory(projectSkillsDir);
29480
29618
  const nestedGlob = new Glob2("**/.oldpal/skills/*/SKILL.md");
29481
29619
  for await (const file of nestedGlob.scan({ cwd: projectDir })) {
29482
- await this.loadSkillFile(join3(projectDir, file));
29620
+ await this.loadSkillFile(join4(projectDir, file));
29483
29621
  }
29484
29622
  }
29485
29623
  async loadFromDirectory(dir) {
29486
29624
  try {
29487
- const glob = new Glob2("*/SKILL.md");
29488
- for await (const file of glob.scan({ cwd: dir })) {
29489
- await this.loadSkillFile(join3(dir, file));
29625
+ const { stat } = await import("fs/promises");
29626
+ try {
29627
+ const stats = await stat(dir);
29628
+ if (!stats.isDirectory())
29629
+ return;
29630
+ } catch {
29631
+ return;
29632
+ }
29633
+ const filesToLoad = [];
29634
+ const skillPrefixGlob = new Glob2("skill-*/SKILL.md");
29635
+ for await (const file of skillPrefixGlob.scan({ cwd: dir })) {
29636
+ filesToLoad.push(join4(dir, file));
29637
+ }
29638
+ const regularGlob = new Glob2("*/SKILL.md");
29639
+ for await (const file of regularGlob.scan({ cwd: dir })) {
29640
+ const dirName = file.split("/")[0];
29641
+ if (!dirName.startsWith("skill-")) {
29642
+ filesToLoad.push(join4(dir, file));
29643
+ }
29490
29644
  }
29645
+ await Promise.all(filesToLoad.map((file) => this.loadSkillFile(file)));
29491
29646
  } catch {}
29492
29647
  }
29493
29648
  async loadSkillFile(filePath) {
@@ -29548,8 +29703,9 @@ class SkillLoader {
29548
29703
  }
29549
29704
 
29550
29705
  // packages/core/src/skills/executor.ts
29706
+ import { dirname as dirname2 } from "path";
29551
29707
  class SkillExecutor {
29552
- prepare(skill, args) {
29708
+ async prepare(skill, args) {
29553
29709
  let content = skill.content;
29554
29710
  content = substituteVariables(content, args);
29555
29711
  if (!skill.content.includes("$ARGUMENTS") && args.length > 0) {
@@ -29557,15 +29713,28 @@ class SkillExecutor {
29557
29713
 
29558
29714
  ARGUMENTS: ${args.join(" ")}`;
29559
29715
  }
29560
- content = this.executeDynamicContext(content);
29716
+ content = await this.executeDynamicContext(content, skill.filePath);
29561
29717
  return content;
29562
29718
  }
29563
- executeDynamicContext(content) {
29564
- const backtickPattern = /`!([^`]+)`/g;
29719
+ async executeDynamicContext(content, skillFilePath) {
29720
+ const backtickPattern = /!\`([^`]+)\`/g;
29721
+ const matches = [...content.matchAll(backtickPattern)];
29722
+ if (matches.length === 0) {
29723
+ return content;
29724
+ }
29725
+ const skillDir = dirname2(skillFilePath);
29565
29726
  let result = content;
29566
- result = result.replace(backtickPattern, (match, command) => {
29567
- return `[Dynamic context: ${command}]`;
29568
- });
29727
+ for (const match of matches) {
29728
+ const fullMatch = match[0];
29729
+ const command = match[1];
29730
+ try {
29731
+ const output = await Bun.$`cd ${skillDir} && ${command}`.quiet().text();
29732
+ result = result.replace(fullMatch, output.trim());
29733
+ } catch (error) {
29734
+ const errorMsg = error instanceof Error ? error.message : String(error);
29735
+ result = result.replace(fullMatch, `[Command failed: ${errorMsg}]`);
29736
+ }
29737
+ }
29569
29738
  return result;
29570
29739
  }
29571
29740
  shouldAutoInvoke(skill, userMessage) {
@@ -29735,9 +29904,9 @@ class HookExecutor {
29735
29904
  }
29736
29905
  }
29737
29906
  // packages/core/src/commands/loader.ts
29738
- import { existsSync as existsSync2, readdirSync, statSync } from "fs";
29739
- import { join as join4, basename, extname } from "path";
29740
- import { homedir as homedir3 } from "os";
29907
+ import { existsSync as existsSync3, readdirSync, statSync } from "fs";
29908
+ import { join as join5, basename, extname } from "path";
29909
+ import { homedir as homedir4 } from "os";
29741
29910
 
29742
29911
  class CommandLoader {
29743
29912
  commands = new Map;
@@ -29747,17 +29916,17 @@ class CommandLoader {
29747
29916
  }
29748
29917
  async loadAll() {
29749
29918
  this.commands.clear();
29750
- const globalDir = join4(homedir3(), ".oldpal", "commands");
29919
+ const globalDir = join5(homedir4(), ".oldpal", "commands");
29751
29920
  await this.loadFromDirectory(globalDir, "global");
29752
- const projectDir = join4(this.cwd, ".oldpal", "commands");
29921
+ const projectDir = join5(this.cwd, ".oldpal", "commands");
29753
29922
  await this.loadFromDirectory(projectDir, "project");
29754
29923
  }
29755
29924
  async loadFromDirectory(dir, source, prefix = "") {
29756
- if (!existsSync2(dir))
29925
+ if (!existsSync3(dir))
29757
29926
  return;
29758
29927
  const entries = readdirSync(dir);
29759
29928
  for (const entry of entries) {
29760
- const fullPath = join4(dir, entry);
29929
+ const fullPath = join5(dir, entry);
29761
29930
  const stat = statSync(fullPath);
29762
29931
  if (stat.isDirectory()) {
29763
29932
  const newPrefix = prefix ? `${prefix}:${entry}` : entry;
@@ -29935,9 +30104,9 @@ ${stderr}`;
29935
30104
  }
29936
30105
  }
29937
30106
  // packages/core/src/commands/builtin.ts
29938
- import { join as join5 } from "path";
29939
- import { homedir as homedir4 } from "os";
29940
- import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
30107
+ import { join as join6 } from "path";
30108
+ import { homedir as homedir5 } from "os";
30109
+ import { existsSync as existsSync4, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
29941
30110
 
29942
30111
  class BuiltinCommands {
29943
30112
  tokenUsage = {
@@ -30107,8 +30276,8 @@ Format the summary as a brief bullet-point list. This summary will replace the c
30107
30276
  content: "",
30108
30277
  handler: async (args, context) => {
30109
30278
  const configPaths = [
30110
- join5(context.cwd, ".oldpal", "config.json"),
30111
- join5(homedir4(), ".oldpal", "config.json")
30279
+ join6(context.cwd, ".oldpal", "config.json"),
30280
+ join6(homedir5(), ".oldpal", "config.json")
30112
30281
  ];
30113
30282
  let message = `
30114
30283
  **Configuration**
@@ -30117,16 +30286,16 @@ Format the summary as a brief bullet-point list. This summary will replace the c
30117
30286
  message += `**Config File Locations:**
30118
30287
  `;
30119
30288
  for (const path of configPaths) {
30120
- const exists = existsSync3(path);
30289
+ const exists = existsSync4(path);
30121
30290
  message += ` ${exists ? "\u2713" : "\u25CB"} ${path}
30122
30291
  `;
30123
30292
  }
30124
30293
  message += `
30125
30294
  **Commands Directories:**
30126
30295
  `;
30127
- message += ` - Project: ${join5(context.cwd, ".oldpal", "commands")}
30296
+ message += ` - Project: ${join6(context.cwd, ".oldpal", "commands")}
30128
30297
  `;
30129
- message += ` - Global: ${join5(homedir4(), ".oldpal", "commands")}
30298
+ message += ` - Global: ${join6(homedir5(), ".oldpal", "commands")}
30130
30299
  `;
30131
30300
  context.emit("text", message);
30132
30301
  context.emit("done");
@@ -30142,7 +30311,7 @@ Format the summary as a brief bullet-point list. This summary will replace the c
30142
30311
  selfHandled: true,
30143
30312
  content: "",
30144
30313
  handler: async (args, context) => {
30145
- const commandsDir = join5(context.cwd, ".oldpal", "commands");
30314
+ const commandsDir = join6(context.cwd, ".oldpal", "commands");
30146
30315
  mkdirSync(commandsDir, { recursive: true });
30147
30316
  const exampleCommand = `---
30148
30317
  name: review
@@ -30171,9 +30340,9 @@ Please review the current code changes and provide feedback on:
30171
30340
 
30172
30341
  If there are staged git changes, focus on those. Otherwise, ask what code to review.
30173
30342
  `;
30174
- const examplePath = join5(commandsDir, "review.md");
30175
- if (!existsSync3(examplePath)) {
30176
- writeFileSync(examplePath, exampleCommand);
30343
+ const examplePath = join6(commandsDir, "review.md");
30344
+ if (!existsSync4(examplePath)) {
30345
+ writeFileSync2(examplePath, exampleCommand);
30177
30346
  }
30178
30347
  let message = `
30179
30348
  **Initialized oldpal**
@@ -30347,8 +30516,8 @@ async function createLLMClient(config) {
30347
30516
  }
30348
30517
 
30349
30518
  // packages/core/src/config.ts
30350
- import { join as join7 } from "path";
30351
- import { homedir as homedir6 } from "os";
30519
+ import { join as join8 } from "path";
30520
+ import { homedir as homedir7 } from "os";
30352
30521
  var DEFAULT_CONFIG = {
30353
30522
  llm: {
30354
30523
  provider: "anthropic",
@@ -30378,13 +30547,13 @@ var DEFAULT_CONFIG = {
30378
30547
  ]
30379
30548
  };
30380
30549
  function getConfigDir() {
30381
- return join7(homedir6(), ".oldpal");
30550
+ return join8(homedir7(), ".oldpal");
30382
30551
  }
30383
30552
  function getConfigPath(filename) {
30384
- return join7(getConfigDir(), filename);
30553
+ return join8(getConfigDir(), filename);
30385
30554
  }
30386
30555
  function getProjectConfigDir(cwd2 = process.cwd()) {
30387
- return join7(cwd2, ".oldpal");
30556
+ return join8(cwd2, ".oldpal");
30388
30557
  }
30389
30558
  async function loadConfig(cwd2 = process.cwd()) {
30390
30559
  const config = { ...DEFAULT_CONFIG };
@@ -30397,7 +30566,7 @@ async function loadConfig(cwd2 = process.cwd()) {
30397
30566
  if (userConfig.voice)
30398
30567
  config.voice = { ...config.voice, ...userConfig.voice };
30399
30568
  }
30400
- const projectConfigPath = join7(getProjectConfigDir(cwd2), "settings.json");
30569
+ const projectConfigPath = join8(getProjectConfigDir(cwd2), "settings.json");
30401
30570
  const projectConfig = await loadJsonFile(projectConfigPath);
30402
30571
  if (projectConfig) {
30403
30572
  Object.assign(config, projectConfig);
@@ -30406,7 +30575,7 @@ async function loadConfig(cwd2 = process.cwd()) {
30406
30575
  if (projectConfig.voice)
30407
30576
  config.voice = { ...config.voice, ...projectConfig.voice };
30408
30577
  }
30409
- const localConfigPath = join7(getProjectConfigDir(cwd2), "settings.local.json");
30578
+ const localConfigPath = join8(getProjectConfigDir(cwd2), "settings.local.json");
30410
30579
  const localConfig = await loadJsonFile(localConfigPath);
30411
30580
  if (localConfig) {
30412
30581
  Object.assign(config, localConfig);
@@ -30424,7 +30593,7 @@ async function loadHooksConfig(cwd2 = process.cwd()) {
30424
30593
  if (userHooks?.hooks) {
30425
30594
  mergeHooks(hooks, userHooks.hooks);
30426
30595
  }
30427
- const projectHooksPath = join7(getProjectConfigDir(cwd2), "hooks.json");
30596
+ const projectHooksPath = join8(getProjectConfigDir(cwd2), "hooks.json");
30428
30597
  const projectHooks = await loadJsonFile(projectHooksPath);
30429
30598
  if (projectHooks?.hooks) {
30430
30599
  mergeHooks(hooks, projectHooks.hooks);
@@ -30450,6 +30619,47 @@ async function loadJsonFile(path) {
30450
30619
  return null;
30451
30620
  }
30452
30621
  }
30622
+ async function ensureConfigDir() {
30623
+ const { mkdir } = await import("fs/promises");
30624
+ const configDir = getConfigDir();
30625
+ await Promise.all([
30626
+ mkdir(configDir, { recursive: true }),
30627
+ mkdir(join8(configDir, "sessions"), { recursive: true }),
30628
+ mkdir(join8(configDir, "skills"), { recursive: true })
30629
+ ]);
30630
+ }
30631
+ async function loadSystemPrompt(cwd2 = process.cwd()) {
30632
+ const prompts = [];
30633
+ const globalPromptPath = getConfigPath("OLDPAL.md");
30634
+ const globalPrompt = await loadTextFile(globalPromptPath);
30635
+ if (globalPrompt) {
30636
+ prompts.push(globalPrompt);
30637
+ }
30638
+ const projectPromptPath = join8(getProjectConfigDir(cwd2), "OLDPAL.md");
30639
+ const projectPrompt = await loadTextFile(projectPromptPath);
30640
+ if (projectPrompt) {
30641
+ prompts.push(projectPrompt);
30642
+ }
30643
+ if (prompts.length === 0) {
30644
+ return null;
30645
+ }
30646
+ return prompts.join(`
30647
+
30648
+ ---
30649
+
30650
+ `);
30651
+ }
30652
+ async function loadTextFile(path) {
30653
+ try {
30654
+ const file = Bun.file(path);
30655
+ if (!await file.exists()) {
30656
+ return null;
30657
+ }
30658
+ return await file.text();
30659
+ } catch {
30660
+ return null;
30661
+ }
30662
+ }
30453
30663
 
30454
30664
  // packages/core/src/agent/loop.ts
30455
30665
  class AgentLoop {
@@ -30492,18 +30702,31 @@ class AgentLoop {
30492
30702
  this.onTokenUsage = options.onTokenUsage;
30493
30703
  }
30494
30704
  async initialize() {
30495
- this.config = await loadConfig(this.cwd);
30496
- this.llmClient = await createLLMClient(this.config.llm);
30705
+ const [config] = await Promise.all([
30706
+ loadConfig(this.cwd),
30707
+ ensureConfigDir()
30708
+ ]);
30709
+ this.config = config;
30710
+ const [, , , hooksConfig, systemPrompt] = await Promise.all([
30711
+ createLLMClient(this.config.llm).then((client) => {
30712
+ this.llmClient = client;
30713
+ }),
30714
+ this.connectorBridge.discover(this.config.connectors),
30715
+ this.skillLoader.loadAll(this.cwd),
30716
+ loadHooksConfig(this.cwd),
30717
+ loadSystemPrompt(this.cwd),
30718
+ this.commandLoader.loadAll()
30719
+ ]);
30497
30720
  this.toolRegistry.register(BashTool.tool, BashTool.executor);
30498
30721
  FilesystemTools.registerAll(this.toolRegistry);
30499
30722
  WebTools.registerAll(this.toolRegistry);
30500
- await this.connectorBridge.discover(this.config.connectors);
30723
+ ImageTools.registerAll(this.toolRegistry);
30501
30724
  this.connectorBridge.registerAll(this.toolRegistry);
30502
- await this.skillLoader.loadAll(this.cwd);
30503
- await this.commandLoader.loadAll();
30504
30725
  this.builtinCommands.registerAll(this.commandLoader);
30505
- const hooksConfig = await loadHooksConfig(this.cwd);
30506
30726
  this.hookLoader.load(hooksConfig);
30727
+ if (systemPrompt) {
30728
+ this.context.addSystemMessage(systemPrompt);
30729
+ }
30507
30730
  await this.hookExecutor.execute(this.hookLoader.getHooks("SessionStart"), {
30508
30731
  session_id: this.sessionId,
30509
30732
  hook_event_name: "SessionStart",
@@ -30656,7 +30879,7 @@ class AgentLoop {
30656
30879
  return false;
30657
30880
  }
30658
30881
  const argsList = args ? args.split(/\s+/) : [];
30659
- const content = this.skillExecutor.prepare(skill, argsList);
30882
+ const content = await this.skillExecutor.prepare(skill, argsList);
30660
30883
  this.context.addSystemMessage(content);
30661
30884
  this.context.addUserMessage(`Execute the "${skillName}" skill with arguments: ${args || "(none)"}`);
30662
30885
  await this.runLoop();
@@ -30702,9 +30925,9 @@ class AgentLoop {
30702
30925
  init_anthropic();
30703
30926
 
30704
30927
  // packages/core/src/logger.ts
30705
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync } from "fs";
30706
- import { join as join8 } from "path";
30707
- import { homedir as homedir7 } from "os";
30928
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, appendFileSync } from "fs";
30929
+ import { join as join9 } from "path";
30930
+ import { homedir as homedir8 } from "os";
30708
30931
 
30709
30932
  class Logger {
30710
30933
  logDir;
@@ -30712,13 +30935,13 @@ class Logger {
30712
30935
  sessionId;
30713
30936
  constructor(sessionId) {
30714
30937
  this.sessionId = sessionId;
30715
- this.logDir = join8(homedir7(), ".oldpal", "logs");
30938
+ this.logDir = join9(homedir8(), ".oldpal", "logs");
30716
30939
  this.ensureDir(this.logDir);
30717
30940
  const date = new Date().toISOString().split("T")[0];
30718
- this.logFile = join8(this.logDir, `${date}.log`);
30941
+ this.logFile = join9(this.logDir, `${date}.log`);
30719
30942
  }
30720
30943
  ensureDir(dir) {
30721
- if (!existsSync5(dir)) {
30944
+ if (!existsSync6(dir)) {
30722
30945
  mkdirSync2(dir, { recursive: true });
30723
30946
  }
30724
30947
  }
@@ -30758,12 +30981,12 @@ class SessionStorage {
30758
30981
  sessionId;
30759
30982
  constructor(sessionId) {
30760
30983
  this.sessionId = sessionId;
30761
- this.sessionsDir = join8(homedir7(), ".oldpal", "sessions");
30984
+ this.sessionsDir = join9(homedir8(), ".oldpal", "sessions");
30762
30985
  this.ensureDir(this.sessionsDir);
30763
- this.sessionFile = join8(this.sessionsDir, `${sessionId}.json`);
30986
+ this.sessionFile = join9(this.sessionsDir, `${sessionId}.json`);
30764
30987
  }
30765
30988
  ensureDir(dir) {
30766
- if (!existsSync5(dir)) {
30989
+ if (!existsSync6(dir)) {
30767
30990
  mkdirSync2(dir, { recursive: true });
30768
30991
  }
30769
30992
  }
@@ -30777,15 +31000,15 @@ class SessionStorage {
30777
31000
  }
30778
31001
  }
30779
31002
  function initOldpalDir() {
30780
- const baseDir = join8(homedir7(), ".oldpal");
31003
+ const baseDir = join9(homedir8(), ".oldpal");
30781
31004
  const dirs = [
30782
31005
  baseDir,
30783
- join8(baseDir, "sessions"),
30784
- join8(baseDir, "logs"),
30785
- join8(baseDir, "skills")
31006
+ join9(baseDir, "sessions"),
31007
+ join9(baseDir, "logs"),
31008
+ join9(baseDir, "skills")
30786
31009
  ];
30787
31010
  for (const dir of dirs) {
30788
- if (!existsSync5(dir)) {
31011
+ if (!existsSync6(dir)) {
30789
31012
  mkdirSync2(dir, { recursive: true });
30790
31013
  }
30791
31014
  }
@@ -31118,8 +31341,18 @@ function parseMarkdown(text) {
31118
31341
 
31119
31342
  // packages/terminal/src/components/Messages.tsx
31120
31343
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
31121
- function Messages4({ messages, currentResponse, currentToolCall, lastToolResult, activityLog = [] }) {
31122
- const visibleMessages = messages.slice(-10);
31344
+ function Messages4({
31345
+ messages,
31346
+ currentResponse,
31347
+ currentToolCall,
31348
+ lastToolResult,
31349
+ activityLog = [],
31350
+ scrollOffset = 0,
31351
+ maxVisible = 10
31352
+ }) {
31353
+ const endIndex = messages.length - scrollOffset;
31354
+ const startIndex = Math.max(0, endIndex - maxVisible);
31355
+ const visibleMessages = messages.slice(startIndex, endIndex);
31123
31356
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31124
31357
  flexDirection: "column",
31125
31358
  children: [
@@ -31144,30 +31377,6 @@ function Messages4({ messages, currentResponse, currentToolCall, lastToolResult,
31144
31377
  ]
31145
31378
  }, entry.id, true, undefined, this);
31146
31379
  }
31147
- if (entry.type === "tool_call" && entry.toolCall) {
31148
- return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31149
- marginTop: 1,
31150
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31151
- dimColor: true,
31152
- children: [
31153
- " \u25D0 ",
31154
- formatToolCall(entry.toolCall)
31155
- ]
31156
- }, undefined, true, undefined, this)
31157
- }, entry.id, false, undefined, this);
31158
- }
31159
- if (entry.type === "tool_result" && entry.toolResult) {
31160
- return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31161
- marginBottom: 1,
31162
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31163
- dimColor: true,
31164
- children: [
31165
- " \u2192 ",
31166
- truncate(entry.toolResult.content, 80)
31167
- ]
31168
- }, undefined, true, undefined, this)
31169
- }, entry.id, false, undefined, this);
31170
- }
31171
31380
  return null;
31172
31381
  }),
31173
31382
  currentResponse && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -31184,17 +31393,7 @@ function Messages4({ messages, currentResponse, currentToolCall, lastToolResult,
31184
31393
  }, undefined, false, undefined, this)
31185
31394
  }, undefined, false, undefined, this)
31186
31395
  ]
31187
- }, undefined, true, undefined, this),
31188
- currentToolCall && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31189
- marginTop: 1,
31190
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31191
- dimColor: true,
31192
- children: [
31193
- " \u25D0 ",
31194
- formatToolCall(currentToolCall)
31195
- ]
31196
- }, undefined, true, undefined, this)
31197
- }, undefined, false, undefined, this)
31396
+ }, undefined, true, undefined, this)
31198
31397
  ]
31199
31398
  }, undefined, true, undefined, this);
31200
31399
  }
@@ -31218,6 +31417,7 @@ function MessageBubble({ message }) {
31218
31417
  ]
31219
31418
  }, undefined, true, undefined, this);
31220
31419
  }
31420
+ const toolCount = message.toolCalls?.length || 0;
31221
31421
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31222
31422
  marginY: 1,
31223
31423
  flexDirection: "column",
@@ -31236,60 +31436,22 @@ function MessageBubble({ message }) {
31236
31436
  }, undefined, false, undefined, this)
31237
31437
  ]
31238
31438
  }, undefined, true, undefined, this),
31239
- message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31439
+ toolCount > 0 && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31240
31440
  marginLeft: 2,
31241
- marginTop: 1,
31242
31441
  children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31243
31442
  dimColor: true,
31244
31443
  children: [
31245
- "\u25D0 ",
31246
- formatToolCall(toolCall)
31444
+ "[",
31445
+ toolCount,
31446
+ " tool",
31447
+ toolCount > 1 ? "s" : "",
31448
+ " used]"
31247
31449
  ]
31248
31450
  }, undefined, true, undefined, this)
31249
- }, toolCall.id, false, undefined, this))
31451
+ }, undefined, false, undefined, this)
31250
31452
  ]
31251
31453
  }, undefined, true, undefined, this);
31252
31454
  }
31253
- function formatToolCall(toolCall) {
31254
- const { name, input } = toolCall;
31255
- switch (name) {
31256
- case "bash":
31257
- return `Running: ${truncate(String(input.command || ""), 60)}`;
31258
- case "curl":
31259
- return `Fetching: ${truncate(String(input.url || ""), 60)}`;
31260
- case "web_fetch":
31261
- return `Fetching: ${truncate(String(input.url || ""), 60)}`;
31262
- case "web_search":
31263
- return `Searching: ${truncate(String(input.query || ""), 60)}`;
31264
- case "read":
31265
- return `Reading: ${truncate(String(input.path || input.file_path || ""), 60)}`;
31266
- case "write":
31267
- return `Writing: ${truncate(String(input.path || input.file_path || ""), 60)}`;
31268
- case "glob":
31269
- return `Finding: ${truncate(String(input.pattern || ""), 60)}`;
31270
- case "grep":
31271
- return `Searching: ${truncate(String(input.pattern || ""), 60)}`;
31272
- case "notion":
31273
- return `Notion: ${truncate(String(input.command || input.action || ""), 60)}`;
31274
- case "gmail":
31275
- return `Gmail: ${truncate(String(input.command || input.action || ""), 60)}`;
31276
- case "googledrive":
31277
- return `Drive: ${truncate(String(input.command || input.action || ""), 60)}`;
31278
- case "googlecalendar":
31279
- return `Calendar: ${truncate(String(input.command || input.action || ""), 60)}`;
31280
- case "linear":
31281
- return `Linear: ${truncate(String(input.command || input.action || ""), 60)}`;
31282
- case "slack":
31283
- return `Slack: ${truncate(String(input.command || input.action || ""), 60)}`;
31284
- default:
31285
- return `${name}: ${truncate(JSON.stringify(input), 50)}`;
31286
- }
31287
- }
31288
- function truncate(text, maxLength) {
31289
- if (text.length <= maxLength)
31290
- return text;
31291
- return text.slice(0, maxLength - 3) + "...";
31292
- }
31293
31455
 
31294
31456
  // packages/terminal/src/components/Status.tsx
31295
31457
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
@@ -31386,26 +31548,232 @@ function Spinner2({ label }) {
31386
31548
  }, undefined, true, undefined, this);
31387
31549
  }
31388
31550
 
31389
- // packages/terminal/src/components/App.tsx
31551
+ // packages/terminal/src/components/ProcessingIndicator.tsx
31552
+ var import_react25 = __toESM(require_react(), 1);
31390
31553
  var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
31554
+ function ProcessingIndicator({
31555
+ isProcessing,
31556
+ startTime,
31557
+ tokenCount = 0,
31558
+ isThinking = false
31559
+ }) {
31560
+ const [elapsed, setElapsed] = import_react25.useState(0);
31561
+ import_react25.useEffect(() => {
31562
+ if (!isProcessing || !startTime) {
31563
+ setElapsed(0);
31564
+ return;
31565
+ }
31566
+ const interval = setInterval(() => {
31567
+ setElapsed(Math.floor((Date.now() - startTime) / 1000));
31568
+ }, 1000);
31569
+ setElapsed(Math.floor((Date.now() - startTime) / 1000));
31570
+ return () => clearInterval(interval);
31571
+ }, [isProcessing, startTime]);
31572
+ if (!isProcessing) {
31573
+ return null;
31574
+ }
31575
+ const formatTime = (seconds) => {
31576
+ if (seconds < 60) {
31577
+ return `${seconds}s`;
31578
+ }
31579
+ const mins = Math.floor(seconds / 60);
31580
+ const secs = seconds % 60;
31581
+ return `${mins}m ${secs}s`;
31582
+ };
31583
+ const formatTokens = (tokens) => {
31584
+ if (tokens >= 1000) {
31585
+ return `${(tokens / 1000).toFixed(1)}k`;
31586
+ }
31587
+ return String(tokens);
31588
+ };
31589
+ const parts = [];
31590
+ parts.push("esc to interrupt");
31591
+ if (elapsed > 0) {
31592
+ parts.push(formatTime(elapsed));
31593
+ }
31594
+ if (tokenCount > 0) {
31595
+ parts.push(`\u2193 ${formatTokens(tokenCount)} tokens`);
31596
+ }
31597
+ if (isThinking) {
31598
+ parts.push("thinking");
31599
+ }
31600
+ const label = isThinking ? "Thinking" : "Working";
31601
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
31602
+ marginY: 1,
31603
+ children: [
31604
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
31605
+ dimColor: true,
31606
+ children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(build_default2, {
31607
+ type: "dots"
31608
+ }, undefined, false, undefined, this)
31609
+ }, undefined, false, undefined, this),
31610
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
31611
+ dimColor: true,
31612
+ children: [
31613
+ " ",
31614
+ label,
31615
+ "... "
31616
+ ]
31617
+ }, undefined, true, undefined, this),
31618
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
31619
+ dimColor: true,
31620
+ children: [
31621
+ "(",
31622
+ parts.join(" \xB7 "),
31623
+ ")"
31624
+ ]
31625
+ }, undefined, true, undefined, this)
31626
+ ]
31627
+ }, undefined, true, undefined, this);
31628
+ }
31629
+
31630
+ // packages/terminal/src/components/ToolCallBox.tsx
31631
+ var import_react26 = __toESM(require_react(), 1);
31632
+ var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
31633
+ function ToolCallBox({
31634
+ entries,
31635
+ maxVisible = 3,
31636
+ isExpanded = false,
31637
+ onToggleExpand
31638
+ }) {
31639
+ if (entries.length === 0) {
31640
+ return null;
31641
+ }
31642
+ const visibleEntries = isExpanded ? entries : entries.slice(-maxVisible);
31643
+ const hiddenCount = entries.length - visibleEntries.length;
31644
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
31645
+ flexDirection: "column",
31646
+ borderStyle: "round",
31647
+ borderColor: "gray",
31648
+ paddingX: 1,
31649
+ marginY: 1,
31650
+ children: [
31651
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
31652
+ justifyContent: "space-between",
31653
+ children: [
31654
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31655
+ dimColor: true,
31656
+ bold: true,
31657
+ children: [
31658
+ "Tools (",
31659
+ entries.length,
31660
+ ")"
31661
+ ]
31662
+ }, undefined, true, undefined, this),
31663
+ entries.length > maxVisible && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31664
+ dimColor: true,
31665
+ children: isExpanded ? "Ctrl+O to collapse" : "Ctrl+O to expand"
31666
+ }, undefined, false, undefined, this)
31667
+ ]
31668
+ }, undefined, true, undefined, this),
31669
+ hiddenCount > 0 && !isExpanded && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31670
+ dimColor: true,
31671
+ children: [
31672
+ " +",
31673
+ hiddenCount,
31674
+ " more above..."
31675
+ ]
31676
+ }, undefined, true, undefined, this),
31677
+ visibleEntries.map((entry, index) => /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(ToolCallRow, {
31678
+ entry
31679
+ }, entry.toolCall.id, false, undefined, this))
31680
+ ]
31681
+ }, undefined, true, undefined, this);
31682
+ }
31683
+ function ToolCallRow({ entry }) {
31684
+ const { toolCall, result } = entry;
31685
+ const statusIcon = result ? result.isError ? "\u2717" : "\u2713" : "\u25D0";
31686
+ const statusColor = result ? result.isError ? "red" : "green" : "yellow";
31687
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
31688
+ children: [
31689
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31690
+ color: statusColor,
31691
+ children: [
31692
+ statusIcon,
31693
+ " "
31694
+ ]
31695
+ }, undefined, true, undefined, this),
31696
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31697
+ dimColor: true,
31698
+ children: formatToolCall(toolCall)
31699
+ }, undefined, false, undefined, this)
31700
+ ]
31701
+ }, undefined, true, undefined, this);
31702
+ }
31703
+ function formatToolCall(toolCall) {
31704
+ const { name, input } = toolCall;
31705
+ switch (name) {
31706
+ case "bash":
31707
+ return `bash: ${truncate(String(input.command || ""), 50)}`;
31708
+ case "curl":
31709
+ case "web_fetch":
31710
+ return `fetch: ${truncate(String(input.url || ""), 50)}`;
31711
+ case "web_search":
31712
+ return `search: ${truncate(String(input.query || ""), 50)}`;
31713
+ case "read":
31714
+ return `read: ${truncate(String(input.path || input.file_path || ""), 50)}`;
31715
+ case "write":
31716
+ return `write: ${truncate(String(input.path || input.file_path || ""), 50)}`;
31717
+ case "glob":
31718
+ return `glob: ${truncate(String(input.pattern || ""), 50)}`;
31719
+ case "grep":
31720
+ return `grep: ${truncate(String(input.pattern || ""), 50)}`;
31721
+ case "notion":
31722
+ return `notion: ${truncate(String(input.command || input.action || ""), 50)}`;
31723
+ case "gmail":
31724
+ return `gmail: ${truncate(String(input.command || input.action || ""), 50)}`;
31725
+ case "googledrive":
31726
+ return `drive: ${truncate(String(input.command || input.action || ""), 50)}`;
31727
+ case "googlecalendar":
31728
+ return `calendar: ${truncate(String(input.command || input.action || ""), 50)}`;
31729
+ case "linear":
31730
+ return `linear: ${truncate(String(input.command || input.action || ""), 50)}`;
31731
+ case "slack":
31732
+ return `slack: ${truncate(String(input.command || input.action || ""), 50)}`;
31733
+ default:
31734
+ return `${name}: ${truncate(JSON.stringify(input), 40)}`;
31735
+ }
31736
+ }
31737
+ function truncate(text, maxLength) {
31738
+ if (text.length <= maxLength)
31739
+ return text;
31740
+ return text.slice(0, maxLength - 3) + "...";
31741
+ }
31742
+ function useToolCallExpansion() {
31743
+ const [isExpanded, setIsExpanded] = import_react26.useState(false);
31744
+ use_input_default((input, key) => {
31745
+ if (key.ctrl && input === "o") {
31746
+ setIsExpanded((prev) => !prev);
31747
+ }
31748
+ });
31749
+ return { isExpanded, setIsExpanded };
31750
+ }
31751
+
31752
+ // packages/terminal/src/components/App.tsx
31753
+ var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
31391
31754
  function App2({ cwd: cwd2 }) {
31392
31755
  const { exit } = use_app_default();
31393
- const [client, setClient] = import_react25.useState(null);
31394
- const [messages, setMessages] = import_react25.useState([]);
31395
- const [currentResponse, setCurrentResponse] = import_react25.useState("");
31396
- const [currentToolCall, setCurrentToolCall] = import_react25.useState();
31397
- const [lastToolResult, setLastToolResult] = import_react25.useState();
31398
- const [isProcessing, setIsProcessing] = import_react25.useState(false);
31399
- const [isInitializing, setIsInitializing] = import_react25.useState(true);
31400
- const [error, setError] = import_react25.useState(null);
31401
- const [messageQueue, setMessageQueue] = import_react25.useState([]);
31402
- const [activityLog, setActivityLog] = import_react25.useState([]);
31403
- const [tokenUsage, setTokenUsage] = import_react25.useState();
31404
- const responseRef = import_react25.useRef("");
31405
- const clientRef = import_react25.useRef(null);
31406
- const toolCallsRef = import_react25.useRef([]);
31407
- const toolResultsRef = import_react25.useRef([]);
31408
- const processQueue = import_react25.useCallback(async () => {
31756
+ const [client, setClient] = import_react27.useState(null);
31757
+ const [messages, setMessages] = import_react27.useState([]);
31758
+ const [currentResponse, setCurrentResponse] = import_react27.useState("");
31759
+ const [currentToolCall, setCurrentToolCall] = import_react27.useState();
31760
+ const [lastToolResult, setLastToolResult] = import_react27.useState();
31761
+ const [isProcessing, setIsProcessing] = import_react27.useState(false);
31762
+ const [isInitializing, setIsInitializing] = import_react27.useState(true);
31763
+ const [error, setError] = import_react27.useState(null);
31764
+ const [messageQueue, setMessageQueue] = import_react27.useState([]);
31765
+ const [activityLog, setActivityLog] = import_react27.useState([]);
31766
+ const [tokenUsage, setTokenUsage] = import_react27.useState();
31767
+ const [processingStartTime, setProcessingStartTime] = import_react27.useState();
31768
+ const [currentTurnTokens, setCurrentTurnTokens] = import_react27.useState(0);
31769
+ const [scrollOffset, setScrollOffset] = import_react27.useState(0);
31770
+ const [autoScroll, setAutoScroll] = import_react27.useState(true);
31771
+ const { isExpanded: toolsExpanded } = useToolCallExpansion();
31772
+ const responseRef = import_react27.useRef("");
31773
+ const clientRef = import_react27.useRef(null);
31774
+ const toolCallsRef = import_react27.useRef([]);
31775
+ const toolResultsRef = import_react27.useRef([]);
31776
+ const processQueue = import_react27.useCallback(async () => {
31409
31777
  if (!clientRef.current || messageQueue.length === 0)
31410
31778
  return;
31411
31779
  const nextMessage = messageQueue[0];
@@ -31425,10 +31793,12 @@ function App2({ cwd: cwd2 }) {
31425
31793
  setCurrentToolCall(undefined);
31426
31794
  setLastToolResult(undefined);
31427
31795
  setActivityLog([]);
31796
+ setProcessingStartTime(Date.now());
31797
+ setCurrentTurnTokens(0);
31428
31798
  setIsProcessing(true);
31429
31799
  await clientRef.current.send(nextMessage);
31430
31800
  }, [messageQueue]);
31431
- import_react25.useEffect(() => {
31801
+ import_react27.useEffect(() => {
31432
31802
  const initClient = async () => {
31433
31803
  try {
31434
31804
  const newClient = new EmbeddedClient(cwd2);
@@ -31479,6 +31849,7 @@ function App2({ cwd: cwd2 }) {
31479
31849
  setIsProcessing(false);
31480
31850
  } else if (chunk.type === "usage" && chunk.usage) {
31481
31851
  setTokenUsage(chunk.usage);
31852
+ setCurrentTurnTokens((prev) => prev + (chunk.usage?.outputTokens || 0));
31482
31853
  } else if (chunk.type === "done") {
31483
31854
  if (responseRef.current.trim()) {
31484
31855
  setActivityLog((prev) => [
@@ -31513,6 +31884,8 @@ function App2({ cwd: cwd2 }) {
31513
31884
  toolResultsRef.current = [];
31514
31885
  setCurrentToolCall(undefined);
31515
31886
  setActivityLog([]);
31887
+ setProcessingStartTime(undefined);
31888
+ setCurrentTurnTokens(0);
31516
31889
  setIsProcessing(false);
31517
31890
  if (newClient) {
31518
31891
  setTokenUsage(newClient.getTokenUsage());
@@ -31533,11 +31906,17 @@ function App2({ cwd: cwd2 }) {
31533
31906
  };
31534
31907
  initClient();
31535
31908
  }, [cwd2]);
31536
- import_react25.useEffect(() => {
31909
+ import_react27.useEffect(() => {
31537
31910
  if (!isProcessing && messageQueue.length > 0) {
31538
31911
  processQueue();
31539
31912
  }
31540
31913
  }, [isProcessing, messageQueue.length, processQueue]);
31914
+ import_react27.useEffect(() => {
31915
+ if (autoScroll) {
31916
+ setScrollOffset(0);
31917
+ }
31918
+ }, [messages.length, autoScroll]);
31919
+ const maxVisibleMessages = 10;
31541
31920
  use_input_default((input, key) => {
31542
31921
  if (key.ctrl && input === "c") {
31543
31922
  if (isProcessing && client) {
@@ -31581,8 +31960,34 @@ function App2({ cwd: cwd2 }) {
31581
31960
  }
31582
31961
  setIsProcessing(false);
31583
31962
  }
31963
+ if (key.pageUp || key.shift && key.upArrow) {
31964
+ setScrollOffset((prev) => {
31965
+ const maxOffset = Math.max(0, messages.length - maxVisibleMessages);
31966
+ const newOffset = Math.min(prev + 3, maxOffset);
31967
+ if (newOffset > 0)
31968
+ setAutoScroll(false);
31969
+ return newOffset;
31970
+ });
31971
+ }
31972
+ if (key.pageDown || key.shift && key.downArrow) {
31973
+ setScrollOffset((prev) => {
31974
+ const newOffset = Math.max(0, prev - 3);
31975
+ if (newOffset === 0)
31976
+ setAutoScroll(true);
31977
+ return newOffset;
31978
+ });
31979
+ }
31980
+ if (key.ctrl && input === "u") {
31981
+ const maxOffset = Math.max(0, messages.length - maxVisibleMessages);
31982
+ setScrollOffset(maxOffset);
31983
+ setAutoScroll(false);
31984
+ }
31985
+ if (key.ctrl && input === "d") {
31986
+ setScrollOffset(0);
31987
+ setAutoScroll(true);
31988
+ }
31584
31989
  });
31585
- const handleSubmit = import_react25.useCallback(async (input, mode = "normal") => {
31990
+ const handleSubmit = import_react27.useCallback(async (input, mode = "normal") => {
31586
31991
  if (!client || !input.trim())
31587
31992
  return;
31588
31993
  const trimmedInput = input.trim();
@@ -31625,32 +32030,56 @@ function App2({ cwd: cwd2 }) {
31625
32030
  setCurrentToolCall(undefined);
31626
32031
  setLastToolResult(undefined);
31627
32032
  setActivityLog([]);
32033
+ setProcessingStartTime(Date.now());
32034
+ setCurrentTurnTokens(0);
31628
32035
  setIsProcessing(true);
31629
32036
  await client.send(trimmedInput);
31630
32037
  }, [client, isProcessing]);
31631
32038
  if (isInitializing) {
31632
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
32039
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
31633
32040
  flexDirection: "column",
31634
32041
  padding: 1,
31635
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Spinner2, {
32042
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Spinner2, {
31636
32043
  label: "Initializing..."
31637
32044
  }, undefined, false, undefined, this)
31638
32045
  }, undefined, false, undefined, this);
31639
32046
  }
31640
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
32047
+ const toolCallEntries = activityLog.filter((e) => e.type === "tool_call" && e.toolCall).map((e) => {
32048
+ const result = activityLog.find((r) => r.type === "tool_result" && r.toolResult?.toolCallId === e.toolCall?.id)?.toolResult;
32049
+ return { toolCall: e.toolCall, result };
32050
+ });
32051
+ const isThinking = isProcessing && !currentResponse && !currentToolCall && toolCallEntries.length === 0;
32052
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
31641
32053
  flexDirection: "column",
31642
32054
  padding: 1,
31643
32055
  children: [
31644
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Messages4, {
32056
+ scrollOffset > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32057
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
32058
+ dimColor: true,
32059
+ children: [
32060
+ "\u2191 ",
32061
+ scrollOffset,
32062
+ " more messages above (Shift+\u2193 or Ctrl+D to scroll down)"
32063
+ ]
32064
+ }, undefined, true, undefined, this)
32065
+ }, undefined, false, undefined, this),
32066
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Messages4, {
31645
32067
  messages,
31646
32068
  currentResponse: isProcessing ? currentResponse : undefined,
31647
- currentToolCall,
31648
- lastToolResult,
31649
- activityLog: isProcessing ? activityLog : []
32069
+ currentToolCall: undefined,
32070
+ lastToolResult: undefined,
32071
+ activityLog: isProcessing ? activityLog.filter((e) => e.type === "text") : [],
32072
+ scrollOffset,
32073
+ maxVisible: maxVisibleMessages
32074
+ }, undefined, false, undefined, this),
32075
+ isProcessing && toolCallEntries.length > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(ToolCallBox, {
32076
+ entries: toolCallEntries,
32077
+ maxVisible: 3,
32078
+ isExpanded: toolsExpanded
31650
32079
  }, undefined, false, undefined, this),
31651
- messageQueue.length > 0 && /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
32080
+ messageQueue.length > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
31652
32081
  marginY: 1,
31653
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
32082
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
31654
32083
  dimColor: true,
31655
32084
  children: [
31656
32085
  messageQueue.length,
@@ -31660,9 +32089,9 @@ function App2({ cwd: cwd2 }) {
31660
32089
  ]
31661
32090
  }, undefined, true, undefined, this)
31662
32091
  }, undefined, false, undefined, this),
31663
- error && /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
32092
+ error && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
31664
32093
  marginY: 1,
31665
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
32094
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
31666
32095
  color: "red",
31667
32096
  children: [
31668
32097
  "Error: ",
@@ -31670,18 +32099,18 @@ function App2({ cwd: cwd2 }) {
31670
32099
  ]
31671
32100
  }, undefined, true, undefined, this)
31672
32101
  }, undefined, false, undefined, this),
31673
- isProcessing && !currentToolCall && !currentResponse && /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
31674
- marginY: 1,
31675
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Spinner2, {
31676
- label: "Thinking..."
31677
- }, undefined, false, undefined, this)
32102
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(ProcessingIndicator, {
32103
+ isProcessing,
32104
+ startTime: processingStartTime,
32105
+ tokenCount: currentTurnTokens,
32106
+ isThinking
31678
32107
  }, undefined, false, undefined, this),
31679
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Input, {
32108
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Input, {
31680
32109
  onSubmit: handleSubmit,
31681
32110
  isProcessing,
31682
32111
  queueLength: messageQueue.length
31683
32112
  }, undefined, false, undefined, this),
31684
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Status, {
32113
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Status, {
31685
32114
  isProcessing,
31686
32115
  cwd: cwd2,
31687
32116
  queueLength: messageQueue.length,
@@ -31692,7 +32121,7 @@ function App2({ cwd: cwd2 }) {
31692
32121
  }
31693
32122
 
31694
32123
  // packages/terminal/src/index.tsx
31695
- var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
32124
+ var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
31696
32125
  var args = process.argv.slice(2);
31697
32126
  var options = {
31698
32127
  cwd: process.cwd(),
@@ -31700,7 +32129,7 @@ var options = {
31700
32129
  help: args.includes("--help") || args.includes("-h")
31701
32130
  };
31702
32131
  if (options.version) {
31703
- console.log("oldpal v0.2.0");
32132
+ console.log("oldpal v0.3.0");
31704
32133
  process.exit(0);
31705
32134
  }
31706
32135
  if (options.help) {
@@ -31724,11 +32153,11 @@ In interactive mode:
31724
32153
  `);
31725
32154
  process.exit(0);
31726
32155
  }
31727
- var { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime7.jsxDEV(App2, {
32156
+ var { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(App2, {
31728
32157
  cwd: options.cwd
31729
32158
  }, undefined, false, undefined, this));
31730
32159
  waitUntilExit().then(() => {
31731
32160
  process.exit(0);
31732
32161
  });
31733
32162
 
31734
- //# debugId=21C263FACC119E6E64756E2164756E21
32163
+ //# debugId=439BE8F926EBDCB864756E2164756E21