openplanter 0.1.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.
Files changed (126) hide show
  1. package/README.md +210 -0
  2. package/dist/builder.d.ts +11 -0
  3. package/dist/builder.d.ts.map +1 -0
  4. package/dist/builder.js +179 -0
  5. package/dist/builder.js.map +1 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +548 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +51 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +114 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/credentials.d.ts +52 -0
  15. package/dist/credentials.d.ts.map +1 -0
  16. package/dist/credentials.js +371 -0
  17. package/dist/credentials.js.map +1 -0
  18. package/dist/demo.d.ts +26 -0
  19. package/dist/demo.d.ts.map +1 -0
  20. package/dist/demo.js +95 -0
  21. package/dist/demo.js.map +1 -0
  22. package/dist/engine.d.ts +91 -0
  23. package/dist/engine.d.ts.map +1 -0
  24. package/dist/engine.js +1036 -0
  25. package/dist/engine.js.map +1 -0
  26. package/dist/index.d.ts +30 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +39 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/investigation-tools/aph-holdings.d.ts +61 -0
  31. package/dist/investigation-tools/aph-holdings.d.ts.map +1 -0
  32. package/dist/investigation-tools/aph-holdings.js +459 -0
  33. package/dist/investigation-tools/aph-holdings.js.map +1 -0
  34. package/dist/investigation-tools/asic-officer-lookup.d.ts +42 -0
  35. package/dist/investigation-tools/asic-officer-lookup.d.ts.map +1 -0
  36. package/dist/investigation-tools/asic-officer-lookup.js +197 -0
  37. package/dist/investigation-tools/asic-officer-lookup.js.map +1 -0
  38. package/dist/investigation-tools/asx-calendar-fetcher.d.ts +42 -0
  39. package/dist/investigation-tools/asx-calendar-fetcher.d.ts.map +1 -0
  40. package/dist/investigation-tools/asx-calendar-fetcher.js +271 -0
  41. package/dist/investigation-tools/asx-calendar-fetcher.js.map +1 -0
  42. package/dist/investigation-tools/asx-parser.d.ts +66 -0
  43. package/dist/investigation-tools/asx-parser.d.ts.map +1 -0
  44. package/dist/investigation-tools/asx-parser.js +314 -0
  45. package/dist/investigation-tools/asx-parser.js.map +1 -0
  46. package/dist/investigation-tools/bulk-asx-announcements.d.ts +53 -0
  47. package/dist/investigation-tools/bulk-asx-announcements.d.ts.map +1 -0
  48. package/dist/investigation-tools/bulk-asx-announcements.js +204 -0
  49. package/dist/investigation-tools/bulk-asx-announcements.js.map +1 -0
  50. package/dist/investigation-tools/entity-resolver.d.ts +77 -0
  51. package/dist/investigation-tools/entity-resolver.d.ts.map +1 -0
  52. package/dist/investigation-tools/entity-resolver.js +346 -0
  53. package/dist/investigation-tools/entity-resolver.js.map +1 -0
  54. package/dist/investigation-tools/hotcopper-scraper.d.ts +73 -0
  55. package/dist/investigation-tools/hotcopper-scraper.d.ts.map +1 -0
  56. package/dist/investigation-tools/hotcopper-scraper.js +318 -0
  57. package/dist/investigation-tools/hotcopper-scraper.js.map +1 -0
  58. package/dist/investigation-tools/index.d.ts +15 -0
  59. package/dist/investigation-tools/index.d.ts.map +1 -0
  60. package/dist/investigation-tools/index.js +15 -0
  61. package/dist/investigation-tools/index.js.map +1 -0
  62. package/dist/investigation-tools/insider-graph.d.ts +173 -0
  63. package/dist/investigation-tools/insider-graph.d.ts.map +1 -0
  64. package/dist/investigation-tools/insider-graph.js +732 -0
  65. package/dist/investigation-tools/insider-graph.js.map +1 -0
  66. package/dist/investigation-tools/insider-suspicion-scorer.d.ts +97 -0
  67. package/dist/investigation-tools/insider-suspicion-scorer.d.ts.map +1 -0
  68. package/dist/investigation-tools/insider-suspicion-scorer.js +327 -0
  69. package/dist/investigation-tools/insider-suspicion-scorer.js.map +1 -0
  70. package/dist/investigation-tools/multi-forum-scraper.d.ts +104 -0
  71. package/dist/investigation-tools/multi-forum-scraper.d.ts.map +1 -0
  72. package/dist/investigation-tools/multi-forum-scraper.js +415 -0
  73. package/dist/investigation-tools/multi-forum-scraper.js.map +1 -0
  74. package/dist/investigation-tools/price-fetcher.d.ts +81 -0
  75. package/dist/investigation-tools/price-fetcher.d.ts.map +1 -0
  76. package/dist/investigation-tools/price-fetcher.js +268 -0
  77. package/dist/investigation-tools/price-fetcher.js.map +1 -0
  78. package/dist/investigation-tools/shared.d.ts +39 -0
  79. package/dist/investigation-tools/shared.d.ts.map +1 -0
  80. package/dist/investigation-tools/shared.js +203 -0
  81. package/dist/investigation-tools/shared.js.map +1 -0
  82. package/dist/investigation-tools/timeline-linker.d.ts +90 -0
  83. package/dist/investigation-tools/timeline-linker.d.ts.map +1 -0
  84. package/dist/investigation-tools/timeline-linker.js +219 -0
  85. package/dist/investigation-tools/timeline-linker.js.map +1 -0
  86. package/dist/investigation-tools/volume-scanner.d.ts +70 -0
  87. package/dist/investigation-tools/volume-scanner.d.ts.map +1 -0
  88. package/dist/investigation-tools/volume-scanner.js +227 -0
  89. package/dist/investigation-tools/volume-scanner.js.map +1 -0
  90. package/dist/model.d.ts +136 -0
  91. package/dist/model.d.ts.map +1 -0
  92. package/dist/model.js +1071 -0
  93. package/dist/model.js.map +1 -0
  94. package/dist/patching.d.ts +45 -0
  95. package/dist/patching.d.ts.map +1 -0
  96. package/dist/patching.js +317 -0
  97. package/dist/patching.js.map +1 -0
  98. package/dist/prompts.d.ts +15 -0
  99. package/dist/prompts.d.ts.map +1 -0
  100. package/dist/prompts.js +351 -0
  101. package/dist/prompts.js.map +1 -0
  102. package/dist/replay-log.d.ts +54 -0
  103. package/dist/replay-log.d.ts.map +1 -0
  104. package/dist/replay-log.js +94 -0
  105. package/dist/replay-log.js.map +1 -0
  106. package/dist/runtime.d.ts +53 -0
  107. package/dist/runtime.d.ts.map +1 -0
  108. package/dist/runtime.js +259 -0
  109. package/dist/runtime.js.map +1 -0
  110. package/dist/settings.d.ts +39 -0
  111. package/dist/settings.d.ts.map +1 -0
  112. package/dist/settings.js +146 -0
  113. package/dist/settings.js.map +1 -0
  114. package/dist/tool-defs.d.ts +58 -0
  115. package/dist/tool-defs.d.ts.map +1 -0
  116. package/dist/tool-defs.js +1029 -0
  117. package/dist/tool-defs.js.map +1 -0
  118. package/dist/tools.d.ts +72 -0
  119. package/dist/tools.d.ts.map +1 -0
  120. package/dist/tools.js +1454 -0
  121. package/dist/tools.js.map +1 -0
  122. package/dist/tui.d.ts +49 -0
  123. package/dist/tui.d.ts.map +1 -0
  124. package/dist/tui.js +699 -0
  125. package/dist/tui.js.map +1 -0
  126. package/package.json +126 -0
package/dist/tui.js ADDED
@@ -0,0 +1,699 @@
1
+ /**
2
+ * Terminal UI – migrated from agent/tui.py.
3
+ *
4
+ * Provides the interactive Rich-style REPL with splash screen, thinking
5
+ * spinner, tool-call tree display, and slash command handling.
6
+ */
7
+ import * as readline from "node:readline";
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ import * as os from "node:os";
11
+ import figlet from "figlet";
12
+ import chalk from "chalk";
13
+ import { _MODEL_CONTEXT_WINDOWS, _DEFAULT_CONTEXT_WINDOW } from "./engine.js";
14
+ import { EchoFallbackModel, ModelError } from "./model.js";
15
+ // ---------------------------------------------------------------------------
16
+ // Constants
17
+ // ---------------------------------------------------------------------------
18
+ export const SLASH_COMMANDS = [
19
+ "/quit", "/exit", "/help", "/status", "/clear", "/model", "/reasoning",
20
+ ];
21
+ export const HELP_LINES = [
22
+ "Commands:",
23
+ " /model Show current model, provider, aliases",
24
+ " /model <name> Switch model (e.g. /model opus, /model gpt5)",
25
+ " /model <name> --save Switch and persist as default",
26
+ " /model list [all] List available models",
27
+ " /reasoning [low|medium|high|off] Change reasoning effort",
28
+ " /status /clear /quit /exit /help",
29
+ ];
30
+ export const MODEL_ALIASES = {
31
+ opus: "claude-opus-4-6",
32
+ "opus4.6": "claude-opus-4-6",
33
+ sonnet: "claude-sonnet-4-5-20250929",
34
+ "sonnet4.5": "claude-sonnet-4-5-20250929",
35
+ haiku: "claude-haiku-4-5-20251001",
36
+ "haiku4.5": "claude-haiku-4-5-20251001",
37
+ gpt5: "gpt-5.2",
38
+ "gpt5.2": "gpt-5.2",
39
+ gpt4: "gpt-4.1",
40
+ "gpt4.1": "gpt-4.1",
41
+ gpt4o: "gpt-4o",
42
+ o4: "o4-mini",
43
+ "o4-mini": "o4-mini",
44
+ o3: "o3-mini",
45
+ "o3-mini": "o3-mini",
46
+ cerebras: "qwen-3-235b-a22b-instruct-2507",
47
+ qwen235b: "qwen-3-235b-a22b-instruct-2507",
48
+ oss120b: "gpt-oss-120b",
49
+ grok: "grok-4-1-fast-reasoning",
50
+ grok3mini: "grok-3-mini",
51
+ grok4: "grok-4-0709",
52
+ grok4fast: "grok-4-fast-reasoning",
53
+ grok41fast: "grok-4-1-fast-reasoning",
54
+ };
55
+ // ---------------------------------------------------------------------------
56
+ // Formatting helpers
57
+ // ---------------------------------------------------------------------------
58
+ export function formatTokenCount(n) {
59
+ if (n < 1000)
60
+ return String(n);
61
+ if (n < 10_000)
62
+ return `${(n / 1000).toFixed(1)}k`;
63
+ if (n < 1_000_000)
64
+ return `${Math.round(n / 1000)}k`;
65
+ return `${(n / 1_000_000).toFixed(1)}M`;
66
+ }
67
+ export function formatSessionTokens(sessionTokens) {
68
+ let totalIn = 0;
69
+ let totalOut = 0;
70
+ for (const v of Object.values(sessionTokens)) {
71
+ totalIn += v.input;
72
+ totalOut += v.output;
73
+ }
74
+ if (totalIn === 0 && totalOut === 0)
75
+ return "";
76
+ return `${formatTokenCount(totalIn)} in / ${formatTokenCount(totalOut)} out`;
77
+ }
78
+ export function getModelDisplayName(engine) {
79
+ const model = engine.model;
80
+ if (model instanceof EchoFallbackModel)
81
+ return "(no model)";
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ return model.model ?? "(unknown)";
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Event trace helpers
87
+ // ---------------------------------------------------------------------------
88
+ const _RE_PREFIX = /^\[d(\d+)(?:\/s(\d+))?\]\s*/;
89
+ const _RE_CALLING = /calling model/;
90
+ const _RE_SUBTASK = />> entering subtask/;
91
+ const _RE_EXECUTE = />> executing leaf/;
92
+ const _RE_ERROR = /model error:/i;
93
+ const _EVENT_MAX_CHARS = 300;
94
+ export function _clip_event(text) {
95
+ const firstLine = text.split("\n", 1)[0];
96
+ if (firstLine.length > _EVENT_MAX_CHARS) {
97
+ return firstLine.slice(0, _EVENT_MAX_CHARS) + "...";
98
+ }
99
+ const rest = text.slice(firstLine.length + 1);
100
+ if (rest) {
101
+ const extraLines = (rest.match(/\n/g) ?? []).length + 1;
102
+ return firstLine + ` (+${extraLines} lines)`;
103
+ }
104
+ return firstLine;
105
+ }
106
+ const _KEY_ARGS = {
107
+ read_file: "path",
108
+ write_file: "path",
109
+ edit_file: "path",
110
+ hashline_edit: "path",
111
+ apply_patch: "patch",
112
+ run_shell: "command",
113
+ run_shell_bg: "command",
114
+ web_search: "query",
115
+ fetch_url: "urls",
116
+ search_files: "query",
117
+ list_files: "glob",
118
+ repo_map: "glob",
119
+ subtask: "objective",
120
+ execute: "objective",
121
+ think: "note",
122
+ check_shell_bg: "job_id",
123
+ kill_shell_bg: "job_id",
124
+ };
125
+ // ---------------------------------------------------------------------------
126
+ // Helper functions
127
+ // ---------------------------------------------------------------------------
128
+ function _extractKeyArg(name, args) {
129
+ const key = _KEY_ARGS[name];
130
+ if (!key) {
131
+ for (const v of Object.values(args)) {
132
+ if (typeof v === "string" && v.trim()) {
133
+ const s = v.trim();
134
+ return s.length > 60 ? s.slice(0, 57) + "..." : s;
135
+ }
136
+ }
137
+ return "";
138
+ }
139
+ let val = args[key];
140
+ if (Array.isArray(val)) {
141
+ val = val.slice(0, 3).map(String).join(", ");
142
+ }
143
+ const s = String(val ?? "").trim();
144
+ return s.length > 60 ? s.slice(0, 57) + "..." : s;
145
+ }
146
+ function _apiKeyForProvider(cfg, provider) {
147
+ const map = {
148
+ openai: cfg.openaiApiKey,
149
+ anthropic: cfg.anthropicApiKey,
150
+ openrouter: cfg.openrouterApiKey,
151
+ cerebras: cfg.cerebrasApiKey,
152
+ grok: cfg.grokApiKey,
153
+ };
154
+ return map[provider] ?? null;
155
+ }
156
+ function _availableProviders(cfg) {
157
+ const providers = [];
158
+ if (cfg.openaiApiKey)
159
+ providers.push("openai");
160
+ if (cfg.anthropicApiKey)
161
+ providers.push("anthropic");
162
+ if (cfg.openrouterApiKey)
163
+ providers.push("openrouter");
164
+ if (cfg.cerebrasApiKey)
165
+ providers.push("cerebras");
166
+ if (cfg.grokApiKey)
167
+ providers.push("grok");
168
+ return providers;
169
+ }
170
+ function _getModeLabel(cfg) {
171
+ return cfg.recursive ? "recursive" : "flat";
172
+ }
173
+ // ---------------------------------------------------------------------------
174
+ // Splash art
175
+ // ---------------------------------------------------------------------------
176
+ const _PLANT_LEFT = [
177
+ " .oOo. ",
178
+ "oO.|.Oo ",
179
+ "Oo.|.oO ",
180
+ " .|. ",
181
+ "[=====] ",
182
+ " \\___/ ",
183
+ ];
184
+ const _PLANT_RIGHT = [
185
+ " .oOo. ",
186
+ " oO.|.Oo",
187
+ " Oo.|.oO",
188
+ " .|. ",
189
+ " [=====]",
190
+ " \\___/ ",
191
+ ];
192
+ function _buildSplash() {
193
+ let art;
194
+ try {
195
+ art = figlet.textSync("OpenPlanter", { font: "Standard" }).trimEnd();
196
+ }
197
+ catch {
198
+ art = " OpenPlanter";
199
+ }
200
+ const lines = art.split("\n");
201
+ const minIndent = Math.min(...lines.filter((l) => l.trim()).map((l) => l.length - l.trimStart().length));
202
+ const stripped = lines.map((l) => l.slice(minIndent));
203
+ const maxW = Math.max(...stripped.map((l) => l.length));
204
+ const padded = stripped.map((l) => l.padEnd(maxW));
205
+ const n = padded.length;
206
+ const pwL = Math.max(..._PLANT_LEFT.map((l) => l.length));
207
+ const pwR = Math.max(..._PLANT_RIGHT.map((l) => l.length));
208
+ const left = n > _PLANT_LEFT.length
209
+ ? [...Array(n - _PLANT_LEFT.length).fill(" ".repeat(pwL)), ..._PLANT_LEFT]
210
+ : _PLANT_LEFT.slice(-n);
211
+ const right = n > _PLANT_RIGHT.length
212
+ ? [...Array(n - _PLANT_RIGHT.length).fill(" ".repeat(pwR)), ..._PLANT_RIGHT]
213
+ : _PLANT_RIGHT.slice(-n);
214
+ return padded.map((line, i) => `${left[i]} ${line} ${right[i]}`).join("\n");
215
+ }
216
+ export const SPLASH_ART = _buildSplash();
217
+ // ---------------------------------------------------------------------------
218
+ // Slash command: /model
219
+ // ---------------------------------------------------------------------------
220
+ export async function handleModelCommand(args, ctx) {
221
+ const { _fetchModelsForProvider, buildEngine, inferProviderForModel } = await import("./builder.js");
222
+ const parts = args.trim().split(/\s+/);
223
+ if (!parts[0]) {
224
+ const modelName = getModelDisplayName(ctx.runtime.engine);
225
+ const effort = ctx.cfg.reasoningEffort || "(off)";
226
+ const avail = _availableProviders(ctx.cfg).join(", ") || "none";
227
+ return [
228
+ `Provider: ${ctx.cfg.provider} | Model: ${modelName} | Reasoning: ${effort}`,
229
+ `Configured providers: ${avail}`,
230
+ `Aliases: ${Object.keys(MODEL_ALIASES).sort().join(", ")}`,
231
+ ];
232
+ }
233
+ if (parts[0] === "list") {
234
+ const listTarget = parts[1] ?? null;
235
+ let providers;
236
+ if (listTarget === "all") {
237
+ providers = _availableProviders(ctx.cfg);
238
+ }
239
+ else if (listTarget &&
240
+ ["openai", "anthropic", "openrouter", "cerebras", "grok"].includes(listTarget)) {
241
+ providers = [listTarget];
242
+ }
243
+ else {
244
+ providers = [ctx.cfg.provider];
245
+ }
246
+ const lines = [];
247
+ for (const provider of providers) {
248
+ try {
249
+ const models = await _fetchModelsForProvider(ctx.cfg, provider);
250
+ lines.push(`${provider}: ${models.length} models`);
251
+ for (const row of models.slice(0, 15)) {
252
+ lines.push(` ${row["id"]}`);
253
+ }
254
+ if (models.length > 15) {
255
+ lines.push(` ...and ${models.length - 15} more`);
256
+ }
257
+ }
258
+ catch (exc) {
259
+ lines.push(`${provider}: skipped (${exc})`);
260
+ }
261
+ }
262
+ return lines;
263
+ }
264
+ const rawModel = parts[0];
265
+ const newModel = MODEL_ALIASES[rawModel.toLowerCase()] ?? rawModel;
266
+ const save = parts.includes("--save");
267
+ const inferred = inferProviderForModel(newModel);
268
+ let providerSwitched = false;
269
+ if (inferred && inferred !== ctx.cfg.provider && ctx.cfg.provider !== "openrouter") {
270
+ const key = _apiKeyForProvider(ctx.cfg, inferred);
271
+ if (!key) {
272
+ return [
273
+ `Model '${newModel}' requires provider '${inferred}', but no API key is configured for it.`,
274
+ ];
275
+ }
276
+ ctx.cfg.provider = inferred;
277
+ providerSwitched = true;
278
+ }
279
+ ctx.cfg.model = newModel;
280
+ let newEngine;
281
+ try {
282
+ newEngine = buildEngine(ctx.cfg);
283
+ }
284
+ catch (exc) {
285
+ if (exc instanceof ModelError) {
286
+ return [`Failed to switch model: ${exc.message}`];
287
+ }
288
+ throw exc;
289
+ }
290
+ ctx.runtime.engine = newEngine;
291
+ const aliasNote = rawModel.toLowerCase() in MODEL_ALIASES ? ` (alias: ${rawModel})` : "";
292
+ const lines = [`Switched to model: ${newModel}${aliasNote}`];
293
+ if (providerSwitched) {
294
+ lines.push(`Provider auto-switched to: ${ctx.cfg.provider}`);
295
+ }
296
+ if (save) {
297
+ const settings = ctx.settingsStore.load();
298
+ const provider = ctx.cfg.provider;
299
+ if (provider === "openai")
300
+ settings.defaultModelOpenai = newModel;
301
+ else if (provider === "anthropic")
302
+ settings.defaultModelAnthropic = newModel;
303
+ else if (provider === "openrouter")
304
+ settings.defaultModelOpenrouter = newModel;
305
+ else if (provider === "cerebras")
306
+ settings.defaultModelCerebras = newModel;
307
+ else if (provider === "grok")
308
+ settings.defaultModelGrok = newModel;
309
+ else
310
+ settings.defaultModel = newModel;
311
+ ctx.settingsStore.save(settings);
312
+ lines.push("Saved as workspace default.");
313
+ }
314
+ return lines;
315
+ }
316
+ // ---------------------------------------------------------------------------
317
+ // Slash command: /reasoning
318
+ // ---------------------------------------------------------------------------
319
+ export async function handleReasoningCommand(args, ctx) {
320
+ const { buildEngine } = await import("./builder.js");
321
+ const parts = args.trim().split(/\s+/);
322
+ if (!parts[0]) {
323
+ const effort = ctx.cfg.reasoningEffort || "(off)";
324
+ return [
325
+ `Current reasoning effort: ${effort}`,
326
+ "Usage: /reasoning <low|medium|high|off> [--save]",
327
+ ];
328
+ }
329
+ const value = parts[0].toLowerCase();
330
+ const save = parts.includes("--save");
331
+ if (["off", "none", "disable", "disabled"].includes(value)) {
332
+ ctx.cfg.reasoningEffort = null;
333
+ }
334
+ else if (["low", "medium", "high"].includes(value)) {
335
+ ctx.cfg.reasoningEffort = value;
336
+ }
337
+ else {
338
+ return [`Invalid effort '${value}'. Use: low, medium, high, off`];
339
+ }
340
+ try {
341
+ const newEngine = buildEngine(ctx.cfg);
342
+ ctx.runtime.engine = newEngine;
343
+ }
344
+ catch (exc) {
345
+ if (exc instanceof ModelError) {
346
+ return [`Failed to apply reasoning change: ${exc.message}`];
347
+ }
348
+ throw exc;
349
+ }
350
+ const display = ctx.cfg.reasoningEffort || "off";
351
+ const lines = [`Reasoning effort set to: ${display}`];
352
+ if (save) {
353
+ const settings = ctx.settingsStore.load();
354
+ settings.defaultReasoningEffort = ctx.cfg.reasoningEffort;
355
+ ctx.settingsStore.save(settings);
356
+ lines.push("Saved as workspace default.");
357
+ }
358
+ return lines;
359
+ }
360
+ // ---------------------------------------------------------------------------
361
+ // Slash command dispatch
362
+ // ---------------------------------------------------------------------------
363
+ export async function dispatchSlashCommand(command, ctx, emit) {
364
+ if (command === "/quit" || command === "/exit")
365
+ return "quit";
366
+ if (command === "/help") {
367
+ for (const ln of HELP_LINES)
368
+ emit(ln);
369
+ return "handled";
370
+ }
371
+ if (command === "/status") {
372
+ const modelName = getModelDisplayName(ctx.runtime.engine);
373
+ const effort = ctx.cfg.reasoningEffort || "(off)";
374
+ const mode = _getModeLabel(ctx.cfg);
375
+ emit(`Provider: ${ctx.cfg.provider} | Model: ${modelName} | Reasoning: ${effort} | Mode: ${mode}`);
376
+ const tokens = ctx.runtime.engine.sessionTokens;
377
+ if (Object.keys(tokens).length > 0) {
378
+ for (const [mname, counts] of Object.entries(tokens)) {
379
+ emit(` ${mname}: ${formatTokenCount(counts.input)} in / ${formatTokenCount(counts.output)} out`);
380
+ }
381
+ }
382
+ else {
383
+ emit(" Tokens: (none yet)");
384
+ }
385
+ return "handled";
386
+ }
387
+ if (command === "/clear")
388
+ return "clear";
389
+ if (command.startsWith("/model")) {
390
+ const cmdArgs = command.slice("/model".length).trim();
391
+ const lines = await handleModelCommand(cmdArgs, ctx);
392
+ for (const line of lines)
393
+ emit(line);
394
+ return "handled";
395
+ }
396
+ if (command.startsWith("/reasoning")) {
397
+ const cmdArgs = command.slice("/reasoning".length).trim();
398
+ const lines = await handleReasoningCommand(cmdArgs, ctx);
399
+ for (const line of lines)
400
+ emit(line);
401
+ return "handled";
402
+ }
403
+ return null;
404
+ }
405
+ // ---------------------------------------------------------------------------
406
+ // Thinking display
407
+ // ---------------------------------------------------------------------------
408
+ const _THINKING_TAIL_LINES = 6;
409
+ const _THINKING_MAX_LINE_WIDTH = 80;
410
+ class ThinkingDisplay {
411
+ _buf = "";
412
+ _startTime = 0;
413
+ _active = false;
414
+ _interval = null;
415
+ start() {
416
+ if (this._active)
417
+ return;
418
+ this._buf = "";
419
+ this._startTime = performance.now();
420
+ this._active = true;
421
+ this._interval = setInterval(() => this._render(), 250);
422
+ }
423
+ stop() {
424
+ if (!this._active)
425
+ return;
426
+ this._active = false;
427
+ if (this._interval !== null) {
428
+ clearInterval(this._interval);
429
+ this._interval = null;
430
+ }
431
+ process.stdout.write("\r\x1b[K");
432
+ }
433
+ feed(deltaType, text) {
434
+ if (!this._active || deltaType !== "thinking")
435
+ return;
436
+ this._buf += text;
437
+ this._render();
438
+ }
439
+ _render() {
440
+ if (!this._active)
441
+ return;
442
+ const elapsed = (performance.now() - this._startTime) / 1000;
443
+ const header = chalk.bold.cyan("Thinking...") + chalk.dim(` (${elapsed.toFixed(1)}s)`);
444
+ if (!this._buf) {
445
+ process.stdout.write(`\r\x1b[K ${header}`);
446
+ return;
447
+ }
448
+ const lines = this._buf.split("\n");
449
+ const tail = lines.slice(-_THINKING_TAIL_LINES);
450
+ const clipped = tail.map((ln) => ln.length > _THINKING_MAX_LINE_WIDTH
451
+ ? ln.slice(0, _THINKING_MAX_LINE_WIDTH - 3) + "..."
452
+ : ln);
453
+ process.stdout.write(`\r\x1b[K ${header} ${chalk.dim.italic(clipped[clipped.length - 1] ?? "")}`);
454
+ }
455
+ get active() {
456
+ return this._active;
457
+ }
458
+ }
459
+ // ---------------------------------------------------------------------------
460
+ // RichREPL
461
+ // ---------------------------------------------------------------------------
462
+ export class RichREPL {
463
+ ctx;
464
+ startupInfo;
465
+ currentStep = null;
466
+ thinking;
467
+ censorFn = null;
468
+ historyPath;
469
+ history = [];
470
+ constructor(ctx, startupInfo) {
471
+ this.ctx = ctx;
472
+ this.startupInfo = startupInfo ?? {};
473
+ this.thinking = new ThinkingDisplay();
474
+ if (ctx.cfg.demo) {
475
+ // Lazy-load demo censor
476
+ const { DemoCensor } = require("./demo.js");
477
+ const censor = new DemoCensor(ctx.cfg.workspace);
478
+ this.censorFn = censor.censorText.bind(censor);
479
+ }
480
+ const historyDir = path.join(os.homedir(), ".openplanter");
481
+ fs.mkdirSync(historyDir, { recursive: true });
482
+ this.historyPath = path.join(historyDir, "repl_history");
483
+ this._loadHistory();
484
+ }
485
+ _loadHistory() {
486
+ try {
487
+ if (fs.existsSync(this.historyPath)) {
488
+ this.history = fs
489
+ .readFileSync(this.historyPath, "utf-8")
490
+ .split("\n")
491
+ .filter((l) => l.trim());
492
+ }
493
+ }
494
+ catch {
495
+ this.history = [];
496
+ }
497
+ }
498
+ _saveHistory() {
499
+ try {
500
+ const last500 = this.history.slice(-500);
501
+ fs.writeFileSync(this.historyPath, last500.join("\n") + "\n", "utf-8");
502
+ }
503
+ catch {
504
+ // ignore
505
+ }
506
+ }
507
+ _print(text) {
508
+ const output = this.censorFn ? this.censorFn(text) : text;
509
+ console.log(output);
510
+ }
511
+ // -- Event callbacks ---
512
+ _onEvent = (msg) => {
513
+ const m = _RE_PREFIX.exec(msg);
514
+ const body = m ? msg.slice(m[0].length) : msg;
515
+ if (_RE_CALLING.test(body)) {
516
+ this._flushStep();
517
+ this.thinking.start();
518
+ return;
519
+ }
520
+ if (_RE_SUBTASK.test(body) || _RE_EXECUTE.test(body)) {
521
+ this._flushStep();
522
+ this.thinking.stop();
523
+ const label = body.replace(/>> (entering subtask|executing leaf):\s*/, "").trim();
524
+ console.log(chalk.dim(`--- ${label} ---`));
525
+ return;
526
+ }
527
+ if (_RE_ERROR.test(body)) {
528
+ this.thinking.stop();
529
+ const firstLine = msg.split("\n", 1)[0];
530
+ console.log(chalk.bold.red(firstLine.length > _EVENT_MAX_CHARS
531
+ ? firstLine.slice(0, _EVENT_MAX_CHARS) + "..."
532
+ : firstLine));
533
+ }
534
+ };
535
+ _onStep = (stepEvent) => {
536
+ const action = stepEvent.action;
537
+ if (!action || typeof action !== "object")
538
+ return;
539
+ const name = action.name;
540
+ if (name === "_model_turn") {
541
+ this.thinking.stop();
542
+ this.currentStep = {
543
+ depth: stepEvent.depth ?? 0,
544
+ step: stepEvent.step ?? 0,
545
+ maxSteps: this.ctx.cfg.maxStepsPerCall,
546
+ modelText: stepEvent.model_text ?? "",
547
+ modelElapsedSec: stepEvent.elapsed_sec ?? 0,
548
+ inputTokens: stepEvent.input_tokens ?? 0,
549
+ outputTokens: stepEvent.output_tokens ?? 0,
550
+ toolCalls: [],
551
+ };
552
+ return;
553
+ }
554
+ if (name === "final") {
555
+ this._flushStep();
556
+ return;
557
+ }
558
+ if (this.currentStep !== null) {
559
+ const keyArg = _extractKeyArg(name, (action.arguments ?? {}));
560
+ const elapsed = stepEvent.elapsed_sec ?? 0;
561
+ const obs = stepEvent.observation ?? "";
562
+ const isError = obs.startsWith("Tool ") && obs.includes("crashed");
563
+ this.currentStep.toolCalls.push({
564
+ name,
565
+ keyArg,
566
+ elapsedSec: elapsed,
567
+ isError,
568
+ });
569
+ }
570
+ };
571
+ _onContentDelta = (deltaType, text) => {
572
+ this.thinking.feed(deltaType, text);
573
+ };
574
+ // -- Step flushing ---
575
+ _flushStep() {
576
+ const step = this.currentStep;
577
+ if (step === null)
578
+ return;
579
+ this.currentStep = null;
580
+ const ts = new Date().toLocaleTimeString("en-US", { hour12: false });
581
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
582
+ const modelName = this.ctx.runtime.engine.model.model ?? "(unknown)";
583
+ const contextWindow = _MODEL_CONTEXT_WINDOWS[modelName] ?? _DEFAULT_CONTEXT_WINDOW;
584
+ const ctxStr = `${formatTokenCount(step.inputTokens)}/${formatTokenCount(contextWindow)}`;
585
+ const rightParts = [];
586
+ if (step.depth > 0)
587
+ rightParts.push(`depth ${step.depth}`);
588
+ if (step.maxSteps)
589
+ rightParts.push(`${step.step}/${step.maxSteps}`);
590
+ if (step.inputTokens || step.outputTokens) {
591
+ rightParts.push(`${formatTokenCount(step.inputTokens)}in/${formatTokenCount(step.outputTokens)}out`);
592
+ }
593
+ rightParts.push(`[${ctxStr}]`);
594
+ const right = rightParts.join(" | ");
595
+ console.log(chalk.cyan(`--- ${ts} Step ${step.step} ${chalk.dim(right)} ---`));
596
+ if (step.modelText) {
597
+ let preview = step.modelText.trim();
598
+ if (preview.length > 200)
599
+ preview = preview.slice(0, 197) + "...";
600
+ console.log(chalk.dim(` (${step.modelElapsedSec.toFixed(1)}s) ${preview}`));
601
+ }
602
+ const n = step.toolCalls.length;
603
+ for (let i = 0; i < n; i++) {
604
+ const tc = step.toolCalls[i];
605
+ const isLast = i === n - 1;
606
+ const connector = isLast ? "\u2514\u2500" : "\u251c\u2500";
607
+ const nameStr = tc.isError ? chalk.bold.red(tc.name) : tc.name;
608
+ const argStr = tc.keyArg ? chalk.dim(` "${tc.keyArg}"`) : "";
609
+ console.log(` ${chalk.dim(connector)} ${nameStr}${argStr} ${chalk.dim(`${tc.elapsedSec.toFixed(1)}s`)}`);
610
+ }
611
+ }
612
+ // -- Main REPL loop ---
613
+ async run() {
614
+ console.log(chalk.bold.cyan(SPLASH_ART));
615
+ if (Object.keys(this.startupInfo).length > 0) {
616
+ for (const [key, val] of Object.entries(this.startupInfo)) {
617
+ this._print(chalk.dim(` ${key.padStart(10)} ${val}`));
618
+ }
619
+ console.log();
620
+ }
621
+ console.log(chalk.dim("Type /help for commands, Ctrl+D to exit."));
622
+ console.log();
623
+ const rl = readline.createInterface({
624
+ input: process.stdin,
625
+ output: process.stdout,
626
+ prompt: "you> ",
627
+ completer: (line) => {
628
+ if (line.startsWith("/") && !line.includes(" ")) {
629
+ const hits = SLASH_COMMANDS.filter((c) => c.startsWith(line));
630
+ return [hits.length > 0 ? hits : SLASH_COMMANDS, line];
631
+ }
632
+ return [[], line];
633
+ },
634
+ });
635
+ const askLine = () => {
636
+ return new Promise((resolve) => {
637
+ rl.question("you> ", (answer) => {
638
+ if (answer === undefined) {
639
+ resolve(null);
640
+ return;
641
+ }
642
+ resolve(answer);
643
+ });
644
+ });
645
+ };
646
+ let running = true;
647
+ while (running) {
648
+ let userInput;
649
+ try {
650
+ userInput = await askLine();
651
+ }
652
+ catch {
653
+ break;
654
+ }
655
+ if (userInput === null)
656
+ break;
657
+ const trimmed = userInput.trim();
658
+ if (!trimmed)
659
+ continue;
660
+ this.history.push(trimmed);
661
+ this._saveHistory();
662
+ const result = await dispatchSlashCommand(trimmed, this.ctx, (line) => console.log(chalk.cyan(line)));
663
+ if (result === "quit") {
664
+ running = false;
665
+ break;
666
+ }
667
+ if (result === "clear") {
668
+ process.stdout.write("\x1b[2J\x1b[H");
669
+ continue;
670
+ }
671
+ if (result === "handled")
672
+ continue;
673
+ console.log();
674
+ const answer = await this.ctx.runtime.solve(trimmed, {
675
+ onEvent: this._onEvent,
676
+ onStep: this._onStep,
677
+ onContentDelta: this._onContentDelta,
678
+ });
679
+ this.thinking.stop();
680
+ this._flushStep();
681
+ console.log();
682
+ this._print(answer);
683
+ const tokenStr = formatSessionTokens(this.ctx.runtime.engine.sessionTokens);
684
+ if (tokenStr) {
685
+ console.log(chalk.dim(` tokens: ${tokenStr}`));
686
+ }
687
+ console.log();
688
+ }
689
+ rl.close();
690
+ }
691
+ }
692
+ // ---------------------------------------------------------------------------
693
+ // Entry point
694
+ // ---------------------------------------------------------------------------
695
+ export async function runRichRepl(ctx, startupInfo) {
696
+ const repl = new RichREPL(ctx, startupInfo);
697
+ await repl.run();
698
+ }
699
+ //# sourceMappingURL=tui.js.map