@hasna/oldpal 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20451,10 +20451,10 @@ __export(exports_anthropic, {
20451
20451
  AnthropicClient: () => AnthropicClient
20452
20452
  });
20453
20453
  import { readFileSync as readFileSync2, existsSync as existsSync5 } from "fs";
20454
- import { homedir as homedir6 } from "os";
20454
+ import { homedir as homedir7 } from "os";
20455
20455
  import { join as join7 } from "path";
20456
20456
  function loadApiKeyFromSecrets() {
20457
- const secretsPath = join7(homedir6(), ".secrets");
20457
+ const secretsPath = join7(homedir7(), ".secrets");
20458
20458
  if (existsSync5(secretsPath)) {
20459
20459
  try {
20460
20460
  const content = readFileSync2(secretsPath, "utf-8");
@@ -28328,6 +28328,25 @@ var getInstance = (stdout, createInstance) => {
28328
28328
  };
28329
28329
  // 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/components/Static.js
28330
28330
  var import_react11 = __toESM(require_react(), 1);
28331
+ function Static(props) {
28332
+ const { items, children: render2, style: customStyle } = props;
28333
+ const [index, setIndex] = import_react11.useState(0);
28334
+ const itemsToRender = import_react11.useMemo(() => {
28335
+ return items.slice(index);
28336
+ }, [items, index]);
28337
+ import_react11.useLayoutEffect(() => {
28338
+ setIndex(items.length);
28339
+ }, [items.length]);
28340
+ const children = itemsToRender.map((item, itemIndex) => {
28341
+ return render2(item, index + itemIndex);
28342
+ });
28343
+ const style = import_react11.useMemo(() => ({
28344
+ position: "absolute",
28345
+ flexDirection: "column",
28346
+ ...customStyle
28347
+ }), [customStyle]);
28348
+ return import_react11.default.createElement("ink-box", { internal_static: true, style }, children);
28349
+ }
28331
28350
  // 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/components/Transform.js
28332
28351
  var import_react12 = __toESM(require_react(), 1);
28333
28352
  // 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/components/Newline.js
@@ -28594,7 +28613,7 @@ var import_react20 = __toESM(require_react(), 1);
28594
28613
  // 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
28595
28614
  var import_react21 = __toESM(require_react(), 1);
28596
28615
  // packages/terminal/src/components/App.tsx
28597
- var import_react26 = __toESM(require_react(), 1);
28616
+ var import_react27 = __toESM(require_react(), 1);
28598
28617
  // packages/shared/src/utils.ts
28599
28618
  import { randomUUID } from "crypto";
28600
28619
  function generateId() {
@@ -28973,13 +28992,13 @@ class ConnectorBridge {
28973
28992
  class BashTool {
28974
28993
  static tool = {
28975
28994
  name: "bash",
28976
- description: "Execute a shell command. Use for system operations, running scripts, git commands, etc.",
28995
+ description: "Execute a shell command. RESTRICTED to read-only operations: ls, cat, grep, find, git status/log/diff, pwd, which, echo. Cannot modify files, install packages, or run destructive commands.",
28977
28996
  parameters: {
28978
28997
  type: "object",
28979
28998
  properties: {
28980
28999
  command: {
28981
29000
  type: "string",
28982
- description: "The shell command to execute"
29001
+ description: "The shell command to execute (read-only commands only)"
28983
29002
  },
28984
29003
  cwd: {
28985
29004
  type: "string",
@@ -28987,29 +29006,111 @@ class BashTool {
28987
29006
  },
28988
29007
  timeout: {
28989
29008
  type: "number",
28990
- description: "Timeout in milliseconds (default: 120000)"
29009
+ description: "Timeout in milliseconds (default: 30000)"
28991
29010
  }
28992
29011
  },
28993
29012
  required: ["command"]
28994
29013
  }
28995
29014
  };
29015
+ static ALLOWED_COMMANDS = [
29016
+ "cat",
29017
+ "head",
29018
+ "tail",
29019
+ "less",
29020
+ "more",
29021
+ "ls",
29022
+ "tree",
29023
+ "find",
29024
+ "locate",
29025
+ "grep",
29026
+ "rg",
29027
+ "ag",
29028
+ "ack",
29029
+ "wc",
29030
+ "file",
29031
+ "stat",
29032
+ "du",
29033
+ "df",
29034
+ "pwd",
29035
+ "whoami",
29036
+ "date",
29037
+ "which",
29038
+ "where",
29039
+ "type",
29040
+ "env",
29041
+ "printenv",
29042
+ "echo",
29043
+ "git status",
29044
+ "git log",
29045
+ "git diff",
29046
+ "git branch",
29047
+ "git show",
29048
+ "git remote",
29049
+ "git tag",
29050
+ "connect-",
29051
+ "node --version",
29052
+ "bun --version",
29053
+ "npm --version",
29054
+ "pnpm --version"
29055
+ ];
29056
+ static BLOCKED_PATTERNS = [
29057
+ /\brm\b/,
29058
+ /\brmdir\b/,
29059
+ /\bunlink\b/,
29060
+ /\bmv\b/,
29061
+ /\bcp\b/,
29062
+ /\bchmod\b/,
29063
+ /\bchown\b/,
29064
+ /\bchgrp\b/,
29065
+ /\bsudo\b/,
29066
+ /\bsu\b/,
29067
+ /\bdoas\b/,
29068
+ /\bnpm\s+(install|i|add|ci)\b/,
29069
+ /\bpnpm\s+(install|i|add)\b/,
29070
+ /\byarn\s+(install|add)\b/,
29071
+ /\bbun\s+(install|add|i)\b/,
29072
+ /\bpip\s+install\b/,
29073
+ /\bpip3\s+install\b/,
29074
+ /\bbrew\s+install\b/,
29075
+ /\bapt\s+install\b/,
29076
+ /\bapt-get\s+install\b/,
29077
+ /\bgit\s+(push|commit|checkout|reset|rebase|merge|pull|stash|cherry-pick|revert)\b/,
29078
+ /\bgit\s+add\b/,
29079
+ /\|\s*(bash|sh|zsh|fish)\b/,
29080
+ /curl.*\|\s*(bash|sh)/,
29081
+ /wget.*\|\s*(bash|sh)/,
29082
+ />\s*[^|]/,
29083
+ />>/,
29084
+ /\bkill\b/,
29085
+ /\bpkill\b/,
29086
+ /\bkillall\b/,
29087
+ /\bmkfs\b/,
29088
+ /\bdd\b/,
29089
+ /\bfdisk\b/,
29090
+ /\bparted\b/,
29091
+ /\bnc\s+-l/,
29092
+ /\bnetcat\s+-l/,
29093
+ /\bvim?\b/,
29094
+ /\bnano\b/,
29095
+ /\bemacs\b/,
29096
+ /\bmake\b/,
29097
+ /\bcmake\b/,
29098
+ /\bdocker\s+(run|exec|build|push)\b/
29099
+ ];
28996
29100
  static executor = async (input) => {
28997
29101
  const command = input.command;
28998
29102
  const cwd2 = input.cwd || process.cwd();
28999
- const timeout = input.timeout || 120000;
29000
- const dangerousPatterns = [
29001
- /rm\s+-rf\s+[\/~]/i,
29002
- /rm\s+-rf\s+\*/i,
29003
- /mkfs/i,
29004
- /dd\s+if=/i,
29005
- />\s*\/dev\/sd/i,
29006
- /chmod\s+-R\s+777\s+\//i
29007
- ];
29008
- for (const pattern of dangerousPatterns) {
29103
+ const timeout = input.timeout || 30000;
29104
+ for (const pattern of this.BLOCKED_PATTERNS) {
29009
29105
  if (pattern.test(command)) {
29010
- return `Error: This command appears dangerous and was blocked for safety.`;
29106
+ return `Error: This command is not allowed. Only read-only commands are permitted (ls, cat, grep, find, git status/log/diff, etc.)`;
29011
29107
  }
29012
29108
  }
29109
+ const commandTrimmed = command.trim().toLowerCase();
29110
+ const isAllowed = this.ALLOWED_COMMANDS.some((allowed) => commandTrimmed.startsWith(allowed.toLowerCase()));
29111
+ if (!isAllowed) {
29112
+ return `Error: Command not in allowed list. Permitted commands: cat, head, tail, ls, find, grep, wc, file, stat, pwd, which, echo, git status/log/diff/branch/show, connect-*`;
29113
+ }
29013
29114
  try {
29014
29115
  const proc = Bun.spawn(["bash", "-c", command], {
29015
29116
  cwd: cwd2,
@@ -29038,15 +29139,31 @@ ${stderr || stdout}`.trim();
29038
29139
 
29039
29140
  // packages/core/src/tools/filesystem.ts
29040
29141
  import { join as join2, resolve, dirname } from "path";
29142
+ import { homedir as homedir2 } from "os";
29041
29143
  var {Glob } = globalThis.Bun;
29144
+ var currentSessionId = "default";
29145
+ function getTempFolder() {
29146
+ return join2(homedir2(), ".oldpal", "temp", currentSessionId);
29147
+ }
29148
+ function isInTempFolder(path) {
29149
+ const tempFolder = getTempFolder();
29150
+ const resolved = resolve(path);
29151
+ return resolved.startsWith(tempFolder);
29152
+ }
29042
29153
 
29043
29154
  class FilesystemTools {
29044
- static registerAll(registry) {
29155
+ static registerAll(registry, sessionId) {
29156
+ if (sessionId) {
29157
+ currentSessionId = sessionId;
29158
+ }
29045
29159
  registry.register(this.readTool, this.readExecutor);
29046
29160
  registry.register(this.writeTool, this.writeExecutor);
29047
29161
  registry.register(this.globTool, this.globExecutor);
29048
29162
  registry.register(this.grepTool, this.grepExecutor);
29049
29163
  }
29164
+ static setSessionId(sessionId) {
29165
+ currentSessionId = sessionId;
29166
+ }
29050
29167
  static readTool = {
29051
29168
  name: "read",
29052
29169
  description: "Read the contents of a file",
@@ -29093,30 +29210,38 @@ class FilesystemTools {
29093
29210
  };
29094
29211
  static writeTool = {
29095
29212
  name: "write",
29096
- description: "Write content to a file (creates or overwrites)",
29213
+ description: "Write content to a file. RESTRICTED: Can only write to ~/.oldpal/temp/{session}/ folder. Provide just the filename and it will be saved to the temp folder.",
29097
29214
  parameters: {
29098
29215
  type: "object",
29099
29216
  properties: {
29100
- path: {
29217
+ filename: {
29101
29218
  type: "string",
29102
- description: "The file path to write to"
29219
+ description: "The filename to write to (will be saved in temp folder)"
29103
29220
  },
29104
29221
  content: {
29105
29222
  type: "string",
29106
29223
  description: "The content to write"
29107
29224
  }
29108
29225
  },
29109
- required: ["path", "content"]
29226
+ required: ["filename", "content"]
29110
29227
  }
29111
29228
  };
29112
29229
  static writeExecutor = async (input) => {
29113
- const path = resolve(process.cwd(), input.path);
29230
+ const filename = input.filename || input.path;
29114
29231
  const content = input.content;
29232
+ const tempFolder = getTempFolder();
29233
+ const sanitizedFilename = filename.replace(/\.\./g, "").replace(/^\/+/, "");
29234
+ const path = join2(tempFolder, sanitizedFilename);
29235
+ if (!isInTempFolder(path)) {
29236
+ return `Error: Cannot write outside temp folder. Files are saved to ${tempFolder}`;
29237
+ }
29115
29238
  try {
29116
29239
  const dir = dirname(path);
29117
29240
  await Bun.$`mkdir -p ${dir}`.quiet();
29118
29241
  await Bun.write(path, content);
29119
- return `Successfully wrote ${content.length} characters to ${path}`;
29242
+ return `Successfully wrote ${content.length} characters to ${path}
29243
+
29244
+ You can review and copy this file to your project if needed.`;
29120
29245
  } catch (error) {
29121
29246
  return `Error: ${error instanceof Error ? error.message : String(error)}`;
29122
29247
  }
@@ -29506,11 +29631,11 @@ class WebTools {
29506
29631
  import { existsSync as existsSync2, writeFileSync, unlinkSync } from "fs";
29507
29632
  import { tmpdir } from "os";
29508
29633
  import { join as join3 } from "path";
29509
- import { homedir as homedir2 } from "os";
29634
+ import { homedir as homedir3 } from "os";
29510
29635
  async function getViuPath() {
29511
29636
  const locations = [
29512
29637
  "viu",
29513
- join3(homedir2(), ".cargo", "bin", "viu"),
29638
+ join3(homedir3(), ".cargo", "bin", "viu"),
29514
29639
  "/usr/local/bin/viu",
29515
29640
  "/opt/homebrew/bin/viu"
29516
29641
  ];
@@ -29621,12 +29746,12 @@ class ImageTools {
29621
29746
 
29622
29747
  // packages/core/src/skills/loader.ts
29623
29748
  import { join as join4 } from "path";
29624
- import { homedir as homedir3 } from "os";
29749
+ import { homedir as homedir4 } from "os";
29625
29750
  var {Glob: Glob2 } = globalThis.Bun;
29626
29751
  class SkillLoader {
29627
29752
  skills = new Map;
29628
29753
  async loadAll(projectDir = process.cwd()) {
29629
- const userSkillsDir = join4(homedir3(), ".oldpal", "skills");
29754
+ const userSkillsDir = join4(homedir4(), ".oldpal", "skills");
29630
29755
  await this.loadFromDirectory(userSkillsDir);
29631
29756
  const projectSkillsDir = join4(projectDir, ".oldpal", "skills");
29632
29757
  await this.loadFromDirectory(projectSkillsDir);
@@ -29743,7 +29868,8 @@ ARGUMENTS: ${args.join(" ")}`;
29743
29868
  const fullMatch = match[0];
29744
29869
  const command = match[1];
29745
29870
  try {
29746
- const output = await Bun.$`cd ${skillDir} && ${command}`.quiet().text();
29871
+ const fullCommand = `cd ${skillDir} && ${command}`;
29872
+ const output = await Bun.$`sh -c ${fullCommand}`.quiet().text();
29747
29873
  result = result.replace(fullMatch, output.trim());
29748
29874
  } catch (error) {
29749
29875
  const errorMsg = error instanceof Error ? error.message : String(error);
@@ -29921,7 +30047,7 @@ class HookExecutor {
29921
30047
  // packages/core/src/commands/loader.ts
29922
30048
  import { existsSync as existsSync3, readdirSync, statSync } from "fs";
29923
30049
  import { join as join5, basename, extname } from "path";
29924
- import { homedir as homedir4 } from "os";
30050
+ import { homedir as homedir5 } from "os";
29925
30051
 
29926
30052
  class CommandLoader {
29927
30053
  commands = new Map;
@@ -29931,7 +30057,7 @@ class CommandLoader {
29931
30057
  }
29932
30058
  async loadAll() {
29933
30059
  this.commands.clear();
29934
- const globalDir = join5(homedir4(), ".oldpal", "commands");
30060
+ const globalDir = join5(homedir5(), ".oldpal", "commands");
29935
30061
  await this.loadFromDirectory(globalDir, "global");
29936
30062
  const projectDir = join5(this.cwd, ".oldpal", "commands");
29937
30063
  await this.loadFromDirectory(projectDir, "project");
@@ -30120,7 +30246,7 @@ ${stderr}`;
30120
30246
  }
30121
30247
  // packages/core/src/commands/builtin.ts
30122
30248
  import { join as join6 } from "path";
30123
- import { homedir as homedir5 } from "os";
30249
+ import { homedir as homedir6 } from "os";
30124
30250
  import { existsSync as existsSync4, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
30125
30251
 
30126
30252
  class BuiltinCommands {
@@ -30134,6 +30260,7 @@ class BuiltinCommands {
30134
30260
  loader.register(this.helpCommand(loader));
30135
30261
  loader.register(this.clearCommand());
30136
30262
  loader.register(this.newCommand());
30263
+ loader.register(this.sessionCommand());
30137
30264
  loader.register(this.statusCommand());
30138
30265
  loader.register(this.tokensCommand());
30139
30266
  loader.register(this.compactCommand());
@@ -30256,6 +30383,29 @@ class BuiltinCommands {
30256
30383
  }
30257
30384
  };
30258
30385
  }
30386
+ sessionCommand() {
30387
+ return {
30388
+ name: "session",
30389
+ description: "List sessions or switch to a session by number",
30390
+ builtin: true,
30391
+ selfHandled: true,
30392
+ content: "",
30393
+ handler: async (args, context) => {
30394
+ const arg = args.trim();
30395
+ if (arg === "new") {
30396
+ context.emit("done");
30397
+ return { handled: true, sessionAction: "new" };
30398
+ }
30399
+ const num = parseInt(arg, 10);
30400
+ if (!isNaN(num) && num > 0) {
30401
+ context.emit("done");
30402
+ return { handled: true, sessionAction: "switch", sessionNumber: num };
30403
+ }
30404
+ context.emit("done");
30405
+ return { handled: true, sessionAction: "list" };
30406
+ }
30407
+ };
30408
+ }
30259
30409
  tokensCommand() {
30260
30410
  return {
30261
30411
  name: "tokens",
@@ -30407,7 +30557,7 @@ Format the summary as a brief bullet-point list. This summary will replace the c
30407
30557
  handler: async (args, context) => {
30408
30558
  const configPaths = [
30409
30559
  join6(context.cwd, ".oldpal", "config.json"),
30410
- join6(homedir5(), ".oldpal", "config.json")
30560
+ join6(homedir6(), ".oldpal", "config.json")
30411
30561
  ];
30412
30562
  let message = `
30413
30563
  **Configuration**
@@ -30425,7 +30575,7 @@ Format the summary as a brief bullet-point list. This summary will replace the c
30425
30575
  `;
30426
30576
  message += ` - Project: ${join6(context.cwd, ".oldpal", "commands")}
30427
30577
  `;
30428
- message += ` - Global: ${join6(homedir5(), ".oldpal", "commands")}
30578
+ message += ` - Global: ${join6(homedir6(), ".oldpal", "commands")}
30429
30579
  `;
30430
30580
  context.emit("text", message);
30431
30581
  context.emit("done");
@@ -30647,7 +30797,7 @@ async function createLLMClient(config) {
30647
30797
 
30648
30798
  // packages/core/src/config.ts
30649
30799
  import { join as join8 } from "path";
30650
- import { homedir as homedir7 } from "os";
30800
+ import { homedir as homedir8 } from "os";
30651
30801
  var DEFAULT_CONFIG = {
30652
30802
  llm: {
30653
30803
  provider: "anthropic",
@@ -30677,7 +30827,7 @@ var DEFAULT_CONFIG = {
30677
30827
  ]
30678
30828
  };
30679
30829
  function getConfigDir() {
30680
- return join8(homedir7(), ".oldpal");
30830
+ return join8(homedir8(), ".oldpal");
30681
30831
  }
30682
30832
  function getConfigPath(filename) {
30683
30833
  return join8(getConfigDir(), filename);
@@ -30749,14 +30899,19 @@ async function loadJsonFile(path) {
30749
30899
  return null;
30750
30900
  }
30751
30901
  }
30752
- async function ensureConfigDir() {
30902
+ async function ensureConfigDir(sessionId) {
30753
30903
  const { mkdir } = await import("fs/promises");
30754
30904
  const configDir = getConfigDir();
30755
- await Promise.all([
30905
+ const dirs = [
30756
30906
  mkdir(configDir, { recursive: true }),
30757
30907
  mkdir(join8(configDir, "sessions"), { recursive: true }),
30758
- mkdir(join8(configDir, "skills"), { recursive: true })
30759
- ]);
30908
+ mkdir(join8(configDir, "skills"), { recursive: true }),
30909
+ mkdir(join8(configDir, "temp"), { recursive: true })
30910
+ ];
30911
+ if (sessionId) {
30912
+ dirs.push(mkdir(join8(configDir, "temp", sessionId), { recursive: true }));
30913
+ }
30914
+ await Promise.all(dirs);
30760
30915
  }
30761
30916
  async function loadSystemPrompt(cwd2 = process.cwd()) {
30762
30917
  const prompts = [];
@@ -30834,7 +30989,7 @@ class AgentLoop {
30834
30989
  async initialize() {
30835
30990
  const [config] = await Promise.all([
30836
30991
  loadConfig(this.cwd),
30837
- ensureConfigDir()
30992
+ ensureConfigDir(this.sessionId)
30838
30993
  ]);
30839
30994
  this.config = config;
30840
30995
  const [, , , hooksConfig, systemPrompt] = await Promise.all([
@@ -30848,7 +31003,7 @@ class AgentLoop {
30848
31003
  this.commandLoader.loadAll()
30849
31004
  ]);
30850
31005
  this.toolRegistry.register(BashTool.tool, BashTool.executor);
30851
- FilesystemTools.registerAll(this.toolRegistry);
31006
+ FilesystemTools.registerAll(this.toolRegistry, this.sessionId);
30852
31007
  WebTools.registerAll(this.toolRegistry);
30853
31008
  ImageTools.registerAll(this.toolRegistry);
30854
31009
  this.connectorBridge.registerAll(this.toolRegistry);
@@ -31060,7 +31215,7 @@ init_anthropic();
31060
31215
  // packages/core/src/logger.ts
31061
31216
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, appendFileSync } from "fs";
31062
31217
  import { join as join9 } from "path";
31063
- import { homedir as homedir8 } from "os";
31218
+ import { homedir as homedir9 } from "os";
31064
31219
 
31065
31220
  class Logger {
31066
31221
  logDir;
@@ -31068,7 +31223,7 @@ class Logger {
31068
31223
  sessionId;
31069
31224
  constructor(sessionId) {
31070
31225
  this.sessionId = sessionId;
31071
- this.logDir = join9(homedir8(), ".oldpal", "logs");
31226
+ this.logDir = join9(homedir9(), ".oldpal", "logs");
31072
31227
  this.ensureDir(this.logDir);
31073
31228
  const date = new Date().toISOString().split("T")[0];
31074
31229
  this.logFile = join9(this.logDir, `${date}.log`);
@@ -31114,7 +31269,7 @@ class SessionStorage {
31114
31269
  sessionId;
31115
31270
  constructor(sessionId) {
31116
31271
  this.sessionId = sessionId;
31117
- this.sessionsDir = join9(homedir8(), ".oldpal", "sessions");
31272
+ this.sessionsDir = join9(homedir9(), ".oldpal", "sessions");
31118
31273
  this.ensureDir(this.sessionsDir);
31119
31274
  this.sessionFile = join9(this.sessionsDir, `${sessionId}.json`);
31120
31275
  }
@@ -31133,7 +31288,7 @@ class SessionStorage {
31133
31288
  }
31134
31289
  }
31135
31290
  function initOldpalDir() {
31136
- const baseDir = join9(homedir8(), ".oldpal");
31291
+ const baseDir = join9(homedir9(), ".oldpal");
31137
31292
  const dirs = [
31138
31293
  baseDir,
31139
31294
  join9(baseDir, "sessions"),
@@ -31293,6 +31448,153 @@ class EmbeddedClient {
31293
31448
  this.messages = [];
31294
31449
  this.logger.info("Conversation cleared");
31295
31450
  }
31451
+ getCwd() {
31452
+ return this.cwd;
31453
+ }
31454
+ getStartedAt() {
31455
+ return this.startedAt;
31456
+ }
31457
+ getMessages() {
31458
+ return [...this.messages];
31459
+ }
31460
+ }
31461
+ // packages/core/src/sessions/registry.ts
31462
+ class SessionRegistry {
31463
+ sessions = new Map;
31464
+ activeSessionId = null;
31465
+ chunkBuffers = new Map;
31466
+ chunkCallbacks = [];
31467
+ errorCallbacks = [];
31468
+ async createSession(cwd2) {
31469
+ const client = new EmbeddedClient(cwd2);
31470
+ await client.initialize();
31471
+ const sessionInfo = {
31472
+ id: client.getSessionId(),
31473
+ cwd: cwd2,
31474
+ startedAt: Date.now(),
31475
+ updatedAt: Date.now(),
31476
+ isProcessing: false,
31477
+ client
31478
+ };
31479
+ client.onChunk((chunk) => {
31480
+ this.handleChunk(sessionInfo.id, chunk);
31481
+ });
31482
+ client.onError((error) => {
31483
+ if (this.activeSessionId === sessionInfo.id) {
31484
+ for (const callback of this.errorCallbacks) {
31485
+ callback(error);
31486
+ }
31487
+ }
31488
+ });
31489
+ this.sessions.set(sessionInfo.id, sessionInfo);
31490
+ this.chunkBuffers.set(sessionInfo.id, []);
31491
+ if (this.activeSessionId === null) {
31492
+ this.activeSessionId = sessionInfo.id;
31493
+ }
31494
+ return sessionInfo;
31495
+ }
31496
+ handleChunk(sessionId, chunk) {
31497
+ const session = this.sessions.get(sessionId);
31498
+ if (session) {
31499
+ session.updatedAt = Date.now();
31500
+ if (chunk.type === "done") {
31501
+ session.isProcessing = false;
31502
+ }
31503
+ }
31504
+ if (sessionId === this.activeSessionId) {
31505
+ for (const callback of this.chunkCallbacks) {
31506
+ callback(chunk);
31507
+ }
31508
+ } else {
31509
+ const buffer = this.chunkBuffers.get(sessionId);
31510
+ if (buffer) {
31511
+ buffer.push(chunk);
31512
+ }
31513
+ }
31514
+ }
31515
+ async switchSession(id) {
31516
+ if (!this.sessions.has(id)) {
31517
+ throw new Error(`Session ${id} not found`);
31518
+ }
31519
+ if (this.activeSessionId === id) {
31520
+ return;
31521
+ }
31522
+ this.activeSessionId = id;
31523
+ const buffer = this.chunkBuffers.get(id);
31524
+ if (buffer && buffer.length > 0) {
31525
+ for (const chunk of buffer) {
31526
+ for (const callback of this.chunkCallbacks) {
31527
+ callback(chunk);
31528
+ }
31529
+ }
31530
+ this.chunkBuffers.set(id, []);
31531
+ }
31532
+ }
31533
+ listSessions() {
31534
+ return Array.from(this.sessions.values()).sort((a, b) => b.updatedAt - a.updatedAt);
31535
+ }
31536
+ getActiveSession() {
31537
+ if (!this.activeSessionId)
31538
+ return null;
31539
+ return this.sessions.get(this.activeSessionId) || null;
31540
+ }
31541
+ getActiveSessionId() {
31542
+ return this.activeSessionId;
31543
+ }
31544
+ getSession(id) {
31545
+ return this.sessions.get(id) || null;
31546
+ }
31547
+ getSessionIndex(id) {
31548
+ const sessions = this.listSessions();
31549
+ return sessions.findIndex((s) => s.id === id) + 1;
31550
+ }
31551
+ getSessionCount() {
31552
+ return this.sessions.size;
31553
+ }
31554
+ closeSession(id) {
31555
+ const session = this.sessions.get(id);
31556
+ if (session) {
31557
+ session.client.disconnect();
31558
+ this.sessions.delete(id);
31559
+ this.chunkBuffers.delete(id);
31560
+ if (this.activeSessionId === id) {
31561
+ const remaining = this.listSessions();
31562
+ this.activeSessionId = remaining.length > 0 ? remaining[0].id : null;
31563
+ }
31564
+ }
31565
+ }
31566
+ closeAll() {
31567
+ for (const session of this.sessions.values()) {
31568
+ session.client.disconnect();
31569
+ }
31570
+ this.sessions.clear();
31571
+ this.chunkBuffers.clear();
31572
+ this.activeSessionId = null;
31573
+ }
31574
+ onChunk(callback) {
31575
+ this.chunkCallbacks.push(callback);
31576
+ }
31577
+ onError(callback) {
31578
+ this.errorCallbacks.push(callback);
31579
+ }
31580
+ setProcessing(id, isProcessing) {
31581
+ const session = this.sessions.get(id);
31582
+ if (session) {
31583
+ session.isProcessing = isProcessing;
31584
+ session.updatedAt = Date.now();
31585
+ }
31586
+ }
31587
+ hasProcessingSession() {
31588
+ for (const session of this.sessions.values()) {
31589
+ if (session.isProcessing) {
31590
+ return true;
31591
+ }
31592
+ }
31593
+ return false;
31594
+ }
31595
+ getBackgroundProcessingSessions() {
31596
+ return Array.from(this.sessions.values()).filter((s) => s.isProcessing && s.id !== this.activeSessionId);
31597
+ }
31296
31598
  }
31297
31599
  // packages/terminal/src/components/Input.tsx
31298
31600
  var import_react23 = __toESM(require_react(), 1);
@@ -31579,19 +31881,28 @@ function Messages4({
31579
31881
  const startIndex = Math.max(0, endIndex - maxVisible);
31580
31882
  const visibleMessages = messages.slice(startIndex, endIndex);
31581
31883
  const groupedMessages = groupConsecutiveToolMessages(visibleMessages);
31884
+ const historicalItems = groupedMessages.map((group) => {
31885
+ if (group.type === "single") {
31886
+ return { id: group.message.id, group };
31887
+ }
31888
+ return { id: group.messages[0].id, group };
31889
+ });
31582
31890
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31583
31891
  flexDirection: "column",
31584
31892
  children: [
31585
- groupedMessages.map((group) => {
31586
- if (group.type === "single") {
31587
- return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(MessageBubble, {
31588
- message: group.message
31589
- }, group.message.id, false, undefined, this);
31590
- }
31591
- return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(CombinedToolMessage, {
31592
- messages: group.messages
31593
- }, group.messages[0].id, false, undefined, this);
31594
- }),
31893
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Static, {
31894
+ items: historicalItems,
31895
+ children: (item) => {
31896
+ if (item.group.type === "single") {
31897
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(MessageBubble, {
31898
+ message: item.group.message
31899
+ }, item.id, false, undefined, this);
31900
+ }
31901
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(CombinedToolMessage, {
31902
+ messages: item.group.messages
31903
+ }, item.id, false, undefined, this);
31904
+ }
31905
+ }, undefined, false, undefined, this),
31595
31906
  activityLog.map((entry) => {
31596
31907
  if (entry.type === "text" && entry.content) {
31597
31908
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -31861,26 +32172,47 @@ function truncate(text, maxLength) {
31861
32172
 
31862
32173
  // packages/terminal/src/components/Status.tsx
31863
32174
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
31864
- function Status({ isProcessing, cwd: cwd2, queueLength = 0, tokenUsage }) {
32175
+ function Status({
32176
+ isProcessing,
32177
+ cwd: cwd2,
32178
+ queueLength = 0,
32179
+ tokenUsage,
32180
+ sessionIndex,
32181
+ sessionCount,
32182
+ backgroundProcessingCount = 0
32183
+ }) {
31865
32184
  let contextInfo = "";
31866
32185
  if (tokenUsage && tokenUsage.maxContextTokens > 0) {
31867
32186
  const percent = Math.round(tokenUsage.totalTokens / tokenUsage.maxContextTokens * 100);
31868
- contextInfo = `${percent}% context used`;
32187
+ contextInfo = `${percent}% context`;
31869
32188
  }
32189
+ const sessionInfo = sessionIndex && sessionCount ? `Session ${sessionIndex}/${sessionCount}` : "";
32190
+ const bgIndicator = backgroundProcessingCount > 0 ? ` (${backgroundProcessingCount} processing)` : "";
31870
32191
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
31871
32192
  marginTop: 1,
31872
32193
  justifyContent: "space-between",
31873
32194
  children: [
31874
32195
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
31875
32196
  dimColor: true,
31876
- children: "/help for commands"
31877
- }, undefined, false, undefined, this),
32197
+ children: [
32198
+ "/help for commands",
32199
+ sessionCount && sessionCount > 1 ? " | Ctrl+S sessions" : ""
32200
+ ]
32201
+ }, undefined, true, undefined, this),
31878
32202
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
31879
32203
  children: [
31880
32204
  isProcessing && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
31881
32205
  dimColor: true,
31882
32206
  children: "esc to stop \xB7 "
31883
32207
  }, undefined, false, undefined, this),
32208
+ sessionInfo && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
32209
+ dimColor: true,
32210
+ children: [
32211
+ sessionInfo,
32212
+ bgIndicator,
32213
+ contextInfo ? " \xB7 " : ""
32214
+ ]
32215
+ }, undefined, true, undefined, this),
31884
32216
  contextInfo && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
31885
32217
  dimColor: true,
31886
32218
  children: contextInfo
@@ -32079,8 +32411,131 @@ function WelcomeBanner({ version, model, directory }) {
32079
32411
  }, undefined, true, undefined, this);
32080
32412
  }
32081
32413
 
32082
- // packages/terminal/src/components/App.tsx
32414
+ // packages/terminal/src/components/SessionSelector.tsx
32415
+ var import_react26 = __toESM(require_react(), 1);
32083
32416
  var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
32417
+ function formatSessionTime(timestamp) {
32418
+ const date = new Date(timestamp);
32419
+ const now2 = new Date;
32420
+ const isToday = date.toDateString() === now2.toDateString();
32421
+ if (isToday) {
32422
+ return date.toLocaleTimeString("en-US", {
32423
+ hour: "numeric",
32424
+ minute: "2-digit",
32425
+ hour12: true
32426
+ }).toLowerCase();
32427
+ }
32428
+ return date.toLocaleDateString("en-US", {
32429
+ month: "short",
32430
+ day: "numeric",
32431
+ hour: "numeric",
32432
+ minute: "2-digit",
32433
+ hour12: true
32434
+ }).toLowerCase();
32435
+ }
32436
+ function formatPath(cwd2) {
32437
+ const home = process.env.HOME || "";
32438
+ if (cwd2.startsWith(home)) {
32439
+ return "~" + cwd2.slice(home.length);
32440
+ }
32441
+ return cwd2;
32442
+ }
32443
+ function SessionSelector({
32444
+ sessions,
32445
+ activeSessionId,
32446
+ onSelect,
32447
+ onNew,
32448
+ onCancel
32449
+ }) {
32450
+ const [selectedIndex, setSelectedIndex] = import_react26.useState(0);
32451
+ use_input_default((input, key) => {
32452
+ if (key.escape) {
32453
+ onCancel();
32454
+ return;
32455
+ }
32456
+ if (key.return) {
32457
+ if (selectedIndex === sessions.length) {
32458
+ onNew();
32459
+ } else {
32460
+ onSelect(sessions[selectedIndex].id);
32461
+ }
32462
+ return;
32463
+ }
32464
+ if (key.upArrow) {
32465
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
32466
+ }
32467
+ if (key.downArrow) {
32468
+ setSelectedIndex((prev) => Math.min(sessions.length, prev + 1));
32469
+ }
32470
+ const num = parseInt(input, 10);
32471
+ if (!isNaN(num) && num >= 1 && num <= sessions.length) {
32472
+ onSelect(sessions[num - 1].id);
32473
+ }
32474
+ if (input === "n") {
32475
+ onNew();
32476
+ }
32477
+ });
32478
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32479
+ flexDirection: "column",
32480
+ paddingY: 1,
32481
+ children: [
32482
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32483
+ marginBottom: 1,
32484
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
32485
+ bold: true,
32486
+ children: "Sessions"
32487
+ }, undefined, false, undefined, this)
32488
+ }, undefined, false, undefined, this),
32489
+ sessions.map((session, index) => {
32490
+ const isActive = session.id === activeSessionId;
32491
+ const isSelected = index === selectedIndex;
32492
+ const prefix = isActive ? "[*]" : " ";
32493
+ const time = formatSessionTime(session.updatedAt);
32494
+ const path = formatPath(session.cwd);
32495
+ const processing = session.isProcessing ? " (processing)" : "";
32496
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32497
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
32498
+ inverse: isSelected,
32499
+ color: isActive ? "green" : undefined,
32500
+ dimColor: !isSelected && !isActive,
32501
+ children: [
32502
+ prefix,
32503
+ " ",
32504
+ index + 1,
32505
+ ". ",
32506
+ time,
32507
+ " ",
32508
+ path,
32509
+ processing
32510
+ ]
32511
+ }, undefined, true, undefined, this)
32512
+ }, session.id, false, undefined, this);
32513
+ }),
32514
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32515
+ marginTop: 1,
32516
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
32517
+ inverse: selectedIndex === sessions.length,
32518
+ dimColor: selectedIndex !== sessions.length,
32519
+ children: "+ New session (n)"
32520
+ }, undefined, false, undefined, this)
32521
+ }, undefined, false, undefined, this),
32522
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32523
+ marginTop: 1,
32524
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
32525
+ dimColor: true,
32526
+ children: [
32527
+ "Enter to select | Esc to cancel | 1-",
32528
+ sessions.length,
32529
+ " to switch | n for new"
32530
+ ]
32531
+ }, undefined, true, undefined, this)
32532
+ }, undefined, false, undefined, this)
32533
+ ]
32534
+ }, undefined, true, undefined, this);
32535
+ }
32536
+
32537
+ // packages/terminal/src/components/App.tsx
32538
+ var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
32084
32539
  function formatToolName(toolCall) {
32085
32540
  const { name, input } = toolCall;
32086
32541
  switch (name) {
@@ -32107,27 +32562,183 @@ function formatToolName(toolCall) {
32107
32562
  }
32108
32563
  function App2({ cwd: cwd2 }) {
32109
32564
  const { exit } = use_app_default();
32110
- const [client, setClient] = import_react26.useState(null);
32111
- const [messages, setMessages] = import_react26.useState([]);
32112
- const [currentResponse, setCurrentResponse] = import_react26.useState("");
32113
- const [currentToolCall, setCurrentToolCall] = import_react26.useState();
32114
- const [lastToolResult, setLastToolResult] = import_react26.useState();
32115
- const [isProcessing, setIsProcessing] = import_react26.useState(false);
32116
- const [isInitializing, setIsInitializing] = import_react26.useState(true);
32117
- const [error, setError] = import_react26.useState(null);
32118
- const [messageQueue, setMessageQueue] = import_react26.useState([]);
32119
- const [activityLog, setActivityLog] = import_react26.useState([]);
32120
- const [tokenUsage, setTokenUsage] = import_react26.useState();
32121
- const [processingStartTime, setProcessingStartTime] = import_react26.useState();
32122
- const [currentTurnTokens, setCurrentTurnTokens] = import_react26.useState(0);
32123
- const [scrollOffset, setScrollOffset] = import_react26.useState(0);
32124
- const [autoScroll, setAutoScroll] = import_react26.useState(true);
32125
- const responseRef = import_react26.useRef("");
32126
- const clientRef = import_react26.useRef(null);
32127
- const toolCallsRef = import_react26.useRef([]);
32128
- const toolResultsRef = import_react26.useRef([]);
32129
- const processQueue = import_react26.useCallback(async () => {
32130
- if (!clientRef.current || messageQueue.length === 0)
32565
+ const [registry2] = import_react27.useState(() => new SessionRegistry);
32566
+ const registryRef = import_react27.useRef(registry2);
32567
+ const [activeSessionId, setActiveSessionId] = import_react27.useState(null);
32568
+ const [isInitializing, setIsInitializing] = import_react27.useState(true);
32569
+ const [showSessionSelector, setShowSessionSelector] = import_react27.useState(false);
32570
+ const sessionUIStates = import_react27.useRef(new Map);
32571
+ const [messages, setMessages] = import_react27.useState([]);
32572
+ const [currentResponse, setCurrentResponse] = import_react27.useState("");
32573
+ const [currentToolCall, setCurrentToolCall] = import_react27.useState();
32574
+ const [isProcessing, setIsProcessing] = import_react27.useState(false);
32575
+ const [error, setError] = import_react27.useState(null);
32576
+ const [messageQueue, setMessageQueue] = import_react27.useState([]);
32577
+ const [activityLog, setActivityLog] = import_react27.useState([]);
32578
+ const [tokenUsage, setTokenUsage] = import_react27.useState();
32579
+ const [processingStartTime, setProcessingStartTime] = import_react27.useState();
32580
+ const [currentTurnTokens, setCurrentTurnTokens] = import_react27.useState(0);
32581
+ const [scrollOffset, setScrollOffset] = import_react27.useState(0);
32582
+ const [autoScroll, setAutoScroll] = import_react27.useState(true);
32583
+ const responseRef = import_react27.useRef("");
32584
+ const toolCallsRef = import_react27.useRef([]);
32585
+ const toolResultsRef = import_react27.useRef([]);
32586
+ const activityLogRef = import_react27.useRef([]);
32587
+ const saveCurrentSessionState = import_react27.useCallback(() => {
32588
+ if (activeSessionId) {
32589
+ sessionUIStates.current.set(activeSessionId, {
32590
+ messages,
32591
+ currentResponse: responseRef.current,
32592
+ activityLog: activityLogRef.current,
32593
+ tokenUsage,
32594
+ processingStartTime,
32595
+ currentTurnTokens,
32596
+ messageQueue,
32597
+ error
32598
+ });
32599
+ }
32600
+ }, [activeSessionId, messages, tokenUsage, processingStartTime, currentTurnTokens, messageQueue, error]);
32601
+ const loadSessionState = import_react27.useCallback((sessionId) => {
32602
+ const state = sessionUIStates.current.get(sessionId);
32603
+ if (state) {
32604
+ setMessages(state.messages);
32605
+ setCurrentResponse(state.currentResponse);
32606
+ responseRef.current = state.currentResponse;
32607
+ setActivityLog(state.activityLog);
32608
+ activityLogRef.current = state.activityLog;
32609
+ setTokenUsage(state.tokenUsage);
32610
+ setProcessingStartTime(state.processingStartTime);
32611
+ setCurrentTurnTokens(state.currentTurnTokens);
32612
+ setMessageQueue(state.messageQueue);
32613
+ setError(state.error);
32614
+ } else {
32615
+ setMessages([]);
32616
+ setCurrentResponse("");
32617
+ responseRef.current = "";
32618
+ setActivityLog([]);
32619
+ activityLogRef.current = [];
32620
+ setTokenUsage(undefined);
32621
+ setProcessingStartTime(undefined);
32622
+ setCurrentTurnTokens(0);
32623
+ setMessageQueue([]);
32624
+ setError(null);
32625
+ }
32626
+ setScrollOffset(0);
32627
+ setAutoScroll(true);
32628
+ }, []);
32629
+ const handleChunk = import_react27.useCallback((chunk) => {
32630
+ if (chunk.type === "text" && chunk.content) {
32631
+ responseRef.current += chunk.content;
32632
+ setCurrentResponse(responseRef.current);
32633
+ } else if (chunk.type === "tool_use" && chunk.toolCall) {
32634
+ if (responseRef.current.trim()) {
32635
+ const textEntry = {
32636
+ id: generateId(),
32637
+ type: "text",
32638
+ content: responseRef.current,
32639
+ timestamp: now()
32640
+ };
32641
+ activityLogRef.current = [...activityLogRef.current, textEntry];
32642
+ setActivityLog(activityLogRef.current);
32643
+ setCurrentResponse("");
32644
+ responseRef.current = "";
32645
+ }
32646
+ toolCallsRef.current.push(chunk.toolCall);
32647
+ const toolEntry = {
32648
+ id: generateId(),
32649
+ type: "tool_call",
32650
+ toolCall: chunk.toolCall,
32651
+ timestamp: now()
32652
+ };
32653
+ activityLogRef.current = [...activityLogRef.current, toolEntry];
32654
+ setActivityLog(activityLogRef.current);
32655
+ setCurrentToolCall(chunk.toolCall);
32656
+ } else if (chunk.type === "tool_result" && chunk.toolResult) {
32657
+ toolResultsRef.current.push(chunk.toolResult);
32658
+ const resultEntry = {
32659
+ id: generateId(),
32660
+ type: "tool_result",
32661
+ toolResult: chunk.toolResult,
32662
+ timestamp: now()
32663
+ };
32664
+ activityLogRef.current = [...activityLogRef.current, resultEntry];
32665
+ setActivityLog(activityLogRef.current);
32666
+ setCurrentToolCall(undefined);
32667
+ } else if (chunk.type === "error" && chunk.error) {
32668
+ setError(chunk.error);
32669
+ setIsProcessing(false);
32670
+ } else if (chunk.type === "exit") {
32671
+ registry2.closeAll();
32672
+ exit();
32673
+ } else if (chunk.type === "usage" && chunk.usage) {
32674
+ setTokenUsage(chunk.usage);
32675
+ setCurrentTurnTokens((prev) => prev + (chunk.usage?.outputTokens || 0));
32676
+ } else if (chunk.type === "done") {
32677
+ if (responseRef.current.trim()) {
32678
+ const textEntry = {
32679
+ id: generateId(),
32680
+ type: "text",
32681
+ content: responseRef.current,
32682
+ timestamp: now()
32683
+ };
32684
+ activityLogRef.current = [...activityLogRef.current, textEntry];
32685
+ }
32686
+ const fullContent = activityLogRef.current.filter((e) => e.type === "text").map((e) => e.content).join(`
32687
+ `) + (responseRef.current ? `
32688
+ ` + responseRef.current : "");
32689
+ if (fullContent.trim() || toolCallsRef.current.length > 0) {
32690
+ setMessages((prev) => [
32691
+ ...prev,
32692
+ {
32693
+ id: generateId(),
32694
+ role: "assistant",
32695
+ content: fullContent.trim(),
32696
+ timestamp: now(),
32697
+ toolCalls: toolCallsRef.current.length > 0 ? [...toolCallsRef.current] : undefined,
32698
+ toolResults: toolResultsRef.current.length > 0 ? [...toolResultsRef.current] : undefined
32699
+ }
32700
+ ]);
32701
+ }
32702
+ setCurrentResponse("");
32703
+ responseRef.current = "";
32704
+ toolCallsRef.current = [];
32705
+ toolResultsRef.current = [];
32706
+ setCurrentToolCall(undefined);
32707
+ setActivityLog([]);
32708
+ activityLogRef.current = [];
32709
+ setProcessingStartTime(undefined);
32710
+ setCurrentTurnTokens(0);
32711
+ setIsProcessing(false);
32712
+ const activeSession2 = registry2.getActiveSession();
32713
+ if (activeSession2) {
32714
+ setTokenUsage(activeSession2.client.getTokenUsage());
32715
+ }
32716
+ }
32717
+ }, [registry2, exit]);
32718
+ import_react27.useEffect(() => {
32719
+ const initSession = async () => {
32720
+ try {
32721
+ registry2.onChunk(handleChunk);
32722
+ registry2.onError((err) => {
32723
+ setError(err.message);
32724
+ setIsProcessing(false);
32725
+ });
32726
+ const session = await registry2.createSession(cwd2);
32727
+ setActiveSessionId(session.id);
32728
+ setIsInitializing(false);
32729
+ } catch (err) {
32730
+ setError(err instanceof Error ? err.message : String(err));
32731
+ setIsInitializing(false);
32732
+ }
32733
+ };
32734
+ initSession();
32735
+ return () => {
32736
+ registry2.closeAll();
32737
+ };
32738
+ }, [cwd2, registry2, handleChunk]);
32739
+ const processQueue = import_react27.useCallback(async () => {
32740
+ const activeSession2 = registryRef.current.getActiveSession();
32741
+ if (!activeSession2 || messageQueue.length === 0)
32131
32742
  return;
32132
32743
  const nextMessage = messageQueue[0];
32133
32744
  setMessageQueue((prev) => prev.slice(1));
@@ -32144,129 +32755,20 @@ function App2({ cwd: cwd2 }) {
32144
32755
  toolResultsRef.current = [];
32145
32756
  setError(null);
32146
32757
  setCurrentToolCall(undefined);
32147
- setLastToolResult(undefined);
32148
32758
  setActivityLog([]);
32759
+ activityLogRef.current = [];
32149
32760
  setProcessingStartTime(Date.now());
32150
32761
  setCurrentTurnTokens(0);
32151
32762
  setIsProcessing(true);
32152
- await clientRef.current.send(nextMessage);
32763
+ registryRef.current.setProcessing(activeSession2.id, true);
32764
+ await activeSession2.client.send(nextMessage);
32153
32765
  }, [messageQueue]);
32154
- import_react26.useEffect(() => {
32155
- const initClient = async () => {
32156
- try {
32157
- const newClient = new EmbeddedClient(cwd2);
32158
- clientRef.current = newClient;
32159
- newClient.onChunk((chunk) => {
32160
- if (chunk.type === "text" && chunk.content) {
32161
- responseRef.current += chunk.content;
32162
- setCurrentResponse(responseRef.current);
32163
- } else if (chunk.type === "tool_use" && chunk.toolCall) {
32164
- if (responseRef.current.trim()) {
32165
- setActivityLog((prev) => [
32166
- ...prev,
32167
- {
32168
- id: generateId(),
32169
- type: "text",
32170
- content: responseRef.current,
32171
- timestamp: now()
32172
- }
32173
- ]);
32174
- setCurrentResponse("");
32175
- responseRef.current = "";
32176
- }
32177
- toolCallsRef.current.push(chunk.toolCall);
32178
- setActivityLog((prev) => [
32179
- ...prev,
32180
- {
32181
- id: generateId(),
32182
- type: "tool_call",
32183
- toolCall: chunk.toolCall,
32184
- timestamp: now()
32185
- }
32186
- ]);
32187
- setCurrentToolCall(chunk.toolCall);
32188
- } else if (chunk.type === "tool_result" && chunk.toolResult) {
32189
- toolResultsRef.current.push(chunk.toolResult);
32190
- setActivityLog((prev) => [
32191
- ...prev,
32192
- {
32193
- id: generateId(),
32194
- type: "tool_result",
32195
- toolResult: chunk.toolResult,
32196
- timestamp: now()
32197
- }
32198
- ]);
32199
- setCurrentToolCall(undefined);
32200
- } else if (chunk.type === "error" && chunk.error) {
32201
- setError(chunk.error);
32202
- setIsProcessing(false);
32203
- } else if (chunk.type === "exit") {
32204
- exit();
32205
- } else if (chunk.type === "usage" && chunk.usage) {
32206
- setTokenUsage(chunk.usage);
32207
- setCurrentTurnTokens((prev) => prev + (chunk.usage?.outputTokens || 0));
32208
- } else if (chunk.type === "done") {
32209
- if (responseRef.current.trim()) {
32210
- setActivityLog((prev) => [
32211
- ...prev,
32212
- {
32213
- id: generateId(),
32214
- type: "text",
32215
- content: responseRef.current,
32216
- timestamp: now()
32217
- }
32218
- ]);
32219
- }
32220
- const fullContent = activityLog.filter((e) => e.type === "text").map((e) => e.content).join(`
32221
- `) + (responseRef.current ? `
32222
- ` + responseRef.current : "");
32223
- if (fullContent.trim() || toolCallsRef.current.length > 0) {
32224
- setMessages((prev) => [
32225
- ...prev,
32226
- {
32227
- id: generateId(),
32228
- role: "assistant",
32229
- content: fullContent.trim(),
32230
- timestamp: now(),
32231
- toolCalls: toolCallsRef.current.length > 0 ? [...toolCallsRef.current] : undefined,
32232
- toolResults: toolResultsRef.current.length > 0 ? [...toolResultsRef.current] : undefined
32233
- }
32234
- ]);
32235
- }
32236
- setCurrentResponse("");
32237
- responseRef.current = "";
32238
- toolCallsRef.current = [];
32239
- toolResultsRef.current = [];
32240
- setCurrentToolCall(undefined);
32241
- setActivityLog([]);
32242
- setProcessingStartTime(undefined);
32243
- setCurrentTurnTokens(0);
32244
- setIsProcessing(false);
32245
- if (newClient) {
32246
- setTokenUsage(newClient.getTokenUsage());
32247
- }
32248
- }
32249
- });
32250
- newClient.onError((err) => {
32251
- setError(err.message);
32252
- setIsProcessing(false);
32253
- });
32254
- await newClient.initialize();
32255
- setClient(newClient);
32256
- setIsInitializing(false);
32257
- } catch (err) {
32258
- setError(err instanceof Error ? err.message : String(err));
32259
- setIsInitializing(false);
32260
- }
32261
- };
32262
- initClient();
32263
- }, [cwd2]);
32264
- import_react26.useEffect(() => {
32766
+ import_react27.useEffect(() => {
32265
32767
  if (!isProcessing && messageQueue.length > 0) {
32266
32768
  processQueue();
32267
32769
  }
32268
32770
  }, [isProcessing, messageQueue.length, processQueue]);
32269
- import_react26.useEffect(() => {
32771
+ import_react27.useEffect(() => {
32270
32772
  if (autoScroll) {
32271
32773
  setScrollOffset(0);
32272
32774
  }
@@ -32274,10 +32776,48 @@ function App2({ cwd: cwd2 }) {
32274
32776
  const baseMaxVisible = 10;
32275
32777
  const toolCallsHeight = isProcessing ? Math.min(toolCallsRef.current.length, 5) : 0;
32276
32778
  const maxVisibleMessages = Math.max(3, baseMaxVisible - toolCallsHeight);
32779
+ const sessions = registry2.listSessions();
32780
+ const activeSession = registry2.getActiveSession();
32781
+ const sessionIndex = activeSessionId ? registry2.getSessionIndex(activeSessionId) : 0;
32782
+ const sessionCount = registry2.getSessionCount();
32783
+ const backgroundProcessingCount = registry2.getBackgroundProcessingSessions().length;
32784
+ const handleSessionSwitch = import_react27.useCallback(async (sessionId) => {
32785
+ if (sessionId === activeSessionId) {
32786
+ setShowSessionSelector(false);
32787
+ return;
32788
+ }
32789
+ saveCurrentSessionState();
32790
+ await registry2.switchSession(sessionId);
32791
+ setActiveSessionId(sessionId);
32792
+ loadSessionState(sessionId);
32793
+ const session = registry2.getSession(sessionId);
32794
+ if (session) {
32795
+ setIsProcessing(session.isProcessing);
32796
+ }
32797
+ setShowSessionSelector(false);
32798
+ }, [activeSessionId, registry2, saveCurrentSessionState, loadSessionState]);
32799
+ const handleNewSession = import_react27.useCallback(async () => {
32800
+ saveCurrentSessionState();
32801
+ const newSession = await registry2.createSession(cwd2);
32802
+ await registry2.switchSession(newSession.id);
32803
+ setActiveSessionId(newSession.id);
32804
+ loadSessionState(newSession.id);
32805
+ setIsProcessing(false);
32806
+ setShowSessionSelector(false);
32807
+ }, [cwd2, registry2, saveCurrentSessionState, loadSessionState]);
32277
32808
  use_input_default((input, key) => {
32809
+ if (showSessionSelector) {
32810
+ return;
32811
+ }
32812
+ if (key.ctrl && input === "s") {
32813
+ if (sessions.length > 0) {
32814
+ setShowSessionSelector(true);
32815
+ }
32816
+ return;
32817
+ }
32278
32818
  if (key.ctrl && input === "c") {
32279
- if (isProcessing && client) {
32280
- client.stop();
32819
+ if (isProcessing && activeSession) {
32820
+ activeSession.client.stop();
32281
32821
  if (responseRef.current) {
32282
32822
  setMessages((prev) => [
32283
32823
  ...prev,
@@ -32295,27 +32835,30 @@ function App2({ cwd: cwd2 }) {
32295
32835
  }
32296
32836
  setIsProcessing(false);
32297
32837
  } else {
32838
+ registry2.closeAll();
32298
32839
  exit();
32299
32840
  }
32300
32841
  }
32301
- if (key.escape && isProcessing && client) {
32302
- client.stop();
32303
- if (responseRef.current) {
32304
- setMessages((prev) => [
32305
- ...prev,
32306
- {
32307
- id: generateId(),
32308
- role: "assistant",
32309
- content: responseRef.current + `
32842
+ if (key.escape) {
32843
+ if (isProcessing && activeSession) {
32844
+ activeSession.client.stop();
32845
+ if (responseRef.current) {
32846
+ setMessages((prev) => [
32847
+ ...prev,
32848
+ {
32849
+ id: generateId(),
32850
+ role: "assistant",
32851
+ content: responseRef.current + `
32310
32852
 
32311
32853
  [stopped]`,
32312
- timestamp: now()
32313
- }
32314
- ]);
32315
- setCurrentResponse("");
32316
- responseRef.current = "";
32854
+ timestamp: now()
32855
+ }
32856
+ ]);
32857
+ setCurrentResponse("");
32858
+ responseRef.current = "";
32859
+ }
32860
+ setIsProcessing(false);
32317
32861
  }
32318
- setIsProcessing(false);
32319
32862
  }
32320
32863
  if (key.pageUp || key.shift && key.upArrow) {
32321
32864
  setScrollOffset((prev) => {
@@ -32344,16 +32887,30 @@ function App2({ cwd: cwd2 }) {
32344
32887
  setAutoScroll(true);
32345
32888
  }
32346
32889
  });
32347
- const handleSubmit = import_react26.useCallback(async (input, mode = "normal") => {
32348
- if (!client || !input.trim())
32890
+ const handleSubmit = import_react27.useCallback(async (input, mode = "normal") => {
32891
+ if (!activeSession || !input.trim())
32349
32892
  return;
32350
32893
  const trimmedInput = input.trim();
32894
+ if (trimmedInput.startsWith("/session")) {
32895
+ const arg = trimmedInput.slice(8).trim();
32896
+ if (arg === "new") {
32897
+ await handleNewSession();
32898
+ return;
32899
+ }
32900
+ const num = parseInt(arg, 10);
32901
+ if (!isNaN(num) && num > 0 && num <= sessions.length) {
32902
+ await handleSessionSwitch(sessions[num - 1].id);
32903
+ return;
32904
+ }
32905
+ setShowSessionSelector(true);
32906
+ return;
32907
+ }
32351
32908
  if (mode === "queue" || isProcessing && mode === "normal") {
32352
32909
  setMessageQueue((prev) => [...prev, trimmedInput]);
32353
32910
  return;
32354
32911
  }
32355
32912
  if (mode === "interrupt" && isProcessing) {
32356
- client.stop();
32913
+ activeSession.client.stop();
32357
32914
  if (responseRef.current) {
32358
32915
  setMessages((prev) => [
32359
32916
  ...prev,
@@ -32385,39 +32942,65 @@ function App2({ cwd: cwd2 }) {
32385
32942
  toolResultsRef.current = [];
32386
32943
  setError(null);
32387
32944
  setCurrentToolCall(undefined);
32388
- setLastToolResult(undefined);
32389
32945
  setActivityLog([]);
32946
+ activityLogRef.current = [];
32390
32947
  setProcessingStartTime(Date.now());
32391
32948
  setCurrentTurnTokens(0);
32392
32949
  setIsProcessing(true);
32393
- await client.send(trimmedInput);
32394
- }, [client, isProcessing]);
32950
+ registry2.setProcessing(activeSession.id, true);
32951
+ await activeSession.client.send(trimmedInput);
32952
+ }, [activeSession, isProcessing, registry2, sessions, handleNewSession, handleSessionSwitch]);
32395
32953
  if (isInitializing) {
32396
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32954
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
32397
32955
  flexDirection: "column",
32398
32956
  padding: 1,
32399
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Spinner2, {
32957
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Spinner2, {
32400
32958
  label: "Initializing..."
32401
32959
  }, undefined, false, undefined, this)
32402
32960
  }, undefined, false, undefined, this);
32403
32961
  }
32962
+ if (showSessionSelector) {
32963
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
32964
+ flexDirection: "column",
32965
+ padding: 1,
32966
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(SessionSelector, {
32967
+ sessions,
32968
+ activeSessionId,
32969
+ onSelect: handleSessionSwitch,
32970
+ onNew: handleNewSession,
32971
+ onCancel: () => setShowSessionSelector(false)
32972
+ }, undefined, false, undefined, this)
32973
+ }, undefined, false, undefined, this);
32974
+ }
32404
32975
  const toolCallEntries = activityLog.filter((e) => e.type === "tool_call" && e.toolCall).map((e) => {
32405
32976
  const result = activityLog.find((r) => r.type === "tool_result" && r.toolResult?.toolCallId === e.toolCall?.id)?.toolResult;
32406
32977
  return { toolCall: e.toolCall, result };
32407
32978
  });
32408
32979
  const isThinking = isProcessing && !currentResponse && !currentToolCall && toolCallEntries.length === 0;
32409
32980
  const showWelcome = messages.length === 0 && !isProcessing;
32410
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32981
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
32411
32982
  flexDirection: "column",
32412
32983
  padding: 1,
32413
32984
  children: [
32414
- showWelcome && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(WelcomeBanner, {
32415
- version: "0.4.0",
32416
- model: "claude-4-sonnet",
32417
- directory: cwd2
32985
+ showWelcome && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(WelcomeBanner, {
32986
+ version: "0.4.1",
32987
+ model: "claude-sonnet-4",
32988
+ directory: activeSession?.cwd || cwd2
32418
32989
  }, undefined, false, undefined, this),
32419
- scrollOffset > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32420
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
32990
+ backgroundProcessingCount > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
32991
+ marginBottom: 1,
32992
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
32993
+ color: "yellow",
32994
+ children: [
32995
+ backgroundProcessingCount,
32996
+ " session",
32997
+ backgroundProcessingCount > 1 ? "s" : "",
32998
+ " processing in background (Ctrl+S to switch)"
32999
+ ]
33000
+ }, undefined, true, undefined, this)
33001
+ }, undefined, false, undefined, this),
33002
+ scrollOffset > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
33003
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
32421
33004
  dimColor: true,
32422
33005
  children: [
32423
33006
  "\u2191 ",
@@ -32426,7 +33009,7 @@ function App2({ cwd: cwd2 }) {
32426
33009
  ]
32427
33010
  }, undefined, true, undefined, this)
32428
33011
  }, undefined, false, undefined, this),
32429
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Messages4, {
33012
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Messages4, {
32430
33013
  messages,
32431
33014
  currentResponse: isProcessing ? currentResponse : undefined,
32432
33015
  currentToolCall: undefined,
@@ -32435,9 +33018,9 @@ function App2({ cwd: cwd2 }) {
32435
33018
  scrollOffset,
32436
33019
  maxVisible: maxVisibleMessages
32437
33020
  }, undefined, false, undefined, this),
32438
- isProcessing && toolCallEntries.length > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
33021
+ isProcessing && toolCallEntries.length > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
32439
33022
  marginY: 1,
32440
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
33023
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
32441
33024
  dimColor: true,
32442
33025
  children: [
32443
33026
  "\u2699 ",
@@ -32450,9 +33033,9 @@ function App2({ cwd: cwd2 }) {
32450
33033
  ]
32451
33034
  }, undefined, true, undefined, this)
32452
33035
  }, undefined, false, undefined, this),
32453
- messageQueue.length > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
33036
+ messageQueue.length > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
32454
33037
  marginY: 1,
32455
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
33038
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
32456
33039
  dimColor: true,
32457
33040
  children: [
32458
33041
  messageQueue.length,
@@ -32462,9 +33045,9 @@ function App2({ cwd: cwd2 }) {
32462
33045
  ]
32463
33046
  }, undefined, true, undefined, this)
32464
33047
  }, undefined, false, undefined, this),
32465
- error && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
33048
+ error && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
32466
33049
  marginY: 1,
32467
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
33050
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
32468
33051
  color: "red",
32469
33052
  children: [
32470
33053
  "Error: ",
@@ -32472,29 +33055,32 @@ function App2({ cwd: cwd2 }) {
32472
33055
  ]
32473
33056
  }, undefined, true, undefined, this)
32474
33057
  }, undefined, false, undefined, this),
32475
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(ProcessingIndicator, {
33058
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ProcessingIndicator, {
32476
33059
  isProcessing,
32477
33060
  startTime: processingStartTime,
32478
33061
  tokenCount: currentTurnTokens,
32479
33062
  isThinking
32480
33063
  }, undefined, false, undefined, this),
32481
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Input, {
33064
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Input, {
32482
33065
  onSubmit: handleSubmit,
32483
33066
  isProcessing,
32484
33067
  queueLength: messageQueue.length
32485
33068
  }, undefined, false, undefined, this),
32486
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Status, {
33069
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Status, {
32487
33070
  isProcessing,
32488
- cwd: cwd2,
33071
+ cwd: activeSession?.cwd || cwd2,
32489
33072
  queueLength: messageQueue.length,
32490
- tokenUsage
33073
+ tokenUsage,
33074
+ sessionIndex,
33075
+ sessionCount,
33076
+ backgroundProcessingCount
32491
33077
  }, undefined, false, undefined, this)
32492
33078
  ]
32493
33079
  }, undefined, true, undefined, this);
32494
33080
  }
32495
33081
 
32496
33082
  // packages/terminal/src/index.tsx
32497
- var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
33083
+ var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
32498
33084
  var args = process.argv.slice(2);
32499
33085
  var options = {
32500
33086
  cwd: process.cwd(),
@@ -32502,7 +33088,7 @@ var options = {
32502
33088
  help: args.includes("--help") || args.includes("-h")
32503
33089
  };
32504
33090
  if (options.version) {
32505
- console.log("oldpal v0.4.0");
33091
+ console.log("oldpal v0.4.1");
32506
33092
  process.exit(0);
32507
33093
  }
32508
33094
  if (options.help) {
@@ -32526,11 +33112,11 @@ In interactive mode:
32526
33112
  `);
32527
33113
  process.exit(0);
32528
33114
  }
32529
- var { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(App2, {
33115
+ var { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime10.jsxDEV(App2, {
32530
33116
  cwd: options.cwd
32531
33117
  }, undefined, false, undefined, this));
32532
33118
  waitUntilExit().then(() => {
32533
33119
  process.exit(0);
32534
33120
  });
32535
33121
 
32536
- //# debugId=CE824906A098BE1564756E2164756E21
33122
+ //# debugId=E37514F436BDCDD964756E2164756E21