agentram 0.1.21 → 0.1.30
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/README.md +159 -146
- package/agentram/cli.py +447 -91
- package/agentram/orchestration.py +159 -28
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/agentram/cli.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
@@ -101,6 +101,135 @@ SLASH_COMMANDS: list[tuple[str, str]] = [
|
|
|
101
101
|
]
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
|
|
105
|
+
CLAUDE_COMPAT_COMMANDS: list[tuple[str, str]] = [
|
|
106
|
+
("/add-dir <path>", "Claude: add a working directory for file access"),
|
|
107
|
+
("/advisor [model|off]", "Claude: enable or disable advisor model"),
|
|
108
|
+
("/autofix-pr [prompt]", "Claude: cloud session fixes PR CI/review comments"),
|
|
109
|
+
("/background [prompt]", "Claude: detach session as background agent"),
|
|
110
|
+
("/bg [prompt]", "Claude alias: /background"),
|
|
111
|
+
("/batch <instruction>", "Claude workflow: parallel codebase change"),
|
|
112
|
+
("/branch [name]", "Claude: fork conversation branch"),
|
|
113
|
+
("/btw <question>", "Claude: side question without history"),
|
|
114
|
+
("/cd <path>", "Claude: move session to another directory"),
|
|
115
|
+
("/chrome", "Claude: configure Claude in Chrome"),
|
|
116
|
+
("/claude-api [migrate|managed-agents-onboard]", "Claude skill: API reference and migration"),
|
|
117
|
+
("/reset [name]", "Claude alias: /clear"),
|
|
118
|
+
("/new [name]", "Claude alias: /clear"),
|
|
119
|
+
("/code-review [level] [--fix] [--comment] [target]", "Claude skill: review current diff"),
|
|
120
|
+
("/color [color|default]", "Claude: prompt bar color"),
|
|
121
|
+
("/compact [instructions]", "Claude: summarize context to free space"),
|
|
122
|
+
("/config [key=value ...]", "Claude: settings interface"),
|
|
123
|
+
("/settings [key=value ...]", "Claude alias: /config"),
|
|
124
|
+
("/context [all]", "Claude: visualize context usage"),
|
|
125
|
+
("/copy [N]", "Claude: copy assistant response"),
|
|
126
|
+
("/cost", "Claude alias: /usage"),
|
|
127
|
+
("/dataviz [request]", "Claude skill: chart/dashboard design guidance"),
|
|
128
|
+
("/debug [description]", "Claude skill: enable and inspect debug logs"),
|
|
129
|
+
("/deep-research <question>", "Claude workflow: web research report"),
|
|
130
|
+
("/design-login", "Claude: authorize design-system access"),
|
|
131
|
+
("/design-sync [hint]", "Claude skill: sync React design system"),
|
|
132
|
+
("/desktop", "Claude: continue in desktop app"),
|
|
133
|
+
("/app", "Claude alias: /desktop"),
|
|
134
|
+
("/diff", "Claude: interactive diff viewer"),
|
|
135
|
+
("/doctor", "Claude: diagnose install/runtime"),
|
|
136
|
+
("/effort [level|auto]", "Claude: set reasoning effort"),
|
|
137
|
+
("/export [filename]", "Claude: export conversation"),
|
|
138
|
+
("/fast [on|off]", "Claude: toggle fast mode"),
|
|
139
|
+
("/feedback [report]", "Claude: send feedback"),
|
|
140
|
+
("/bug [report]", "Claude alias: /feedback"),
|
|
141
|
+
("/share [report]", "Claude alias: /feedback"),
|
|
142
|
+
("/fewer-permission-prompts", "Claude skill: reduce permission prompts"),
|
|
143
|
+
("/focus", "Claude: toggle focus view"),
|
|
144
|
+
("/fork <directive>", "Claude: forked background subagent"),
|
|
145
|
+
("/heapdump", "Claude: write heap snapshot"),
|
|
146
|
+
("/hooks", "Claude: view hook config"),
|
|
147
|
+
("/ide", "Claude: manage IDE integration"),
|
|
148
|
+
("/insights", "Claude: analyze Claude Code sessions"),
|
|
149
|
+
("/install-github-app", "Claude: install GitHub app"),
|
|
150
|
+
("/install-slack-app", "Claude: install Slack app"),
|
|
151
|
+
("/keybindings", "Claude: open keyboard shortcut file"),
|
|
152
|
+
("/login", "Claude: sign in"),
|
|
153
|
+
("/logout", "Claude: sign out"),
|
|
154
|
+
("/loop [interval] [prompt]", "Claude skill: repeated prompt loop"),
|
|
155
|
+
("/proactive [interval] [prompt]", "Claude alias: /loop"),
|
|
156
|
+
("/mcp [reconnect|enable|disable]", "Claude: manage MCP servers"),
|
|
157
|
+
("/memory", "Claude: edit CLAUDE.md memory"),
|
|
158
|
+
("/mobile", "Claude: show mobile app QR"),
|
|
159
|
+
("/ios", "Claude alias: /mobile"),
|
|
160
|
+
("/android", "Claude alias: /mobile"),
|
|
161
|
+
("/model [model]", "Claude: switch model"),
|
|
162
|
+
("/passes", "Claude: share Claude Code pass"),
|
|
163
|
+
("/permissions", "Claude: manage tool permissions"),
|
|
164
|
+
("/allowed-tools", "Claude alias: /permissions"),
|
|
165
|
+
("/plan [description]", "Claude: enter plan mode"),
|
|
166
|
+
("/plugin [subcommand]", "Claude: manage plugins"),
|
|
167
|
+
("/powerup", "Claude: feature lessons"),
|
|
168
|
+
("/pr-comments [PR]", "Claude legacy: PR comments"),
|
|
169
|
+
("/privacy-settings", "Claude: privacy settings"),
|
|
170
|
+
("/radio", "Claude: Claude FM radio"),
|
|
171
|
+
("/recap", "Claude: one-line session summary"),
|
|
172
|
+
("/release-notes", "Claude: changelog picker"),
|
|
173
|
+
("/reload-plugins [--force]", "Claude: reload plugins"),
|
|
174
|
+
("/reload-skills", "Claude: reload skills and commands"),
|
|
175
|
+
("/remote-control", "Claude: remote control from claude.ai"),
|
|
176
|
+
("/rc", "Claude alias: /remote-control"),
|
|
177
|
+
("/remote-env", "Claude: cloud agent environment"),
|
|
178
|
+
("/rename [name]", "Claude: rename session"),
|
|
179
|
+
("/resume [session]", "Claude: resume conversation"),
|
|
180
|
+
("/continue [session]", "Claude alias: /resume"),
|
|
181
|
+
("/review [PR]", "Claude: review GitHub PR"),
|
|
182
|
+
("/rewind", "Claude: rewind conversation/code"),
|
|
183
|
+
("/checkpoint", "Claude alias: /rewind"),
|
|
184
|
+
("/undo", "Claude alias: /rewind"),
|
|
185
|
+
("/run", "Claude skill: run app and verify change"),
|
|
186
|
+
("/run-skill-generator", "Claude skill: generate run/verify skill"),
|
|
187
|
+
("/sandbox", "Claude: toggle sandbox mode"),
|
|
188
|
+
("/schedule [description]", "Claude: cloud routine"),
|
|
189
|
+
("/routines [description]", "Claude alias: /schedule"),
|
|
190
|
+
("/scroll-speed", "Claude: adjust mouse wheel speed"),
|
|
191
|
+
("/security-review", "Claude: security review current diff"),
|
|
192
|
+
("/setup-bedrock", "Claude: Bedrock setup wizard"),
|
|
193
|
+
("/setup-vertex", "Claude: Vertex setup wizard"),
|
|
194
|
+
("/simplify [target]", "Claude skill: cleanup review and fixes"),
|
|
195
|
+
("/skills", "Claude: list skills"),
|
|
196
|
+
("/stats", "Claude alias: /usage"),
|
|
197
|
+
("/statusline", "Claude: configure status line"),
|
|
198
|
+
("/stickers", "Claude: order stickers"),
|
|
199
|
+
("/stop", "Claude: stop background session"),
|
|
200
|
+
("/tasks", "Claude: view background work"),
|
|
201
|
+
("/bashes", "Claude alias: /tasks"),
|
|
202
|
+
("/team-onboarding", "Claude: generate team onboarding"),
|
|
203
|
+
("/teleport", "Claude: pull web session into terminal"),
|
|
204
|
+
("/tp", "Claude alias: /teleport"),
|
|
205
|
+
("/terminal-setup", "Claude: terminal keybindings setup"),
|
|
206
|
+
("/theme", "Claude: change color theme"),
|
|
207
|
+
("/tui [default|fullscreen]", "Claude: switch renderer"),
|
|
208
|
+
("/ultraplan <prompt>", "Claude: draft cloud plan"),
|
|
209
|
+
("/ultrareview [PR]", "Claude: deep cloud review"),
|
|
210
|
+
("/upgrade", "Claude: open upgrade page"),
|
|
211
|
+
("/usage", "Claude: show usage and cost"),
|
|
212
|
+
("/usage-credits", "Claude: configure usage credits"),
|
|
213
|
+
("/verify", "Claude skill: verify app behavior"),
|
|
214
|
+
("/vim", "Claude legacy: use /config for editor mode"),
|
|
215
|
+
("/voice [hold|tap|off]", "Claude: voice dictation"),
|
|
216
|
+
("/web-setup", "Claude: connect GitHub for web"),
|
|
217
|
+
("/workflows", "Claude: workflow progress view"),
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
CLAUDE_COMPAT_NAMES = {command.split()[0] for command, _ in CLAUDE_COMPAT_COMMANDS}
|
|
221
|
+
|
|
222
|
+
def slash_entries() -> list[tuple[str, str]]:
|
|
223
|
+
seen: set[str] = set()
|
|
224
|
+
entries: list[tuple[str, str]] = []
|
|
225
|
+
for command, description in [*SLASH_COMMANDS, *CLAUDE_COMPAT_COMMANDS]:
|
|
226
|
+
name = command.split()[0]
|
|
227
|
+
if name in seen:
|
|
228
|
+
continue
|
|
229
|
+
seen.add(name)
|
|
230
|
+
entries.append((command, description))
|
|
231
|
+
return entries
|
|
232
|
+
|
|
104
233
|
SLASH_HELP = """Slash commands:
|
|
105
234
|
/help Show commands
|
|
106
235
|
/init Create .agentram files
|
|
@@ -346,13 +475,17 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
346
475
|
def __init__(self, mcp_context: McpContext) -> None:
|
|
347
476
|
super().__init__()
|
|
348
477
|
self.context = mcp_context
|
|
349
|
-
self.messages: list[str] = []
|
|
350
|
-
self.busy = False
|
|
351
|
-
self.
|
|
478
|
+
self.messages: list[str] = []
|
|
479
|
+
self.busy = False
|
|
480
|
+
self.prompt_queue: list[str] = []
|
|
481
|
+
self.processing_task: asyncio.Task[None] | None = None
|
|
482
|
+
self.suggestions: list[str] = []
|
|
352
483
|
self.setup_done = False
|
|
353
484
|
self.prompt_history: list[str] = []
|
|
354
485
|
self.history_index: int | None = None
|
|
355
486
|
self.last_ctrl_c_at = 0.0
|
|
487
|
+
self.setup_slots = ("supervisor", "main", "small")
|
|
488
|
+
self.setup_index = 0
|
|
356
489
|
|
|
357
490
|
def compose(self) -> ComposeResult:
|
|
358
491
|
yield Header(show_clock=True)
|
|
@@ -360,18 +493,20 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
360
493
|
yield Static(id="sidebar")
|
|
361
494
|
with Vertical(id="main"):
|
|
362
495
|
with Vertical(id="setup"):
|
|
363
|
-
yield Static("Model setup -
|
|
496
|
+
yield Static("Model setup - 1/3 Supervisor", id="setup-title")
|
|
364
497
|
for slot, label, default_model in [
|
|
365
|
-
("supervisor", "Supervisor", ""),
|
|
366
|
-
("main", "Main coding", ""),
|
|
367
|
-
("small", "Small memory", ""),
|
|
498
|
+
("supervisor", "1. Supervisor", ""),
|
|
499
|
+
("main", "2. Main coding", ""),
|
|
500
|
+
("small", "3. Small memory", ""),
|
|
368
501
|
]:
|
|
369
|
-
with Vertical(classes="agent-setup"):
|
|
502
|
+
with Vertical(id=f"setup-{slot}-panel", classes="agent-setup"):
|
|
370
503
|
yield Static(f"{label} agent")
|
|
371
504
|
yield Select(
|
|
372
505
|
[
|
|
373
506
|
("OpenAI Compatible", "openai-compatible"),
|
|
374
507
|
("Anthropic Compatible", "anthropic"),
|
|
508
|
+
("Claude Code CLI", "claude-code"),
|
|
509
|
+
("Codex CLI", "codex-cli"),
|
|
375
510
|
("Custom", "custom"),
|
|
376
511
|
],
|
|
377
512
|
value="openai-compatible",
|
|
@@ -380,14 +515,16 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
380
515
|
)
|
|
381
516
|
yield Input(placeholder="Custom provider if preset=Custom", id=f"setup-{slot}-custom-provider", classes="setup-input")
|
|
382
517
|
yield Input(value=default_model, placeholder="Model name", id=f"setup-{slot}-model", classes="setup-input")
|
|
383
|
-
yield Input(placeholder="Base URL, e.g. http://localhost:20128/v1", id=f"setup-{slot}-base-url", classes="setup-input")
|
|
518
|
+
yield Input(placeholder="Base URL or CLI command, e.g. http://localhost:20128/v1 / claude / codex exec", id=f"setup-{slot}-base-url", classes="setup-input")
|
|
384
519
|
yield Input(value="OPENAI_API_KEY", placeholder="API key or env name", id=f"setup-{slot}-api-key-env", classes="setup-input")
|
|
385
520
|
with Horizontal(id="setup-actions"):
|
|
386
|
-
yield Button("
|
|
521
|
+
yield Button("Back", id="setup-prev")
|
|
522
|
+
yield Button("Next", id="setup-next", variant="primary")
|
|
523
|
+
yield Button("Save setup", id="setup-save", variant="success")
|
|
387
524
|
yield Button("Skip", id="setup-skip")
|
|
388
525
|
yield RichLog(id="chat", wrap=True, highlight=True, markup=False, auto_scroll=True)
|
|
389
526
|
yield Static(id="palette")
|
|
390
|
-
yield Input(placeholder="Prompt
|
|
527
|
+
yield Input(placeholder="Prompt or /command. Mouse wheel scroll in chat.", id="input")
|
|
391
528
|
yield Footer()
|
|
392
529
|
|
|
393
530
|
def on_mount(self) -> None:
|
|
@@ -395,10 +532,11 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
395
532
|
self.chat.write("system > AgentRAM Textual TUI ready")
|
|
396
533
|
self.chat.write(f"system > RAM root: {self.context.ram_root}")
|
|
397
534
|
self.prefill_setup_from_profile()
|
|
535
|
+
self.refresh_setup_step()
|
|
398
536
|
self.refresh_sidebar()
|
|
399
537
|
self.set_interval(1.0, self.refresh_sidebar)
|
|
400
538
|
try:
|
|
401
|
-
self.
|
|
539
|
+
self.focus_current_setup_model()
|
|
402
540
|
except Exception: # noqa: BLE001 - fallback if setup is hidden/unmounted
|
|
403
541
|
self.query_one("#input", Input).focus()
|
|
404
542
|
|
|
@@ -418,7 +556,7 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
418
556
|
if not endpoint:
|
|
419
557
|
continue
|
|
420
558
|
provider = endpoint.provider
|
|
421
|
-
preset = provider if provider in {"openai-compatible", "anthropic"} else "custom"
|
|
559
|
+
preset = provider if provider in {"openai-compatible", "anthropic", "claude-code", "codex-cli"} else "custom"
|
|
422
560
|
self.query_one(f"#setup-{slot}-provider", Select).value = preset
|
|
423
561
|
self.query_one(f"#setup-{slot}-custom-provider", Input).value = "" if preset != "custom" else provider
|
|
424
562
|
self.query_one(f"#setup-{slot}-model", Input).value = endpoint.model
|
|
@@ -426,6 +564,37 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
426
564
|
self.query_one(f"#setup-{slot}-api-key-env", Input).value = endpoint.api_key_env
|
|
427
565
|
self.chat.write("system > Existing workflow models loaded. Save to update, or Skip to keep.")
|
|
428
566
|
|
|
567
|
+
def current_setup_slot(self) -> str:
|
|
568
|
+
return self.setup_slots[self.setup_index]
|
|
569
|
+
|
|
570
|
+
def current_setup_label(self) -> str:
|
|
571
|
+
labels = {"supervisor": "Supervisor", "main": "Main coding", "small": "Small memory"}
|
|
572
|
+
return labels[self.current_setup_slot()]
|
|
573
|
+
|
|
574
|
+
def refresh_setup_step(self) -> None:
|
|
575
|
+
total = len(self.setup_slots)
|
|
576
|
+
slot = self.current_setup_slot()
|
|
577
|
+
self.query_one("#setup-title", Static).update(f"Model setup - {self.setup_index + 1}/{total} {self.current_setup_label()}")
|
|
578
|
+
for item in self.setup_slots:
|
|
579
|
+
self.query_one(f"#setup-{item}-panel", Vertical).styles.display = "block" if item == slot else "none"
|
|
580
|
+
|
|
581
|
+
def focus_current_setup_model(self) -> None:
|
|
582
|
+
self.query_one(f"#setup-{self.current_setup_slot()}-model", Input).focus()
|
|
583
|
+
|
|
584
|
+
def action_next_setup(self) -> None:
|
|
585
|
+
if self.setup_index < len(self.setup_slots) - 1:
|
|
586
|
+
self.setup_index += 1
|
|
587
|
+
self.refresh_setup_step()
|
|
588
|
+
self.focus_current_setup_model()
|
|
589
|
+
else:
|
|
590
|
+
self.action_save_setup()
|
|
591
|
+
|
|
592
|
+
def action_prev_setup(self) -> None:
|
|
593
|
+
if self.setup_index > 0:
|
|
594
|
+
self.setup_index -= 1
|
|
595
|
+
self.refresh_setup_step()
|
|
596
|
+
self.focus_current_setup_model()
|
|
597
|
+
|
|
429
598
|
def setup_value(self, widget_id: str) -> str:
|
|
430
599
|
return self.query_one(f"#{widget_id}", Input).value.strip()
|
|
431
600
|
|
|
@@ -473,8 +642,10 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
473
642
|
|
|
474
643
|
def show_setup(self) -> None:
|
|
475
644
|
self.setup_done = False
|
|
645
|
+
self.setup_index = 0
|
|
476
646
|
self.query_one("#setup", Vertical).styles.display = "block"
|
|
477
|
-
self.
|
|
647
|
+
self.refresh_setup_step()
|
|
648
|
+
self.focus_current_setup_model()
|
|
478
649
|
|
|
479
650
|
def action_history_prev(self) -> None:
|
|
480
651
|
input_widget = self.query_one("#input", Input)
|
|
@@ -515,6 +686,10 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
515
686
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
516
687
|
if event.button.id == "setup-save":
|
|
517
688
|
self.action_save_setup()
|
|
689
|
+
elif event.button.id == "setup-next":
|
|
690
|
+
self.action_next_setup()
|
|
691
|
+
elif event.button.id == "setup-prev":
|
|
692
|
+
self.action_prev_setup()
|
|
518
693
|
elif event.button.id == "setup-skip":
|
|
519
694
|
self.chat.write("system > Model setup skipped. Use /bind-agent later.")
|
|
520
695
|
self.hide_setup()
|
|
@@ -535,7 +710,7 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
535
710
|
return
|
|
536
711
|
query = value.strip().lower().lstrip("/")
|
|
537
712
|
matches = []
|
|
538
|
-
for command, description in
|
|
713
|
+
for command, description in slash_entries():
|
|
539
714
|
if not command.startswith("/"):
|
|
540
715
|
continue
|
|
541
716
|
command_name = command.split()[0]
|
|
@@ -547,7 +722,7 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
547
722
|
if not matches:
|
|
548
723
|
palette.update("No matching slash command")
|
|
549
724
|
else:
|
|
550
|
-
lines = ["Slash commands
|
|
725
|
+
lines = ["Slash commands - Tab completes first match"]
|
|
551
726
|
lines.extend(f" {command.ljust(18)} {description}" for command, description in matches[:8])
|
|
552
727
|
palette.update("\n".join(lines))
|
|
553
728
|
palette.styles.display = "block"
|
|
@@ -571,7 +746,7 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
571
746
|
|
|
572
747
|
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
573
748
|
if event.input.id and str(event.input.id).startswith("setup-"):
|
|
574
|
-
self.
|
|
749
|
+
self.action_next_setup()
|
|
575
750
|
return
|
|
576
751
|
text = event.value.strip()
|
|
577
752
|
event.input.value = ""
|
|
@@ -596,23 +771,37 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
596
771
|
self.chat.clear()
|
|
597
772
|
self.chat.write("system > Chat cleared.")
|
|
598
773
|
return
|
|
599
|
-
if self.busy:
|
|
600
|
-
self.
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
self.
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
self.
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
774
|
+
if self.busy:
|
|
775
|
+
self.prompt_queue.append(text)
|
|
776
|
+
self.chat.write(f"agentram > queued #{len(self.prompt_queue)}")
|
|
777
|
+
return
|
|
778
|
+
self.prompt_queue.append(text)
|
|
779
|
+
self.processing_task = asyncio.create_task(self.process_prompt_queue())
|
|
780
|
+
|
|
781
|
+
async def process_prompt_queue(self) -> None:
|
|
782
|
+
if self.busy:
|
|
783
|
+
return
|
|
784
|
+
self.busy = True
|
|
785
|
+
try:
|
|
786
|
+
while self.prompt_queue:
|
|
787
|
+
text = self.prompt_queue.pop(0)
|
|
788
|
+
pending = len(self.prompt_queue)
|
|
789
|
+
suffix = f" ({pending} queued)" if pending else ""
|
|
790
|
+
self.chat.write(f"agentram > thinking...{suffix}")
|
|
791
|
+
try:
|
|
792
|
+
response = await asyncio.to_thread(handle_chat_input, self.context, text, self.messages)
|
|
793
|
+
except Exception as error: # noqa: BLE001 - TUI boundary
|
|
794
|
+
response = f"error: {error}"
|
|
795
|
+
if response == "__clear__":
|
|
796
|
+
self.chat.clear()
|
|
797
|
+
self.chat.write("system > Chat cleared.")
|
|
798
|
+
elif response:
|
|
799
|
+
self.chat.write("agentram > " + response)
|
|
800
|
+
self.refresh_sidebar()
|
|
801
|
+
finally:
|
|
802
|
+
self.busy = False
|
|
803
|
+
self.processing_task = None
|
|
804
|
+
self.query_one("#input", Input).focus()
|
|
616
805
|
|
|
617
806
|
AgentRAMTextualApp(context).run()
|
|
618
807
|
return True
|
|
@@ -631,11 +820,11 @@ def run_with_activity(context: McpContext, messages: list[str], user_input: str)
|
|
|
631
820
|
|
|
632
821
|
|
|
633
822
|
def slash_command_names() -> list[str]:
|
|
634
|
-
return [command.split()[0] for command, _ in
|
|
823
|
+
return [command.split()[0] for command, _ in slash_entries() if command.startswith("/")]
|
|
635
824
|
|
|
636
825
|
|
|
637
826
|
def slash_command_meta() -> dict[str, str]:
|
|
638
|
-
return {command.split()[0]: description for command, description in
|
|
827
|
+
return {command.split()[0]: description for command, description in slash_entries() if command.startswith("/")}
|
|
639
828
|
|
|
640
829
|
|
|
641
830
|
def create_prompt_session() -> Any | None:
|
|
@@ -655,7 +844,7 @@ def create_prompt_session() -> Any | None:
|
|
|
655
844
|
completer=completer,
|
|
656
845
|
complete_while_typing=True,
|
|
657
846
|
reserve_space_for_menu=12,
|
|
658
|
-
bottom_toolbar="Type / for slash commands
|
|
847
|
+
bottom_toolbar="Type / for slash commands | /agents for subagents | /ask to call bound models",
|
|
659
848
|
)
|
|
660
849
|
|
|
661
850
|
|
|
@@ -755,9 +944,25 @@ def handle_slash_command(context: McpContext, text: str, messages: list[str] | N
|
|
|
755
944
|
return note_text(context, raw_rest)
|
|
756
945
|
if command == "/clear":
|
|
757
946
|
return "__clear__"
|
|
947
|
+
if command in CLAUDE_COMPAT_NAMES:
|
|
948
|
+
return claude_compat_command_text(command, raw_rest)
|
|
758
949
|
return f"Unknown slash command: {command}\n\n{SLASH_HELP}"
|
|
759
950
|
|
|
760
951
|
|
|
952
|
+
def claude_compat_command_text(command: str, arguments: str) -> str:
|
|
953
|
+
descriptions = slash_command_meta()
|
|
954
|
+
description = descriptions.get(command, "Claude Code command")
|
|
955
|
+
return "\n".join(
|
|
956
|
+
[
|
|
957
|
+
f"Claude-compatible command: {command}",
|
|
958
|
+
f"Purpose: {description}",
|
|
959
|
+
f"Args: {arguments or '-'}",
|
|
960
|
+
"Status: listed for Claude parity in AgentRAM TUI.",
|
|
961
|
+
"Native execution is not implemented here unless AgentRAM has its own handler for this command.",
|
|
962
|
+
"Run the same command inside Claude Code for Claude-native behavior.",
|
|
963
|
+
]
|
|
964
|
+
)
|
|
965
|
+
|
|
761
966
|
def init_text(context: McpContext) -> str:
|
|
762
967
|
data = payload(call_tool(context, "agentram_init", {}))
|
|
763
968
|
created = data.get("created", [])
|
|
@@ -852,7 +1057,7 @@ def agents_text() -> str:
|
|
|
852
1057
|
def slash_palette_text(filter_text: str = "") -> str:
|
|
853
1058
|
normalized = filter_text.strip().lower().lstrip("/")
|
|
854
1059
|
rows = []
|
|
855
|
-
for command, description in
|
|
1060
|
+
for command, description in slash_entries():
|
|
856
1061
|
command_key = command.lower().lstrip("/")
|
|
857
1062
|
if normalized and normalized not in command_key:
|
|
858
1063
|
continue
|
|
@@ -993,45 +1198,192 @@ def workflow_text(context: McpContext) -> str:
|
|
|
993
1198
|
lines.append(f"- {slot}: not bound")
|
|
994
1199
|
return "\n".join(lines)
|
|
995
1200
|
|
|
996
|
-
def supervisor_workflow_text(context: McpContext, prompt: str) -> str:
|
|
997
|
-
agents = context.profile_store.list_agents()
|
|
998
|
-
supervisor = agents.get("supervisor")
|
|
999
|
-
main_agent = agents.get("main")
|
|
1000
|
-
small_agent = agents.get("small")
|
|
1001
|
-
memory_context = retrieve_text(context, prompt, task_id="codex", limit=8)
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1201
|
+
def supervisor_workflow_text(context: McpContext, prompt: str) -> str:
|
|
1202
|
+
agents = context.profile_store.list_agents()
|
|
1203
|
+
supervisor = agents.get("supervisor")
|
|
1204
|
+
main_agent = agents.get("main")
|
|
1205
|
+
small_agent = agents.get("small")
|
|
1206
|
+
memory_context = retrieve_text(context, prompt, task_id="codex", limit=8)
|
|
1207
|
+
route = classify_agent_runtime_route(context, prompt, main_agent)
|
|
1208
|
+
intent = str(route.get("intent", "chat"))
|
|
1209
|
+
outputs: dict[str, str] = {}
|
|
1210
|
+
|
|
1211
|
+
if intent == "planning":
|
|
1212
|
+
plan = supervisor_plan(supervisor, prompt, memory_context)
|
|
1213
|
+
outputs["supervisor"] = plan
|
|
1214
|
+
outputs["small"] = small_agent_observe(context, small_agent, prompt, "supervisor_plan", plan, plan, memory_context)
|
|
1215
|
+
sync_goal_state_from_small_agent(context, prompt, plan, outputs.get("small", ""))
|
|
1216
|
+
persist_workflow_memory(context, prompt, plan, outputs)
|
|
1217
|
+
return "\n".join(
|
|
1218
|
+
[
|
|
1219
|
+
f"Workflow: route=planning confidence={float(route.get('confidence', 0.0)):.2f} -> supervisor -> small memory note -> merge",
|
|
1220
|
+
"Supervisor plan:",
|
|
1221
|
+
plan,
|
|
1222
|
+
"",
|
|
1223
|
+
"Small memory agent notes:",
|
|
1224
|
+
outputs.get("small", "No small agent bound."),
|
|
1225
|
+
]
|
|
1226
|
+
)
|
|
1227
|
+
|
|
1228
|
+
if intent == "memory":
|
|
1229
|
+
plan = "Memory-only event. Small agent writes RAM note directly."
|
|
1230
|
+
outputs["small"] = small_agent_observe(context, small_agent, prompt, "memory_event", prompt, plan, memory_context)
|
|
1231
|
+
sync_goal_state_from_small_agent(context, prompt, plan, outputs.get("small", ""))
|
|
1232
|
+
persist_workflow_memory(context, prompt, plan, outputs)
|
|
1233
|
+
return "\n".join(
|
|
1234
|
+
[
|
|
1235
|
+
f"Workflow: route=memory confidence={float(route.get('confidence', 0.0)):.2f} -> small memory note -> merge",
|
|
1236
|
+
"Small memory agent notes:",
|
|
1237
|
+
outputs.get("small", "No small agent bound."),
|
|
1238
|
+
]
|
|
1239
|
+
)
|
|
1240
|
+
|
|
1241
|
+
plan = runtime_execution_plan(context, supervisor, prompt, memory_context, route)
|
|
1242
|
+
outputs["supervisor"] = plan
|
|
1243
|
+
outputs["small_after_plan"] = small_agent_observe(context, small_agent, prompt, "supervisor_plan", plan, plan, memory_context)
|
|
1244
|
+
if main_agent:
|
|
1245
|
+
try:
|
|
1246
|
+
outputs["main"] = call_agent_endpoint(main_agent, build_main_agent_prompt(prompt, plan, memory_context))
|
|
1247
|
+
except Exception as error: # noqa: BLE001 - agent boundary
|
|
1248
|
+
outputs["main"] = f"error: {model_error_hint(str(error))}"
|
|
1249
|
+
else:
|
|
1250
|
+
outputs["main"] = "No main agent bound. Use /bind-agent main <provider> <model>."
|
|
1251
|
+
outputs["small"] = small_agent_observe(context, small_agent, prompt, "main_result", outputs.get("main", ""), plan, memory_context)
|
|
1252
|
+
sync_goal_state_from_small_agent(context, prompt, plan, outputs.get("small", ""))
|
|
1253
|
+
persist_workflow_memory(context, prompt, plan, outputs)
|
|
1254
|
+
return "\n".join(
|
|
1255
|
+
[
|
|
1256
|
+
f"Workflow: route={intent} confidence={float(route.get('confidence', 0.0)):.2f} -> main -> small memory note -> merge",
|
|
1257
|
+
"Supervisor plan:",
|
|
1258
|
+
plan,
|
|
1259
|
+
"",
|
|
1260
|
+
"Main agent result:",
|
|
1261
|
+
outputs.get("main", "-"),
|
|
1262
|
+
"",
|
|
1263
|
+
"Small memory agent after plan:",
|
|
1264
|
+
outputs.get("small_after_plan", "No small agent bound."),
|
|
1265
|
+
"",
|
|
1266
|
+
"Small memory agent notes:",
|
|
1267
|
+
outputs.get("small", "No small agent bound."),
|
|
1268
|
+
]
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1271
|
+
def classify_agent_runtime_route(context: McpContext, prompt: str, main_agent: AgentModelEndpoint | None = None) -> dict[str, object]:
|
|
1272
|
+
if main_agent:
|
|
1273
|
+
try:
|
|
1274
|
+
response = call_agent_endpoint(main_agent, build_agent_runtime_router_prompt(prompt))
|
|
1275
|
+
data = json.loads(extract_json_object(response))
|
|
1276
|
+
intent = str(data.get("intent", "")).strip().lower()
|
|
1277
|
+
if intent in {"planning", "execution", "memory", "chat"}:
|
|
1278
|
+
return {
|
|
1279
|
+
"intent": intent,
|
|
1280
|
+
"confidence": float(data.get("confidence", 0.75)),
|
|
1281
|
+
"reason": str(data.get("reason", "main-agent router")),
|
|
1282
|
+
"source": "main-agent",
|
|
1283
|
+
}
|
|
1284
|
+
except Exception as error: # noqa: BLE001 - router boundary
|
|
1285
|
+
fallback = heuristic_agent_runtime_route(prompt)
|
|
1286
|
+
fallback["source"] = "heuristic"
|
|
1287
|
+
fallback["router_error"] = str(error)
|
|
1288
|
+
return fallback
|
|
1289
|
+
|
|
1290
|
+
return heuristic_agent_runtime_route(prompt)
|
|
1291
|
+
|
|
1292
|
+
def heuristic_agent_runtime_route(prompt: str) -> dict[str, object]:
|
|
1293
|
+
lower = prompt.lower()
|
|
1294
|
+
execute_words = [
|
|
1295
|
+
"bắt đầu làm", "bat dau lam", "làm đi", "lam di", "tiến hành", "tien hanh", "thực hiện", "thuc hien",
|
|
1296
|
+
"chốt kế hoạch", "chot ke hoach", "bắt tay", "start", "execute", "implement", "do it", "go ahead",
|
|
1297
|
+
]
|
|
1298
|
+
plan_words = ["lên kế hoạch", "len ke hoach", "kế hoạch", "ke hoach", "plan", "roadmap", "milestone", "thiết kế", "thiet ke"]
|
|
1299
|
+
if any(word in lower for word in execute_words):
|
|
1300
|
+
return {"intent": "execution", "confidence": 0.9, "source": "heuristic"}
|
|
1301
|
+
if any(word in lower for word in plan_words):
|
|
1302
|
+
return {"intent": "planning", "confidence": 0.85, "source": "heuristic"}
|
|
1303
|
+
decision = heuristic_intent(prompt)
|
|
1304
|
+
intent = str(decision.get("intent", "chat"))
|
|
1305
|
+
if intent == "coding":
|
|
1306
|
+
decision["intent"] = "execution"
|
|
1307
|
+
decision["source"] = "heuristic"
|
|
1308
|
+
return decision
|
|
1309
|
+
|
|
1310
|
+
def build_agent_runtime_router_prompt(prompt: str) -> str:
|
|
1311
|
+
return "\n".join(
|
|
1312
|
+
[
|
|
1313
|
+
"You are AgentRAM orchestration router using the main agent model.",
|
|
1314
|
+
"Classify which agent should handle the user prompt. Do not edit files. Do not run tools. Return only JSON.",
|
|
1315
|
+
"Schema: {\"intent\":\"planning|execution|memory|chat\",\"confidence\":0.0,\"reason\":\"short\"}",
|
|
1316
|
+
"planning = user asks to plan, design, set roadmap, discuss goal, no execution yet.",
|
|
1317
|
+
"execution = user approves plan, says start/do it, or asks coding/refactor/fix/test/file work.",
|
|
1318
|
+
"memory = user asks to remember/save/note a decision/fact.",
|
|
1319
|
+
"chat = normal conversation.",
|
|
1320
|
+
"Nếu user prompt bằng tiếng Việt, reason dùng tiếng Việt.",
|
|
1321
|
+
"User prompt:",
|
|
1322
|
+
prompt,
|
|
1323
|
+
]
|
|
1324
|
+
)
|
|
1325
|
+
|
|
1326
|
+
def runtime_execution_plan(context: McpContext, supervisor: AgentModelEndpoint | None, prompt: str, memory_context: str, route: dict[str, object]) -> str:
|
|
1327
|
+
state = context.goal_store.load().context_block()
|
|
1328
|
+
if state:
|
|
1329
|
+
return "\n".join(["Use current accepted Goal Stack as execution plan.", state])
|
|
1330
|
+
if str(route.get("intent")) == "execution" and any(word in prompt.lower() for word in ["bắt đầu", "bat dau", "làm đi", "lam di", "execute", "do it"]):
|
|
1331
|
+
return "User approved previous plan. Use AgentRAM memory context and current prompt to execute next concrete task."
|
|
1332
|
+
return supervisor_plan(supervisor, prompt, memory_context)
|
|
1333
|
+
|
|
1334
|
+
def small_agent_observe(context: McpContext, small_agent: AgentModelEndpoint | None, prompt: str, event_type: str, event_content: str, plan: str, memory_context: str) -> str:
|
|
1335
|
+
if not small_agent:
|
|
1336
|
+
return "No small agent bound."
|
|
1337
|
+
try:
|
|
1338
|
+
return call_agent_endpoint(
|
|
1339
|
+
small_agent,
|
|
1340
|
+
build_small_agent_prompt(prompt, plan, memory_context, event_content, event_type=event_type),
|
|
1341
|
+
)
|
|
1342
|
+
except Exception as error: # noqa: BLE001 - agent boundary
|
|
1343
|
+
return f"error: {model_error_hint(str(error))}"
|
|
1344
|
+
|
|
1345
|
+
def sync_goal_state_from_small_agent(context: McpContext, prompt: str, plan: str, small_notes: str) -> None:
|
|
1346
|
+
if not small_notes.strip() or small_notes.strip().startswith("error:"):
|
|
1347
|
+
return
|
|
1348
|
+
state = context.goal_store.load()
|
|
1349
|
+
goal = extract_labeled_value(small_notes, ("current_goal", "goal", "mục tiêu", "muc tieu")) or first_nonempty_line(plan)
|
|
1350
|
+
task = extract_labeled_value(small_notes, ("current_task", "task", "nhiệm vụ", "nhiem vu")) or first_nonempty_line(small_notes) or prompt
|
|
1351
|
+
patch: dict[str, object] = {
|
|
1352
|
+
"current_task": trim(task.strip(), 180),
|
|
1353
|
+
"goal_history": [trim(goal.strip() or prompt.strip(), 180)],
|
|
1354
|
+
}
|
|
1355
|
+
if not state.mission:
|
|
1356
|
+
patch["mission"] = "Xây dựng và vận hành AgentRAM cho coding agents."
|
|
1357
|
+
if not state.project_goal:
|
|
1358
|
+
patch["project_goal"] = "AgentRAM workflow: supervisor lập kế hoạch, main coding agent thực thi, small memory agent ghi RAM."
|
|
1359
|
+
if goal:
|
|
1360
|
+
patch["current_goal"] = trim(goal.strip(), 180)
|
|
1361
|
+
context.goal_store.update(patch)
|
|
1362
|
+
|
|
1363
|
+
def extract_labeled_value(text: str, labels: tuple[str, ...]) -> str:
|
|
1364
|
+
for line in text.splitlines():
|
|
1365
|
+
cleaned = line.strip().lstrip("-*").strip()
|
|
1366
|
+
for label in labels:
|
|
1367
|
+
pattern = rf"^{re.escape(label)}\s*[:=]\s*(.+)$"
|
|
1368
|
+
match = re.match(pattern, cleaned, flags=re.IGNORECASE)
|
|
1369
|
+
if match:
|
|
1370
|
+
return match.group(1).strip().strip("`*_-")
|
|
1371
|
+
return ""
|
|
1372
|
+
|
|
1373
|
+
def first_nonempty_line(value: str) -> str:
|
|
1374
|
+
for line in value.splitlines():
|
|
1375
|
+
cleaned = line.strip().lstrip("-0123456789. )")
|
|
1376
|
+
if cleaned and not cleaned.lower().startswith(("supervisor error", "fallback plan")):
|
|
1377
|
+
return cleaned
|
|
1378
|
+
return ""
|
|
1379
|
+
|
|
1380
|
+
def persist_workflow_memory(context: McpContext, prompt: str, plan: str, outputs: dict[str, str]) -> None:
|
|
1381
|
+
small_notes = "\n".join(
|
|
1382
|
+
value.strip()
|
|
1383
|
+
for key, value in outputs.items()
|
|
1384
|
+
if key.startswith("small") and value.strip() and not value.strip().startswith("error:")
|
|
1385
|
+
)
|
|
1386
|
+
main_result = outputs.get("main", "").strip()
|
|
1035
1387
|
note_parts = [
|
|
1036
1388
|
f"User request: {prompt}",
|
|
1037
1389
|
f"Supervisor plan: {trim(plan.strip(), 1200)}" if plan.strip() else "Supervisor plan: -",
|
|
@@ -1062,14 +1414,14 @@ def supervisor_plan(supervisor: AgentModelEndpoint | None, prompt: str, memory_c
|
|
|
1062
1414
|
def call_agent_endpoint(endpoint: AgentModelEndpoint, prompt: str) -> str:
|
|
1063
1415
|
return MultiModelCodingOrchestrator([endpoint])._call_endpoint(endpoint, prompt)
|
|
1064
1416
|
|
|
1065
|
-
def build_supervisor_prompt(prompt: str, memory_context: str) -> str:
|
|
1066
|
-
return "\n".join(["You are AgentRAM supervisor. Act like a senior coding-agent boss.", "Create concise plan
|
|
1067
|
-
|
|
1068
|
-
def build_main_agent_prompt(prompt: str, plan: str, memory_context: str) -> str:
|
|
1069
|
-
return "\n".join(["You are AgentRAM main coding agent.", "Follow supervisor plan. Focus on planning, orchestration, coding, refactor, bug fix, and final result.", "Do not invent repo facts. Mention files to inspect/change.", "User request:", prompt, "Supervisor plan:", plan, "AgentRAM context:", memory_context])
|
|
1070
|
-
|
|
1071
|
-
def build_small_agent_prompt(prompt: str, plan: str, memory_context: str) -> str:
|
|
1072
|
-
return "\n".join(["You are AgentRAM small memory agent.", "Do not edit code.
|
|
1417
|
+
def build_supervisor_prompt(prompt: str, memory_context: str) -> str:
|
|
1418
|
+
return "\n".join(["You are AgentRAM supervisor. Act like a senior coding-agent boss.", "Nếu user prompt bằng tiếng Việt, trả lời bằng tiếng Việt. Giữ code, command, path, errors nguyên văn.", "Create a concise plan only. Do not code. Main agent runs later only after user approves/starts execution.", "Small memory agent observes this plan and writes RAM notes.", "Return only actionable plan. No markdown table.", "User request:", prompt, "AgentRAM context:", memory_context])
|
|
1419
|
+
|
|
1420
|
+
def build_main_agent_prompt(prompt: str, plan: str, memory_context: str) -> str:
|
|
1421
|
+
return "\n".join(["You are AgentRAM main coding agent.", "Nếu user prompt bằng tiếng Việt, trả lời bằng tiếng Việt. Giữ code, command, path, errors nguyên văn.", "Follow supervisor plan. Focus on planning, orchestration, coding, refactor, bug fix, and final result.", "Do not invent repo facts. Mention files to inspect/change.", "User request:", prompt, "Supervisor plan:", plan, "AgentRAM context:", memory_context])
|
|
1422
|
+
|
|
1423
|
+
def build_small_agent_prompt(prompt: str, plan: str, memory_context: str, event_content: str = "", event_type: str = "main_result") -> str:
|
|
1424
|
+
return "\n".join(["You are AgentRAM small memory agent.", "Nếu user prompt bằng tiếng Việt, ghi note RAM bằng tiếng Việt. Giữ code, command, path, errors nguyên văn.", "You participate in every AgentRAM event directly: user request, supervisor plan, main result, compact/merge.", "Do not edit code. Observe event content, then note decisions, risks, files, next tasks, and coding facts for RAM.", "You are responsible for setting the visible RAM goal/task from the latest event.", "Return concise memory notes only. Không dùng tiếng Anh nếu user đang dùng tiếng Việt.", "Bắt buộc gồm 2 dòng đầu theo format: current_goal: ... và current_task: ...", "User request:", prompt, "Supervisor plan:", plan, "Observed event type:", event_type, "Observed event content:", event_content or "-", "AgentRAM context:", memory_context])
|
|
1073
1425
|
|
|
1074
1426
|
def bind_router_text(context: McpContext, options: dict[str, object]) -> str:
|
|
1075
1427
|
api_key_env = str(options.get("api_key_env", ""))
|
|
@@ -1154,9 +1506,9 @@ def extract_json_object(text: str) -> str:
|
|
|
1154
1506
|
|
|
1155
1507
|
def heuristic_intent(prompt: str) -> dict[str, object]:
|
|
1156
1508
|
lower = prompt.lower()
|
|
1157
|
-
memory_words = ["ghi
|
|
1158
|
-
planning_words = ["goal", "
|
|
1159
|
-
coding_words = ["
|
|
1509
|
+
memory_words = ["ghi nhớ", "remember", "note", "decision", "lưu lại", "save this"]
|
|
1510
|
+
planning_words = ["goal", "kế hoạch", "ke hoach", "milestone", "mission", "drift", "next task", "roadmap", "mục tiêu", "muc tieu"]
|
|
1511
|
+
coding_words = ["sá»a", "sua", "fix", "bug", "code", "test", "refactor", "review", "file", ".py", ".js", "endpoint", "class", "function"]
|
|
1160
1512
|
if any(word in lower for word in memory_words):
|
|
1161
1513
|
return {"intent": "memory", "confidence": 0.75}
|
|
1162
1514
|
if any(word in lower for word in planning_words):
|
|
@@ -1191,12 +1543,16 @@ def model_error_hint(message: str) -> str:
|
|
|
1191
1543
|
return message + " | Fix: /remove-model <id> or re-bind with --base-url http://localhost:8000/v1"
|
|
1192
1544
|
if "actively refused" in lower or "connection refused" in lower or "winerror 10061" in lower:
|
|
1193
1545
|
return message + " | Fix: start your OpenAI-compatible server at base_url, or change --base-url."
|
|
1546
|
+
if "http 403" in lower or "403" in lower and "groq" in lower:
|
|
1547
|
+
return message + " | Fix: Groq rejected auth/model. Rotate/check API key, ensure provider=openai-compatible, base_url=https://api.groq.com/openai/v1, and use a Groq-supported model name."
|
|
1194
1548
|
if "http 404" in lower or "404: not found" in lower:
|
|
1195
1549
|
return message + " | Fix: verify base_url includes /v1, provider supports /chat/completions, and model name exists."
|
|
1196
1550
|
if "http 400" in lower and "not supported" in lower:
|
|
1197
1551
|
return message + " | Fix: this account/provider rejects that model. Bind a supported model name from your local endpoint."
|
|
1198
1552
|
if "returned non-json" in lower or "expecting value" in lower:
|
|
1199
1553
|
return message + " | Fix: endpoint returned empty/HTML/non-JSON. Check base_url, auth key, proxy, and server logs."
|
|
1554
|
+
if "command not found" in lower or "winerror 2" in lower or "the system cannot find the file" in lower:
|
|
1555
|
+
return message + r" | Fix: install the CLI or set Main base_url to full command path. For Codex use base_url='C:\Users\admin\AppData\Roaming\npm\codex.cmd exec' or paste codex.cmd path; AgentRAM auto-adds exec when missing."
|
|
1200
1556
|
if "missing api key env" in lower:
|
|
1201
1557
|
return message + " | Fix: set environment variable, then bind using --api-key-env ENV_NAME, not raw key."
|
|
1202
1558
|
return message
|
|
@@ -1273,7 +1629,7 @@ def draw_screen(context: McpContext, messages: list[str], activity: str | None =
|
|
|
1273
1629
|
left_width = 30
|
|
1274
1630
|
right_width = width - left_width - 3
|
|
1275
1631
|
print("+" + "-" * (width - 2) + "+")
|
|
1276
|
-
print(box_text("
|
|
1632
|
+
print(box_text("AgentRAM Code | RAM + Claude subagents + slash commands", width))
|
|
1277
1633
|
print("+" + "-" * (width - 2) + "+")
|
|
1278
1634
|
|
|
1279
1635
|
sidebar = sidebar_lines(context)
|
|
@@ -1328,7 +1684,7 @@ def sidebar_lines(context: McpContext) -> list[str]:
|
|
|
1328
1684
|
|
|
1329
1685
|
|
|
1330
1686
|
def trim(text: str, limit: int) -> str:
|
|
1331
|
-
return text if len(text) <= limit else text[: limit -
|
|
1687
|
+
return text if len(text) <= limit else text[: limit - 3] + "..."
|
|
1332
1688
|
|
|
1333
1689
|
|
|
1334
1690
|
def menu_lines() -> list[str]:
|