@ouro.bot/cli 0.1.0-alpha.174 → 0.1.0-alpha.176
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/changelog.json +9 -0
- package/dist/heart/active-work.js +2 -2
- package/dist/heart/core.js +13 -2
- package/dist/heart/daemon/daemon-cli.js +17 -14
- package/dist/heart/daemon/daemon-entry.js +17 -0
- package/dist/heart/daemon/daemon.js +5 -2
- package/dist/heart/daemon/launchd.js +8 -4
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/prompt.js +119 -60
- package/dist/mind/scrutiny.js +173 -0
- package/dist/nerves/coverage/audit.js +1 -1
- package/dist/repertoire/ado-semantic.js +5 -5
- package/dist/repertoire/coding/tools.js +35 -5
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/tools-base.js +145 -16
- package/dist/repertoire/tools.js +37 -0
- package/dist/senses/cli/adapter.js +307 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +68 -0
- package/dist/senses/cli/message-list.js +84 -0
- package/dist/senses/cli/ouro-tui.js +296 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +64 -0
- package/dist/senses/cli/streaming-markdown.js +486 -0
- package/dist/senses/cli/tool-display.js +77 -0
- package/dist/senses/cli/tool-render.js +89 -0
- package/dist/senses/cli/tui-store.js +198 -0
- package/dist/senses/cli/virtual-list.js +68 -0
- package/dist/senses/cli.js +231 -157
- package/dist/senses/pipeline.js +53 -10
- package/package.json +1 -1
- package/dist/outlook-ui/assets/index-DN6vVYPm.css +0 -1
- package/dist/outlook-ui/assets/index-M1l1tDTX.js +0 -61
- package/dist/outlook-ui/index.html +0 -15
|
@@ -40,8 +40,11 @@ const fg = __importStar(require("fast-glob"));
|
|
|
40
40
|
const child_process_1 = require("child_process");
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const skills_1 = require("./skills");
|
|
43
|
+
const shell_sessions_1 = require("./shell-sessions");
|
|
43
44
|
const config_1 = require("../heart/config");
|
|
44
45
|
const runtime_1 = require("../nerves/runtime");
|
|
46
|
+
const file_state_1 = require("../mind/file-state");
|
|
47
|
+
const scrutiny_1 = require("../mind/scrutiny");
|
|
45
48
|
const identity_1 = require("../heart/identity");
|
|
46
49
|
const identity_2 = require("../heart/identity");
|
|
47
50
|
const socket_client_1 = require("../heart/daemon/socket-client");
|
|
@@ -337,7 +340,7 @@ exports.baseToolDefinitions = [
|
|
|
337
340
|
type: "function",
|
|
338
341
|
function: {
|
|
339
342
|
name: "read_file",
|
|
340
|
-
description: "read file
|
|
343
|
+
description: "Read file contents. Results include line numbers. Use offset/limit for large files -- don't read the whole thing if you only need a section. Use this tool before editing any file. When reading code, read enough context to understand the surrounding logic, not just the target line.",
|
|
341
344
|
parameters: {
|
|
342
345
|
type: "object",
|
|
343
346
|
properties: {
|
|
@@ -355,6 +358,17 @@ exports.baseToolDefinitions = [
|
|
|
355
358
|
exports.editFileReadTracker.add(resolvedPath);
|
|
356
359
|
const offset = a.offset ? parseInt(a.offset, 10) : undefined;
|
|
357
360
|
const limit = a.limit ? parseInt(a.limit, 10) : undefined;
|
|
361
|
+
// Record in file state cache for staleness detection
|
|
362
|
+
try {
|
|
363
|
+
const mtime = fs.statSync(resolvedPath).mtimeMs;
|
|
364
|
+
const readContent = (offset === undefined && limit === undefined)
|
|
365
|
+
? content
|
|
366
|
+
: content.split("\n").slice(offset ? offset - 1 : 0, limit !== undefined ? (offset ? offset - 1 : 0) + limit : undefined).join("\n");
|
|
367
|
+
file_state_1.fileStateCache.record(resolvedPath, readContent, mtime, offset, limit);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// stat failed -- skip cache recording
|
|
371
|
+
}
|
|
358
372
|
if (offset === undefined && limit === undefined)
|
|
359
373
|
return content;
|
|
360
374
|
const lines = content.split("\n");
|
|
@@ -369,7 +383,7 @@ exports.baseToolDefinitions = [
|
|
|
369
383
|
type: "function",
|
|
370
384
|
function: {
|
|
371
385
|
name: "write_file",
|
|
372
|
-
description: "
|
|
386
|
+
description: "Prefer this tool for creating new files or fully replacing existing ones. You MUST read an existing file with read_file before overwriting it. Prefer edit_file for modifying existing files -- it only sends the diff. Do not create documentation files (*.md, README) by default; only do so when explicitly asked or when documentation is clearly part of the requested change.",
|
|
373
387
|
parameters: {
|
|
374
388
|
type: "object",
|
|
375
389
|
properties: { path: { type: "string" }, content: { type: "string" } },
|
|
@@ -381,7 +395,10 @@ exports.baseToolDefinitions = [
|
|
|
381
395
|
const resolvedPath = resolveLocalToolPath(a.path);
|
|
382
396
|
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
383
397
|
fs.writeFileSync(resolvedPath, a.content, "utf-8");
|
|
384
|
-
|
|
398
|
+
(0, scrutiny_1.trackModifiedFile)(resolvedPath);
|
|
399
|
+
const scrutiny = (0, scrutiny_1.getPostImplementationScrutiny)((0, scrutiny_1.getModifiedFileCount)());
|
|
400
|
+
/* v8 ignore next -- scrutiny appendix branch depends on session-level file count @preserve */
|
|
401
|
+
return scrutiny ? `ok\n\n${scrutiny}` : "ok";
|
|
385
402
|
},
|
|
386
403
|
summaryKeys: ["path"],
|
|
387
404
|
},
|
|
@@ -390,7 +407,7 @@ exports.baseToolDefinitions = [
|
|
|
390
407
|
type: "function",
|
|
391
408
|
function: {
|
|
392
409
|
name: "edit_file",
|
|
393
|
-
description: "
|
|
410
|
+
description: "Surgically edit a file by replacing an exact string. The file MUST have been read via read_file first -- this tool will reject the call otherwise. old_string must match EXACTLY ONE location in the file -- if it matches zero or multiple, the edit fails. To fix: provide more surrounding context to make the match unique. Preserve exact indentation (tabs/spaces) from the file. Prefer this over write_file for modifications -- it only sends the diff.",
|
|
394
411
|
parameters: {
|
|
395
412
|
type: "object",
|
|
396
413
|
properties: {
|
|
@@ -407,6 +424,8 @@ exports.baseToolDefinitions = [
|
|
|
407
424
|
if (!exports.editFileReadTracker.has(resolvedPath)) {
|
|
408
425
|
return `error: you must read the file with read_file before editing it. call read_file on ${a.path} first.`;
|
|
409
426
|
}
|
|
427
|
+
// Check staleness before editing
|
|
428
|
+
const stalenessCheck = file_state_1.fileStateCache.isStale(resolvedPath);
|
|
410
429
|
let content;
|
|
411
430
|
try {
|
|
412
431
|
content = fs.readFileSync(resolvedPath, "utf-8");
|
|
@@ -434,13 +453,33 @@ exports.baseToolDefinitions = [
|
|
|
434
453
|
const idx = occurrences[0];
|
|
435
454
|
const updated = content.slice(0, idx) + a.new_string + content.slice(idx + a.old_string.length);
|
|
436
455
|
fs.writeFileSync(resolvedPath, updated, "utf-8");
|
|
456
|
+
// Update file state cache with new content
|
|
457
|
+
try {
|
|
458
|
+
const newMtime = fs.statSync(resolvedPath).mtimeMs;
|
|
459
|
+
file_state_1.fileStateCache.record(resolvedPath, updated, newMtime);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// stat failed -- skip cache update
|
|
463
|
+
}
|
|
437
464
|
// Build contextual diff
|
|
438
465
|
const lines = updated.split("\n");
|
|
439
466
|
const prefixLines = content.slice(0, idx).split("\n");
|
|
440
467
|
const changeStartLine = prefixLines.length - 1;
|
|
441
468
|
const newStringLines = a.new_string.split("\n");
|
|
442
469
|
const changeEndLine = changeStartLine + newStringLines.length;
|
|
443
|
-
|
|
470
|
+
const diffResult = buildContextDiff(lines, changeStartLine, changeEndLine);
|
|
471
|
+
// Track modified file and compute scrutiny appendix
|
|
472
|
+
(0, scrutiny_1.trackModifiedFile)(resolvedPath);
|
|
473
|
+
const scrutiny = (0, scrutiny_1.getPostImplementationScrutiny)((0, scrutiny_1.getModifiedFileCount)());
|
|
474
|
+
// Append staleness warning if detected (do not block -- TTFA)
|
|
475
|
+
/* v8 ignore start -- staleness+diff+scrutiny combo not exercised in integration tests @preserve */
|
|
476
|
+
if (stalenessCheck.stale) {
|
|
477
|
+
const base = `${diffResult}\n\n⚠️ warning: file changed externally since last read -- re-read recommended`;
|
|
478
|
+
return scrutiny ? `${base}\n\n${scrutiny}` : base;
|
|
479
|
+
}
|
|
480
|
+
/* v8 ignore stop */
|
|
481
|
+
/* v8 ignore next -- scrutiny appendix branch depends on session-level file count @preserve */
|
|
482
|
+
return scrutiny ? `${diffResult}\n\n${scrutiny}` : diffResult;
|
|
444
483
|
},
|
|
445
484
|
summaryKeys: ["path"],
|
|
446
485
|
},
|
|
@@ -449,7 +488,7 @@ exports.baseToolDefinitions = [
|
|
|
449
488
|
type: "function",
|
|
450
489
|
function: {
|
|
451
490
|
name: "glob",
|
|
452
|
-
description: "
|
|
491
|
+
description: "Find files matching a glob pattern, sorted alphabetically. Use this instead of shell commands like `find` or `ls`. For broad exploratory searches that would require multiple rounds of globbing and grepping, consider using claude or coding_spawn.",
|
|
453
492
|
parameters: {
|
|
454
493
|
type: "object",
|
|
455
494
|
properties: {
|
|
@@ -472,7 +511,7 @@ exports.baseToolDefinitions = [
|
|
|
472
511
|
type: "function",
|
|
473
512
|
function: {
|
|
474
513
|
name: "grep",
|
|
475
|
-
description: "
|
|
514
|
+
description: "Search file contents for lines matching a regex pattern. Searches recursively in directories. Use this instead of shell commands like `grep` or `rg`. Returns matching lines with file path and line numbers. Use context_lines for surrounding context. Use include to filter file types (e.g., '*.ts').",
|
|
476
515
|
parameters: {
|
|
477
516
|
type: "object",
|
|
478
517
|
properties: {
|
|
@@ -581,22 +620,112 @@ exports.baseToolDefinitions = [
|
|
|
581
620
|
type: "function",
|
|
582
621
|
function: {
|
|
583
622
|
name: "shell",
|
|
584
|
-
description: "
|
|
623
|
+
description: "Run a shell command and return stdout/stderr. Working directory persists between calls. Use dedicated tools instead of shell when available: read_file instead of cat, edit_file instead of sed, glob instead of find, grep instead of grep/rg. Reserve shell for operations that genuinely need the shell: installing packages, running builds/tests, git operations, process management. Be careful with destructive commands -- consider reversibility before running. If a command fails, read the error output before retrying with a different approach.",
|
|
585
624
|
parameters: {
|
|
586
625
|
type: "object",
|
|
587
|
-
properties: {
|
|
626
|
+
properties: {
|
|
627
|
+
command: { type: "string" },
|
|
628
|
+
timeout_ms: {
|
|
629
|
+
type: "number",
|
|
630
|
+
description: "Timeout in milliseconds. Default: 30000. Max: 600000.",
|
|
631
|
+
},
|
|
632
|
+
background: {
|
|
633
|
+
type: "boolean",
|
|
634
|
+
description: "Run in background. Returns immediately with a process ID. Use shell_status/shell_tail to monitor.",
|
|
635
|
+
},
|
|
636
|
+
},
|
|
588
637
|
required: ["command"],
|
|
589
638
|
},
|
|
590
639
|
},
|
|
591
640
|
},
|
|
592
641
|
handler: (a) => {
|
|
593
|
-
|
|
642
|
+
// Destructive pattern detection (friction, not a block)
|
|
643
|
+
const destructivePatterns = (0, shell_sessions_1.detectDestructivePatterns)(a.command);
|
|
644
|
+
if (destructivePatterns.length > 0) {
|
|
645
|
+
(0, runtime_1.emitNervesEvent)({
|
|
646
|
+
level: "warn",
|
|
647
|
+
event: "tool.shell.destructive_detected",
|
|
648
|
+
component: "tools",
|
|
649
|
+
message: `destructive pattern detected: ${destructivePatterns.join(", ")}`,
|
|
650
|
+
meta: { command: a.command, patterns: destructivePatterns },
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
// Background mode: spawn and return immediately
|
|
654
|
+
if (a.background === "true") {
|
|
655
|
+
const session = (0, shell_sessions_1.spawnBackgroundShell)(a.command);
|
|
656
|
+
return JSON.stringify({ id: session.id, command: session.command, status: session.status });
|
|
657
|
+
}
|
|
658
|
+
const MAX_TIMEOUT = 600000;
|
|
659
|
+
const requestedTimeout = Number(a.timeout_ms) || 0;
|
|
660
|
+
let configDefault = 30000;
|
|
661
|
+
try {
|
|
662
|
+
configDefault = (0, identity_1.loadAgentConfig)().shell?.defaultTimeout ?? 30000;
|
|
663
|
+
}
|
|
664
|
+
catch { /* test env: no --agent flag */ }
|
|
665
|
+
const baseTimeout = requestedTimeout > 0 ? requestedTimeout : configDefault;
|
|
666
|
+
const timeout = Math.min(baseTimeout, MAX_TIMEOUT);
|
|
667
|
+
const output = (0, child_process_1.execSync)(a.command, {
|
|
594
668
|
encoding: "utf-8",
|
|
595
|
-
timeout
|
|
669
|
+
timeout,
|
|
596
670
|
});
|
|
671
|
+
if (destructivePatterns.length > 0) {
|
|
672
|
+
return `${output}\n\n--- destructive pattern detected: ${destructivePatterns.join(", ")} ---`;
|
|
673
|
+
}
|
|
674
|
+
return output;
|
|
597
675
|
},
|
|
598
676
|
summaryKeys: ["command"],
|
|
599
677
|
},
|
|
678
|
+
{
|
|
679
|
+
tool: {
|
|
680
|
+
type: "function",
|
|
681
|
+
function: {
|
|
682
|
+
name: "shell_status",
|
|
683
|
+
description: "Check status of background shell processes. Omit id to list all.",
|
|
684
|
+
parameters: {
|
|
685
|
+
type: "object",
|
|
686
|
+
properties: {
|
|
687
|
+
id: { type: "string", description: "Background shell process ID" },
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
handler: (a) => {
|
|
693
|
+
if (!a.id) {
|
|
694
|
+
return JSON.stringify((0, shell_sessions_1.listShellSessions)());
|
|
695
|
+
}
|
|
696
|
+
const session = (0, shell_sessions_1.getShellSession)(a.id);
|
|
697
|
+
if (!session)
|
|
698
|
+
return `process not found: ${a.id}`;
|
|
699
|
+
return JSON.stringify(session);
|
|
700
|
+
},
|
|
701
|
+
summaryKeys: ["id"],
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
tool: {
|
|
705
|
+
type: "function",
|
|
706
|
+
function: {
|
|
707
|
+
name: "shell_tail",
|
|
708
|
+
description: "Show recent output from a background shell process.",
|
|
709
|
+
parameters: {
|
|
710
|
+
type: "object",
|
|
711
|
+
properties: {
|
|
712
|
+
id: { type: "string", description: "Background shell process ID" },
|
|
713
|
+
},
|
|
714
|
+
required: ["id"],
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
handler: (a) => {
|
|
719
|
+
/* v8 ignore next -- schema requires id, defensive guard @preserve */
|
|
720
|
+
if (!a.id)
|
|
721
|
+
return "id is required";
|
|
722
|
+
const output = (0, shell_sessions_1.tailShellSession)(a.id);
|
|
723
|
+
if (output === undefined)
|
|
724
|
+
return `process not found: ${a.id}`;
|
|
725
|
+
return output || "(no output yet)";
|
|
726
|
+
},
|
|
727
|
+
summaryKeys: ["id"],
|
|
728
|
+
},
|
|
600
729
|
{
|
|
601
730
|
tool: {
|
|
602
731
|
type: "function",
|
|
@@ -636,7 +765,7 @@ exports.baseToolDefinitions = [
|
|
|
636
765
|
type: "function",
|
|
637
766
|
function: {
|
|
638
767
|
name: "claude",
|
|
639
|
-
description: "use claude code to query this codebase or get an outside perspective.
|
|
768
|
+
description: "use claude code to query this codebase or get an outside perspective. Use for code review, second opinions, or questions that benefit from a fresh perspective outside this conversation's context.",
|
|
640
769
|
parameters: {
|
|
641
770
|
type: "object",
|
|
642
771
|
properties: { prompt: { type: "string" } },
|
|
@@ -709,7 +838,7 @@ exports.baseToolDefinitions = [
|
|
|
709
838
|
type: "function",
|
|
710
839
|
function: {
|
|
711
840
|
name: "recall",
|
|
712
|
-
description: "
|
|
841
|
+
description: "Search my diary and journal for facts, thoughts, and working notes matching a query. Uses semantic similarity -- phrasing matters. Try different angles if the first query doesn't find what you're looking for. Check recall before asking the human something you might already know.",
|
|
713
842
|
parameters: {
|
|
714
843
|
type: "object",
|
|
715
844
|
properties: { query: { type: "string" } },
|
|
@@ -762,7 +891,7 @@ exports.baseToolDefinitions = [
|
|
|
762
891
|
type: "function",
|
|
763
892
|
function: {
|
|
764
893
|
name: "diary_write",
|
|
765
|
-
description: "
|
|
894
|
+
description: "Write an entry in my diary -- something I learned, noticed, or concluded that I want to recall later. Use 'about' to tag the entry to a person, topic, or context. Write for my future self: include enough context that the entry makes sense without the surrounding conversation. Prefer durable conclusions over passing noise. Don't duplicate what already belongs in friend notes.",
|
|
766
895
|
parameters: {
|
|
767
896
|
type: "object",
|
|
768
897
|
properties: {
|
|
@@ -1376,7 +1505,7 @@ exports.ponderTool = {
|
|
|
1376
1505
|
type: "function",
|
|
1377
1506
|
function: {
|
|
1378
1507
|
name: "ponder",
|
|
1379
|
-
description: "i need to sit with this. from a conversation, takes the thread inward with a thought and a parting word. from inner dialog, keeps the wheel turning for another pass. must be the only tool call in the turn.",
|
|
1508
|
+
description: "i need to sit with this. from a conversation, takes the thread inward with a thought and a parting word. from inner dialog, keeps the wheel turning for another pass. must be the only tool call in the turn. Use when a question deserves more thought than this turn allows. Don't ponder trivial questions.",
|
|
1380
1509
|
parameters: {
|
|
1381
1510
|
type: "object",
|
|
1382
1511
|
properties: {
|
|
@@ -1409,7 +1538,7 @@ exports.settleTool = {
|
|
|
1409
1538
|
type: "function",
|
|
1410
1539
|
function: {
|
|
1411
1540
|
name: "settle",
|
|
1412
|
-
description: "respond to the user with your message. call this tool when you are ready to deliver your response.",
|
|
1541
|
+
description: "respond to the user with your message. call this tool when you are ready to deliver your response. Only call when you have a substantive response. If you're settling with 'I'll look into that,' you probably should be using a tool instead.",
|
|
1413
1542
|
parameters: {
|
|
1414
1543
|
type: "object",
|
|
1415
1544
|
properties: {
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -38,6 +38,7 @@ exports.getToolsForChannel = getToolsForChannel;
|
|
|
38
38
|
exports.isConfirmationRequired = isConfirmationRequired;
|
|
39
39
|
exports.execTool = execTool;
|
|
40
40
|
exports.summarizeArgs = summarizeArgs;
|
|
41
|
+
exports.buildToolResultSummary = buildToolResultSummary;
|
|
41
42
|
const tools_base_1 = require("./tools-base");
|
|
42
43
|
const tools_teams_1 = require("./tools-teams");
|
|
43
44
|
const tools_bluebubbles_1 = require("./tools-bluebubbles");
|
|
@@ -387,3 +388,39 @@ function summarizeArgs(name, args) {
|
|
|
387
388
|
}
|
|
388
389
|
return summarizeUnknownArgs(args);
|
|
389
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Build an enriched tool result summary for display on remote channels.
|
|
393
|
+
* For recognized tools, includes result-derived info (diff stats, exit codes, etc.).
|
|
394
|
+
* Falls back to arg-based summary for unrecognized tools.
|
|
395
|
+
*/
|
|
396
|
+
function buildToolResultSummary(name, args, _result, success) {
|
|
397
|
+
switch (name) {
|
|
398
|
+
case "edit_file": {
|
|
399
|
+
if (!success)
|
|
400
|
+
return summarizeArgs(name, args);
|
|
401
|
+
const addedLines = (args.new_string ?? "").split("\n").length;
|
|
402
|
+
const removedLines = (args.old_string ?? "").split("\n").length;
|
|
403
|
+
return `+${addedLines} -${removedLines} lines in ${args.path ?? "unknown"}`;
|
|
404
|
+
}
|
|
405
|
+
case "shell": {
|
|
406
|
+
const cmd = args.command ?? "?";
|
|
407
|
+
const exitCode = success ? 0 : 1;
|
|
408
|
+
return `$ ${cmd} (exit ${exitCode})`;
|
|
409
|
+
}
|
|
410
|
+
case "read_file":
|
|
411
|
+
return `path=${args.path ?? "unknown"}`;
|
|
412
|
+
case "write_file":
|
|
413
|
+
return `path=${args.path ?? "unknown"}`;
|
|
414
|
+
case "glob":
|
|
415
|
+
return `pattern=${args.pattern ?? "?"} ${args.cwd ? `cwd=${args.cwd}` : ""}`.trim();
|
|
416
|
+
case "grep":
|
|
417
|
+
return `pattern=${args.pattern ?? "?"} ${args.path ? `path=${args.path}` : ""}`.trim();
|
|
418
|
+
case "coding_spawn": {
|
|
419
|
+
const taskRef = args.taskRef ?? "unknown";
|
|
420
|
+
const status = success ? "spawned" : "failed";
|
|
421
|
+
return `${taskRef} -> ${status}`;
|
|
422
|
+
}
|
|
423
|
+
default:
|
|
424
|
+
return summarizeArgs(name, args);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CliStore = void 0;
|
|
37
|
+
exports.createInkCallbacks = createInkCallbacks;
|
|
38
|
+
exports.InkCliApp = InkCliApp;
|
|
39
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
41
|
+
const ink_1 = require("ink");
|
|
42
|
+
const streaming_markdown_1 = require("./streaming-markdown");
|
|
43
|
+
const spinner_1 = require("./spinner");
|
|
44
|
+
const tool_render_1 = require("./tool-render");
|
|
45
|
+
const phrases_1 = require("../../mind/phrases");
|
|
46
|
+
const format_1 = require("../../mind/format");
|
|
47
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
48
|
+
/**
|
|
49
|
+
* Ink CLI Adapter — bridges RunCliSessionOptions with Ink/React rendering.
|
|
50
|
+
*
|
|
51
|
+
* Architecture:
|
|
52
|
+
* - CliStore: mutable external state, updated by callbacks
|
|
53
|
+
* - InkCliApp: reads from store via subscription, renders Ink components
|
|
54
|
+
* - createInkCallbacks: returns ChannelCallbacks that update the store
|
|
55
|
+
*
|
|
56
|
+
* The business logic (runAgent, pipeline, commands) stays unchanged.
|
|
57
|
+
* Only the rendering layer is replaced: readline -> Ink.
|
|
58
|
+
*/
|
|
59
|
+
// Ouroboros brand palette
|
|
60
|
+
const OURO_GREEN = "#2ecc40";
|
|
61
|
+
const OURO_TEAL = "#4ec9b0";
|
|
62
|
+
/**
|
|
63
|
+
* Mutable external store. Callbacks mutate state; React subscribes via
|
|
64
|
+
* useSyncExternalStore pattern (simplified: we use a version counter +
|
|
65
|
+
* forceUpdate in the component).
|
|
66
|
+
*/
|
|
67
|
+
class CliStore {
|
|
68
|
+
state = {
|
|
69
|
+
streamingText: "",
|
|
70
|
+
loading: false,
|
|
71
|
+
spinnerPhrase: "",
|
|
72
|
+
activeTool: null,
|
|
73
|
+
toolResults: [],
|
|
74
|
+
errorMessage: null,
|
|
75
|
+
kickMessage: null,
|
|
76
|
+
inputSuppressed: false,
|
|
77
|
+
bannerLines: [],
|
|
78
|
+
};
|
|
79
|
+
listeners = new Set();
|
|
80
|
+
spinnerStartTime = 0;
|
|
81
|
+
hadToolRun = false;
|
|
82
|
+
getState() {
|
|
83
|
+
return this.state;
|
|
84
|
+
}
|
|
85
|
+
subscribe(listener) {
|
|
86
|
+
this.listeners.add(listener);
|
|
87
|
+
return () => { this.listeners.delete(listener); };
|
|
88
|
+
}
|
|
89
|
+
notify() {
|
|
90
|
+
for (const fn of this.listeners)
|
|
91
|
+
fn();
|
|
92
|
+
}
|
|
93
|
+
// ─── Mutation methods called by callbacks ───
|
|
94
|
+
modelStart() {
|
|
95
|
+
const phrases = (0, phrases_1.getPhrases)();
|
|
96
|
+
const pool = this.hadToolRun ? phrases.followup : phrases.thinking;
|
|
97
|
+
const first = (0, phrases_1.pickPhrase)(pool);
|
|
98
|
+
this.state = {
|
|
99
|
+
...this.state,
|
|
100
|
+
loading: true,
|
|
101
|
+
spinnerPhrase: first,
|
|
102
|
+
streamingText: "",
|
|
103
|
+
activeTool: null,
|
|
104
|
+
toolResults: [],
|
|
105
|
+
errorMessage: null,
|
|
106
|
+
kickMessage: null,
|
|
107
|
+
};
|
|
108
|
+
this.spinnerStartTime = Date.now();
|
|
109
|
+
this.notify();
|
|
110
|
+
}
|
|
111
|
+
clearText() {
|
|
112
|
+
this.state = { ...this.state, streamingText: "" };
|
|
113
|
+
this.notify();
|
|
114
|
+
}
|
|
115
|
+
appendText(text) {
|
|
116
|
+
this.state = {
|
|
117
|
+
...this.state,
|
|
118
|
+
loading: false,
|
|
119
|
+
streamingText: this.state.streamingText + text,
|
|
120
|
+
};
|
|
121
|
+
this.notify();
|
|
122
|
+
}
|
|
123
|
+
toolStart(name, args) {
|
|
124
|
+
this.hadToolRun = true;
|
|
125
|
+
this.state = {
|
|
126
|
+
...this.state,
|
|
127
|
+
loading: true,
|
|
128
|
+
activeTool: { name, args },
|
|
129
|
+
spinnerPhrase: (0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().tool),
|
|
130
|
+
};
|
|
131
|
+
this.spinnerStartTime = Date.now();
|
|
132
|
+
this.notify();
|
|
133
|
+
}
|
|
134
|
+
toolEnd(name, argSummary, success) {
|
|
135
|
+
this.state = {
|
|
136
|
+
...this.state,
|
|
137
|
+
loading: false,
|
|
138
|
+
activeTool: null,
|
|
139
|
+
toolResults: [...this.state.toolResults, { name, argSummary, success }],
|
|
140
|
+
};
|
|
141
|
+
this.notify();
|
|
142
|
+
}
|
|
143
|
+
setError(message) {
|
|
144
|
+
this.state = { ...this.state, loading: false, errorMessage: message };
|
|
145
|
+
this.notify();
|
|
146
|
+
}
|
|
147
|
+
setKick() {
|
|
148
|
+
this.state = { ...this.state, loading: false, kickMessage: (0, format_1.formatKick)() };
|
|
149
|
+
this.notify();
|
|
150
|
+
}
|
|
151
|
+
suppressInput() {
|
|
152
|
+
this.state = { ...this.state, inputSuppressed: true };
|
|
153
|
+
this.notify();
|
|
154
|
+
}
|
|
155
|
+
restoreInput() {
|
|
156
|
+
this.state = { ...this.state, inputSuppressed: false };
|
|
157
|
+
this.notify();
|
|
158
|
+
}
|
|
159
|
+
/** Flush streaming state at end of turn */
|
|
160
|
+
endTurn() {
|
|
161
|
+
this.state = {
|
|
162
|
+
...this.state,
|
|
163
|
+
loading: false,
|
|
164
|
+
activeTool: null,
|
|
165
|
+
errorMessage: null,
|
|
166
|
+
kickMessage: null,
|
|
167
|
+
};
|
|
168
|
+
this.notify();
|
|
169
|
+
}
|
|
170
|
+
setBanner(lines) {
|
|
171
|
+
this.state = { ...this.state, bannerLines: lines };
|
|
172
|
+
this.notify();
|
|
173
|
+
}
|
|
174
|
+
getElapsedSeconds() {
|
|
175
|
+
if (!this.state.loading)
|
|
176
|
+
return 0;
|
|
177
|
+
return Math.floor((Date.now() - this.spinnerStartTime) / 1000);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
exports.CliStore = CliStore;
|
|
181
|
+
// ─── Callbacks factory ─────────────────────────────────────────────
|
|
182
|
+
function createInkCallbacks(store) {
|
|
183
|
+
(0, runtime_1.emitNervesEvent)({
|
|
184
|
+
component: "senses",
|
|
185
|
+
event: "senses.cli_callbacks_created",
|
|
186
|
+
message: "ink cli callbacks created",
|
|
187
|
+
meta: {},
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
onModelStart: () => { store.modelStart(); },
|
|
191
|
+
onModelStreamStart: () => { },
|
|
192
|
+
onClearText: () => { store.clearText(); },
|
|
193
|
+
onTextChunk: (text) => { store.appendText(text); },
|
|
194
|
+
onReasoningChunk: (_text) => { },
|
|
195
|
+
onToolStart: (name, args) => { store.toolStart(name, args); },
|
|
196
|
+
onToolEnd: (name, argSummary, success) => { store.toolEnd(name, argSummary, success); },
|
|
197
|
+
onError: (error, severity) => {
|
|
198
|
+
if (severity === "transient") {
|
|
199
|
+
store.setError(error.message);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
store.setError((0, format_1.formatError)(error));
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
onKick: () => { store.setKick(); },
|
|
206
|
+
flushMarkdown: () => { store.endTurn(); },
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// ─── Input Area ────────────────────────────────────────────────────
|
|
210
|
+
function InputArea({ onSubmit, suppressed, }) {
|
|
211
|
+
const [input, setInput] = (0, react_1.useState)("");
|
|
212
|
+
const inputRef = (0, react_1.useRef)("");
|
|
213
|
+
const [history, setHistory] = (0, react_1.useState)([]);
|
|
214
|
+
const historyIdx = (0, react_1.useRef)(-1);
|
|
215
|
+
(0, ink_1.useInput)((inputChar, key) => {
|
|
216
|
+
if (suppressed)
|
|
217
|
+
return;
|
|
218
|
+
if (key.return) {
|
|
219
|
+
const text = inputRef.current;
|
|
220
|
+
if (text.trim()) {
|
|
221
|
+
onSubmit(text);
|
|
222
|
+
setHistory(prev => {
|
|
223
|
+
const next = [...prev, text];
|
|
224
|
+
return next;
|
|
225
|
+
});
|
|
226
|
+
historyIdx.current = -1;
|
|
227
|
+
}
|
|
228
|
+
inputRef.current = "";
|
|
229
|
+
setInput("");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (key.backspace || key.delete) {
|
|
233
|
+
inputRef.current = inputRef.current.slice(0, -1);
|
|
234
|
+
setInput(inputRef.current);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// History navigation
|
|
238
|
+
if (key.upArrow) {
|
|
239
|
+
if (history.length > 0) {
|
|
240
|
+
const idx = historyIdx.current < 0 ? history.length - 1 : Math.max(0, historyIdx.current - 1);
|
|
241
|
+
historyIdx.current = idx;
|
|
242
|
+
inputRef.current = history[idx];
|
|
243
|
+
setInput(history[idx]);
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (key.downArrow) {
|
|
248
|
+
if (historyIdx.current >= 0) {
|
|
249
|
+
const idx = historyIdx.current + 1;
|
|
250
|
+
if (idx >= history.length) {
|
|
251
|
+
historyIdx.current = -1;
|
|
252
|
+
inputRef.current = "";
|
|
253
|
+
setInput("");
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
historyIdx.current = idx;
|
|
257
|
+
inputRef.current = history[idx];
|
|
258
|
+
setInput(history[idx]);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!key.ctrl && !key.meta && inputChar) {
|
|
264
|
+
inputRef.current += inputChar;
|
|
265
|
+
setInput(inputRef.current);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
if (suppressed) {
|
|
269
|
+
return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
|
|
270
|
+
}
|
|
271
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO_TEAL, bold: true, children: ") " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: input })] }));
|
|
272
|
+
}
|
|
273
|
+
function InkCliApp({ store, onSubmit, onExit }) {
|
|
274
|
+
const { exit } = (0, ink_1.useApp)();
|
|
275
|
+
const [, forceUpdate] = (0, react_1.useState)(0);
|
|
276
|
+
const [elapsed, setElapsed] = (0, react_1.useState)(0);
|
|
277
|
+
// Subscribe to store changes
|
|
278
|
+
(0, react_1.useEffect)(() => {
|
|
279
|
+
return store.subscribe(() => {
|
|
280
|
+
forceUpdate(n => n + 1);
|
|
281
|
+
});
|
|
282
|
+
}, [store]);
|
|
283
|
+
// Elapsed timer for spinner
|
|
284
|
+
(0, react_1.useEffect)(() => {
|
|
285
|
+
const iv = setInterval(() => {
|
|
286
|
+
setElapsed(store.getElapsedSeconds());
|
|
287
|
+
}, 1000);
|
|
288
|
+
return () => clearInterval(iv);
|
|
289
|
+
}, [store]);
|
|
290
|
+
// Ctrl-C handling
|
|
291
|
+
(0, ink_1.useInput)((_input, key) => {
|
|
292
|
+
if (key.ctrl && _input === "c") {
|
|
293
|
+
if (onExit) {
|
|
294
|
+
onExit();
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
exit();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
const state = store.getState();
|
|
302
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [state.bannerLines.length > 0 ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: state.bannerLines.map((line, i) => ((0, jsx_runtime_1.jsx)(ink_1.Text, { children: line }, i))) })) : null, state.streamingText ? ((0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: state.streamingText })) : null, state.toolResults.map((tr, i) => {
|
|
303
|
+
const color = tr.success ? OURO_GREEN : "#e74c3c";
|
|
304
|
+
const icon = tr.success ? "\u2713" : "\u2717";
|
|
305
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { color: color, children: [icon, " "] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO_TEAL, bold: true, children: tr.name }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" ", tr.argSummary] })] }, i));
|
|
306
|
+
}), state.activeTool ? ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(tool_render_1.ToolBadge, { name: state.activeTool.name }), (0, jsx_runtime_1.jsx)(tool_render_1.ToolParams, { name: state.activeTool.name, args: state.activeTool.args })] })) : null, state.loading ? ((0, jsx_runtime_1.jsx)(spinner_1.EnhancedSpinner, { elapsedSeconds: elapsed, phrase: state.spinnerPhrase })) : null, state.errorMessage ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "#e74c3c", children: state.errorMessage })) : null, state.kickMessage ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "#f39c12", children: state.kickMessage })) : null, (0, jsx_runtime_1.jsx)(InputArea, { onSubmit: onSubmit, suppressed: state.inputSuppressed })] }));
|
|
307
|
+
}
|