memstack-skill-loader 4.0.4__tar.gz → 4.0.5__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.5}/PKG-INFO +1 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/pyproject.toml +1 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/agent_runner.py +123 -11
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/dashboard.html +217 -4
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/dashboard.py +67 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/README.md +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/setup.cfg +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/categories.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/license.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/server.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/stats.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader.egg-info/SOURCES.txt +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
|
@@ -88,6 +88,49 @@ SYSTEM_PROMPTS = {
|
|
|
88
88
|
),
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
BUILDER_TOOLS_CONFIG_FILE = Path.home() / ".memstack" / "builder-tools-config.json"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# MCP server discovery & Builder tools config
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
def discover_mcp_servers(workdir: str) -> list[str]:
|
|
99
|
+
"""Read .mcp.json from workdir and return the list of MCP server name keys."""
|
|
100
|
+
try:
|
|
101
|
+
mcp_file = Path(workdir) / ".mcp.json"
|
|
102
|
+
if not mcp_file.is_file():
|
|
103
|
+
return []
|
|
104
|
+
data = json.loads(mcp_file.read_text(encoding="utf-8"))
|
|
105
|
+
servers = data.get("mcpServers", {})
|
|
106
|
+
return sorted(servers.keys()) if isinstance(servers, dict) else []
|
|
107
|
+
except Exception:
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def load_builder_tools_config() -> dict:
|
|
112
|
+
"""Load per-project blocked MCP server lists from ~/.memstack/builder-tools-config.json."""
|
|
113
|
+
try:
|
|
114
|
+
if BUILDER_TOOLS_CONFIG_FILE.is_file():
|
|
115
|
+
return json.loads(BUILDER_TOOLS_CONFIG_FILE.read_text(encoding="utf-8"))
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
return {}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def save_builder_tools_config(config: dict) -> None:
|
|
122
|
+
"""Save per-project blocked MCP server config."""
|
|
123
|
+
BUILDER_TOOLS_CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
BUILDER_TOOLS_CONFIG_FILE.write_text(json.dumps(config, indent=2), encoding="utf-8")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_blocked_servers_for_project(workdir: str) -> list[str]:
|
|
128
|
+
"""Return blocked MCP servers for a project, falling back to global defaults."""
|
|
129
|
+
cfg = load_builder_tools_config()
|
|
130
|
+
if workdir in cfg:
|
|
131
|
+
return cfg[workdir]
|
|
132
|
+
return cfg.get("_global_defaults", [])
|
|
133
|
+
|
|
91
134
|
|
|
92
135
|
# ---------------------------------------------------------------------------
|
|
93
136
|
# Project context gathering
|
|
@@ -259,6 +302,30 @@ def _parse_stream_json(raw: str) -> tuple[str, int, int, float, int]:
|
|
|
259
302
|
return text, input_tokens, output_tokens, cost_usd, context_tokens
|
|
260
303
|
|
|
261
304
|
|
|
305
|
+
def _extract_text_from_stream_line(line: str) -> Optional[str]:
|
|
306
|
+
"""Extract user-visible text from a single stream-json line.
|
|
307
|
+
|
|
308
|
+
Returns the text if the line contains assistant text or a result, else None.
|
|
309
|
+
"""
|
|
310
|
+
try:
|
|
311
|
+
obj = json.loads(line.strip())
|
|
312
|
+
except (json.JSONDecodeError, ValueError):
|
|
313
|
+
return None
|
|
314
|
+
msg_type = obj.get("type")
|
|
315
|
+
if msg_type == "assistant":
|
|
316
|
+
parts = []
|
|
317
|
+
for block in obj.get("message", {}).get("content", []):
|
|
318
|
+
if block.get("type") == "text":
|
|
319
|
+
t = block.get("text", "")
|
|
320
|
+
if t:
|
|
321
|
+
parts.append(t)
|
|
322
|
+
return "\n".join(parts) if parts else None
|
|
323
|
+
if msg_type == "result":
|
|
324
|
+
t = obj.get("result", "")
|
|
325
|
+
return t if t else None
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
|
|
262
329
|
# ---------------------------------------------------------------------------
|
|
263
330
|
# Direct API invocation (Manager / Reviewer — no file tools needed)
|
|
264
331
|
# ---------------------------------------------------------------------------
|
|
@@ -359,7 +426,8 @@ def _invoke_api_agent(name: str, prompt: str, system_prompt: str,
|
|
|
359
426
|
def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[Path] = None,
|
|
360
427
|
skip_permissions: bool = False,
|
|
361
428
|
session_id: Optional[str] = None, timeout: int = AGENT_TIMEOUT,
|
|
362
|
-
model: str = ""
|
|
429
|
+
model: str = "", disallowed_tools: Optional[list[str]] = None,
|
|
430
|
+
session: Optional["Session"] = None) -> tuple[str, int, int, int]:
|
|
363
431
|
"""Run a single claude --print invocation and return the output."""
|
|
364
432
|
claude_bin = shutil.which("claude")
|
|
365
433
|
if not claude_bin:
|
|
@@ -370,6 +438,9 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
370
438
|
cmd.append("--dangerously-skip-permissions")
|
|
371
439
|
if model:
|
|
372
440
|
cmd.extend(["--model", model])
|
|
441
|
+
if disallowed_tools:
|
|
442
|
+
patterns = ",".join(f"mcp__{srv}__*" for srv in disallowed_tools)
|
|
443
|
+
cmd.extend(["--disallowedTools", patterns])
|
|
373
444
|
|
|
374
445
|
if log_path:
|
|
375
446
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -393,9 +464,6 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
393
464
|
stdout=subprocess.PIPE,
|
|
394
465
|
stderr=subprocess.PIPE,
|
|
395
466
|
cwd=working_dir,
|
|
396
|
-
text=True,
|
|
397
|
-
encoding="utf-8",
|
|
398
|
-
errors="replace",
|
|
399
467
|
env=_build_env(),
|
|
400
468
|
creationflags=0,
|
|
401
469
|
)
|
|
@@ -413,12 +481,38 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
413
481
|
killed_by_watchdog.set()
|
|
414
482
|
proc.kill()
|
|
415
483
|
|
|
484
|
+
stdout_chunks: list[str] = []
|
|
485
|
+
stderr_chunks: list[str] = []
|
|
486
|
+
partial_text: list[str] = []
|
|
487
|
+
|
|
488
|
+
def _read_stdout() -> None:
|
|
489
|
+
for raw_line in proc.stdout:
|
|
490
|
+
line = raw_line.decode("utf-8", errors="replace")
|
|
491
|
+
stdout_chunks.append(line)
|
|
492
|
+
extracted = _extract_text_from_stream_line(line)
|
|
493
|
+
if extracted and session is not None:
|
|
494
|
+
partial_text.append(extracted)
|
|
495
|
+
try:
|
|
496
|
+
session.agents[name]["last_output"] = "".join(partial_text)[-500:]
|
|
497
|
+
session._save_state()
|
|
498
|
+
except Exception:
|
|
499
|
+
pass
|
|
500
|
+
|
|
501
|
+
def _read_stderr() -> None:
|
|
502
|
+
for raw_line in proc.stderr:
|
|
503
|
+
stderr_chunks.append(raw_line.decode("utf-8", errors="replace"))
|
|
504
|
+
|
|
505
|
+
t_out = threading.Thread(target=_read_stdout, daemon=True)
|
|
506
|
+
t_err = threading.Thread(target=_read_stderr, daemon=True)
|
|
507
|
+
t_out.start()
|
|
508
|
+
t_err.start()
|
|
509
|
+
|
|
416
510
|
watchdog = threading.Timer(timeout, _watchdog_kill)
|
|
417
511
|
watchdog.daemon = True
|
|
418
512
|
watchdog.start()
|
|
419
513
|
|
|
420
514
|
try:
|
|
421
|
-
|
|
515
|
+
proc.wait()
|
|
422
516
|
except Exception as e:
|
|
423
517
|
watchdog.cancel()
|
|
424
518
|
with _lock:
|
|
@@ -426,20 +520,26 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
426
520
|
_current_process = None
|
|
427
521
|
if log_path:
|
|
428
522
|
with open(log_path, "a", encoding="utf-8") as f:
|
|
429
|
-
f.write(f"[{ts}]
|
|
430
|
-
raise RuntimeError(f"{name}
|
|
523
|
+
f.write(f"[{ts}] wait() error: {e}\n")
|
|
524
|
+
raise RuntimeError(f"{name} wait() failed: {e}") from e
|
|
431
525
|
finally:
|
|
432
526
|
watchdog.cancel()
|
|
433
527
|
with _lock:
|
|
434
528
|
if _current_process is proc:
|
|
435
529
|
_current_process = None
|
|
436
530
|
|
|
531
|
+
t_out.join(timeout=10)
|
|
532
|
+
t_err.join(timeout=10)
|
|
533
|
+
|
|
437
534
|
if killed_by_watchdog.is_set():
|
|
438
535
|
raise subprocess.TimeoutExpired(cmd, timeout)
|
|
439
536
|
|
|
537
|
+
stdout_data = "".join(stdout_chunks)
|
|
538
|
+
stderr_data = "".join(stderr_chunks)
|
|
539
|
+
|
|
440
540
|
if log_path:
|
|
441
541
|
with open(log_path, "a", encoding="utf-8") as f:
|
|
442
|
-
f.write(f"[{ts}]
|
|
542
|
+
f.write(f"[{ts}] Process exited: stdout={len(stdout_data)} stderr={len(stderr_data)}\n")
|
|
443
543
|
|
|
444
544
|
raw_stdout = stdout_data or ""
|
|
445
545
|
stderr = stderr_data or ""
|
|
@@ -485,7 +585,8 @@ class Session:
|
|
|
485
585
|
"""Tracks the full state of an agent run session."""
|
|
486
586
|
|
|
487
587
|
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 = ""
|
|
588
|
+
manager_model: str = "", builder_model: str = "", reviewer_model: str = "", user_name: str = "",
|
|
589
|
+
blocked_mcp_servers: Optional[list[str]] = None):
|
|
489
590
|
self.session_id = uuid.uuid4().hex[:12]
|
|
490
591
|
self.task = task
|
|
491
592
|
self.working_dir = working_dir
|
|
@@ -493,6 +594,7 @@ class Session:
|
|
|
493
594
|
self.user_name = user_name
|
|
494
595
|
self.context = context
|
|
495
596
|
self.auto_commit = auto_commit
|
|
597
|
+
self.blocked_mcp_servers = blocked_mcp_servers or []
|
|
496
598
|
self.auto_committed = False
|
|
497
599
|
self.timeout = max(5, min(120, timeout_minutes)) * 60
|
|
498
600
|
self.status = "running"
|
|
@@ -607,6 +709,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
607
709
|
# Step 1: Manager analyzes the task
|
|
608
710
|
session.agents["manager"]["status"] = "busy"
|
|
609
711
|
session.agents["manager"]["started_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
712
|
+
session.agents["manager"]["last_output"] = ""
|
|
610
713
|
session._save_state()
|
|
611
714
|
|
|
612
715
|
context = _gather_project_context(session.working_dir)
|
|
@@ -666,6 +769,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
666
769
|
# Builder
|
|
667
770
|
session.agents["builder"]["status"] = "busy"
|
|
668
771
|
session.agents["builder"]["started_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
772
|
+
session.agents["builder"]["last_output"] = ""
|
|
669
773
|
session._save_state()
|
|
670
774
|
|
|
671
775
|
builder_prompt = (
|
|
@@ -684,6 +788,8 @@ def _orchestrate(session: Session) -> None:
|
|
|
684
788
|
skip_permissions=True, session_id=session.session_id,
|
|
685
789
|
timeout=session.timeout,
|
|
686
790
|
model=session.models.get("builder", ""),
|
|
791
|
+
disallowed_tools=session.blocked_mcp_servers,
|
|
792
|
+
session=session,
|
|
687
793
|
)
|
|
688
794
|
except subprocess.TimeoutExpired:
|
|
689
795
|
session.agents["builder"]["status"] = "timeout"
|
|
@@ -733,6 +839,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
733
839
|
# Reviewer
|
|
734
840
|
session.agents["reviewer"]["status"] = "busy"
|
|
735
841
|
session.agents["reviewer"]["started_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
842
|
+
session.agents["reviewer"]["last_output"] = ""
|
|
736
843
|
session._save_state()
|
|
737
844
|
|
|
738
845
|
reviewer_prompt = (
|
|
@@ -876,7 +983,8 @@ _BLOCKED_NIX = {"/etc", "/var", "/proc", "/sys", "/boot"}
|
|
|
876
983
|
|
|
877
984
|
|
|
878
985
|
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 = ""
|
|
986
|
+
manager_model: str = "", builder_model: str = "", reviewer_model: str = "", user_name: str = "",
|
|
987
|
+
blocked_mcp_servers: Optional[list[str]] = None) -> dict:
|
|
880
988
|
"""Start a new agent run. Returns session info."""
|
|
881
989
|
global _current_session, _orchestration_thread
|
|
882
990
|
|
|
@@ -895,12 +1003,16 @@ def start_run(task: str, working_dir: Optional[str] = None, context: Optional[st
|
|
|
895
1003
|
if any(str_wd == bp or str_wd.startswith(bp + "/") for bp in _BLOCKED_NIX):
|
|
896
1004
|
return {"error": f"Working directory not allowed: {wd}"}
|
|
897
1005
|
|
|
1006
|
+
if blocked_mcp_servers is None:
|
|
1007
|
+
blocked_mcp_servers = get_blocked_servers_for_project(str(wd))
|
|
1008
|
+
|
|
898
1009
|
with _lock:
|
|
899
1010
|
if _current_session and _current_session.status == "running":
|
|
900
1011
|
return {"error": "A session is already running", "session_id": _current_session.session_id}
|
|
901
1012
|
|
|
902
1013
|
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
|
|
1014
|
+
manager_model=manager_model, builder_model=builder_model, reviewer_model=reviewer_model, user_name=user_name,
|
|
1015
|
+
blocked_mcp_servers=blocked_mcp_servers)
|
|
904
1016
|
_current_session = session
|
|
905
1017
|
|
|
906
1018
|
session._save_state()
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/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.5}/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")
|
|
@@ -725,8 +746,10 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
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.5}/src/memstack_skill_loader/__main__.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/categories.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/compression.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/config.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/indexer.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/license.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/memory_db.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/search.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/src/memstack_skill_loader/server.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.0.4 → memstack_skill_loader-4.0.5}/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
|