memstack-skill-loader 4.0.4__tar.gz → 4.0.6__tar.gz
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.
- {memstack_skill_loader-4.0.4/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.0.6}/PKG-INFO +1 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/pyproject.toml +1 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/agent_runner.py +125 -12
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/dashboard.html +217 -4
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/dashboard.py +68 -2
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/README.md +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/setup.cfg +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/categories.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/license.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/server.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/stats.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader.egg-info/SOURCES.txt +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
|
@@ -66,7 +66,8 @@ SYSTEM_PROMPTS = {
|
|
|
66
66
|
"external stylesheet.\n"
|
|
67
67
|
"- Never hardcode secrets, API keys, tokens, passwords, or credentials in source code. "
|
|
68
68
|
"Always use environment variables loaded from .env files. If a secret is needed, create "
|
|
69
|
-
"or update a .env.example file with the variable name and a placeholder value
|
|
69
|
+
"or update a .env.example file with the variable name and a placeholder value.\n"
|
|
70
|
+
"- Do NOT run git add, git commit, or any git commands. Only make file changes."
|
|
70
71
|
),
|
|
71
72
|
"reviewer": (
|
|
72
73
|
"You are a Reviewer agent. You review the Builder's changes against the original "
|
|
@@ -88,6 +89,49 @@ SYSTEM_PROMPTS = {
|
|
|
88
89
|
),
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
BUILDER_TOOLS_CONFIG_FILE = Path.home() / ".memstack" / "builder-tools-config.json"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# MCP server discovery & Builder tools config
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
def discover_mcp_servers(workdir: str) -> list[str]:
|
|
100
|
+
"""Read .mcp.json from workdir and return the list of MCP server name keys."""
|
|
101
|
+
try:
|
|
102
|
+
mcp_file = Path(workdir) / ".mcp.json"
|
|
103
|
+
if not mcp_file.is_file():
|
|
104
|
+
return []
|
|
105
|
+
data = json.loads(mcp_file.read_text(encoding="utf-8"))
|
|
106
|
+
servers = data.get("mcpServers", {})
|
|
107
|
+
return sorted(servers.keys()) if isinstance(servers, dict) else []
|
|
108
|
+
except Exception:
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def load_builder_tools_config() -> dict:
|
|
113
|
+
"""Load per-project blocked MCP server lists from ~/.memstack/builder-tools-config.json."""
|
|
114
|
+
try:
|
|
115
|
+
if BUILDER_TOOLS_CONFIG_FILE.is_file():
|
|
116
|
+
return json.loads(BUILDER_TOOLS_CONFIG_FILE.read_text(encoding="utf-8"))
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
return {}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def save_builder_tools_config(config: dict) -> None:
|
|
123
|
+
"""Save per-project blocked MCP server config."""
|
|
124
|
+
BUILDER_TOOLS_CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
BUILDER_TOOLS_CONFIG_FILE.write_text(json.dumps(config, indent=2), encoding="utf-8")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_blocked_servers_for_project(workdir: str) -> list[str]:
|
|
129
|
+
"""Return blocked MCP servers for a project, falling back to global defaults."""
|
|
130
|
+
cfg = load_builder_tools_config()
|
|
131
|
+
if workdir in cfg:
|
|
132
|
+
return cfg[workdir]
|
|
133
|
+
return cfg.get("_global_defaults", [])
|
|
134
|
+
|
|
91
135
|
|
|
92
136
|
# ---------------------------------------------------------------------------
|
|
93
137
|
# Project context gathering
|
|
@@ -259,6 +303,30 @@ def _parse_stream_json(raw: str) -> tuple[str, int, int, float, int]:
|
|
|
259
303
|
return text, input_tokens, output_tokens, cost_usd, context_tokens
|
|
260
304
|
|
|
261
305
|
|
|
306
|
+
def _extract_text_from_stream_line(line: str) -> Optional[str]:
|
|
307
|
+
"""Extract user-visible text from a single stream-json line.
|
|
308
|
+
|
|
309
|
+
Returns the text if the line contains assistant text or a result, else None.
|
|
310
|
+
"""
|
|
311
|
+
try:
|
|
312
|
+
obj = json.loads(line.strip())
|
|
313
|
+
except (json.JSONDecodeError, ValueError):
|
|
314
|
+
return None
|
|
315
|
+
msg_type = obj.get("type")
|
|
316
|
+
if msg_type == "assistant":
|
|
317
|
+
parts = []
|
|
318
|
+
for block in obj.get("message", {}).get("content", []):
|
|
319
|
+
if block.get("type") == "text":
|
|
320
|
+
t = block.get("text", "")
|
|
321
|
+
if t:
|
|
322
|
+
parts.append(t)
|
|
323
|
+
return "\n".join(parts) if parts else None
|
|
324
|
+
if msg_type == "result":
|
|
325
|
+
t = obj.get("result", "")
|
|
326
|
+
return t if t else None
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
|
|
262
330
|
# ---------------------------------------------------------------------------
|
|
263
331
|
# Direct API invocation (Manager / Reviewer — no file tools needed)
|
|
264
332
|
# ---------------------------------------------------------------------------
|
|
@@ -359,7 +427,8 @@ def _invoke_api_agent(name: str, prompt: str, system_prompt: str,
|
|
|
359
427
|
def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[Path] = None,
|
|
360
428
|
skip_permissions: bool = False,
|
|
361
429
|
session_id: Optional[str] = None, timeout: int = AGENT_TIMEOUT,
|
|
362
|
-
model: str = ""
|
|
430
|
+
model: str = "", disallowed_tools: Optional[list[str]] = None,
|
|
431
|
+
session: Optional["Session"] = None) -> tuple[str, int, int, int]:
|
|
363
432
|
"""Run a single claude --print invocation and return the output."""
|
|
364
433
|
claude_bin = shutil.which("claude")
|
|
365
434
|
if not claude_bin:
|
|
@@ -370,6 +439,9 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
370
439
|
cmd.append("--dangerously-skip-permissions")
|
|
371
440
|
if model:
|
|
372
441
|
cmd.extend(["--model", model])
|
|
442
|
+
if disallowed_tools:
|
|
443
|
+
patterns = ",".join(f"mcp__{srv}__*" for srv in disallowed_tools)
|
|
444
|
+
cmd.extend(["--disallowedTools", patterns])
|
|
373
445
|
|
|
374
446
|
if log_path:
|
|
375
447
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -393,9 +465,6 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
393
465
|
stdout=subprocess.PIPE,
|
|
394
466
|
stderr=subprocess.PIPE,
|
|
395
467
|
cwd=working_dir,
|
|
396
|
-
text=True,
|
|
397
|
-
encoding="utf-8",
|
|
398
|
-
errors="replace",
|
|
399
468
|
env=_build_env(),
|
|
400
469
|
creationflags=0,
|
|
401
470
|
)
|
|
@@ -413,12 +482,38 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
413
482
|
killed_by_watchdog.set()
|
|
414
483
|
proc.kill()
|
|
415
484
|
|
|
485
|
+
stdout_chunks: list[str] = []
|
|
486
|
+
stderr_chunks: list[str] = []
|
|
487
|
+
partial_text: list[str] = []
|
|
488
|
+
|
|
489
|
+
def _read_stdout() -> None:
|
|
490
|
+
for raw_line in proc.stdout:
|
|
491
|
+
line = raw_line.decode("utf-8", errors="replace")
|
|
492
|
+
stdout_chunks.append(line)
|
|
493
|
+
extracted = _extract_text_from_stream_line(line)
|
|
494
|
+
if extracted and session is not None:
|
|
495
|
+
partial_text.append(extracted)
|
|
496
|
+
try:
|
|
497
|
+
session.agents[name]["last_output"] = "".join(partial_text)[-500:]
|
|
498
|
+
session._save_state()
|
|
499
|
+
except Exception:
|
|
500
|
+
pass
|
|
501
|
+
|
|
502
|
+
def _read_stderr() -> None:
|
|
503
|
+
for raw_line in proc.stderr:
|
|
504
|
+
stderr_chunks.append(raw_line.decode("utf-8", errors="replace"))
|
|
505
|
+
|
|
506
|
+
t_out = threading.Thread(target=_read_stdout, daemon=True)
|
|
507
|
+
t_err = threading.Thread(target=_read_stderr, daemon=True)
|
|
508
|
+
t_out.start()
|
|
509
|
+
t_err.start()
|
|
510
|
+
|
|
416
511
|
watchdog = threading.Timer(timeout, _watchdog_kill)
|
|
417
512
|
watchdog.daemon = True
|
|
418
513
|
watchdog.start()
|
|
419
514
|
|
|
420
515
|
try:
|
|
421
|
-
|
|
516
|
+
proc.wait()
|
|
422
517
|
except Exception as e:
|
|
423
518
|
watchdog.cancel()
|
|
424
519
|
with _lock:
|
|
@@ -426,20 +521,26 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
426
521
|
_current_process = None
|
|
427
522
|
if log_path:
|
|
428
523
|
with open(log_path, "a", encoding="utf-8") as f:
|
|
429
|
-
f.write(f"[{ts}]
|
|
430
|
-
raise RuntimeError(f"{name}
|
|
524
|
+
f.write(f"[{ts}] wait() error: {e}\n")
|
|
525
|
+
raise RuntimeError(f"{name} wait() failed: {e}") from e
|
|
431
526
|
finally:
|
|
432
527
|
watchdog.cancel()
|
|
433
528
|
with _lock:
|
|
434
529
|
if _current_process is proc:
|
|
435
530
|
_current_process = None
|
|
436
531
|
|
|
532
|
+
t_out.join(timeout=10)
|
|
533
|
+
t_err.join(timeout=10)
|
|
534
|
+
|
|
437
535
|
if killed_by_watchdog.is_set():
|
|
438
536
|
raise subprocess.TimeoutExpired(cmd, timeout)
|
|
439
537
|
|
|
538
|
+
stdout_data = "".join(stdout_chunks)
|
|
539
|
+
stderr_data = "".join(stderr_chunks)
|
|
540
|
+
|
|
440
541
|
if log_path:
|
|
441
542
|
with open(log_path, "a", encoding="utf-8") as f:
|
|
442
|
-
f.write(f"[{ts}]
|
|
543
|
+
f.write(f"[{ts}] Process exited: stdout={len(stdout_data)} stderr={len(stderr_data)}\n")
|
|
443
544
|
|
|
444
545
|
raw_stdout = stdout_data or ""
|
|
445
546
|
stderr = stderr_data or ""
|
|
@@ -485,7 +586,8 @@ class Session:
|
|
|
485
586
|
"""Tracks the full state of an agent run session."""
|
|
486
587
|
|
|
487
588
|
def __init__(self, task: str, working_dir: str, context: Optional[str] = None, auto_commit: bool = False, timeout_minutes: int = 60,
|
|
488
|
-
manager_model: str = "", builder_model: str = "", reviewer_model: str = "", user_name: str = ""
|
|
589
|
+
manager_model: str = "", builder_model: str = "", reviewer_model: str = "", user_name: str = "",
|
|
590
|
+
blocked_mcp_servers: Optional[list[str]] = None):
|
|
489
591
|
self.session_id = uuid.uuid4().hex[:12]
|
|
490
592
|
self.task = task
|
|
491
593
|
self.working_dir = working_dir
|
|
@@ -493,6 +595,7 @@ class Session:
|
|
|
493
595
|
self.user_name = user_name
|
|
494
596
|
self.context = context
|
|
495
597
|
self.auto_commit = auto_commit
|
|
598
|
+
self.blocked_mcp_servers = blocked_mcp_servers or []
|
|
496
599
|
self.auto_committed = False
|
|
497
600
|
self.timeout = max(5, min(120, timeout_minutes)) * 60
|
|
498
601
|
self.status = "running"
|
|
@@ -607,6 +710,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
607
710
|
# Step 1: Manager analyzes the task
|
|
608
711
|
session.agents["manager"]["status"] = "busy"
|
|
609
712
|
session.agents["manager"]["started_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
713
|
+
session.agents["manager"]["last_output"] = ""
|
|
610
714
|
session._save_state()
|
|
611
715
|
|
|
612
716
|
context = _gather_project_context(session.working_dir)
|
|
@@ -666,6 +770,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
666
770
|
# Builder
|
|
667
771
|
session.agents["builder"]["status"] = "busy"
|
|
668
772
|
session.agents["builder"]["started_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
773
|
+
session.agents["builder"]["last_output"] = ""
|
|
669
774
|
session._save_state()
|
|
670
775
|
|
|
671
776
|
builder_prompt = (
|
|
@@ -684,6 +789,8 @@ def _orchestrate(session: Session) -> None:
|
|
|
684
789
|
skip_permissions=True, session_id=session.session_id,
|
|
685
790
|
timeout=session.timeout,
|
|
686
791
|
model=session.models.get("builder", ""),
|
|
792
|
+
disallowed_tools=session.blocked_mcp_servers,
|
|
793
|
+
session=session,
|
|
687
794
|
)
|
|
688
795
|
except subprocess.TimeoutExpired:
|
|
689
796
|
session.agents["builder"]["status"] = "timeout"
|
|
@@ -733,6 +840,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
733
840
|
# Reviewer
|
|
734
841
|
session.agents["reviewer"]["status"] = "busy"
|
|
735
842
|
session.agents["reviewer"]["started_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
843
|
+
session.agents["reviewer"]["last_output"] = ""
|
|
736
844
|
session._save_state()
|
|
737
845
|
|
|
738
846
|
reviewer_prompt = (
|
|
@@ -876,7 +984,8 @@ _BLOCKED_NIX = {"/etc", "/var", "/proc", "/sys", "/boot"}
|
|
|
876
984
|
|
|
877
985
|
|
|
878
986
|
def start_run(task: str, working_dir: Optional[str] = None, context: Optional[str] = None, auto_commit: bool = True, timeout_minutes: int = 60,
|
|
879
|
-
manager_model: str = "", builder_model: str = "", reviewer_model: str = "", user_name: str = ""
|
|
987
|
+
manager_model: str = "", builder_model: str = "", reviewer_model: str = "", user_name: str = "",
|
|
988
|
+
blocked_mcp_servers: Optional[list[str]] = None) -> dict:
|
|
880
989
|
"""Start a new agent run. Returns session info."""
|
|
881
990
|
global _current_session, _orchestration_thread
|
|
882
991
|
|
|
@@ -895,12 +1004,16 @@ def start_run(task: str, working_dir: Optional[str] = None, context: Optional[st
|
|
|
895
1004
|
if any(str_wd == bp or str_wd.startswith(bp + "/") for bp in _BLOCKED_NIX):
|
|
896
1005
|
return {"error": f"Working directory not allowed: {wd}"}
|
|
897
1006
|
|
|
1007
|
+
if blocked_mcp_servers is None:
|
|
1008
|
+
blocked_mcp_servers = get_blocked_servers_for_project(str(wd))
|
|
1009
|
+
|
|
898
1010
|
with _lock:
|
|
899
1011
|
if _current_session and _current_session.status == "running":
|
|
900
1012
|
return {"error": "A session is already running", "session_id": _current_session.session_id}
|
|
901
1013
|
|
|
902
1014
|
session = Session(task=task, working_dir=working_dir, context=context, auto_commit=auto_commit, timeout_minutes=timeout_minutes,
|
|
903
|
-
manager_model=manager_model, builder_model=builder_model, reviewer_model=reviewer_model, user_name=user_name
|
|
1015
|
+
manager_model=manager_model, builder_model=builder_model, reviewer_model=reviewer_model, user_name=user_name,
|
|
1016
|
+
blocked_mcp_servers=blocked_mcp_servers)
|
|
904
1017
|
_current_session = session
|
|
905
1018
|
|
|
906
1019
|
session._save_state()
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/dashboard.html
RENAMED
|
@@ -835,6 +835,76 @@
|
|
|
835
835
|
font-size: 0.82rem;
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
+
/* ── Builder MCP Tools section ── */
|
|
839
|
+
.mcp-tools-details {
|
|
840
|
+
margin-bottom: 0.8rem;
|
|
841
|
+
}
|
|
842
|
+
.mcp-tools-summary {
|
|
843
|
+
cursor: pointer;
|
|
844
|
+
font-size: 0.82rem;
|
|
845
|
+
color: #8b949e;
|
|
846
|
+
font-weight: 600;
|
|
847
|
+
list-style: none;
|
|
848
|
+
display: flex;
|
|
849
|
+
align-items: center;
|
|
850
|
+
gap: 0.4rem;
|
|
851
|
+
}
|
|
852
|
+
.mcp-tools-summary::-webkit-details-marker { display: none; }
|
|
853
|
+
.mcp-tools-summary::before {
|
|
854
|
+
content: '▶';
|
|
855
|
+
font-size: 0.6rem;
|
|
856
|
+
transition: transform 0.2s;
|
|
857
|
+
}
|
|
858
|
+
.mcp-tools-details[open] > .mcp-tools-summary::before {
|
|
859
|
+
transform: rotate(90deg);
|
|
860
|
+
}
|
|
861
|
+
.mcp-tools-summary:hover { color: #c9d1d9; }
|
|
862
|
+
.mcp-tools-list {
|
|
863
|
+
margin-top: 0.5rem;
|
|
864
|
+
display: flex;
|
|
865
|
+
flex-direction: column;
|
|
866
|
+
gap: 0.3rem;
|
|
867
|
+
}
|
|
868
|
+
.mcp-tools-row {
|
|
869
|
+
display: flex;
|
|
870
|
+
align-items: center;
|
|
871
|
+
gap: 0.5rem;
|
|
872
|
+
font-size: 0.82rem;
|
|
873
|
+
color: #c9d1d9;
|
|
874
|
+
}
|
|
875
|
+
.mcp-tools-row input[type="checkbox"] {
|
|
876
|
+
accent-color: #238636;
|
|
877
|
+
width: 14px;
|
|
878
|
+
height: 14px;
|
|
879
|
+
cursor: pointer;
|
|
880
|
+
flex-shrink: 0;
|
|
881
|
+
}
|
|
882
|
+
.mcp-tools-row label { cursor: pointer; font-family: monospace; }
|
|
883
|
+
.mcp-tools-hint {
|
|
884
|
+
font-size: 0.72rem;
|
|
885
|
+
color: #484f58;
|
|
886
|
+
margin-top: 0.4rem;
|
|
887
|
+
}
|
|
888
|
+
.mcp-tools-save-btn {
|
|
889
|
+
margin-top: 0.5rem;
|
|
890
|
+
background: #21262d;
|
|
891
|
+
color: #8b949e;
|
|
892
|
+
border: 1px solid #30363d;
|
|
893
|
+
padding: 0.3rem 0.8rem;
|
|
894
|
+
border-radius: 6px;
|
|
895
|
+
font-size: 0.75rem;
|
|
896
|
+
cursor: pointer;
|
|
897
|
+
transition: background 0.2s, color 0.2s;
|
|
898
|
+
}
|
|
899
|
+
.mcp-tools-save-btn:hover { background: #30363d; color: #c9d1d9; }
|
|
900
|
+
.mcp-tools-saved {
|
|
901
|
+
font-size: 0.72rem;
|
|
902
|
+
color: #39d353;
|
|
903
|
+
margin-left: 0.5rem;
|
|
904
|
+
opacity: 0;
|
|
905
|
+
transition: opacity 0.3s;
|
|
906
|
+
}
|
|
907
|
+
|
|
838
908
|
/* ── Dancing status phrases ── */
|
|
839
909
|
.agent-status-phrase {
|
|
840
910
|
transition: opacity 0.3s ease;
|
|
@@ -1431,7 +1501,7 @@
|
|
|
1431
1501
|
<label style="display:block;font-size:0.82rem;color:#8b949e;margin-bottom:0.4rem;font-weight:600;">Task Description</label>
|
|
1432
1502
|
<div style="position:relative;margin-bottom:1rem;">
|
|
1433
1503
|
<textarea id="agent-task-input" rows="4" placeholder="Describe what you want the agents to build..." style="width:100%;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#e6edf3;padding:0.7rem;font-size:0.88rem;resize:vertical;font-family:inherit;margin:0;"></textarea>
|
|
1434
|
-
<button onclick="document.getElementById('agent-task-input').value='';document.getElementById('agent-workdir-input').value='';document.getElementById('agent-context-input').value='';document.getElementById('agent-task-input').focus();" style="position:absolute;top:4px;right:4px;background:#21262d;border:1px solid #30363d;color:#8b949e;padding:8px;border-radius:4px;cursor:pointer;font-size:14px;line-height:1;z-index:1;" onmouseenter="this.style.color='#e6edf3'" onmouseleave="this.style.color='#8b949e'" title="Clear task and working directory">×</button>
|
|
1504
|
+
<button onclick="document.getElementById('agent-task-input').value='';document.getElementById('agent-workdir-input').value='';document.getElementById('agent-context-input').value='';fetchMcpServers('');document.getElementById('agent-task-input').focus();" style="position:absolute;top:4px;right:4px;background:#21262d;border:1px solid #30363d;color:#8b949e;padding:8px;border-radius:4px;cursor:pointer;font-size:14px;line-height:1;z-index:1;" onmouseenter="this.style.color='#e6edf3'" onmouseleave="this.style.color='#8b949e'" title="Clear task and working directory">×</button>
|
|
1435
1505
|
</div>
|
|
1436
1506
|
<label style="display:block;font-size:0.82rem;color:#8b949e;margin-bottom:0.4rem;font-weight:600;">Working Directory</label>
|
|
1437
1507
|
<div style="display:flex;gap:0.5rem;margin-bottom:0.3rem;">
|
|
@@ -1474,6 +1544,20 @@
|
|
|
1474
1544
|
</div>
|
|
1475
1545
|
</div>
|
|
1476
1546
|
</details>
|
|
1547
|
+
<details class="mcp-tools-details" id="mcp-tools-details">
|
|
1548
|
+
<summary class="mcp-tools-summary" id="mcp-tools-summary">Builder MCP Tools</summary>
|
|
1549
|
+
<div id="mcp-tools-container">
|
|
1550
|
+
<div id="mcp-tools-loading" style="font-size:0.78rem;color:#484f58;padding:0.3rem 0;">Enter a working directory to discover MCP servers</div>
|
|
1551
|
+
<div id="mcp-tools-list" class="mcp-tools-list" style="display:none;"></div>
|
|
1552
|
+
<div id="mcp-tools-empty" style="display:none;font-size:0.78rem;color:#484f58;padding:0.3rem 0;">No MCP servers found in this project</div>
|
|
1553
|
+
<p class="mcp-tools-hint" id="mcp-tools-hint" style="display:none;">Unchecked servers will be excluded from the Builder's context to reduce token usage</p>
|
|
1554
|
+
<p id="mcp-tools-global-note" style="display:none;font-size:0.72rem;color:#d29922;margin:0.3rem 0 0;">Using global defaults</p>
|
|
1555
|
+
<div id="mcp-tools-actions" style="display:none;margin-top:0.4rem;">
|
|
1556
|
+
<button class="mcp-tools-save-btn" onclick="saveMcpToolsConfig()">Save as Default</button>
|
|
1557
|
+
<span class="mcp-tools-saved" id="mcp-tools-saved">Saved</span>
|
|
1558
|
+
</div>
|
|
1559
|
+
</div>
|
|
1560
|
+
</details>
|
|
1477
1561
|
<label for="agent-autocommit-checkbox" style="display:flex;align-items:center;gap:0.5rem;font-size:0.82rem;color:#8b949e;margin-bottom:1rem;cursor:pointer;position:relative;z-index:1;">
|
|
1478
1562
|
<input id="agent-autocommit-checkbox" type="checkbox" style="accent-color:#238636;width:15px;height:15px;cursor:pointer;flex-shrink:0;">
|
|
1479
1563
|
Auto-commit on approval
|
|
@@ -1658,6 +1742,22 @@
|
|
|
1658
1742
|
</div>
|
|
1659
1743
|
</div>
|
|
1660
1744
|
|
|
1745
|
+
<div class="panel">
|
|
1746
|
+
<details class="mcp-tools-details">
|
|
1747
|
+
<summary class="mcp-tools-summary">Default MCP Tools</summary>
|
|
1748
|
+
<div style="padding:0.3rem 0;">
|
|
1749
|
+
<p style="color:#8b949e;font-size:0.78rem;margin:0 0 0.6rem;">Set which MCP servers are blocked by default across all projects. Per-project settings override these defaults.</p>
|
|
1750
|
+
<label style="font-size:0.78rem;color:#8b949e;display:block;margin-bottom:0.3rem;">Blocked servers (comma-separated)</label>
|
|
1751
|
+
<input type="text" id="settings-global-mcp-blocked" class="settings-text-input" placeholder="e.g. agent-bridge, connectstack-uptimerobot">
|
|
1752
|
+
<div style="display:flex;gap:0.5rem;margin-top:0.5rem;align-items:center;">
|
|
1753
|
+
<button class="mcp-tools-save-btn" onclick="saveGlobalMcpDefaults(false)">Save Defaults</button>
|
|
1754
|
+
<button class="mcp-tools-save-btn" style="background:#30363d;" onclick="applyGlobalMcpToAll()">Apply to All Projects</button>
|
|
1755
|
+
<span id="settings-global-mcp-saved" class="mcp-tools-saved">Saved</span>
|
|
1756
|
+
</div>
|
|
1757
|
+
</div>
|
|
1758
|
+
</details>
|
|
1759
|
+
</div>
|
|
1760
|
+
|
|
1661
1761
|
<div class="panel">
|
|
1662
1762
|
<h3>Dashboard Info</h3>
|
|
1663
1763
|
<div style="display:grid;grid-template-columns:auto 1fr;gap:0.4rem 1.2rem;font-size:0.82rem;">
|
|
@@ -2819,6 +2919,8 @@ async function startAgentTask() {
|
|
|
2819
2919
|
body.builder_model = document.getElementById('agent-model-builder').value;
|
|
2820
2920
|
body.reviewer_model = document.getElementById('agent-model-reviewer').value;
|
|
2821
2921
|
body.user_name = userProfile.user_name || '';
|
|
2922
|
+
const blockedMcp = getBlockedMcpServers();
|
|
2923
|
+
if (blockedMcp.length) body.blocked_mcp_servers = blockedMcp;
|
|
2822
2924
|
const res = await fetch('/api/agent-run', {method:'POST', headers: AUTH_HEADERS, body: JSON.stringify(body)});
|
|
2823
2925
|
const data = await res.json();
|
|
2824
2926
|
if (data.error) { alert(data.error); return; }
|
|
@@ -2857,6 +2959,12 @@ async function loadAgentMonitor() {
|
|
|
2857
2959
|
lbl.textContent = userProfile.agent_names[role];
|
|
2858
2960
|
}
|
|
2859
2961
|
}
|
|
2962
|
+
updateMcpToolsSummaryLabel();
|
|
2963
|
+
const wdInput = document.getElementById('agent-workdir-input');
|
|
2964
|
+
if (wdInput && !wdInput._mcpBound) {
|
|
2965
|
+
wdInput._mcpBound = true;
|
|
2966
|
+
wdInput.addEventListener('blur', () => fetchMcpServers(wdInput.value.trim()));
|
|
2967
|
+
}
|
|
2860
2968
|
fetchAgentStatus();
|
|
2861
2969
|
loadRecentProjects();
|
|
2862
2970
|
loadLastWorkdir();
|
|
@@ -2872,10 +2980,110 @@ async function loadLastWorkdir() {
|
|
|
2872
2980
|
if (input.value.trim()) return;
|
|
2873
2981
|
const res = await fetch('/api/last-workdir', {headers: {'X-Auth-Token': AUTH_TOKEN}});
|
|
2874
2982
|
const data = await res.json();
|
|
2875
|
-
if (data.path) input.value = data.path;
|
|
2983
|
+
if (data.path) { input.value = data.path; fetchMcpServers(data.path); }
|
|
2876
2984
|
} catch(e) { /* ignore */ }
|
|
2877
2985
|
}
|
|
2878
2986
|
|
|
2987
|
+
/* ─── Builder MCP Tools ─── */
|
|
2988
|
+
let _mcpServers = [];
|
|
2989
|
+
let _mcpBlocked = new Set();
|
|
2990
|
+
|
|
2991
|
+
function updateMcpToolsSummaryLabel() {
|
|
2992
|
+
const el = document.getElementById('mcp-tools-summary');
|
|
2993
|
+
if (!el) return;
|
|
2994
|
+
const name = (userProfile.agent_names && userProfile.agent_names.builder) || 'Builder';
|
|
2995
|
+
el.textContent = name + ' MCP Tools';
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
async function fetchMcpServers(workdir) {
|
|
2999
|
+
const listEl = document.getElementById('mcp-tools-list');
|
|
3000
|
+
const emptyEl = document.getElementById('mcp-tools-empty');
|
|
3001
|
+
const loadingEl = document.getElementById('mcp-tools-loading');
|
|
3002
|
+
const hintEl = document.getElementById('mcp-tools-hint');
|
|
3003
|
+
const actionsEl = document.getElementById('mcp-tools-actions');
|
|
3004
|
+
if (!workdir) {
|
|
3005
|
+
listEl.style.display = 'none'; emptyEl.style.display = 'none'; hintEl.style.display = 'none'; actionsEl.style.display = 'none';
|
|
3006
|
+
const gn = document.getElementById('mcp-tools-global-note'); if (gn) gn.style.display = 'none';
|
|
3007
|
+
loadingEl.style.display = ''; loadingEl.textContent = 'Enter a working directory to discover MCP servers';
|
|
3008
|
+
_mcpServers = []; _mcpBlocked = new Set();
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
const globalNoteEl = document.getElementById('mcp-tools-global-note');
|
|
3012
|
+
loadingEl.style.display = ''; loadingEl.textContent = 'Discovering MCP servers...';
|
|
3013
|
+
listEl.style.display = 'none'; emptyEl.style.display = 'none'; hintEl.style.display = 'none'; actionsEl.style.display = 'none';
|
|
3014
|
+
if (globalNoteEl) globalNoteEl.style.display = 'none';
|
|
3015
|
+
try {
|
|
3016
|
+
const res = await fetch('/api/mcp-servers?workdir=' + encodeURIComponent(workdir), {headers: AUTH_GET});
|
|
3017
|
+
const data = await res.json();
|
|
3018
|
+
_mcpServers = data.servers || [];
|
|
3019
|
+
_mcpBlocked = new Set(data.blocked || []);
|
|
3020
|
+
loadingEl.style.display = 'none';
|
|
3021
|
+
if (_mcpServers.length === 0) {
|
|
3022
|
+
emptyEl.style.display = ''; return;
|
|
3023
|
+
}
|
|
3024
|
+
listEl.innerHTML = _mcpServers.map(srv => {
|
|
3025
|
+
const checked = !_mcpBlocked.has(srv) ? 'checked' : '';
|
|
3026
|
+
const id = 'mcp-srv-' + srv.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
3027
|
+
return `<div class="mcp-tools-row"><input type="checkbox" id="${id}" value="${escapeHtml(srv)}" ${checked}><label for="${id}">${escapeHtml(srv)}</label></div>`;
|
|
3028
|
+
}).join('');
|
|
3029
|
+
listEl.style.display = ''; hintEl.style.display = ''; actionsEl.style.display = '';
|
|
3030
|
+
if (globalNoteEl && !data.has_project_config && (data.global_blocked || []).length > 0) {
|
|
3031
|
+
globalNoteEl.style.display = '';
|
|
3032
|
+
}
|
|
3033
|
+
} catch(e) {
|
|
3034
|
+
loadingEl.style.display = 'none'; emptyEl.style.display = ''; emptyEl.textContent = 'Failed to discover MCP servers';
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
function getBlockedMcpServers() {
|
|
3039
|
+
const blocked = [];
|
|
3040
|
+
for (const srv of _mcpServers) {
|
|
3041
|
+
const id = 'mcp-srv-' + srv.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
3042
|
+
const cb = document.getElementById(id);
|
|
3043
|
+
if (cb && !cb.checked) blocked.push(srv);
|
|
3044
|
+
}
|
|
3045
|
+
return blocked;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
async function saveMcpToolsConfig() {
|
|
3049
|
+
const workdir = document.getElementById('agent-workdir-input').value.trim();
|
|
3050
|
+
if (!workdir) return;
|
|
3051
|
+
const blocked = getBlockedMcpServers();
|
|
3052
|
+
try {
|
|
3053
|
+
await fetch('/api/builder-tools-config', {method: 'POST', headers: AUTH_HEADERS, body: JSON.stringify({workdir, blocked})});
|
|
3054
|
+
const savedEl = document.getElementById('mcp-tools-saved');
|
|
3055
|
+
savedEl.style.opacity = '1';
|
|
3056
|
+
setTimeout(() => { savedEl.style.opacity = '0'; }, 2000);
|
|
3057
|
+
} catch(e) { /* ignore */ }
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
async function loadGlobalMcpDefaults() {
|
|
3061
|
+
try {
|
|
3062
|
+
const res = await fetch('/api/global-mcp-defaults', {headers: AUTH_GET});
|
|
3063
|
+
const d = await res.json();
|
|
3064
|
+
const el = document.getElementById('settings-global-mcp-blocked');
|
|
3065
|
+
if (el) el.value = (d.blocked || []).join(', ');
|
|
3066
|
+
} catch(e) { /* ignore */ }
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
async function saveGlobalMcpDefaults(applyAll) {
|
|
3070
|
+
const el = document.getElementById('settings-global-mcp-blocked');
|
|
3071
|
+
if (!el) return;
|
|
3072
|
+
const blocked = el.value.split(',').map(s => s.trim()).filter(Boolean);
|
|
3073
|
+
try {
|
|
3074
|
+
await fetch('/api/global-mcp-defaults', {method: 'POST', headers: AUTH_HEADERS, body: JSON.stringify({blocked, apply_all: !!applyAll})});
|
|
3075
|
+
const savedEl = document.getElementById('settings-global-mcp-saved');
|
|
3076
|
+
savedEl.textContent = applyAll ? 'Applied to all' : 'Saved';
|
|
3077
|
+
savedEl.style.opacity = '1';
|
|
3078
|
+
setTimeout(() => { savedEl.style.opacity = '0'; }, 2000);
|
|
3079
|
+
} catch(e) { /* ignore */ }
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
function applyGlobalMcpToAll() {
|
|
3083
|
+
if (!confirm('This will overwrite all per-project MCP settings. Continue?')) return;
|
|
3084
|
+
saveGlobalMcpDefaults(true);
|
|
3085
|
+
}
|
|
3086
|
+
|
|
2879
3087
|
function autoResize(textarea) {
|
|
2880
3088
|
textarea.style.height = 'auto';
|
|
2881
3089
|
textarea.style.height = textarea.scrollHeight + 'px';
|
|
@@ -2984,7 +3192,7 @@ async function loadRecentProjects() {
|
|
|
2984
3192
|
if (!dirs.length) { dropdown.style.display = 'none'; return; }
|
|
2985
3193
|
dropdown.style.display = 'block';
|
|
2986
3194
|
dropdown.innerHTML = dirs.map(d =>
|
|
2987
|
-
`<div style="padding:0.4rem 0.7rem;cursor:pointer;font-size:0.8rem;font-family:monospace;color:#8b949e;border-bottom:1px solid #21262d;transition:background 0.15s;" onmouseenter="this.style.background='#161b22';this.style.color='#e6edf3'" onmouseleave="this.style.background='';this.style.color='#8b949e'" onclick="document.getElementById('agent-workdir-input').value=this.textContent">${escapeHtml(d)}</div>`
|
|
3195
|
+
`<div style="padding:0.4rem 0.7rem;cursor:pointer;font-size:0.8rem;font-family:monospace;color:#8b949e;border-bottom:1px solid #21262d;transition:background 0.15s;" onmouseenter="this.style.background='#161b22';this.style.color='#e6edf3'" onmouseleave="this.style.background='';this.style.color='#8b949e'" onclick="document.getElementById('agent-workdir-input').value=this.textContent;fetchMcpServers(this.textContent)">${escapeHtml(d)}</div>`
|
|
2988
3196
|
).join('');
|
|
2989
3197
|
} catch(e) { /* ignore */ }
|
|
2990
3198
|
}
|
|
@@ -3006,6 +3214,7 @@ function closeDirBrowser() {
|
|
|
3006
3214
|
function selectDirBrowser() {
|
|
3007
3215
|
document.getElementById('agent-workdir-input').value = dirBrowserCurrentPath;
|
|
3008
3216
|
closeDirBrowser();
|
|
3217
|
+
fetchMcpServers(dirBrowserCurrentPath);
|
|
3009
3218
|
}
|
|
3010
3219
|
|
|
3011
3220
|
function dirBrowserBack() {
|
|
@@ -3245,6 +3454,7 @@ function renderAgentUI(data) {
|
|
|
3245
3454
|
const pulseStyle = a.status === 'busy' ? 'animation:pulse-busy 2s infinite;' : '';
|
|
3246
3455
|
const roleIcon = role === 'manager' ? '💼' : role === 'builder' ? '🛠' : '🔍';
|
|
3247
3456
|
let statusDisplay;
|
|
3457
|
+
const hasStreamOutput = a.status === 'busy' && a.last_output && a.last_output.trim().length > 0;
|
|
3248
3458
|
if (a.status === 'busy' && BUSY_PHRASES[role]) {
|
|
3249
3459
|
const phrase = BUSY_PHRASES[role][busyPhraseIndices[role] % BUSY_PHRASES[role].length];
|
|
3250
3460
|
const customName = (userProfile.agent_names && userProfile.agent_names[role]) || role.charAt(0).toUpperCase() + role.slice(1);
|
|
@@ -3290,6 +3500,8 @@ function renderAgentUI(data) {
|
|
|
3290
3500
|
}
|
|
3291
3501
|
}
|
|
3292
3502
|
const errorBorder = (a.status === 'error' || a.status === 'timeout' || a.status === 'crashed') ? ' agent-card-error-border' : '';
|
|
3503
|
+
const outputMaxHeight = hasStreamOutput ? '6em' : '3em';
|
|
3504
|
+
const outputSnippet = escapeHtml((a.last_output || '').substring(0, hasStreamOutput ? 500 : 150));
|
|
3293
3505
|
return `<div class="agent-card${errorBorder}" style="${pulseStyle}">
|
|
3294
3506
|
<span class="agent-tooltip">${escapeHtml(ROLE_DESCRIPTIONS[role] || '')}</span>
|
|
3295
3507
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.6rem;">
|
|
@@ -3297,7 +3509,7 @@ function renderAgentUI(data) {
|
|
|
3297
3509
|
<span style="background:${color}20;color:${color};padding:2px 8px;border-radius:12px;font-size:0.7rem;font-weight:600;text-transform:uppercase;">${statusDisplay}</span>
|
|
3298
3510
|
</div>
|
|
3299
3511
|
<div style="font-size:0.75rem;color:#8b949e;line-height:1.6;">
|
|
3300
|
-
<div style="margin-top:0.3rem;color:#c9d1d9;font-size:0.72rem;max-height
|
|
3512
|
+
<div style="margin-top:0.3rem;color:#c9d1d9;font-size:0.72rem;max-height:${outputMaxHeight};overflow:hidden;text-overflow:ellipsis;white-space:pre-wrap;word-break:break-word;">${outputSnippet}</div>
|
|
3301
3513
|
</div>
|
|
3302
3514
|
${elapsedHtml}
|
|
3303
3515
|
${contextHtml}
|
|
@@ -3512,6 +3724,7 @@ async function loadSettings() {
|
|
|
3512
3724
|
|
|
3513
3725
|
loadModelPrefs();
|
|
3514
3726
|
await loadUserProfile();
|
|
3727
|
+
loadGlobalMcpDefaults();
|
|
3515
3728
|
settingsLoaded = true;
|
|
3516
3729
|
} catch (e) {
|
|
3517
3730
|
console.error('Settings load failed:', e);
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/dashboard.py
RENAMED
|
@@ -458,6 +458,27 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
458
458
|
pass
|
|
459
459
|
body = json.dumps({"path": path}).encode()
|
|
460
460
|
self._respond(200, "application/json", body)
|
|
461
|
+
elif self.path.startswith("/api/mcp-servers"):
|
|
462
|
+
parsed = urlparse(self.path)
|
|
463
|
+
qs = parse_qs(parsed.query)
|
|
464
|
+
workdir = qs.get("workdir", [""])[0]
|
|
465
|
+
servers = agent_runner.discover_mcp_servers(workdir) if workdir else []
|
|
466
|
+
cfg = agent_runner.load_builder_tools_config()
|
|
467
|
+
blocked: list[str] = []
|
|
468
|
+
has_project_config = False
|
|
469
|
+
if workdir:
|
|
470
|
+
if workdir in cfg:
|
|
471
|
+
blocked = cfg[workdir]
|
|
472
|
+
has_project_config = True
|
|
473
|
+
else:
|
|
474
|
+
blocked = cfg.get("_global_defaults", [])
|
|
475
|
+
global_blocked = cfg.get("_global_defaults", [])
|
|
476
|
+
body = json.dumps({"servers": servers, "blocked": blocked, "global_blocked": global_blocked, "has_project_config": has_project_config}).encode()
|
|
477
|
+
self._respond(200, "application/json", body)
|
|
478
|
+
elif self.path == "/api/global-mcp-defaults":
|
|
479
|
+
cfg = agent_runner.load_builder_tools_config()
|
|
480
|
+
body = json.dumps({"blocked": cfg.get("_global_defaults", [])}).encode()
|
|
481
|
+
self._respond(200, "application/json", body)
|
|
461
482
|
elif self.path == "/api/headroom-stats":
|
|
462
483
|
try:
|
|
463
484
|
req = urllib.request.Request("http://127.0.0.1:8787/stats")
|
|
@@ -719,14 +740,16 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
719
740
|
_LAST_WORKDIR_FILE.write_text(working_dir, encoding="utf-8")
|
|
720
741
|
except OSError:
|
|
721
742
|
pass
|
|
722
|
-
auto_commit = data.get("auto_commit",
|
|
743
|
+
auto_commit = data.get("auto_commit", False)
|
|
723
744
|
timeout_minutes = int(data.get("timeout_minutes", 60))
|
|
724
745
|
manager_model = data.get("manager_model", "")
|
|
725
746
|
builder_model = data.get("builder_model", "")
|
|
726
747
|
reviewer_model = data.get("reviewer_model", "")
|
|
727
748
|
user_name = str(data.get("user_name", "")).strip()
|
|
749
|
+
blocked_mcp = data.get("blocked_mcp_servers", [])
|
|
728
750
|
result = agent_runner.start_run(task=task, working_dir=working_dir, context=context, auto_commit=auto_commit, timeout_minutes=timeout_minutes,
|
|
729
|
-
manager_model=manager_model, builder_model=builder_model, reviewer_model=reviewer_model, user_name=user_name
|
|
751
|
+
manager_model=manager_model, builder_model=builder_model, reviewer_model=reviewer_model, user_name=user_name,
|
|
752
|
+
blocked_mcp_servers=blocked_mcp)
|
|
730
753
|
body = json.dumps(result).encode()
|
|
731
754
|
status_code = 200 if "session_id" in result else 400
|
|
732
755
|
self._respond(status_code, "application/json", body)
|
|
@@ -768,6 +791,49 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
768
791
|
except Exception as exc:
|
|
769
792
|
body = json.dumps({"success": False, "error": str(exc)}).encode()
|
|
770
793
|
self._respond(500, "application/json", body)
|
|
794
|
+
elif self.path == "/api/builder-tools-config":
|
|
795
|
+
try:
|
|
796
|
+
content_len = int(self.headers.get("Content-Length", 0))
|
|
797
|
+
raw = self.rfile.read(content_len).decode("utf-8") if content_len else ""
|
|
798
|
+
data = json.loads(raw)
|
|
799
|
+
workdir = data.get("workdir", "").strip()
|
|
800
|
+
blocked = data.get("blocked", [])
|
|
801
|
+
if not workdir:
|
|
802
|
+
body = json.dumps({"success": False, "error": "Missing 'workdir' field."}).encode()
|
|
803
|
+
self._respond(400, "application/json", body)
|
|
804
|
+
return
|
|
805
|
+
cfg = agent_runner.load_builder_tools_config()
|
|
806
|
+
cfg[workdir] = blocked
|
|
807
|
+
agent_runner.save_builder_tools_config(cfg)
|
|
808
|
+
body = json.dumps({"success": True}).encode()
|
|
809
|
+
self._respond(200, "application/json", body)
|
|
810
|
+
except (json.JSONDecodeError, ValueError):
|
|
811
|
+
body = json.dumps({"success": False, "error": "Invalid JSON body."}).encode()
|
|
812
|
+
self._respond(400, "application/json", body)
|
|
813
|
+
except Exception as exc:
|
|
814
|
+
body = json.dumps({"success": False, "error": str(exc)}).encode()
|
|
815
|
+
self._respond(500, "application/json", body)
|
|
816
|
+
elif self.path == "/api/global-mcp-defaults":
|
|
817
|
+
try:
|
|
818
|
+
content_len = int(self.headers.get("Content-Length", 0))
|
|
819
|
+
raw = self.rfile.read(content_len).decode("utf-8") if content_len else ""
|
|
820
|
+
data = json.loads(raw)
|
|
821
|
+
blocked = data.get("blocked", [])
|
|
822
|
+
cfg = agent_runner.load_builder_tools_config()
|
|
823
|
+
cfg["_global_defaults"] = blocked
|
|
824
|
+
if data.get("apply_all"):
|
|
825
|
+
for key in list(cfg.keys()):
|
|
826
|
+
if key != "_global_defaults":
|
|
827
|
+
cfg[key] = list(blocked)
|
|
828
|
+
agent_runner.save_builder_tools_config(cfg)
|
|
829
|
+
body = json.dumps({"success": True}).encode()
|
|
830
|
+
self._respond(200, "application/json", body)
|
|
831
|
+
except (json.JSONDecodeError, ValueError):
|
|
832
|
+
body = json.dumps({"success": False, "error": "Invalid JSON body."}).encode()
|
|
833
|
+
self._respond(400, "application/json", body)
|
|
834
|
+
except Exception as exc:
|
|
835
|
+
body = json.dumps({"success": False, "error": str(exc)}).encode()
|
|
836
|
+
self._respond(500, "application/json", body)
|
|
771
837
|
elif self.path == "/api/burn-report/reset":
|
|
772
838
|
try:
|
|
773
839
|
from .stats import reset_burn_stats
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/__main__.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/categories.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/compression.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/config.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/indexer.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/license.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/memory_db.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/search.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/server.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.6}/src/memstack_skill_loader/stats.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|