memstack-skill-loader 4.0.5__tar.gz → 4.0.7__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.5/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.0.7}/PKG-INFO +1 -1
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/pyproject.toml +1 -1
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/agent_runner.py +25 -2
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/dashboard.html +92 -58
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/dashboard.py +43 -17
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/stats.py +57 -1
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/README.md +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/setup.cfg +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/categories.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/license.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/server.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader.egg-info/SOURCES.txt +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/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 "
|
|
@@ -189,6 +190,24 @@ def _extract_task_text(raw: str) -> str:
|
|
|
189
190
|
return result[:72]
|
|
190
191
|
|
|
191
192
|
|
|
193
|
+
def _clean_task_description(task: str) -> str:
|
|
194
|
+
"""Strip Working directory/Branch boilerplate, returning the user's task description."""
|
|
195
|
+
import re
|
|
196
|
+
_BOILERPLATE_LINE = re.compile(
|
|
197
|
+
r'^(working\s+directory:|branch:|read\s+all\s+files\s+before\s+modifying\.?)',
|
|
198
|
+
re.IGNORECASE
|
|
199
|
+
)
|
|
200
|
+
lines = task.splitlines()
|
|
201
|
+
start = 0
|
|
202
|
+
for i, line in enumerate(lines):
|
|
203
|
+
stripped = line.strip()
|
|
204
|
+
if not stripped or _BOILERPLATE_LINE.match(stripped):
|
|
205
|
+
start = i + 1
|
|
206
|
+
else:
|
|
207
|
+
break
|
|
208
|
+
return "\n".join(lines[start:]).strip()
|
|
209
|
+
|
|
210
|
+
|
|
192
211
|
def _extract_commit_from_reviewer(reviewer_output: str) -> str:
|
|
193
212
|
"""Extract a commit-friendly summary from the reviewer's APPROVED message."""
|
|
194
213
|
import re
|
|
@@ -333,6 +352,7 @@ def _extract_text_from_stream_line(line: str) -> Optional[str]:
|
|
|
333
352
|
def _invoke_api_agent(name: str, prompt: str, system_prompt: str,
|
|
334
353
|
log_path: Optional[Path] = None, timeout: int = 600,
|
|
335
354
|
model: str = "", session_id: Optional[str] = None,
|
|
355
|
+
working_dir: str = "",
|
|
336
356
|
) -> tuple[str, int, int]:
|
|
337
357
|
"""Call the Anthropic Messages API directly via httpx.
|
|
338
358
|
|
|
@@ -410,7 +430,7 @@ def _invoke_api_agent(name: str, prompt: str, system_prompt: str,
|
|
|
410
430
|
|
|
411
431
|
try:
|
|
412
432
|
log_agent_invocation(
|
|
413
|
-
name, len(prompt), len(output), session_id,
|
|
433
|
+
name, len(prompt), len(output), session_id, working_dir,
|
|
414
434
|
input_tokens=input_tokens, output_tokens=output_tokens, cost_usd=0.0,
|
|
415
435
|
)
|
|
416
436
|
except Exception:
|
|
@@ -645,6 +665,7 @@ class Session:
|
|
|
645
665
|
meta = {
|
|
646
666
|
"session_id": self.session_id,
|
|
647
667
|
"task": self.task,
|
|
668
|
+
"task_description": _clean_task_description(self.task),
|
|
648
669
|
"status": self.status,
|
|
649
670
|
"started_at": self.started_at,
|
|
650
671
|
"iteration": self.iteration,
|
|
@@ -727,6 +748,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
727
748
|
timeout=min(600, session.timeout),
|
|
728
749
|
model=session.models.get("manager", ""),
|
|
729
750
|
session_id=session.session_id,
|
|
751
|
+
working_dir=session.working_dir,
|
|
730
752
|
)
|
|
731
753
|
except subprocess.TimeoutExpired:
|
|
732
754
|
session.agents["manager"]["status"] = "timeout"
|
|
@@ -855,6 +877,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
855
877
|
timeout=session.timeout,
|
|
856
878
|
model=session.models.get("reviewer", ""),
|
|
857
879
|
session_id=session.session_id,
|
|
880
|
+
working_dir=session.working_dir,
|
|
858
881
|
)
|
|
859
882
|
except subprocess.TimeoutExpired:
|
|
860
883
|
session.agents["reviewer"]["status"] = "timeout"
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/dashboard.html
RENAMED
|
@@ -686,6 +686,8 @@
|
|
|
686
686
|
color: #484f58;
|
|
687
687
|
font-size: 0.85rem;
|
|
688
688
|
}
|
|
689
|
+
.stat-green, .card .value.stat-green { color: #3fb950; }
|
|
690
|
+
.stat-orange, .card .value.stat-orange { color: #f0883e; }
|
|
689
691
|
.session-row {
|
|
690
692
|
display: flex;
|
|
691
693
|
align-items: center;
|
|
@@ -1401,7 +1403,7 @@
|
|
|
1401
1403
|
<div class="page-header" style="display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:0.5rem">
|
|
1402
1404
|
<div>
|
|
1403
1405
|
<h2>Burn Report</h2>
|
|
1404
|
-
<p class="meta">Token usage & cost analytics ·
|
|
1406
|
+
<p class="meta">Token usage & cost analytics · Opus ($15/$75 per M) · Sonnet ($3/$15 per M)</p>
|
|
1405
1407
|
</div>
|
|
1406
1408
|
<div style="display:flex;align-items:center;gap:0.6rem">
|
|
1407
1409
|
<div class="burn-period-toggle">
|
|
@@ -1508,7 +1510,6 @@
|
|
|
1508
1510
|
<input id="agent-workdir-input" type="text" placeholder="C:\\Projects\\my-app" style="flex:1;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#e6edf3;padding:0.6rem 0.7rem;font-size:0.88rem;font-family:monospace;">
|
|
1509
1511
|
<button onclick="openDirBrowser()" style="background:#21262d;color:#8b949e;border:1px solid #30363d;padding:0.6rem 0.9rem;border-radius:6px;cursor:pointer;font-size:0.82rem;font-weight:600;white-space:nowrap;transition:background 0.2s,color 0.2s;" onmouseenter="this.style.background='#30363d';this.style.color='#e6edf3'" onmouseleave="this.style.background='#21262d';this.style.color='#8b949e'">Browse</button>
|
|
1510
1512
|
</div>
|
|
1511
|
-
<div id="recent-projects-dropdown" style="margin-bottom:1rem;max-height:140px;overflow-y:auto;border:1px solid #30363d;border-radius:6px;background:#0d1117;display:none;"></div>
|
|
1512
1513
|
<label style="display:block;font-size:0.82rem;color:#8b949e;margin-bottom:0.4rem;font-weight:600;">Additional Context (optional)</label>
|
|
1513
1514
|
<textarea id="agent-context-input" rows="3" placeholder="Optional. Examples: - This project uses Flask, not FastAPI - Don't touch files in the /api directory - Follow existing code style, no type hints - The database is SQLite, not Postgres" 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 0 0.8rem 0;"></textarea>
|
|
1514
1515
|
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:0.8rem;">
|
|
@@ -2710,6 +2711,7 @@ async function saveDiary() {
|
|
|
2710
2711
|
b.textContent = 'Diary Saved!';
|
|
2711
2712
|
b.disabled = true;
|
|
2712
2713
|
}
|
|
2714
|
+
localStorage.setItem('memstack-diary-saved-session', currentAgentSessionId);
|
|
2713
2715
|
if (summary) _renderDiarySummary(summary);
|
|
2714
2716
|
}
|
|
2715
2717
|
function _diaryFail(b, msg) {
|
|
@@ -2776,6 +2778,7 @@ async function saveDiaryCompleted() {
|
|
|
2776
2778
|
btn.classList.add('saved');
|
|
2777
2779
|
btn.textContent = 'Diary Saved!';
|
|
2778
2780
|
btn.disabled = true;
|
|
2781
|
+
localStorage.setItem('memstack-diary-saved-session', currentAgentSessionId);
|
|
2779
2782
|
if (summary) _renderDiarySummary(summary);
|
|
2780
2783
|
}
|
|
2781
2784
|
function _completedDiaryFail(msg) {
|
|
@@ -2885,17 +2888,38 @@ async function resetAgentUI() {
|
|
|
2885
2888
|
document.getElementById('agent-completed').style.display = 'none';
|
|
2886
2889
|
document.getElementById('agent-commit-msg').value = '';
|
|
2887
2890
|
document.getElementById('agent-git-output').style.display = 'none';
|
|
2891
|
+
// Clear task and context inputs
|
|
2892
|
+
document.getElementById('agent-task-input').value = '';
|
|
2888
2893
|
document.getElementById('agent-context-input').value = '';
|
|
2894
|
+
// Reset timeout to default
|
|
2895
|
+
document.getElementById('agent-timeout-input').value = '60';
|
|
2896
|
+
// Collapse Model Selection and MCP Tools
|
|
2897
|
+
const modelDetails = document.querySelector('.model-selection-details');
|
|
2898
|
+
if (modelDetails) modelDetails.removeAttribute('open');
|
|
2899
|
+
const mcpDetails = document.getElementById('mcp-tools-details');
|
|
2900
|
+
if (mcpDetails) mcpDetails.removeAttribute('open');
|
|
2901
|
+
// Uncheck auto-commit
|
|
2902
|
+
const autoCommit = document.getElementById('agent-autocommit-checkbox');
|
|
2903
|
+
if (autoCommit) autoCommit.checked = false;
|
|
2904
|
+
// Reset git buttons
|
|
2889
2905
|
for (const [id, label] of [['agent-commit-btn', 'Commit'], ['agent-push-btn', 'Push'], ['agent-commit-push-btn', 'Commit & Push']]) {
|
|
2890
2906
|
const b = document.getElementById(id);
|
|
2891
2907
|
b.textContent = label; b.disabled = false; b.style.cursor = 'pointer'; b.style.opacity = '1'; b.style.background = '';
|
|
2892
2908
|
}
|
|
2893
2909
|
lastGitOutput = '';
|
|
2894
2910
|
lastGitSuccess = true;
|
|
2911
|
+
// Reset diary summary card
|
|
2895
2912
|
const dsCard = document.getElementById('diary-summary-card');
|
|
2896
2913
|
if (dsCard) { dsCard.style.display = 'none'; dsCard.innerHTML = ''; }
|
|
2897
|
-
|
|
2898
|
-
|
|
2914
|
+
// Reset both diary buttons back to default
|
|
2915
|
+
for (const btnId of ['header-diary-btn', 'completed-diary-btn']) {
|
|
2916
|
+
const b = document.getElementById(btnId);
|
|
2917
|
+
if (b) { b.textContent = 'Save Diary'; b.classList.remove('saving', 'saved', 'save-failed'); b.disabled = false; }
|
|
2918
|
+
}
|
|
2919
|
+
// Restore working directory from localStorage (user likely wants same project)
|
|
2920
|
+
const cachedWd = localStorage.getItem('memstack-last-workdir');
|
|
2921
|
+
const wdInput = document.getElementById('agent-workdir-input');
|
|
2922
|
+
if (cachedWd) { wdInput.value = cachedWd; fetchMcpServers(cachedWd); }
|
|
2899
2923
|
}
|
|
2900
2924
|
|
|
2901
2925
|
async function startAgentTask() {
|
|
@@ -2921,6 +2945,7 @@ async function startAgentTask() {
|
|
|
2921
2945
|
body.user_name = userProfile.user_name || '';
|
|
2922
2946
|
const blockedMcp = getBlockedMcpServers();
|
|
2923
2947
|
if (blockedMcp.length) body.blocked_mcp_servers = blockedMcp;
|
|
2948
|
+
if (workDir) localStorage.setItem('memstack-last-workdir', workDir);
|
|
2924
2949
|
const res = await fetch('/api/agent-run', {method:'POST', headers: AUTH_HEADERS, body: JSON.stringify(body)});
|
|
2925
2950
|
const data = await res.json();
|
|
2926
2951
|
if (data.error) { alert(data.error); return; }
|
|
@@ -2966,7 +2991,6 @@ async function loadAgentMonitor() {
|
|
|
2966
2991
|
wdInput.addEventListener('blur', () => fetchMcpServers(wdInput.value.trim()));
|
|
2967
2992
|
}
|
|
2968
2993
|
fetchAgentStatus();
|
|
2969
|
-
loadRecentProjects();
|
|
2970
2994
|
loadLastWorkdir();
|
|
2971
2995
|
loadModelPrefs();
|
|
2972
2996
|
if (!agentRefreshInterval) {
|
|
@@ -2980,8 +3004,21 @@ async function loadLastWorkdir() {
|
|
|
2980
3004
|
if (input.value.trim()) return;
|
|
2981
3005
|
const res = await fetch('/api/last-workdir', {headers: {'X-Auth-Token': AUTH_TOKEN}});
|
|
2982
3006
|
const data = await res.json();
|
|
2983
|
-
if (data.path) {
|
|
2984
|
-
|
|
3007
|
+
if (data.path) {
|
|
3008
|
+
input.value = data.path;
|
|
3009
|
+
localStorage.setItem('memstack-last-workdir', data.path);
|
|
3010
|
+
fetchMcpServers(data.path);
|
|
3011
|
+
} else {
|
|
3012
|
+
const cached = localStorage.getItem('memstack-last-workdir');
|
|
3013
|
+
if (cached) { input.value = cached; fetchMcpServers(cached); }
|
|
3014
|
+
}
|
|
3015
|
+
} catch(e) {
|
|
3016
|
+
const cached = localStorage.getItem('memstack-last-workdir');
|
|
3017
|
+
if (cached) {
|
|
3018
|
+
const input = document.getElementById('agent-workdir-input');
|
|
3019
|
+
if (!input.value.trim()) { input.value = cached; fetchMcpServers(cached); }
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
2985
3022
|
}
|
|
2986
3023
|
|
|
2987
3024
|
/* ─── Builder MCP Tools ─── */
|
|
@@ -3128,13 +3165,8 @@ async function agentGitPush() {
|
|
|
3128
3165
|
try {
|
|
3129
3166
|
const res = await fetch('/api/agent/push', {method: 'POST', headers: AUTH_HEADERS, body: '{}'});
|
|
3130
3167
|
const data = await res.json();
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
lastGitSuccess = false;
|
|
3134
|
-
} else {
|
|
3135
|
-
lastGitOutput = 'Pushed!';
|
|
3136
|
-
lastGitSuccess = true;
|
|
3137
|
-
}
|
|
3168
|
+
lastGitOutput = data.output || data.error || data.message || 'Done';
|
|
3169
|
+
lastGitSuccess = !!data.success;
|
|
3138
3170
|
outputEl.textContent = lastGitOutput;
|
|
3139
3171
|
outputEl.style.color = lastGitSuccess ? '#3fb950' : '#f85149';
|
|
3140
3172
|
if (lastGitSuccess) _btnSuccess(btn, 'Pushed!');
|
|
@@ -3152,27 +3184,12 @@ async function agentGitCommitAndPush() {
|
|
|
3152
3184
|
const btn = document.getElementById('agent-commit-push-btn');
|
|
3153
3185
|
const outputEl = document.getElementById('agent-git-output');
|
|
3154
3186
|
outputEl.style.display = 'block';
|
|
3155
|
-
outputEl.textContent = 'Committing...';
|
|
3187
|
+
outputEl.textContent = 'Committing & pushing...';
|
|
3156
3188
|
try {
|
|
3157
|
-
const
|
|
3158
|
-
const
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
lastGitSuccess = false;
|
|
3162
|
-
outputEl.textContent = lastGitOutput;
|
|
3163
|
-
outputEl.style.color = '#f85149';
|
|
3164
|
-
return;
|
|
3165
|
-
}
|
|
3166
|
-
outputEl.textContent = 'Pushing...';
|
|
3167
|
-
const pushRes = await fetch('/api/agent/push', {method: 'POST', headers: AUTH_HEADERS, body: '{}'});
|
|
3168
|
-
const pushData = await pushRes.json();
|
|
3169
|
-
if (pushData.status === 'error') {
|
|
3170
|
-
lastGitOutput = pushData.message || 'Push failed.';
|
|
3171
|
-
lastGitSuccess = false;
|
|
3172
|
-
} else {
|
|
3173
|
-
lastGitOutput = 'Committed & pushed!';
|
|
3174
|
-
lastGitSuccess = true;
|
|
3175
|
-
}
|
|
3189
|
+
const res = await fetch('/api/agent-commit-push', {method: 'POST', headers: AUTH_HEADERS, body: JSON.stringify({message: msg})});
|
|
3190
|
+
const data = await res.json();
|
|
3191
|
+
lastGitOutput = data.output || data.error || 'Done';
|
|
3192
|
+
lastGitSuccess = !!data.success;
|
|
3176
3193
|
outputEl.textContent = lastGitOutput;
|
|
3177
3194
|
outputEl.style.color = lastGitSuccess ? '#3fb950' : '#f85149';
|
|
3178
3195
|
if (lastGitSuccess) _btnSuccess(btn, 'Committed & Pushed!');
|
|
@@ -3184,19 +3201,6 @@ async function agentGitCommitAndPush() {
|
|
|
3184
3201
|
}
|
|
3185
3202
|
}
|
|
3186
3203
|
|
|
3187
|
-
async function loadRecentProjects() {
|
|
3188
|
-
try {
|
|
3189
|
-
const res = await fetch('/api/recent-projects', {headers: AUTH_GET});
|
|
3190
|
-
const dirs = await res.json();
|
|
3191
|
-
const dropdown = document.getElementById('recent-projects-dropdown');
|
|
3192
|
-
if (!dirs.length) { dropdown.style.display = 'none'; return; }
|
|
3193
|
-
dropdown.style.display = 'block';
|
|
3194
|
-
dropdown.innerHTML = dirs.map(d =>
|
|
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>`
|
|
3196
|
-
).join('');
|
|
3197
|
-
} catch(e) { /* ignore */ }
|
|
3198
|
-
}
|
|
3199
|
-
|
|
3200
3204
|
/* ─── Directory Browser ─── */
|
|
3201
3205
|
let dirBrowserCurrentPath = '';
|
|
3202
3206
|
|
|
@@ -3340,6 +3344,25 @@ function renderAgentUI(data) {
|
|
|
3340
3344
|
}, 1000);
|
|
3341
3345
|
}
|
|
3342
3346
|
}
|
|
3347
|
+
// Auto-save diary on task completion
|
|
3348
|
+
const _diaryAlreadySaved = currentAgentSessionId && localStorage.getItem('memstack-diary-saved-session') === currentAgentSessionId;
|
|
3349
|
+
if (_diaryAlreadySaved) {
|
|
3350
|
+
setTimeout(() => {
|
|
3351
|
+
const cBtn = document.getElementById('completed-diary-btn');
|
|
3352
|
+
if (cBtn) {
|
|
3353
|
+
cBtn.classList.add('saved');
|
|
3354
|
+
cBtn.textContent = 'Diary Saved!';
|
|
3355
|
+
cBtn.disabled = true;
|
|
3356
|
+
}
|
|
3357
|
+
}, 50);
|
|
3358
|
+
} else {
|
|
3359
|
+
setTimeout(() => {
|
|
3360
|
+
const cBtn = document.getElementById('completed-diary-btn');
|
|
3361
|
+
if (cBtn && cBtn.textContent === 'Save Diary') {
|
|
3362
|
+
saveDiaryCompleted();
|
|
3363
|
+
}
|
|
3364
|
+
}, 500);
|
|
3365
|
+
}
|
|
3343
3366
|
}
|
|
3344
3367
|
launcher.style.display = 'none';
|
|
3345
3368
|
active.style.display = 'none';
|
|
@@ -3386,8 +3409,19 @@ function renderAgentUI(data) {
|
|
|
3386
3409
|
document.getElementById('agent-token-summary').innerHTML = '<h4>Token Usage</h4>' + tokenRows;
|
|
3387
3410
|
const commitInput = document.getElementById('agent-commit-msg');
|
|
3388
3411
|
if (commitInput && !commitInput.value) {
|
|
3389
|
-
const
|
|
3390
|
-
|
|
3412
|
+
const taskDesc = data.task || '';
|
|
3413
|
+
const prefix = /fix|bug|error/i.test(taskDesc) ? 'fix' : 'feat';
|
|
3414
|
+
let sourceText = (data.result || '').trim();
|
|
3415
|
+
sourceText = sourceText.replace(/approved after \d+ iteration\(s\):?\s*/gi, '').trim();
|
|
3416
|
+
sourceText = sourceText.replace(/approved:\s*/gi, '').trim();
|
|
3417
|
+
if (!sourceText) sourceText = taskDesc;
|
|
3418
|
+
let summary = sourceText.split(/\.\s+|\.$|\n/)[0].trim() || 'dashboard updates';
|
|
3419
|
+
if (summary.length > 72) {
|
|
3420
|
+
const cut = summary.substring(0, 72);
|
|
3421
|
+
const sp = cut.lastIndexOf(' ');
|
|
3422
|
+
summary = sp > 0 ? cut.substring(0, sp) : cut;
|
|
3423
|
+
}
|
|
3424
|
+
commitInput.value = 'agent: ' + prefix + ': ' + summary;
|
|
3391
3425
|
autoResize(commitInput);
|
|
3392
3426
|
}
|
|
3393
3427
|
const commitBtn = document.getElementById('agent-commit-btn');
|
|
@@ -3487,7 +3521,7 @@ function renderAgentUI(data) {
|
|
|
3487
3521
|
const barColor = pct < 0 ? '' : pct <= 50 ? 'context-bar-green' : pct <= 77 ? 'context-bar-yellow' : 'context-bar-red';
|
|
3488
3522
|
const contextHtml = pct < 0
|
|
3489
3523
|
? '<div class="context-bar-container">Context: unknown</div>'
|
|
3490
|
-
: '<div class="context-bar-container">' + tokens.toLocaleString() + ' / 200,000
|
|
3524
|
+
: '<div class="context-bar-container">' + tokens.toLocaleString() + ' / 200,000 - Context Window: ' + pct + '%<div class="context-bar"><div class="context-bar-fill ' + barColor + '" style="width:' + pct + '%"></div></div></div>';
|
|
3491
3525
|
if (pct >= 65 && !diaryAutoSaved[role] && tokens > 0) {
|
|
3492
3526
|
const anyStarted = roleOrder.some(r => {
|
|
3493
3527
|
const ag = agents[r] || {};
|
|
@@ -3864,15 +3898,15 @@ async function loadHeadroomStats(burnData) {
|
|
|
3864
3898
|
const el = document.getElementById('headroom-cards');
|
|
3865
3899
|
const lifetimeEl = document.getElementById('headroom-lifetime');
|
|
3866
3900
|
|
|
3867
|
-
const
|
|
3868
|
-
const
|
|
3869
|
-
const
|
|
3870
|
-
const
|
|
3901
|
+
const opusTokens = (burnData && burnData.opus_tokens) || 0;
|
|
3902
|
+
const opusCost = (burnData && burnData.opus_cost) || 0;
|
|
3903
|
+
const sonnetTokens = (burnData && burnData.sonnet_tokens) || 0;
|
|
3904
|
+
const sonnetCost = (burnData && burnData.sonnet_cost) || 0;
|
|
3871
3905
|
el.innerHTML = `
|
|
3872
|
-
<div class="card"><div class="value
|
|
3873
|
-
<div class="card"><div class="value
|
|
3874
|
-
<div class="card"><div class="value
|
|
3875
|
-
<div class="card"><div class="value
|
|
3906
|
+
<div class="card"><div class="value stat-green">${fmt(opusTokens)}</div><div class="label">Opus Tokens</div></div>
|
|
3907
|
+
<div class="card"><div class="value stat-orange">$${opusCost.toFixed(2)}</div><div class="label">Opus Cost</div></div>
|
|
3908
|
+
<div class="card"><div class="value stat-green">${fmt(sonnetTokens)}</div><div class="label">Sonnet Tokens</div></div>
|
|
3909
|
+
<div class="card"><div class="value stat-orange">$${sonnetCost.toFixed(2)}</div><div class="label">Sonnet Cost</div></div>
|
|
3876
3910
|
`;
|
|
3877
3911
|
|
|
3878
3912
|
// Show Headroom lifetime stats as a subtle info line when available
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/dashboard.py
RENAMED
|
@@ -740,7 +740,7 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
740
740
|
_LAST_WORKDIR_FILE.write_text(working_dir, encoding="utf-8")
|
|
741
741
|
except OSError:
|
|
742
742
|
pass
|
|
743
|
-
auto_commit = data.get("auto_commit",
|
|
743
|
+
auto_commit = data.get("auto_commit", False)
|
|
744
744
|
timeout_minutes = int(data.get("timeout_minutes", 60))
|
|
745
745
|
manager_model = data.get("manager_model", "")
|
|
746
746
|
builder_model = data.get("builder_model", "")
|
|
@@ -987,7 +987,7 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
987
987
|
status = agent_runner.get_status()
|
|
988
988
|
work_dir = status.get("working_dir", "")
|
|
989
989
|
if not work_dir or not Path(work_dir).is_dir():
|
|
990
|
-
body = json.dumps({"
|
|
990
|
+
body = json.dumps({"success": False, "error": "No valid working directory from last session."}).encode()
|
|
991
991
|
self._respond(400, "application/json", body)
|
|
992
992
|
return
|
|
993
993
|
|
|
@@ -998,34 +998,60 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
998
998
|
|
|
999
999
|
status_r = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True, cwd=work_dir, timeout=30)
|
|
1000
1000
|
if status_r.stdout.strip():
|
|
1001
|
-
body = json.dumps({"
|
|
1001
|
+
body = json.dumps({"success": False, "error": "Uncommitted changes. Commit first."}).encode()
|
|
1002
1002
|
self._respond(200, "application/json", body)
|
|
1003
1003
|
return
|
|
1004
1004
|
|
|
1005
|
+
output_parts = []
|
|
1006
|
+
ok, out = _run_git_p(["branch", "--show-current"], work_dir)
|
|
1007
|
+
current_branch = out.split("\n", 1)[-1].strip() if ok else ""
|
|
1008
|
+
output_parts.append(out)
|
|
1009
|
+
if not ok or not current_branch:
|
|
1010
|
+
body = json.dumps({"success": False, "error": "Could not detect current branch", "output": "\n".join(output_parts)}).encode()
|
|
1011
|
+
self._respond(500, "application/json", body)
|
|
1012
|
+
return
|
|
1013
|
+
|
|
1014
|
+
default_branch = None
|
|
1015
|
+
for candidate in ("main", "master"):
|
|
1016
|
+
chk, _ = _run_git_p(["rev-parse", "--verify", candidate], work_dir)
|
|
1017
|
+
if chk:
|
|
1018
|
+
default_branch = candidate
|
|
1019
|
+
break
|
|
1020
|
+
|
|
1005
1021
|
try:
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1022
|
+
if default_branch and current_branch != default_branch:
|
|
1023
|
+
steps = [
|
|
1024
|
+
(["checkout", default_branch], 30),
|
|
1025
|
+
(["merge", current_branch, "--ff-only"], 30),
|
|
1026
|
+
(["push", "origin", default_branch], 60),
|
|
1027
|
+
(["checkout", current_branch], 30),
|
|
1028
|
+
(["reset", "--hard", default_branch], 30),
|
|
1029
|
+
]
|
|
1030
|
+
for args, t in steps:
|
|
1031
|
+
ok, out = _run_git_p(args, work_dir, timeout=t)
|
|
1032
|
+
output_parts.append(out)
|
|
1033
|
+
if not ok:
|
|
1034
|
+
body = json.dumps({"success": False, "error": f"git {args[0]} failed", "output": "\n".join(output_parts)}).encode()
|
|
1035
|
+
self._respond(500, "application/json", body)
|
|
1036
|
+
return
|
|
1037
|
+
else:
|
|
1038
|
+
ok, out = _run_git_p(["push", "origin", current_branch], work_dir, timeout=60)
|
|
1039
|
+
output_parts.append(out)
|
|
1015
1040
|
if not ok:
|
|
1016
|
-
body = json.dumps({"
|
|
1041
|
+
body = json.dumps({"success": False, "error": "git push failed", "output": "\n".join(output_parts)}).encode()
|
|
1017
1042
|
self._respond(500, "application/json", body)
|
|
1018
1043
|
return
|
|
1019
1044
|
finally:
|
|
1020
|
-
|
|
1045
|
+
if current_branch:
|
|
1046
|
+
_run_git_p(["checkout", current_branch], work_dir, timeout=30)
|
|
1021
1047
|
|
|
1022
|
-
body = json.dumps({"
|
|
1048
|
+
body = json.dumps({"success": True, "output": "\n\n".join(output_parts)}).encode()
|
|
1023
1049
|
self._respond(200, "application/json", body)
|
|
1024
1050
|
except subprocess.TimeoutExpired:
|
|
1025
|
-
body = json.dumps({"
|
|
1051
|
+
body = json.dumps({"success": False, "error": "Git command timed out."}).encode()
|
|
1026
1052
|
self._respond(500, "application/json", body)
|
|
1027
1053
|
except Exception as exc:
|
|
1028
|
-
body = json.dumps({"
|
|
1054
|
+
body = json.dumps({"success": False, "error": str(exc)}).encode()
|
|
1029
1055
|
self._respond(500, "application/json", body)
|
|
1030
1056
|
elif self.path == "/api/agent-save-diary":
|
|
1031
1057
|
try:
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/stats.py
RENAMED
|
@@ -537,10 +537,30 @@ def get_skill_fire_counts() -> dict[str, int]:
|
|
|
537
537
|
|
|
538
538
|
OPUS_INPUT_COST = 15.0 # $/M tokens
|
|
539
539
|
OPUS_OUTPUT_COST = 75.0 # $/M tokens
|
|
540
|
+
SONNET_INPUT_COST = 3.0 # $/M tokens
|
|
541
|
+
SONNET_OUTPUT_COST = 15.0 # $/M tokens
|
|
540
542
|
AVG_OUTPUT_RATIO = 0.3 # assume ~30% of tokens are output
|
|
541
543
|
|
|
542
544
|
_SESSIONS_DIR = Path.home() / ".memstack" / "agent-runner" / "sessions"
|
|
543
545
|
|
|
546
|
+
_TASK_BOILERPLATE = re.compile(
|
|
547
|
+
r'^(working\s+directory:|branch:|read\s+all\s+files\s+before\s+modifying\.?)',
|
|
548
|
+
re.IGNORECASE
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _clean_task_description(task: str) -> str:
|
|
553
|
+
"""Strip Working directory/Branch boilerplate, returning the user's task description."""
|
|
554
|
+
lines = task.splitlines()
|
|
555
|
+
start = 0
|
|
556
|
+
for i, line in enumerate(lines):
|
|
557
|
+
stripped = line.strip()
|
|
558
|
+
if not stripped or _TASK_BOILERPLATE.match(stripped):
|
|
559
|
+
start = i + 1
|
|
560
|
+
else:
|
|
561
|
+
break
|
|
562
|
+
return "\n".join(lines[start:]).strip()
|
|
563
|
+
|
|
544
564
|
|
|
545
565
|
def get_recent_agent_sessions(limit: int = 10) -> list[dict]:
|
|
546
566
|
"""Scan agent-runner session directories for recent sessions."""
|
|
@@ -560,7 +580,7 @@ def get_recent_agent_sessions(limit: int = 10) -> list[dict]:
|
|
|
560
580
|
mtime = meta_file.stat().st_mtime
|
|
561
581
|
entries.append((mtime, {
|
|
562
582
|
"session_id": meta.get("session_id", d.name),
|
|
563
|
-
"task": (meta.get("task") or "")[:60],
|
|
583
|
+
"task": (_clean_task_description(meta.get("task_description") or meta.get("task") or ""))[:60],
|
|
564
584
|
"status": meta.get("status", "unknown"),
|
|
565
585
|
"started_at": meta.get("started_at", ""),
|
|
566
586
|
"iteration": meta.get("iteration", 0),
|
|
@@ -893,6 +913,38 @@ def get_burn_report_data(period: str = "daily", range_filter: str = "all") -> di
|
|
|
893
913
|
except Exception:
|
|
894
914
|
pass
|
|
895
915
|
|
|
916
|
+
# ── Model-level breakdown (Opus = manager+reviewer, Sonnet = builder) ──
|
|
917
|
+
opus_input = 0
|
|
918
|
+
opus_output = 0
|
|
919
|
+
sonnet_input = 0
|
|
920
|
+
sonnet_output = 0
|
|
921
|
+
try:
|
|
922
|
+
conn = _get_conn()
|
|
923
|
+
try:
|
|
924
|
+
row = conn.execute(
|
|
925
|
+
"SELECT SUM(input_tokens), SUM(output_tokens) FROM skill_fires "
|
|
926
|
+
"WHERE skill_name IN ('agent:manager', 'agent:reviewer') AND timestamp >= ?",
|
|
927
|
+
(since,),
|
|
928
|
+
).fetchone()
|
|
929
|
+
opus_input = int(row[0] or 0)
|
|
930
|
+
opus_output = int(row[1] or 0)
|
|
931
|
+
row2 = conn.execute(
|
|
932
|
+
"SELECT SUM(input_tokens), SUM(output_tokens) FROM skill_fires "
|
|
933
|
+
"WHERE skill_name = 'agent:builder' AND timestamp >= ?",
|
|
934
|
+
(since,),
|
|
935
|
+
).fetchone()
|
|
936
|
+
sonnet_input = int(row2[0] or 0)
|
|
937
|
+
sonnet_output = int(row2[1] or 0)
|
|
938
|
+
finally:
|
|
939
|
+
conn.close()
|
|
940
|
+
except Exception:
|
|
941
|
+
pass
|
|
942
|
+
|
|
943
|
+
opus_tokens = opus_input + opus_output
|
|
944
|
+
sonnet_tokens = sonnet_input + sonnet_output
|
|
945
|
+
opus_cost = round(opus_input / 1_000_000 * OPUS_INPUT_COST + opus_output / 1_000_000 * OPUS_OUTPUT_COST, 4)
|
|
946
|
+
sonnet_cost = round(sonnet_input / 1_000_000 * SONNET_INPUT_COST + sonnet_output / 1_000_000 * SONNET_OUTPUT_COST, 4)
|
|
947
|
+
|
|
896
948
|
return {
|
|
897
949
|
"period": period,
|
|
898
950
|
"range": range_filter,
|
|
@@ -907,6 +959,10 @@ def get_burn_report_data(period: str = "daily", range_filter: str = "all") -> di
|
|
|
907
959
|
"trend": trend,
|
|
908
960
|
"per_skill": per_skill[:20],
|
|
909
961
|
"recent_sessions": recent_sessions,
|
|
962
|
+
"opus_tokens": opus_tokens,
|
|
963
|
+
"opus_cost": opus_cost,
|
|
964
|
+
"sonnet_tokens": sonnet_tokens,
|
|
965
|
+
"sonnet_cost": sonnet_cost,
|
|
910
966
|
}
|
|
911
967
|
|
|
912
968
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/__main__.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/categories.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/compression.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/config.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/indexer.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/license.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/memory_db.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/search.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.5 → memstack_skill_loader-4.0.7}/src/memstack_skill_loader/server.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
|
|
File without changes
|