@yemi33/squad 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard.html +132 -0
- package/engine/spawn-agent.js +11 -6
- package/engine.js +42 -27
- package/package.json +1 -1
package/dashboard.html
CHANGED
|
@@ -243,6 +243,28 @@
|
|
|
243
243
|
.cmd-hints::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
244
244
|
.cmd-hints code { color: var(--blue); font-size: 10px; background: var(--surface2); padding: 1px 5px; border-radius: 3px; }
|
|
245
245
|
|
|
246
|
+
.cmd-history-btn {
|
|
247
|
+
background: none; border: 1px solid var(--border); color: var(--muted); font-size: 11px;
|
|
248
|
+
cursor: pointer; padding: 3px 10px; border-radius: 4px; transition: all 0.2s;
|
|
249
|
+
}
|
|
250
|
+
.cmd-history-btn:hover { color: var(--text); border-color: var(--text); }
|
|
251
|
+
.cmd-history-list { list-style: none; padding: 0; margin: 0; }
|
|
252
|
+
.cmd-history-item {
|
|
253
|
+
display: flex; align-items: flex-start; gap: 10px; padding: 10px 0;
|
|
254
|
+
border-bottom: 1px solid var(--border);
|
|
255
|
+
}
|
|
256
|
+
.cmd-history-item:last-child { border-bottom: none; }
|
|
257
|
+
.cmd-history-item-body { flex: 1; min-width: 0; }
|
|
258
|
+
.cmd-history-item-text { font-size: 12px; color: var(--text); white-space: pre-wrap; word-break: break-word; font-family: Consolas, monospace; }
|
|
259
|
+
.cmd-history-item-meta { font-size: 10px; color: var(--muted); margin-top: 3px; display: flex; gap: 8px; }
|
|
260
|
+
.cmd-history-item-meta .chip { background: var(--surface2); padding: 1px 6px; border-radius: 3px; }
|
|
261
|
+
.cmd-history-resubmit {
|
|
262
|
+
background: var(--surface2); border: 1px solid var(--border); color: var(--blue);
|
|
263
|
+
font-size: 11px; cursor: pointer; padding: 4px 10px; border-radius: 4px; white-space: nowrap;
|
|
264
|
+
transition: all 0.2s; flex-shrink: 0;
|
|
265
|
+
}
|
|
266
|
+
.cmd-history-resubmit:hover { background: rgba(88,166,255,0.1); border-color: var(--blue); }
|
|
267
|
+
.cmd-history-empty { color: var(--muted); font-size: 12px; padding: 20px 0; text-align: center; }
|
|
246
268
|
.cmd-toast {
|
|
247
269
|
display: none; padding: 8px 14px; border-radius: 4px; font-size: 12px;
|
|
248
270
|
margin-top: 10px; animation: fadeIn 0.3s;
|
|
@@ -403,6 +425,7 @@
|
|
|
403
425
|
<span><code>/note</code> team note</span>
|
|
404
426
|
<span><code>/prd</code> PRD item</span>
|
|
405
427
|
<span><code>#project</code> target project</span>
|
|
428
|
+
<button class="cmd-history-btn" onclick="cmdShowHistory()">Past Commands</button>
|
|
406
429
|
</div>
|
|
407
430
|
<div class="cmd-toast" id="cmd-toast"></div>
|
|
408
431
|
</section>
|
|
@@ -1662,6 +1685,42 @@ function cmdKeyDown(e) {
|
|
|
1662
1685
|
}
|
|
1663
1686
|
}
|
|
1664
1687
|
|
|
1688
|
+
// ArrowUp/ArrowDown to navigate command history (only when no popup visible)
|
|
1689
|
+
if (e.key === 'ArrowUp' && !isPopupVisible) {
|
|
1690
|
+
const input = document.getElementById('cmd-input');
|
|
1691
|
+
// Only intercept if cursor is at start of input (or input is single-line)
|
|
1692
|
+
if (input.selectionStart === 0 || !input.value.includes('\n')) {
|
|
1693
|
+
const history = cmdGetHistory();
|
|
1694
|
+
if (history.length === 0) return;
|
|
1695
|
+
if (_cmdHistoryIdx === -1) _cmdHistoryDraft = input.value; // Save current draft
|
|
1696
|
+
if (_cmdHistoryIdx < history.length - 1) {
|
|
1697
|
+
_cmdHistoryIdx++;
|
|
1698
|
+
input.value = history[_cmdHistoryIdx].text;
|
|
1699
|
+
cmdAutoResize();
|
|
1700
|
+
cmdRenderMeta();
|
|
1701
|
+
e.preventDefault();
|
|
1702
|
+
// Move cursor to end
|
|
1703
|
+
setTimeout(() => input.setSelectionRange(input.value.length, input.value.length), 0);
|
|
1704
|
+
}
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
if (e.key === 'ArrowDown' && !isPopupVisible) {
|
|
1709
|
+
const input = document.getElementById('cmd-input');
|
|
1710
|
+
if (input.selectionStart === input.value.length || !input.value.includes('\n')) {
|
|
1711
|
+
if (_cmdHistoryIdx >= 0) {
|
|
1712
|
+
_cmdHistoryIdx--;
|
|
1713
|
+
const history = cmdGetHistory();
|
|
1714
|
+
input.value = _cmdHistoryIdx >= 0 ? history[_cmdHistoryIdx].text : (_cmdHistoryDraft || '');
|
|
1715
|
+
cmdAutoResize();
|
|
1716
|
+
cmdRenderMeta();
|
|
1717
|
+
e.preventDefault();
|
|
1718
|
+
setTimeout(() => input.setSelectionRange(input.value.length, input.value.length), 0);
|
|
1719
|
+
}
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1665
1724
|
// Ctrl+Enter to submit
|
|
1666
1725
|
if (e.key === 'Enter' && e.ctrlKey) {
|
|
1667
1726
|
e.preventDefault();
|
|
@@ -1689,6 +1748,10 @@ async function cmdSubmit() {
|
|
|
1689
1748
|
} else {
|
|
1690
1749
|
await cmdSubmitWorkItem(parsed);
|
|
1691
1750
|
}
|
|
1751
|
+
// Save to history on success
|
|
1752
|
+
cmdSaveHistory(raw, parsed.intent);
|
|
1753
|
+
_cmdHistoryIdx = -1;
|
|
1754
|
+
_cmdHistoryDraft = '';
|
|
1692
1755
|
// Clear on success
|
|
1693
1756
|
input.value = '';
|
|
1694
1757
|
cmdAutoResize();
|
|
@@ -1781,6 +1844,75 @@ async function cmdSubmitPrd(parsed) {
|
|
|
1781
1844
|
const projLabel = (parsed.projects || []).length > 0 ? ' (' + parsed.projects.join(', ') + ')' : '';
|
|
1782
1845
|
showToast('cmd-toast', 'PRD item ' + (data.id || id) + ' added' + projLabel, true);
|
|
1783
1846
|
}
|
|
1847
|
+
// ─── Command History ──────────────────────────────────────────────────────────
|
|
1848
|
+
const CMD_HISTORY_KEY = 'squad-cmd-history';
|
|
1849
|
+
const CMD_HISTORY_MAX = 50;
|
|
1850
|
+
let _cmdHistoryIdx = -1; // -1 = not browsing history
|
|
1851
|
+
let _cmdHistoryDraft = ''; // saves current draft when browsing
|
|
1852
|
+
|
|
1853
|
+
function cmdGetHistory() {
|
|
1854
|
+
try { return JSON.parse(localStorage.getItem(CMD_HISTORY_KEY) || '[]'); } catch { return []; }
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
function cmdSaveHistory(raw, intent) {
|
|
1858
|
+
const history = cmdGetHistory();
|
|
1859
|
+
history.unshift({ text: raw, intent, timestamp: new Date().toISOString() });
|
|
1860
|
+
if (history.length > CMD_HISTORY_MAX) history.length = CMD_HISTORY_MAX;
|
|
1861
|
+
localStorage.setItem(CMD_HISTORY_KEY, JSON.stringify(history));
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function cmdShowHistory() {
|
|
1865
|
+
const history = cmdGetHistory();
|
|
1866
|
+
const title = document.getElementById('modal-title');
|
|
1867
|
+
const body = document.getElementById('modal-body');
|
|
1868
|
+
title.textContent = 'Past Commands (' + history.length + ')';
|
|
1869
|
+
|
|
1870
|
+
if (history.length === 0) {
|
|
1871
|
+
body.innerHTML = '<div class="cmd-history-empty">No commands yet. Submit something from the command center.</div>';
|
|
1872
|
+
} else {
|
|
1873
|
+
const intentColors = { 'work-item': 'var(--blue)', 'note': 'var(--green)', 'plan': 'var(--purple,#a855f7)', 'prd': 'var(--yellow,#d29922)' };
|
|
1874
|
+
const intentLabels = { 'work-item': 'Work Item', 'note': 'Note', 'plan': 'Plan', 'prd': 'PRD' };
|
|
1875
|
+
body.innerHTML = '<ul class="cmd-history-list">' + history.map((item, i) => {
|
|
1876
|
+
const date = new Date(item.timestamp);
|
|
1877
|
+
const ago = timeSinceStr(date);
|
|
1878
|
+
const intentLabel = intentLabels[item.intent] || item.intent || 'work-item';
|
|
1879
|
+
const intentColor = intentColors[item.intent] || 'var(--blue)';
|
|
1880
|
+
return '<li class="cmd-history-item">' +
|
|
1881
|
+
'<div class="cmd-history-item-body">' +
|
|
1882
|
+
'<div class="cmd-history-item-text">' + escHtml(item.text) + '</div>' +
|
|
1883
|
+
'<div class="cmd-history-item-meta">' +
|
|
1884
|
+
'<span class="chip" style="color:' + intentColor + '">' + intentLabel + '</span>' +
|
|
1885
|
+
'<span>' + ago + '</span>' +
|
|
1886
|
+
'<span>' + date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) + '</span>' +
|
|
1887
|
+
'</div>' +
|
|
1888
|
+
'</div>' +
|
|
1889
|
+
'<button class="cmd-history-resubmit" onclick="cmdResubmit(' + i + ')">Resubmit</button>' +
|
|
1890
|
+
'</li>';
|
|
1891
|
+
}).join('') + '</ul>';
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
document.getElementById('modal').classList.add('open');
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
function cmdResubmit(idx) {
|
|
1898
|
+
const history = cmdGetHistory();
|
|
1899
|
+
const item = history[idx];
|
|
1900
|
+
if (!item) return;
|
|
1901
|
+
document.getElementById('modal').classList.remove('open');
|
|
1902
|
+
const input = document.getElementById('cmd-input');
|
|
1903
|
+
input.value = item.text;
|
|
1904
|
+
cmdAutoResize();
|
|
1905
|
+
cmdRenderMeta();
|
|
1906
|
+
input.focus();
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
function timeSinceStr(date) {
|
|
1910
|
+
const s = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
1911
|
+
if (s < 60) return s + 's ago';
|
|
1912
|
+
if (s < 3600) return Math.floor(s / 60) + 'm ago';
|
|
1913
|
+
if (s < 86400) return Math.floor(s / 3600) + 'h ago';
|
|
1914
|
+
return Math.floor(s / 86400) + 'd ago';
|
|
1915
|
+
}
|
|
1784
1916
|
</script>
|
|
1785
1917
|
</body>
|
|
1786
1918
|
</html>
|
package/engine/spawn-agent.js
CHANGED
|
@@ -80,9 +80,13 @@ try {
|
|
|
80
80
|
if (Buffer.byteLength(sysPrompt) < 30000) {
|
|
81
81
|
actualArgs = ['-p', '--system-prompt', sysPrompt, ...extraArgs];
|
|
82
82
|
} else {
|
|
83
|
-
// Too large for inline —
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
// Too large for inline — split: short identity as --system-prompt, rest prepended to user prompt
|
|
84
|
+
// Extract first section (agent identity) as the system prompt, rest goes into user context
|
|
85
|
+
const splitIdx = sysPrompt.indexOf('\n---\n');
|
|
86
|
+
const shortSys = splitIdx > 0 && splitIdx < 2000
|
|
87
|
+
? sysPrompt.slice(0, splitIdx)
|
|
88
|
+
: sysPrompt.slice(0, 1500) + '\n\n[System prompt truncated for CLI arg limit — full context provided below in user message]';
|
|
89
|
+
actualArgs = ['-p', '--system-prompt', shortSys, ...extraArgs];
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
92
|
} catch {
|
|
@@ -100,9 +104,10 @@ fs.appendFileSync(debugPath, `PID=${proc.pid || 'none'}\nargs=${actualArgs.join(
|
|
|
100
104
|
const pidFile = promptFile.replace(/prompt-/, 'pid-').replace(/\.md$/, '.pid');
|
|
101
105
|
fs.writeFileSync(pidFile, String(proc.pid || ''));
|
|
102
106
|
|
|
103
|
-
// Send prompt via stdin — if system prompt
|
|
104
|
-
if (
|
|
105
|
-
|
|
107
|
+
// Send prompt via stdin — if system prompt was truncated, prepend the full context
|
|
108
|
+
if (Buffer.byteLength(sysPrompt) >= 30000) {
|
|
109
|
+
// System prompt was too large for CLI — prepend full context to user prompt
|
|
110
|
+
proc.stdin.write(`## Full Agent Context\n\n${sysPrompt}\n\n---\n\n## Your Task\n\n${prompt}`);
|
|
106
111
|
} else {
|
|
107
112
|
proc.stdin.write(prompt);
|
|
108
113
|
}
|
package/engine.js
CHANGED
|
@@ -506,10 +506,10 @@ function getRepoHostToolRule(project) {
|
|
|
506
506
|
|
|
507
507
|
// ─── System Prompt Builder ──────────────────────────────────────────────────
|
|
508
508
|
|
|
509
|
+
// Lean system prompt: agent identity + rules only (~2-4KB, never grows)
|
|
509
510
|
function buildSystemPrompt(agentId, config, project) {
|
|
510
511
|
const agent = config.agents[agentId];
|
|
511
512
|
const charter = getAgentCharter(agentId);
|
|
512
|
-
const notes = getNotes();
|
|
513
513
|
project = project || config.project || {};
|
|
514
514
|
|
|
515
515
|
let prompt = '';
|
|
@@ -519,57 +519,66 @@ function buildSystemPrompt(agentId, config, project) {
|
|
|
519
519
|
prompt += `Agent ID: ${agentId}\n`;
|
|
520
520
|
prompt += `Skills: ${(agent.skills || []).join(', ')}\n\n`;
|
|
521
521
|
|
|
522
|
-
// Charter (detailed instructions)
|
|
522
|
+
// Charter (detailed instructions — typically 1-2KB)
|
|
523
523
|
if (charter) {
|
|
524
524
|
prompt += `## Your Charter\n\n${charter}\n\n`;
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
-
//
|
|
528
|
-
const history = safeRead(path.join(AGENTS_DIR, agentId, 'history.md'));
|
|
529
|
-
if (history && history.trim() !== '# Agent History') {
|
|
530
|
-
prompt += `## Your Recent History\n\n${history}\n\n`;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Project context
|
|
527
|
+
// Project context (fixed size)
|
|
534
528
|
prompt += `## Project: ${project.name || 'Unknown Project'}\n\n`;
|
|
535
529
|
prompt += `- Repo: ${project.repoName || 'Unknown'} (${project.adoOrg || 'Unknown'}/${project.adoProject || 'Unknown'})\n`;
|
|
536
530
|
prompt += `- Repo ID: ${project.repositoryId || ''}\n`;
|
|
537
531
|
prompt += `- Repo host: ${getRepoHostLabel(project)}\n`;
|
|
538
532
|
prompt += `- Main branch: ${project.mainBranch || 'main'}\n\n`;
|
|
539
533
|
|
|
540
|
-
//
|
|
541
|
-
if (project.localPath) {
|
|
542
|
-
const claudeMd = safeRead(path.join(project.localPath, 'CLAUDE.md'));
|
|
543
|
-
if (claudeMd && claudeMd.trim()) {
|
|
544
|
-
// Truncate to 4KB to avoid bloating the system prompt
|
|
545
|
-
const truncated = claudeMd.length > 4096 ? claudeMd.slice(0, 4096) + '\n\n...(truncated)' : claudeMd;
|
|
546
|
-
prompt += `## Project Conventions (from CLAUDE.md)\n\n${truncated}\n\n`;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Critical rules
|
|
534
|
+
// Critical rules (fixed size)
|
|
551
535
|
prompt += `## Critical Rules\n\n`;
|
|
552
536
|
prompt += `1. Use git worktrees — NEVER checkout on main working tree\n`;
|
|
553
537
|
prompt += `2. ${getRepoHostToolRule(project)}\n`;
|
|
554
|
-
prompt += `3. Follow the project conventions
|
|
538
|
+
prompt += `3. Follow the project conventions in CLAUDE.md if present\n`;
|
|
555
539
|
prompt += `4. Write learnings to: ${SQUAD_DIR}/notes/inbox/${agentId}-${dateStamp()}.md\n`;
|
|
556
540
|
prompt += `5. Do NOT write to agents/*/status.json — the engine manages agent status automatically\n`;
|
|
557
541
|
prompt += `6. If you discover a repeatable workflow, save it as a skill:\n`;
|
|
558
542
|
prompt += ` - Squad-wide: \`${SKILLS_DIR}/<name>.md\` (no PR needed)\n`;
|
|
559
543
|
prompt += ` - Project-specific: \`<project>/.claude/skills/<name>.md\` (requires a PR since it modifies the repo)\n\n`;
|
|
560
544
|
|
|
545
|
+
return prompt;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Bulk context: history, notes, conventions, skills — prepended to user/task prompt.
|
|
549
|
+
// This is the content that grows over time and would bloat the system prompt.
|
|
550
|
+
function buildAgentContext(agentId, config, project) {
|
|
551
|
+
project = project || config.project || {};
|
|
552
|
+
const notes = getNotes();
|
|
553
|
+
let context = '';
|
|
554
|
+
|
|
555
|
+
// Agent history (past tasks)
|
|
556
|
+
const history = safeRead(path.join(AGENTS_DIR, agentId, 'history.md'));
|
|
557
|
+
if (history && history.trim() !== '# Agent History') {
|
|
558
|
+
context += `## Your Recent History\n\n${history}\n\n`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Project conventions (from CLAUDE.md)
|
|
562
|
+
if (project.localPath) {
|
|
563
|
+
const claudeMd = safeRead(path.join(project.localPath, 'CLAUDE.md'));
|
|
564
|
+
if (claudeMd && claudeMd.trim()) {
|
|
565
|
+
const truncated = claudeMd.length > 8192 ? claudeMd.slice(0, 8192) + '\n\n...(truncated)' : claudeMd;
|
|
566
|
+
context += `## Project Conventions (from CLAUDE.md)\n\n${truncated}\n\n`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
561
570
|
// Skills
|
|
562
571
|
const skillIndex = getSkillIndex();
|
|
563
572
|
if (skillIndex) {
|
|
564
|
-
|
|
573
|
+
context += skillIndex + '\n';
|
|
565
574
|
}
|
|
566
575
|
|
|
567
|
-
// Team notes
|
|
576
|
+
// Team notes (the big one — can be 50KB)
|
|
568
577
|
if (notes) {
|
|
569
|
-
|
|
578
|
+
context += `## Team Notes (MUST READ)\n\n${notes}\n\n`;
|
|
570
579
|
}
|
|
571
580
|
|
|
572
|
-
return
|
|
581
|
+
return context;
|
|
573
582
|
}
|
|
574
583
|
|
|
575
584
|
function sanitizeBranch(name) {
|
|
@@ -631,12 +640,18 @@ function spawnAgent(dispatchItem, config) {
|
|
|
631
640
|
}
|
|
632
641
|
}
|
|
633
642
|
|
|
634
|
-
// Build
|
|
643
|
+
// Build lean system prompt (identity + rules, ~2-4KB) and bulk context (history, notes, skills)
|
|
635
644
|
const systemPrompt = buildSystemPrompt(agentId, config, project);
|
|
645
|
+
const agentContext = buildAgentContext(agentId, config, project);
|
|
646
|
+
|
|
647
|
+
// Prepend bulk context to task prompt — keeps system prompt small and stable
|
|
648
|
+
const fullTaskPrompt = agentContext
|
|
649
|
+
? `## Agent Context\n\n${agentContext}\n---\n\n## Your Task\n\n${taskPrompt}`
|
|
650
|
+
: taskPrompt;
|
|
636
651
|
|
|
637
652
|
// Write prompt and system prompt to temp files (avoids shell escaping issues)
|
|
638
653
|
const promptPath = path.join(ENGINE_DIR, `prompt-${id}.md`);
|
|
639
|
-
safeWrite(promptPath,
|
|
654
|
+
safeWrite(promptPath, fullTaskPrompt);
|
|
640
655
|
|
|
641
656
|
const sysPromptPath = path.join(ENGINE_DIR, `sysprompt-${id}.md`);
|
|
642
657
|
safeWrite(sysPromptPath, systemPrompt);
|
package/package.json
CHANGED