Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
All notable changes to the VS Code extension are documented here.

## [Unreleased]
### Security
- **Resolved 17 CodeQL alerts in `media/session.js`, `src/ChatStreamConsumer.ts`, `src/extension.ts`, `src/GovernancePanel.ts` and `src/test/session-logic.test.ts`.** Hardened the chat-webview HTML escaping (`esc()` now also escapes `"` and `'`), rewrote the inline `onclick="rptTool(...)"` / `onclick="rptCrash(...)"` / `onclick="viewFull(...)"` buttons to use `data-action` + a delegated click listener (eliminates the brittle `replace(/'/g,"\\'")` JS-string smuggling and the matching `js/identity-replacement` finding), escaped LLM-controlled values flowing into `addImg` `src=` and the VCS additions/deletions span, swapped `Math.random()` session-id generation for `crypto.randomUUID()`, and made the shell-quote helpers in the preflight + agent-task paths escape backslashes before quotes. Also tightened the `<script>` discovery regex in the build-output integrity test so it matches uppercase tags. No behaviour change for end users.
### Removed
- **Cloud Runs sidebar tree retired.** The `specsmith.cloud` view (`Cloud Runs`) and the `CloudTree` provider have been removed. The CLI-side `specsmith cloud spawn` / `specsmith cloud-serve` commands they fronted are no longer shipped.
## [0.8.0] — 2026-05-01
### Added — Bring-Your-Own-Endpoint (REQ-142)
- **`EndpointsClient` module** shells out to `specsmith endpoints list / test --json` and surfaces results to the host. JSON parsers (`parseEndpointsList`, `parseEndpointHealth`) and the `applyEndpointArg` bridge helper are exported pure-TS so they have direct mocha coverage.
- **`specsmith.endpoints` command.** Quick Pick over the registered endpoints with copy-id / set-default / test actions; `\u2605` marks the current default.
- **`specsmith.testEndpoint` command.** Probes `/v1/models` via `specsmith endpoints test --json` and shows a notification with latency + model count.
- **`SessionConfig.endpointId`** plumbed through `bridge.ts`. When set, the bridge appends `--endpoint <id>` to `specsmith run`, routing the LLM turn to the OpenAI-v1-compatible backend registered with `specsmith endpoints add` (vLLM, llama.cpp `server`, LM Studio, TGI, ...).
- **11 new mocha tests** in `src/test/endpoints-client.test.ts`. Total now 117 passing (was 104).

### Changed
- `package.json` version bumped from 0.7.0 to 0.8.0; description mentions BYOE OpenAI-v1 endpoints.

### Validation
- `npm run lint` (tsc --noEmit): clean.
- `npm run build` (esbuild): 279.8kb bundle.
- `npm test`: **117 passing** (+13 since 0.7.0).
## [0.7.0] — 2026-04-30
### Added — warp parity follow-up
- **Drive sidebar tree (REQ-133).** New `src/DriveTree.ts` exposes the contents of ~/.specsmith/drive as a sidebar tree. Title-bar actions trigger `specsmith drive push` / `pull`.
Expand Down Expand Up @@ -209,10 +228,10 @@ Four iterations of the chat panel — block-rendered webview, voice + interactiv
## [0.5.0] — 2026-04-28

### Added
- **Five additive sidebar tree views** under the specsmith activity bar:
- **Four additive sidebar tree views** under the specsmith activity bar:
Workflows (`.specsmith/workflows/*.yml`), Notebooks (`docs/notebooks/*.md`),
Rules (`docs/governance/*_RULES.md`), MCP Servers (entries from
`.specsmith/mcp.yml`), Cloud Runs (`.specsmith/cloud/*/manifest.json`).
`.specsmith/mcp.yml`).
Each view watches its glob and refreshes automatically; clicking an item
opens or runs it.
- **Execution-profile status bar item** (`ProfileBar`) showing the active
Expand Down
42 changes: 30 additions & 12 deletions media/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ let curMdl='',busy=false,warned=false,lastU='',proposalCount=0;
const CTX={claude:200000,'gpt-4o':128000,o1:200000,o3:200000,gemini:1000000,mistral:128000};
function csize(m){const l=(m||'').toLowerCase();for(const[k,v]of Object.entries(CTX))if(l.includes(k))return v;return 128000}
function ts(){return new Date().toLocaleString([],{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit',second:'2-digit'})}
function esc(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
function esc(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;')}
function rmd(r){
var s=esc(r);
s=s.replace(/```(\S*)\n([\s\S]*?)```/g,function(_,_l,c){return '<pre><code>'+c+'</code></pre>';});
Expand All @@ -17,13 +17,13 @@ const C=document.getElementById('chat');
function sb2(){C.scrollTop=C.scrollHeight}
function addU(t,customTs){
lastU=t;const d=document.createElement('div');d.className='mu';d.dataset.raw=t;
d.innerHTML=`<div class="bbl">${esc(t)}</div><div class="mt">${customTs||ts()}</div>
d.innerHTML=`<div class="bbl">${esc(t)}</div><div class="mt">${esc(customTs||ts())}</div>
<div class="mact"><button class="ab" title="Copy" onclick="cp(this)">⎘</button>
<button class="ab" title="Edit" onclick="ed(this)">✏</button></div>`;
C.appendChild(d);sb2()}
function addA(t,customTs){const d=document.createElement('div');d.className='ma';d.dataset.raw=t;
d.innerHTML=`<div class="rtag">🧠 AEE Agent</div><div class="bbl">${rmd(t)}</div>
<div class="mt">${customTs||ts()}</div><div class="mact">
<div class="mt">${esc(customTs||ts())}</div><div class="mact">
<button class="ab" title="Copy" onclick="cp(this)">⎘</button>
<button class="ab" title="Regenerate" onclick="regen()">&#x21BA;</button></div>`;
C.appendChild(d);sb2()}
Expand Down Expand Up @@ -73,9 +73,9 @@ function addTStart(n,args){
const cmd=String(args.command);
hint=' \u2192 '+esc(cmd.length>80?cmd.slice(0,80)+'\u2026':cmd);
}else if(args&&args.fix==='true'){hint=' (auto-fix)';}
else if(args&&args.path){hint=' \u2014 '+String(args.path).split(/[\/]/).pop();}
else if(args&&args.path){hint=' \u2014 '+esc(String(args.path).split(/[\/]/).pop());}
else if(args&&args.content&&n==='write_file'){hint=' \u2014 writing';}
d.innerHTML=`<span style="color:var(--teal)">\u23f3 ${lbl}${hint}\u2026</span><span class="mts">${ts()}</span>`;
d.innerHTML=`<span style="color:var(--teal)">\u23f3 ${esc(lbl)}${hint}\u2026</span><span class="mts">${esc(ts())}</span>`;
C.appendChild(d);sb2()}
/* REQ-132 — Warp-style command block.
* Shows the command in a header, the output in an expandable body,
Expand Down Expand Up @@ -163,13 +163,16 @@ function addT(n,r,e,args){
// Add a Report Bug button for Python-level crashes (Traceback, ImportError, etc.)
const isPyCrash=/Traceback \(most recent call last\)|ImportError|ModuleNotFoundError|AttributeError:|TypeError: |RuntimeError:/i.test(r);
const rptBtn=isPyCrash
?`<button onclick="rptTool(this,'${esc(n)}','${esc(r.slice(0,2000))}')"
?`<button data-action="rptTool" data-tool="${esc(n)}" data-output="${esc(r.slice(0,2000))}"
style="margin-top:6px;background:var(--red);color:#fff;border:none;border-radius:3px;padding:3px 8px;cursor:pointer;font-size:10px">\uD83D\uDC1B Report Bug</button>`
:'';
const vfBtn=cleanR.length>3000
?`<button class="ab" style="margin-top:4px;font-size:10px;color:var(--dim)" data-action="viewFull" data-text="${esc(cleanR.slice(0,50000))}">\uD83D\uDCC4 View Full</button>`
:'';
d.innerHTML=`<div class="thdr">\u274c ${esc(lbl)}</div>
<details><summary class="tres" style="cursor:pointer;list-style:none">
${esc(summary)}<span style="font-size:9px;margin-left:4px;opacity:.6">(click for details)</span>
</summary><pre class="err-detail" style="margin-top:4px;font-size:10px">${esc(cleanR.slice(0,3000))}${cleanR.length>3000?'\n\u2026(truncated)':''}</pre>${rptBtn}${cleanR.length>3000?'<button class="ab" style="margin-top:4px;font-size:10px;color:var(--dim)" onclick="vscode.postMessage({command:\'viewFull\',text:\''+esc(cleanR.replace(/'/g,"\'").slice(0,50000))+'\'})">' + '\uD83D\uDCC4 View Full</button>':''}</details>`;
</summary><pre class="err-detail" style="margin-top:4px;font-size:10px">${esc(cleanR.slice(0,3000))}${cleanR.length>3000?'\n\u2026(truncated)':''}</pre>${rptBtn}${vfBtn}</details>`;
}else if(e){
d.innerHTML=`<div class="thdr">\u274c ${esc(lbl)}</div><div class="tres">${esc(cleanR.slice(0,200))}</div>`;
}else{
Expand Down Expand Up @@ -215,9 +218,9 @@ function addToolCrash(data){
<div style="font-size:11px;color:var(--dim);margin-bottom:8px">${esc(detail)}</div>
<div style="font-size:11px;color:var(--dim);margin-bottom:8px">The session has stopped. This is an unexpected error — not something you did wrong.</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button onclick="rptCrash(this,'${esc(title)}','${esc(fullDetail)}','${esc(repo)}')"
<button data-action="rptCrash" data-title="${esc(title)}" data-detail="${esc(fullDetail)}" data-repo="${esc(repo)}"
style="background:var(--red);color:#fff;border:none;border-radius:4px;padding:4px 10px;cursor:pointer;font-size:11px">🐛 Report Bug</button>
<button onclick="this.closest('div[style]').remove()"
<button data-action="dismissCrash"
style="background:none;border:1px solid var(--br);border-radius:4px;padding:4px 10px;cursor:pointer;font-size:11px;color:var(--dim)">Dismiss</button>
</div>`;
C.appendChild(d);sb2();}
Expand Down Expand Up @@ -296,7 +299,7 @@ function rptBug(btn){
btn.textContent='✓ Reported';btn.disabled=true;}
function addImg(u,l){const d=document.createElement('div');d.className='mu';
d.innerHTML=`<div class="bbl"><div style="font-size:11px;color:var(--dim);margin-bottom:4px">📎 ${esc(l)}</div>
<img class="iprev" src="${u}" alt="${esc(l)}"></div><div class="mt">${ts()}</div>`;
<img class="iprev" src="${esc(u)}" alt="${esc(l)}"></div><div class="mt">${esc(ts())}</div>`;
C.appendChild(d);sb2()}
function updTok(i,o,c){const t=i+o,sz=csize(curMdl),p=Math.min(100,Math.round(t/sz*100));
const f=document.getElementById('cfil');f.style.width=p+'%';
Expand Down Expand Up @@ -506,7 +509,7 @@ window.addEventListener('message',({data})=>{switch(data.type){
if(vb)vb.textContent=data.branch||'\u2014';
if(vc){const n=data.changes||0;vc.textContent=n>0?n+' change'+(n!==1?'s':''):'clean';vc.className=n>0?'vc':'';}
if(vd){
const a=data.additions||0,d=data.deletions||0;
const a=Number(data.additions)|0,d=Number(data.deletions)|0;
if(a||d){vd.innerHTML='<span style="color:var(--grn);font-weight:600;font-size:9px">+'+a+'</span> <span style="color:var(--red);font-weight:600;font-size:9px">-'+d+'</span>';}
else{vd.textContent='';}
}
Expand Down Expand Up @@ -582,4 +585,19 @@ window.addEventListener('message',({data})=>{switch(data.type){
C.appendChild(pd);sb2();
break;}
}});
vscode.postMessage({command:'ready'});
/* Delegated click handler for data-action buttons (avoids inline onclick with user-controlled data) */
document.addEventListener('click',function(e){
const btn=e.target&&e.target.closest&&e.target.closest('[data-action]');
if(!btn)return;
const action=btn.dataset.action;
if(action==='rptTool'){
rptTool(btn,btn.dataset.tool||'?',btn.dataset.output||'');
}else if(action==='rptCrash'){
rptCrash(btn,btn.dataset.title||'',btn.dataset.detail||'',btn.dataset.repo||'specsmith');
}else if(action==='viewFull'){
vscode.postMessage({command:'viewFull',text:btn.dataset.text||''});
}else if(action==='dismissCrash'){
const card=btn.closest('div[style]');if(card)card.remove();
}
});
vscode.postMessage({command:'ready'});
174 changes: 168 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "specsmith-vscode",
"displayName": "specsmith - AEE Workbench",
"description": "Applied Epistemic Engineering workbench. 6-tab Settings panel, AI agent sessions, execution profiles, FPGA/HDL tool support, Ollama local LLMs, and tool installer.",
"version": "0.7.0",
"description": "Applied Epistemic Engineering workbench. Multi-agent activity routing, BYOE endpoints, AEE phase tree, epistemic notebooks, hierarchical rules, MCP server registry, FPGA/HDL tool support, Ollama local LLMs.",
"version": "0.10.0",
"publisher": "BitConcepts",
"license": "MIT",
"engines": {
Expand Down Expand Up @@ -76,13 +76,18 @@
"type": "tree"
},
{
"id": "specsmith.cloud",
"name": "Cloud Runs",
"id": "specsmith.drive",
"name": "Drive",
"type": "tree"
},
{
"id": "specsmith.drive",
"name": "Drive",
"id": "specsmith.endpoints",
"name": "BYOE Endpoints",
"type": "tree"
},
{
"id": "specsmith.agents",
"name": "Agent Profiles",
"type": "tree"
},
{
Expand Down Expand Up @@ -407,6 +412,101 @@
"title": "Suggest Command",
"category": "specsmith",
"icon": "$(lightbulb)"
},
{
"command": "specsmith.endpoints",
"title": "BYOE Endpoints\u2026",
"category": "specsmith",
"icon": "$(plug)"
},
{
"command": "specsmith.testEndpoint",
"title": "Test BYOE Endpoint",
"category": "specsmith",
"icon": "$(beaker)"
},
{
"command": "specsmith.refreshEndpoints",
"title": "Refresh BYOE Endpoints",
"category": "specsmith",
"icon": "$(refresh)"
},
{
"command": "specsmith.agents",
"title": "Agent Profiles\u2026",
"category": "specsmith",
"icon": "$(person)"
},
{
"command": "specsmith.testAgent",
"title": "Test Agent Profile",
"category": "specsmith",
"icon": "$(beaker)"
},
{
"command": "specsmith.refreshAgents",
"title": "Refresh Agent Profiles",
"category": "specsmith",
"icon": "$(refresh)"
},
{
"command": "specsmith.applyAgentPreset",
"title": "Apply Agent Preset (default / local-only / frontier-only / cost-conscious)",
"category": "specsmith",
"icon": "$(library)"
},
{
"command": "specsmith.routeActivity",
"title": "Route Activity to Agent Profile",
"category": "specsmith",
"icon": "$(arrow-right)"
},
{
"command": "specsmith.pickSessionProfile",
"title": "Pick Session Profile",
"category": "specsmith",
"icon": "$(person-add)"
},
{
"command": "specsmith.refreshWorkflows",
"title": "Refresh Workflows",
"category": "specsmith",
"icon": "$(refresh)"
},
{
"command": "specsmith.advancePhase",
"title": "Advance AEE Phase",
"category": "specsmith",
"icon": "$(arrow-right)"
},
{
"command": "specsmith.openRun",
"title": "Open Run Folder",
"category": "specsmith"
},
{
"command": "specsmith.refreshNotebooks",
"title": "Refresh Epistemic Notebooks",
"category": "specsmith",
"icon": "$(refresh)"
},
{
"command": "specsmith.newNotebook",
"title": "New Epistemic Notebook",
"category": "specsmith",
"icon": "$(add)"
},
{
"command": "specsmith.refreshRules",
"title": "Refresh Rules",
"category": "specsmith",
"icon": "$(refresh)"
},
{
"command": "specsmith.refreshMcp",
"title": "Refresh MCP Servers",
"category": "specsmith",
"icon": "$(refresh)"
}
],
"menus": {
Expand Down Expand Up @@ -475,6 +575,61 @@
"command": "specsmith.refreshBookmarks",
"when": "view == specsmith.bookmarks",
"group": "navigation"
},
{
"command": "specsmith.refreshEndpoints",
"when": "view == specsmith.endpoints",
"group": "navigation"
},
{
"command": "specsmith.endpoints",
"when": "view == specsmith.endpoints",
"group": "navigation"
},
{
"command": "specsmith.refreshAgents",
"when": "view == specsmith.agents",
"group": "navigation"
},
{
"command": "specsmith.agents",
"when": "view == specsmith.agents",
"group": "navigation"
},
{
"command": "specsmith.applyAgentPreset",
"when": "view == specsmith.agents",
"group": "navigation"
},
{
"command": "specsmith.refreshWorkflows",
"when": "view == specsmith.workflows",
"group": "navigation"
},
{
"command": "specsmith.advancePhase",
"when": "view == specsmith.workflows",
"group": "navigation"
},
{
"command": "specsmith.refreshNotebooks",
"when": "view == specsmith.notebooks",
"group": "navigation"
},
{
"command": "specsmith.newNotebook",
"when": "view == specsmith.notebooks",
"group": "navigation"
},
{
"command": "specsmith.refreshRules",
"when": "view == specsmith.rules",
"group": "navigation"
},
{
"command": "specsmith.refreshMcp",
"when": "view == specsmith.mcp",
"group": "navigation"
}
],
"view/item/context": [
Expand Down Expand Up @@ -576,6 +731,13 @@
"default": "specsmith",
"description": "Path to the specsmith CLI. Defaults to 'specsmith' (must be on PATH). If specsmith isn't found, set the full path here."
},
"specsmith.startupTimeoutMs": {
"type": "number",
"default": 60000,
"minimum": 5000,
"maximum": 600000,
"description": "Milliseconds the bridge waits for `specsmith run --json-events` to emit a `ready` event before retrying once and then surfacing a recoverable error. Raise this if cold Ollama model loads (14B+ on first run) routinely time out. Clamped to [5000, 600000]; default 60000."
},
"specsmith.defaultProvider": {
"type": "string",
"default": "ollama",
Expand Down
Loading
Loading