@sanqianx/claude-ai-workbench 0.13.2 → 0.13.3
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/LICENSE +201 -201
- package/README.md +156 -156
- package/apps/agent-terminal/app.css +636 -613
- package/apps/agent-terminal/app.js +125 -65
- package/apps/agent-terminal/conversation-composer.js +44 -0
- package/apps/agent-terminal/index.html +171 -170
- package/apps/agent-terminal/panel-host-proof.html +152 -152
- package/apps/agent-terminal/panel-proof-content.html +41 -41
- package/apps/agent-terminal/right-workspace-dock.css +187 -159
- package/apps/agent-terminal/right-workspace-dock.js +623 -519
- package/apps/ai-profile-settings-host/index.html +25 -25
- package/apps/ai-profile-settings-host/page.css +1 -1
- package/apps/ai-profile-settings-host/page.js +113 -113
- package/apps/minimal-test-host/README.md +15 -15
- package/apps/minimal-test-host/host.css +2 -2
- package/apps/minimal-test-host/host.js +35 -35
- package/apps/minimal-test-host/index.html +58 -58
- package/apps/minimal-test-host/optional-mock/fault-injection.js +15 -15
- package/docs/sdk/FILE_LAYOUT.md +31 -31
- package/docs/sdk/INTEGRATION.md +126 -126
- package/docs/sdk/README.md +21 -21
- package/docs/sdk/SESSION_EVENTS.md +13 -13
- package/docs/sdk/openapi.yaml +8 -1
- package/package.json +1 -1
- package/packages/client/README.md +29 -29
- package/packages/client/index.d.ts +122 -122
- package/packages/client/index.js +260 -260
- package/packages/client/package.json +2 -2
- package/packages/contracts/README.md +9 -9
- package/packages/contracts/index.d.ts +519 -517
- package/packages/contracts/index.js +312 -312
- package/packages/contracts/package.json +1 -1
- package/packages/core/README.md +8 -8
- package/packages/core/index.d.ts +76 -76
- package/packages/core/index.js +222 -222
- package/packages/core/package.json +2 -2
- package/packages/server/README.md +17 -17
- package/packages/server/bin/agent-terminal-server.js +42 -42
- package/packages/server/bin/claude-workbench.js +225 -225
- package/packages/server/index.d.ts +171 -171
- package/packages/server/index.js +230 -230
- package/packages/server/lib/agent-discovery.js +133 -133
- package/packages/server/lib/agent-registry.js +143 -143
- package/packages/server/lib/agent-spawn-env.js +17 -17
- package/packages/server/lib/attachment-store.js +78 -78
- package/packages/server/lib/bridges/app-server-bridge.js +373 -373
- package/packages/server/lib/bridges/opencode-server-bridge.js +506 -506
- package/packages/server/lib/capture-settings-store.js +35 -35
- package/packages/server/lib/claude-history-recovery.js +87 -87
- package/packages/server/lib/cli-session-recovery.js +28 -28
- package/packages/server/lib/context-resolver.js +55 -55
- package/packages/server/lib/conversation-capture.js +51 -51
- package/packages/server/lib/delegation-phase-store.js +357 -357
- package/packages/server/lib/delegation-store.js +474 -468
- package/packages/server/lib/diagnostics.js +32 -32
- package/packages/server/lib/drivers/cli-json-runner.js +974 -974
- package/packages/server/lib/drivers/codex-driver.js +1442 -1442
- package/packages/server/lib/drivers/opencode-driver.js +1554 -1554
- package/packages/server/lib/drivers/zcode-driver.js +1250 -1250
- package/packages/server/lib/drivers/zcode-profile-runtime.js +71 -71
- package/packages/server/lib/event-stream.js +138 -138
- package/packages/server/lib/folder-picker.js +623 -623
- package/packages/server/lib/http-handler.js +339 -339
- package/packages/server/lib/json-file.js +26 -26
- package/packages/server/lib/knowledge-context.js +33 -33
- package/packages/server/lib/model-detection.js +295 -295
- package/packages/server/lib/parent-context.js +44 -0
- package/packages/server/lib/profile-store.js +283 -283
- package/packages/server/lib/project-store.js +102 -102
- package/packages/server/lib/runtime-loader.js +120 -120
- package/packages/server/lib/session-archive-store.js +53 -53
- package/packages/server/lib/session-history.js +612 -612
- package/packages/server/lib/system-doctor.js +125 -125
- package/packages/server/lib/ui-preferences-store.js +48 -48
- package/packages/server/lib/workbench-service.js +1696 -1675
- package/packages/server/package.json +2 -2
- package/packages/ui/README.md +30 -30
- package/packages/ui/ai-profile-settings.d.ts +45 -45
- package/packages/ui/ai-profile-settings.js +283 -283
- package/packages/ui/claude-terminal.d.ts +31 -31
- package/packages/ui/claude-terminal.js +343 -343
- package/packages/ui/index.d.ts +18 -18
- package/packages/ui/index.js +51 -51
- package/packages/ui/package.json +2 -2
- package/packages/ui/theme.css +13 -13
- package/src/assets/claude-prompts.json +21 -21
- package/src/backend/http/ai-profile-routes.js +61 -61
- package/src/backend/http/claude-routes.js +170 -170
- package/src/backend/http/node-helpers.js +45 -45
- package/src/backend/index.js +21 -21
- package/src/backend/lib/ai-adapter.js +144 -144
- package/src/backend/lib/ai-profile-store.js +259 -259
- package/src/backend/lib/ai-vendor-presets.js +83 -83
- package/src/backend/lib/ai-workspace.js +140 -140
- package/src/backend/lib/automation-config.js +253 -253
- package/src/backend/lib/claude-cli-runner.js +2392 -2392
- package/src/backend/lib/data-dir.js +169 -169
- package/src/backend/lib/llm-client.js +176 -176
- package/src/backend/lib/model-context-windows.js +82 -82
- package/src/backend/lib/native-task-control.js +217 -217
- package/src/backend/lib/native-task-history-index.js +475 -475
- package/src/backend/lib/native-task-history-reader.js +526 -526
- package/src/backend/lib/native-task-messages.js +457 -457
- package/src/backend/lib/native-task-registry.js +325 -325
- package/src/backend/lib/native-task-runtime.js +242 -242
- package/src/backend/lib/native-task-trace.js +440 -440
- package/src/backend/lib/native-task-transcript.js +393 -393
- package/src/backend/lib/prompt-registry.js +166 -166
- package/src/backend/lib/runtime-config.js +121 -121
- package/src/backend/lib/sdk-session-stream.js +143 -143
- package/src/backend/lib/sdk-turn-stream.js +16 -16
- package/src/backend/lib/store-scope.js +131 -131
- package/src/backend/lib/task-identity.js +104 -104
- package/src/backend/lib/task-state-projection.js +58 -58
- package/src/index.js +7 -7
- package/src/sdk-wave-a.ts +1 -1
- package/src/sdk-wave-b.ts +1 -1
- package/src/sdk-wave-c.ts +1 -1
- package/src/sdk-wave-d.ts +1 -1
- package/src/sdk-wave-e.ts +1 -1
package/packages/ui/index.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import type { ClaudeWorkbenchClient } from "@claude-ai-workbench/client";
|
|
2
|
-
import type { WorkbenchContext } from "@claude-ai-workbench/contracts";
|
|
3
|
-
|
|
4
|
-
export interface ClaudeWorkbenchPaneElement extends HTMLElement {
|
|
5
|
-
client: ClaudeWorkbenchClient | null;
|
|
6
|
-
adapter: ClaudeWorkbenchClient | null;
|
|
7
|
-
context: WorkbenchContext | null;
|
|
8
|
-
readonly controller: unknown;
|
|
9
|
-
destroy(): void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
declare global {
|
|
13
|
-
interface HTMLElementTagNameMap {
|
|
14
|
-
"claude-workbench-pane": ClaudeWorkbenchPaneElement;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export {};
|
|
1
|
+
import type { ClaudeWorkbenchClient } from "@claude-ai-workbench/client";
|
|
2
|
+
import type { WorkbenchContext } from "@claude-ai-workbench/contracts";
|
|
3
|
+
|
|
4
|
+
export interface ClaudeWorkbenchPaneElement extends HTMLElement {
|
|
5
|
+
client: ClaudeWorkbenchClient | null;
|
|
6
|
+
adapter: ClaudeWorkbenchClient | null;
|
|
7
|
+
context: WorkbenchContext | null;
|
|
8
|
+
readonly controller: unknown;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare global {
|
|
13
|
+
interface HTMLElementTagNameMap {
|
|
14
|
+
"claude-workbench-pane": ClaudeWorkbenchPaneElement;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {};
|
package/packages/ui/index.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
(function (global) {
|
|
2
|
-
'use strict';
|
|
3
|
-
if (!global.customElements || !global.HTMLElement) return;
|
|
4
|
-
|
|
5
|
-
const Core = global.ClaudeWorkbenchCore || (typeof require === 'function' ? require('../core') : null);
|
|
6
|
-
const esc = value => String(value == null ? '' : value).replace(/[&<>"']/g, char => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
|
|
7
|
-
const css = `
|
|
8
|
-
:host{display:block;min-width:0;height:100%;min-height:320px;color:var(--caw-text,#172231);font:13px/1.45 Inter,ui-sans-serif,-apple-system,"Segoe UI","Microsoft YaHei",sans-serif}*{box-sizing:border-box}button,textarea,select,input{font:inherit;color:inherit}button{cursor:pointer}.shell{height:100%;min-height:320px;display:grid;grid-template-rows:54px minmax(0,1fr) auto;overflow:hidden;border:1px solid var(--caw-line,#d7dfe8);border-radius:14px;background:var(--caw-panel,#fff)}.head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:0 13px;border-bottom:1px solid var(--caw-line,#d7dfe8)}.identity,.actions,.foot,.permission{display:flex;align-items:center}.identity{min-width:0;gap:9px}.dot{width:9px;height:9px;border-radius:50%;background:var(--caw-muted,#687789)}.dot.connected{background:var(--caw-good,#2f7d64)}.dot.running{background:var(--caw-accent,#4f6f8f);box-shadow:0 0 0 4px var(--caw-accent-soft);animation:pulse 1.2s infinite}.dot.error{background:var(--caw-bad,#af4b4b)}@keyframes pulse{50%{opacity:.4}}.title{font-weight:750}.sub{color:var(--caw-muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chip{padding:4px 8px;border:1px solid var(--caw-line);border-radius:999px;background:var(--caw-panel-2);color:var(--caw-muted);font-size:10px}.actions{gap:6px}.icon,.send,.small{border:1px solid var(--caw-line);border-radius:8px;background:var(--caw-panel-2)}.icon{width:32px;height:32px}.messages{min-height:0;overflow:auto;padding:14px;background:var(--caw-panel-2)}.empty{height:100%;display:grid;place-items:center;text-align:center;color:var(--caw-muted)}.line{margin:0 0 10px}.bubble{max-width:94%;padding:9px 11px;border:1px solid var(--caw-line);border-radius:10px;background:var(--caw-panel);white-space:pre-wrap;overflow-wrap:anywhere}.user{display:flex;justify-content:flex-end}.user .bubble{max-width:86%;background:var(--caw-accent-soft)}.thinking .bubble{color:var(--caw-muted);font-style:italic;border-style:dashed}.error .bubble{color:var(--caw-bad);background:var(--caw-bad-soft)}.status{text-align:center;color:var(--caw-muted);font-size:11px}.tool{max-width:94%;border:1px solid var(--caw-line);border-radius:10px;background:var(--caw-panel);overflow:hidden}.tool summary{padding:9px 11px;font-weight:700}.tool pre{margin:0;padding:10px;background:#101923;color:#dae6f2;overflow:auto;font-size:11px}.result{padding:9px;border-top:1px solid var(--caw-line);white-space:pre-wrap}.permission{justify-content:space-between;gap:12px;padding:10px;border-top:1px solid var(--caw-line);background:var(--caw-warn-soft)}.permission div{min-width:0}.permission span{display:block;color:var(--caw-muted);font-size:11px;overflow-wrap:anywhere}.permission .actions{flex:none}.small{padding:6px 9px}.allow{border-color:transparent;background:var(--caw-accent);color:#fff}.deny{color:var(--caw-bad)}.composer{border-top:1px solid var(--caw-line)}.input{display:flex;align-items:flex-end;gap:8px;padding:9px 9px 5px}.input textarea{flex:1;min-height:40px;max-height:150px;resize:vertical;padding:9px;border:1px solid var(--caw-line);border-radius:9px;background:var(--caw-panel-2);outline:0}.send{width:40px;height:40px;border:0;background:var(--caw-accent);color:#fff}.send.stop{background:var(--caw-bad)}.foot{justify-content:space-between;padding:0 9px 9px;color:var(--caw-muted);font-size:10px}.foot select{padding:4px;border:1px solid var(--caw-line);border-radius:7px;background:var(--caw-panel-2)}
|
|
9
|
-
`;
|
|
10
|
-
|
|
11
|
-
class SessionHistoryPopover extends HTMLElement {
|
|
12
|
-
constructor(){super();this.attachShadow({mode:'open'});this._sessions=[];this._active='';}
|
|
13
|
-
set sessions(value){this._sessions=Array.isArray(value)?value:[];this.render()} set activeSessionId(value){this._active=value||'';this.render()}
|
|
14
|
-
connectedCallback(){this.render()}
|
|
15
|
-
render(){this.shadowRoot.innerHTML=`<style>:host{position:absolute;right:0;top:39px;z-index:20;width:min(340px,85vw);max-height:360px;overflow:auto;padding:7px;border:1px solid var(--caw-line);border-radius:11px;background:var(--caw-panel);box-shadow:var(--caw-shadow)}button{width:100%;padding:9px;margin-bottom:4px;text-align:left;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--caw-text);cursor:pointer}button:hover,button.on{border-color:var(--caw-line);background:var(--caw-panel-2)}small{display:block;margin-top:4px;color:var(--caw-muted)}</style>${this._sessions.length?this._sessions.map(s=>`<button class="${s.sessionId===this._active?'on':''}" data-id="${esc(s.sessionId)}">${esc(s.title||s.sessionId)}<small>${esc(s.model||s.aiProfileId||'')}</small></button>`).join(''):'<small>No sessions</small>'}`;this.shadowRoot.querySelectorAll('button').forEach(button=>button.onclick=()=>this.dispatchEvent(new CustomEvent('session-select',{bubbles:true,composed:true,detail:{sessionId:button.dataset.id}})));}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class ClaudeWorkbenchPane extends HTMLElement {
|
|
19
|
-
constructor(){super();this.attachShadow({mode:'open'});this._client=null;this._context=null;this._controller=null;this._state=Core.createState();this._history=false;this._queued=false;}
|
|
20
|
-
connectedCallback(){this.ensure();this.render();if(this._context)this._controller.setContext(this._context)}
|
|
21
|
-
disconnectedCallback(){if(!this.hasAttribute('preserve-controller'))this.destroy()}
|
|
22
|
-
set client(value){this._client=value;this.ensure();this._controller.setAdapter(value);if(this.isConnected&&this._context)this._controller.setContext(this._context)} get client(){return this._client}
|
|
23
|
-
set adapter(value){this.client=value} get adapter(){return this.client}
|
|
24
|
-
set context(value){this._context=value?{...value,contextId:value.contextId||value.projectId}:null;this.ensure();if(this.isConnected)this._controller.setContext(this._context);this.schedule()} get context(){return this._context}
|
|
25
|
-
get controller(){this.ensure();return this._controller}
|
|
26
|
-
ensure(){if(this._controller)return;if(!Core)throw new Error('ClaudeWorkbenchCore must be loaded first');this._controller=Core.createController({adapter:this._client,onChange:state=>{this._state=state;this.schedule();this.dispatchEvent(new CustomEvent('workbench-state-change',{detail:state}));this.dispatchEvent(new CustomEvent('running-change',{detail:{running:state.turnRunning,context:state.context}}));}})}
|
|
27
|
-
schedule(){if(!this.isConnected||this._queued)return;this._queued=true;queueMicrotask(()=>{this._queued=false;const old=this.shadowRoot.querySelector('.messages');const bottom=!old||old.scrollHeight-old.scrollTop-old.clientHeight<90;const draft=this.shadowRoot.querySelector('textarea')?.value||'';this.render();const input=this.shadowRoot.querySelector('textarea');if(input)input.value=draft;const next=this.shadowRoot.querySelector('.messages');if(bottom&&next)next.scrollTop=next.scrollHeight})}
|
|
28
|
-
line(item){if(item.kind==='status')return`<div class="line status">${esc(item.text)}</div>`;if(item.kind==='tool')return`<details class="line tool" ${item.status==='running'?'open':''}><summary>${esc(item.name||'tool')} · ${esc(item.status||'running')}</summary><pre>${esc(JSON.stringify(item.input||{},null,2))}</pre>${item.result!=null?`<div class="result">${esc(item.result)}</div>`:''}</details>`;return`<div class="line ${esc(item.kind)}"><div class="bubble">${esc(item.text||item.message||'')}</div></div>`}
|
|
29
|
-
render(){const c=this._context||{};const s=this._state;const usage=s.tokenUsage||{};const percent=usage.hasUsage&&usage.total?Math.min(100,usage.used/usage.total*100):0;const status=s.error?'error':s.turnRunning?'running':s.session.connectionState==='connected'?'connected':'';this.shadowRoot.innerHTML=`<style>${css}</style><section class="shell"><header class="head"><div class="identity"><i class="dot ${status}"></i><div><div class="title">Claude Workbench</div><div class="sub">${esc(c.displayName||'No context')} · ${esc(s.session.connectionState)}</div></div><span class="chip">${esc(s.session.aiProfileId||c.aiProfileId||'No profile')}</span></div><div class="actions" style="position:relative"><button class="icon" data-a="history" title="Session history">◷</button><button class="icon" data-a="new" title="New session">+</button><button class="icon" data-a="settings" title="AI Profiles">⚙</button>${this._history?'<session-history-popover></session-history-popover>':''}</div></header><main class="messages">${s.lines.length?s.lines.map(item=>this.line(item)).join(''):`<div class="empty"><div><strong>${c.contextId?'Start a conversation':'Select a context'}</strong><br>${c.contextId?'This session is isolated to the active context.':'The host supplies WorkbenchContext.'}</div></div>`}</main><div>${s.permission?`<div class="permission"><div><strong>${esc(s.permission.toolName)} requests permission</strong><span>${esc(s.permission.input?.file_path||s.permission.input?.command||s.permission.message||'')}</span></div><div class="actions"><button class="small deny" data-a="deny">Deny</button><button class="small allow" data-a="allow">Allow</button></div></div>`:''}<div class="composer"><div class="input"><textarea placeholder="Ask Claude Code…" ${!c.contextId||s.requestPending||s.permission?'disabled':''}></textarea><button class="send ${s.turnRunning?'stop':''}" data-a="${s.turnRunning?'stop':'send'}">${s.turnRunning?'■':'↑'}</button></div><div class="foot"><select data-a="mode"><option value="default">Default</option><option value="acceptEdits">Accept Edits</option><option value="plan">Plan</option><option value="bypassPermissions">Bypass</option></select><span>${usage.hasUsage?percent.toFixed(1)+'%':'—'} · ${esc(s.session.model||'Claude Code')}</span></div></div></div></section>`;const history=this.shadowRoot.querySelector('session-history-popover');if(history){history.sessions=s.sessions;history.activeSessionId=s.sessionId}this.bind()}
|
|
30
|
-
bind(){const q=a=>this.shadowRoot.querySelector(`[data-a="${a}"]`);q('history')?.addEventListener('click',()=>{this._history=!this._history;this.schedule()});q('new')?.addEventListener('click',()=>this.run(()=>this._controller.newSession()));q('settings')?.addEventListener('click',()=>this.dispatchEvent(new CustomEvent('open-profile-settings',{bubbles:true,composed:true,detail:{context:this._context}})));q('allow')?.addEventListener('click',()=>this.run(()=>this._controller.resolvePermission(true)));q('deny')?.addEventListener('click',()=>this.run(()=>this._controller.resolvePermission(false)));q('stop')?.addEventListener('click',()=>this.run(()=>this._controller.abort()));q('mode')?.addEventListener('change',event=>this.run(()=>this._controller.setPermissionMode(event.target.value)));this.shadowRoot.querySelector('session-history-popover')?.addEventListener('session-select',event=>{this._history=false;this.run(()=>this._controller.restoreSession(event.detail.sessionId))});const input=this.shadowRoot.querySelector('textarea');const send=()=>{const value=input?.value.trim();if(value){input.value='';this.run(()=>this._controller.send(value))}};q('send')?.addEventListener('click',send);input?.addEventListener('keydown',event=>{if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}})}
|
|
31
|
-
async run(fn){try{await fn()}catch(error){this.dispatchEvent(new CustomEvent('workbench-error',{bubbles:true,composed:true,detail:{error}}))}}
|
|
32
|
-
destroy(){this._controller?.destroy();this._controller=null}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
class AiProfileSettings extends HTMLElement {
|
|
36
|
-
constructor(){super();this.attachShadow({mode:'open'});this._client=null;this._profiles=[];this._selected='';this._status='Loading profiles…'}
|
|
37
|
-
set client(value){this._client=value;if(this.isConnected)this.refresh()} get client(){return this._client}
|
|
38
|
-
connectedCallback(){this.render();this.refresh()}
|
|
39
|
-
async refresh(){if(!this._client){this._status='Client not configured';this.render();return}try{this._profiles=await this._client.listProfiles();if(!this._selected)this._selected=this._profiles[0]?.id||'';this._status=`${this._profiles.length} profile(s)`}catch(error){this._status=error.message}this.render()}
|
|
40
|
-
current(){return this._profiles.find(p=>p.id===this._selected)||{id:'',name:'',provider:'',runtime:'claude-code',protocol:'anthropic-compatible',baseUrl:'',credentialRef:'',models:{default:''},contextWindow:200000,timeoutMs:300000,enabled:true}}
|
|
41
|
-
render(){const p=this.current();this.shadowRoot.innerHTML=`<style>:host{display:block;height:100%;min-height:440px;color:var(--caw-text);font:12px Inter,"Segoe UI",sans-serif}*{box-sizing:border-box}button,input,select{font:inherit;color:inherit}.shell{height:100%;display:grid;grid-template-columns:210px 1fr;border:1px solid var(--caw-line);border-radius:13px;overflow:hidden;background:var(--caw-panel)}aside{padding:10px;background:var(--caw-panel-2);border-right:1px solid var(--caw-line);overflow:auto}aside button{width:100%;padding:9px;margin-bottom:5px;text-align:left;border:1px solid var(--caw-line);border-radius:8px;background:var(--caw-panel)}aside button.on{border-color:var(--caw-accent)}main{display:grid;grid-template-rows:auto 1fr auto;min-width:0}header,footer{display:flex;justify-content:space-between;align-items:center;padding:13px;border-bottom:1px solid var(--caw-line)}footer{border-top:1px solid var(--caw-line);border-bottom:0}.form{padding:14px;overflow:auto;display:grid;grid-template-columns:1fr 1fr;gap:11px;align-content:start}label{display:grid;gap:5px;color:var(--caw-muted)}input,select{padding:8px;border:1px solid var(--caw-line);border-radius:8px;background:var(--caw-panel-2)}.wide{grid-column:1/-1}.actions{display:flex;gap:7px}button{padding:7px 9px;border:1px solid var(--caw-line);border-radius:8px;background:var(--caw-panel-2);cursor:pointer}.primary{color:#fff;background:var(--caw-accent);border-color:transparent}@media(max-width:700px){.shell{grid-template-columns:1fr}aside{display:none}.form{grid-template-columns:1fr}.wide{grid-column:auto}}</style><section class="shell"><aside><h3>AI Profiles</h3>${this._profiles.map(item=>`<button class="${item.id===this._selected?'on':''}" data-id="${esc(item.id)}"><strong>${esc(item.name||item.id)}</strong><br><small>${esc(item.provider)} · ${item.credentialConfigured?'credential set':'no credential'}</small></button>`).join('')}<button data-a="new">+ New profile</button></aside><main><header><div><strong>${esc(p.name||'New profile')}</strong><br><small>${esc(this._status)}</small></div><div class="actions"><button data-a="credential">Credential</button><button data-a="test">Real runtime test</button></div></header><form class="form" id="profile"><label>ID<input name="id" value="${esc(p.id)}" required></label><label>Name<input name="name" value="${esc(p.name)}" required></label><label>Provider<input name="provider" value="${esc(p.provider)}" required></label><label>Runtime<select name="runtime"><option value="claude-code">Claude Code</option></select></label><label>Protocol<select name="protocol"><option value="anthropic-compatible">Anthropic compatible</option><option value="openai-compatible" ${p.protocol==='openai-compatible'?'selected':''}>OpenAI compatible</option></select></label><label>Base URL<input name="baseUrl" value="${esc(p.baseUrl)}" required></label><label class="wide">Default model<input name="model" value="${esc(p.models?.default||'')}" required></label><label>Context window<input name="contextWindow" type="number" value="${Number(p.contextWindow||200000)}"></label><label>Timeout ms<input name="timeoutMs" type="number" value="${Number(p.timeoutMs||300000)}"></label></form><footer><span>${p.credentialConfigured?'Secret stored on server':'Credential required'}</span><div class="actions"><button data-a="delete">Delete</button><button class="primary" data-a="save">Save</button></div></footer></main></section>`;this.bind()}
|
|
42
|
-
draft(){const f=new FormData(this.shadowRoot.querySelector('#profile'));const model=String(f.get('model')||'').trim();return{...this.current(),id:String(f.get('id')||'').trim(),name:String(f.get('name')||'').trim(),provider:String(f.get('provider')||'').trim(),runtime:'claude-code',protocol:String(f.get('protocol')),baseUrl:String(f.get('baseUrl')||'').trim(),credentialRef:this.current().credentialRef||`credential:${String(f.get('id')||'').trim()}`,models:{default:model,fast:model,reasoning:model,coding:model,largeContext:model},contextWindow:Number(f.get('contextWindow')),timeoutMs:Number(f.get('timeoutMs')),enabled:true}}
|
|
43
|
-
bind(){this.shadowRoot.querySelectorAll('[data-id]').forEach(button=>button.onclick=()=>{this._selected=button.dataset.id;this.render()});this.shadowRoot.querySelector('[data-a="new"]')?.addEventListener('click',()=>{this._selected='';this._profiles=[];this._status='Create a new profile';this.render()});this.shadowRoot.querySelector('[data-a="save"]')?.addEventListener('click',()=>this.action(async()=>{const profile=await this._client.saveProfile(this.draft());this._selected=profile.id;this.dispatchEvent(new CustomEvent('profile-save',{bubbles:true,composed:true,detail:{profile}}));await this.refresh()}));this.shadowRoot.querySelector('[data-a="delete"]')?.addEventListener('click',()=>this.action(async()=>{if(!this.current().id)return;await this._client.deleteProfile(this.current().id);this._selected='';await this.refresh()}));this.shadowRoot.querySelector('[data-a="credential"]')?.addEventListener('click',()=>this.action(async()=>{const id=this.draft().id;if(!id)throw new Error('Save a profile ID first');const secret=global.prompt?.(`Set credential for ${id}. It will be sent once to the local server and never displayed again.`);if(secret){await this._client.setCredential(id,secret);await this.refresh()}}));this.shadowRoot.querySelector('[data-a="test"]')?.addEventListener('click',()=>this.action(async()=>{const id=this.current().id;if(!id)throw new Error('Save the profile first');const result=await this._client.testProfile(id);this._status=result.runtimeVerified?`REAL PASS · ${result.model} · ${result.durationMs}ms`:'FAILED · runtime not verified';this.dispatchEvent(new CustomEvent('profile-test',{bubbles:true,composed:true,detail:result}));this.render()}))}
|
|
44
|
-
async action(fn){try{this._status='Working…';this.render();await fn()}catch(error){this._status=error.message;this.dispatchEvent(new CustomEvent('workbench-error',{bubbles:true,composed:true,detail:{error}}));this.render()}}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if(!customElements.get('session-history-popover'))customElements.define('session-history-popover',SessionHistoryPopover);
|
|
48
|
-
if(!customElements.get('claude-workbench-pane'))customElements.define('claude-workbench-pane',ClaudeWorkbenchPane);
|
|
49
|
-
if(!customElements.get('ai-profile-settings'))customElements.define('ai-profile-settings',AiProfileSettings);
|
|
50
|
-
global.ClaudeWorkbenchPane=ClaudeWorkbenchPane;global.AiProfileSettings=AiProfileSettings;global.SessionHistoryPopover=SessionHistoryPopover;
|
|
51
|
-
})(typeof window !== 'undefined' ? window : globalThis);
|
|
1
|
+
(function (global) {
|
|
2
|
+
'use strict';
|
|
3
|
+
if (!global.customElements || !global.HTMLElement) return;
|
|
4
|
+
|
|
5
|
+
const Core = global.ClaudeWorkbenchCore || (typeof require === 'function' ? require('../core') : null);
|
|
6
|
+
const esc = value => String(value == null ? '' : value).replace(/[&<>"']/g, char => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
|
|
7
|
+
const css = `
|
|
8
|
+
:host{display:block;min-width:0;height:100%;min-height:320px;color:var(--caw-text,#172231);font:13px/1.45 Inter,ui-sans-serif,-apple-system,"Segoe UI","Microsoft YaHei",sans-serif}*{box-sizing:border-box}button,textarea,select,input{font:inherit;color:inherit}button{cursor:pointer}.shell{height:100%;min-height:320px;display:grid;grid-template-rows:54px minmax(0,1fr) auto;overflow:hidden;border:1px solid var(--caw-line,#d7dfe8);border-radius:14px;background:var(--caw-panel,#fff)}.head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:0 13px;border-bottom:1px solid var(--caw-line,#d7dfe8)}.identity,.actions,.foot,.permission{display:flex;align-items:center}.identity{min-width:0;gap:9px}.dot{width:9px;height:9px;border-radius:50%;background:var(--caw-muted,#687789)}.dot.connected{background:var(--caw-good,#2f7d64)}.dot.running{background:var(--caw-accent,#4f6f8f);box-shadow:0 0 0 4px var(--caw-accent-soft);animation:pulse 1.2s infinite}.dot.error{background:var(--caw-bad,#af4b4b)}@keyframes pulse{50%{opacity:.4}}.title{font-weight:750}.sub{color:var(--caw-muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chip{padding:4px 8px;border:1px solid var(--caw-line);border-radius:999px;background:var(--caw-panel-2);color:var(--caw-muted);font-size:10px}.actions{gap:6px}.icon,.send,.small{border:1px solid var(--caw-line);border-radius:8px;background:var(--caw-panel-2)}.icon{width:32px;height:32px}.messages{min-height:0;overflow:auto;padding:14px;background:var(--caw-panel-2)}.empty{height:100%;display:grid;place-items:center;text-align:center;color:var(--caw-muted)}.line{margin:0 0 10px}.bubble{max-width:94%;padding:9px 11px;border:1px solid var(--caw-line);border-radius:10px;background:var(--caw-panel);white-space:pre-wrap;overflow-wrap:anywhere}.user{display:flex;justify-content:flex-end}.user .bubble{max-width:86%;background:var(--caw-accent-soft)}.thinking .bubble{color:var(--caw-muted);font-style:italic;border-style:dashed}.error .bubble{color:var(--caw-bad);background:var(--caw-bad-soft)}.status{text-align:center;color:var(--caw-muted);font-size:11px}.tool{max-width:94%;border:1px solid var(--caw-line);border-radius:10px;background:var(--caw-panel);overflow:hidden}.tool summary{padding:9px 11px;font-weight:700}.tool pre{margin:0;padding:10px;background:#101923;color:#dae6f2;overflow:auto;font-size:11px}.result{padding:9px;border-top:1px solid var(--caw-line);white-space:pre-wrap}.permission{justify-content:space-between;gap:12px;padding:10px;border-top:1px solid var(--caw-line);background:var(--caw-warn-soft)}.permission div{min-width:0}.permission span{display:block;color:var(--caw-muted);font-size:11px;overflow-wrap:anywhere}.permission .actions{flex:none}.small{padding:6px 9px}.allow{border-color:transparent;background:var(--caw-accent);color:#fff}.deny{color:var(--caw-bad)}.composer{border-top:1px solid var(--caw-line)}.input{display:flex;align-items:flex-end;gap:8px;padding:9px 9px 5px}.input textarea{flex:1;min-height:40px;max-height:150px;resize:vertical;padding:9px;border:1px solid var(--caw-line);border-radius:9px;background:var(--caw-panel-2);outline:0}.send{width:40px;height:40px;border:0;background:var(--caw-accent);color:#fff}.send.stop{background:var(--caw-bad)}.foot{justify-content:space-between;padding:0 9px 9px;color:var(--caw-muted);font-size:10px}.foot select{padding:4px;border:1px solid var(--caw-line);border-radius:7px;background:var(--caw-panel-2)}
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
class SessionHistoryPopover extends HTMLElement {
|
|
12
|
+
constructor(){super();this.attachShadow({mode:'open'});this._sessions=[];this._active='';}
|
|
13
|
+
set sessions(value){this._sessions=Array.isArray(value)?value:[];this.render()} set activeSessionId(value){this._active=value||'';this.render()}
|
|
14
|
+
connectedCallback(){this.render()}
|
|
15
|
+
render(){this.shadowRoot.innerHTML=`<style>:host{position:absolute;right:0;top:39px;z-index:20;width:min(340px,85vw);max-height:360px;overflow:auto;padding:7px;border:1px solid var(--caw-line);border-radius:11px;background:var(--caw-panel);box-shadow:var(--caw-shadow)}button{width:100%;padding:9px;margin-bottom:4px;text-align:left;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--caw-text);cursor:pointer}button:hover,button.on{border-color:var(--caw-line);background:var(--caw-panel-2)}small{display:block;margin-top:4px;color:var(--caw-muted)}</style>${this._sessions.length?this._sessions.map(s=>`<button class="${s.sessionId===this._active?'on':''}" data-id="${esc(s.sessionId)}">${esc(s.title||s.sessionId)}<small>${esc(s.model||s.aiProfileId||'')}</small></button>`).join(''):'<small>No sessions</small>'}`;this.shadowRoot.querySelectorAll('button').forEach(button=>button.onclick=()=>this.dispatchEvent(new CustomEvent('session-select',{bubbles:true,composed:true,detail:{sessionId:button.dataset.id}})));}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class ClaudeWorkbenchPane extends HTMLElement {
|
|
19
|
+
constructor(){super();this.attachShadow({mode:'open'});this._client=null;this._context=null;this._controller=null;this._state=Core.createState();this._history=false;this._queued=false;}
|
|
20
|
+
connectedCallback(){this.ensure();this.render();if(this._context)this._controller.setContext(this._context)}
|
|
21
|
+
disconnectedCallback(){if(!this.hasAttribute('preserve-controller'))this.destroy()}
|
|
22
|
+
set client(value){this._client=value;this.ensure();this._controller.setAdapter(value);if(this.isConnected&&this._context)this._controller.setContext(this._context)} get client(){return this._client}
|
|
23
|
+
set adapter(value){this.client=value} get adapter(){return this.client}
|
|
24
|
+
set context(value){this._context=value?{...value,contextId:value.contextId||value.projectId}:null;this.ensure();if(this.isConnected)this._controller.setContext(this._context);this.schedule()} get context(){return this._context}
|
|
25
|
+
get controller(){this.ensure();return this._controller}
|
|
26
|
+
ensure(){if(this._controller)return;if(!Core)throw new Error('ClaudeWorkbenchCore must be loaded first');this._controller=Core.createController({adapter:this._client,onChange:state=>{this._state=state;this.schedule();this.dispatchEvent(new CustomEvent('workbench-state-change',{detail:state}));this.dispatchEvent(new CustomEvent('running-change',{detail:{running:state.turnRunning,context:state.context}}));}})}
|
|
27
|
+
schedule(){if(!this.isConnected||this._queued)return;this._queued=true;queueMicrotask(()=>{this._queued=false;const old=this.shadowRoot.querySelector('.messages');const bottom=!old||old.scrollHeight-old.scrollTop-old.clientHeight<90;const draft=this.shadowRoot.querySelector('textarea')?.value||'';this.render();const input=this.shadowRoot.querySelector('textarea');if(input)input.value=draft;const next=this.shadowRoot.querySelector('.messages');if(bottom&&next)next.scrollTop=next.scrollHeight})}
|
|
28
|
+
line(item){if(item.kind==='status')return`<div class="line status">${esc(item.text)}</div>`;if(item.kind==='tool')return`<details class="line tool" ${item.status==='running'?'open':''}><summary>${esc(item.name||'tool')} · ${esc(item.status||'running')}</summary><pre>${esc(JSON.stringify(item.input||{},null,2))}</pre>${item.result!=null?`<div class="result">${esc(item.result)}</div>`:''}</details>`;return`<div class="line ${esc(item.kind)}"><div class="bubble">${esc(item.text||item.message||'')}</div></div>`}
|
|
29
|
+
render(){const c=this._context||{};const s=this._state;const usage=s.tokenUsage||{};const percent=usage.hasUsage&&usage.total?Math.min(100,usage.used/usage.total*100):0;const status=s.error?'error':s.turnRunning?'running':s.session.connectionState==='connected'?'connected':'';this.shadowRoot.innerHTML=`<style>${css}</style><section class="shell"><header class="head"><div class="identity"><i class="dot ${status}"></i><div><div class="title">Claude Workbench</div><div class="sub">${esc(c.displayName||'No context')} · ${esc(s.session.connectionState)}</div></div><span class="chip">${esc(s.session.aiProfileId||c.aiProfileId||'No profile')}</span></div><div class="actions" style="position:relative"><button class="icon" data-a="history" title="Session history">◷</button><button class="icon" data-a="new" title="New session">+</button><button class="icon" data-a="settings" title="AI Profiles">⚙</button>${this._history?'<session-history-popover></session-history-popover>':''}</div></header><main class="messages">${s.lines.length?s.lines.map(item=>this.line(item)).join(''):`<div class="empty"><div><strong>${c.contextId?'Start a conversation':'Select a context'}</strong><br>${c.contextId?'This session is isolated to the active context.':'The host supplies WorkbenchContext.'}</div></div>`}</main><div>${s.permission?`<div class="permission"><div><strong>${esc(s.permission.toolName)} requests permission</strong><span>${esc(s.permission.input?.file_path||s.permission.input?.command||s.permission.message||'')}</span></div><div class="actions"><button class="small deny" data-a="deny">Deny</button><button class="small allow" data-a="allow">Allow</button></div></div>`:''}<div class="composer"><div class="input"><textarea placeholder="Ask Claude Code…" ${!c.contextId||s.requestPending||s.permission?'disabled':''}></textarea><button class="send ${s.turnRunning?'stop':''}" data-a="${s.turnRunning?'stop':'send'}">${s.turnRunning?'■':'↑'}</button></div><div class="foot"><select data-a="mode"><option value="default">Default</option><option value="acceptEdits">Accept Edits</option><option value="plan">Plan</option><option value="bypassPermissions">Bypass</option></select><span>${usage.hasUsage?percent.toFixed(1)+'%':'—'} · ${esc(s.session.model||'Claude Code')}</span></div></div></div></section>`;const history=this.shadowRoot.querySelector('session-history-popover');if(history){history.sessions=s.sessions;history.activeSessionId=s.sessionId}this.bind()}
|
|
30
|
+
bind(){const q=a=>this.shadowRoot.querySelector(`[data-a="${a}"]`);q('history')?.addEventListener('click',()=>{this._history=!this._history;this.schedule()});q('new')?.addEventListener('click',()=>this.run(()=>this._controller.newSession()));q('settings')?.addEventListener('click',()=>this.dispatchEvent(new CustomEvent('open-profile-settings',{bubbles:true,composed:true,detail:{context:this._context}})));q('allow')?.addEventListener('click',()=>this.run(()=>this._controller.resolvePermission(true)));q('deny')?.addEventListener('click',()=>this.run(()=>this._controller.resolvePermission(false)));q('stop')?.addEventListener('click',()=>this.run(()=>this._controller.abort()));q('mode')?.addEventListener('change',event=>this.run(()=>this._controller.setPermissionMode(event.target.value)));this.shadowRoot.querySelector('session-history-popover')?.addEventListener('session-select',event=>{this._history=false;this.run(()=>this._controller.restoreSession(event.detail.sessionId))});const input=this.shadowRoot.querySelector('textarea');const send=()=>{const value=input?.value.trim();if(value){input.value='';this.run(()=>this._controller.send(value))}};q('send')?.addEventListener('click',send);input?.addEventListener('keydown',event=>{if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}})}
|
|
31
|
+
async run(fn){try{await fn()}catch(error){this.dispatchEvent(new CustomEvent('workbench-error',{bubbles:true,composed:true,detail:{error}}))}}
|
|
32
|
+
destroy(){this._controller?.destroy();this._controller=null}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class AiProfileSettings extends HTMLElement {
|
|
36
|
+
constructor(){super();this.attachShadow({mode:'open'});this._client=null;this._profiles=[];this._selected='';this._status='Loading profiles…'}
|
|
37
|
+
set client(value){this._client=value;if(this.isConnected)this.refresh()} get client(){return this._client}
|
|
38
|
+
connectedCallback(){this.render();this.refresh()}
|
|
39
|
+
async refresh(){if(!this._client){this._status='Client not configured';this.render();return}try{this._profiles=await this._client.listProfiles();if(!this._selected)this._selected=this._profiles[0]?.id||'';this._status=`${this._profiles.length} profile(s)`}catch(error){this._status=error.message}this.render()}
|
|
40
|
+
current(){return this._profiles.find(p=>p.id===this._selected)||{id:'',name:'',provider:'',runtime:'claude-code',protocol:'anthropic-compatible',baseUrl:'',credentialRef:'',models:{default:''},contextWindow:200000,timeoutMs:300000,enabled:true}}
|
|
41
|
+
render(){const p=this.current();this.shadowRoot.innerHTML=`<style>:host{display:block;height:100%;min-height:440px;color:var(--caw-text);font:12px Inter,"Segoe UI",sans-serif}*{box-sizing:border-box}button,input,select{font:inherit;color:inherit}.shell{height:100%;display:grid;grid-template-columns:210px 1fr;border:1px solid var(--caw-line);border-radius:13px;overflow:hidden;background:var(--caw-panel)}aside{padding:10px;background:var(--caw-panel-2);border-right:1px solid var(--caw-line);overflow:auto}aside button{width:100%;padding:9px;margin-bottom:5px;text-align:left;border:1px solid var(--caw-line);border-radius:8px;background:var(--caw-panel)}aside button.on{border-color:var(--caw-accent)}main{display:grid;grid-template-rows:auto 1fr auto;min-width:0}header,footer{display:flex;justify-content:space-between;align-items:center;padding:13px;border-bottom:1px solid var(--caw-line)}footer{border-top:1px solid var(--caw-line);border-bottom:0}.form{padding:14px;overflow:auto;display:grid;grid-template-columns:1fr 1fr;gap:11px;align-content:start}label{display:grid;gap:5px;color:var(--caw-muted)}input,select{padding:8px;border:1px solid var(--caw-line);border-radius:8px;background:var(--caw-panel-2)}.wide{grid-column:1/-1}.actions{display:flex;gap:7px}button{padding:7px 9px;border:1px solid var(--caw-line);border-radius:8px;background:var(--caw-panel-2);cursor:pointer}.primary{color:#fff;background:var(--caw-accent);border-color:transparent}@media(max-width:700px){.shell{grid-template-columns:1fr}aside{display:none}.form{grid-template-columns:1fr}.wide{grid-column:auto}}</style><section class="shell"><aside><h3>AI Profiles</h3>${this._profiles.map(item=>`<button class="${item.id===this._selected?'on':''}" data-id="${esc(item.id)}"><strong>${esc(item.name||item.id)}</strong><br><small>${esc(item.provider)} · ${item.credentialConfigured?'credential set':'no credential'}</small></button>`).join('')}<button data-a="new">+ New profile</button></aside><main><header><div><strong>${esc(p.name||'New profile')}</strong><br><small>${esc(this._status)}</small></div><div class="actions"><button data-a="credential">Credential</button><button data-a="test">Real runtime test</button></div></header><form class="form" id="profile"><label>ID<input name="id" value="${esc(p.id)}" required></label><label>Name<input name="name" value="${esc(p.name)}" required></label><label>Provider<input name="provider" value="${esc(p.provider)}" required></label><label>Runtime<select name="runtime"><option value="claude-code">Claude Code</option></select></label><label>Protocol<select name="protocol"><option value="anthropic-compatible">Anthropic compatible</option><option value="openai-compatible" ${p.protocol==='openai-compatible'?'selected':''}>OpenAI compatible</option></select></label><label>Base URL<input name="baseUrl" value="${esc(p.baseUrl)}" required></label><label class="wide">Default model<input name="model" value="${esc(p.models?.default||'')}" required></label><label>Context window<input name="contextWindow" type="number" value="${Number(p.contextWindow||200000)}"></label><label>Timeout ms<input name="timeoutMs" type="number" value="${Number(p.timeoutMs||300000)}"></label></form><footer><span>${p.credentialConfigured?'Secret stored on server':'Credential required'}</span><div class="actions"><button data-a="delete">Delete</button><button class="primary" data-a="save">Save</button></div></footer></main></section>`;this.bind()}
|
|
42
|
+
draft(){const f=new FormData(this.shadowRoot.querySelector('#profile'));const model=String(f.get('model')||'').trim();return{...this.current(),id:String(f.get('id')||'').trim(),name:String(f.get('name')||'').trim(),provider:String(f.get('provider')||'').trim(),runtime:'claude-code',protocol:String(f.get('protocol')),baseUrl:String(f.get('baseUrl')||'').trim(),credentialRef:this.current().credentialRef||`credential:${String(f.get('id')||'').trim()}`,models:{default:model,fast:model,reasoning:model,coding:model,largeContext:model},contextWindow:Number(f.get('contextWindow')),timeoutMs:Number(f.get('timeoutMs')),enabled:true}}
|
|
43
|
+
bind(){this.shadowRoot.querySelectorAll('[data-id]').forEach(button=>button.onclick=()=>{this._selected=button.dataset.id;this.render()});this.shadowRoot.querySelector('[data-a="new"]')?.addEventListener('click',()=>{this._selected='';this._profiles=[];this._status='Create a new profile';this.render()});this.shadowRoot.querySelector('[data-a="save"]')?.addEventListener('click',()=>this.action(async()=>{const profile=await this._client.saveProfile(this.draft());this._selected=profile.id;this.dispatchEvent(new CustomEvent('profile-save',{bubbles:true,composed:true,detail:{profile}}));await this.refresh()}));this.shadowRoot.querySelector('[data-a="delete"]')?.addEventListener('click',()=>this.action(async()=>{if(!this.current().id)return;await this._client.deleteProfile(this.current().id);this._selected='';await this.refresh()}));this.shadowRoot.querySelector('[data-a="credential"]')?.addEventListener('click',()=>this.action(async()=>{const id=this.draft().id;if(!id)throw new Error('Save a profile ID first');const secret=global.prompt?.(`Set credential for ${id}. It will be sent once to the local server and never displayed again.`);if(secret){await this._client.setCredential(id,secret);await this.refresh()}}));this.shadowRoot.querySelector('[data-a="test"]')?.addEventListener('click',()=>this.action(async()=>{const id=this.current().id;if(!id)throw new Error('Save the profile first');const result=await this._client.testProfile(id);this._status=result.runtimeVerified?`REAL PASS · ${result.model} · ${result.durationMs}ms`:'FAILED · runtime not verified';this.dispatchEvent(new CustomEvent('profile-test',{bubbles:true,composed:true,detail:result}));this.render()}))}
|
|
44
|
+
async action(fn){try{this._status='Working…';this.render();await fn()}catch(error){this._status=error.message;this.dispatchEvent(new CustomEvent('workbench-error',{bubbles:true,composed:true,detail:{error}}));this.render()}}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if(!customElements.get('session-history-popover'))customElements.define('session-history-popover',SessionHistoryPopover);
|
|
48
|
+
if(!customElements.get('claude-workbench-pane'))customElements.define('claude-workbench-pane',ClaudeWorkbenchPane);
|
|
49
|
+
if(!customElements.get('ai-profile-settings'))customElements.define('ai-profile-settings',AiProfileSettings);
|
|
50
|
+
global.ClaudeWorkbenchPane=ClaudeWorkbenchPane;global.AiProfileSettings=AiProfileSettings;global.SessionHistoryPopover=SessionHistoryPopover;
|
|
51
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
package/packages/ui/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-ai-workbench/ui",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"main": "ai-profile-settings.js",
|
|
6
6
|
"browser": "ai-profile-settings.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@claude-ai-workbench/core": "0.13.
|
|
36
|
+
"@claude-ai-workbench/core": "0.13.3"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=18"
|
package/packages/ui/theme.css
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
--caw-page:#eef2f6; --caw-panel:#fff; --caw-panel-2:#f6f8fb; --caw-panel-3:#e8edf3;
|
|
3
|
-
--caw-line:#d7dfe8; --caw-line-strong:#bcc8d5; --caw-text:#172231; --caw-muted:#687789;
|
|
4
|
-
--caw-accent:#4f6f8f; --caw-accent-soft:rgba(79,111,143,.12); --caw-good:#2f7d64;
|
|
5
|
-
--caw-warn:#9a7428; --caw-warn-soft:rgba(154,116,40,.12); --caw-bad:#af4b4b; --caw-bad-soft:rgba(175,75,75,.11);
|
|
6
|
-
--caw-shadow:0 18px 56px rgba(24,34,48,.12);
|
|
7
|
-
}
|
|
8
|
-
[data-theme="dark"],.theme-dark {
|
|
9
|
-
--caw-page:#090e14; --caw-panel:#151d27; --caw-panel-2:#1a2430; --caw-panel-3:#202c39;
|
|
10
|
-
--caw-line:#2b3949; --caw-line-strong:#3a4a5d; --caw-text:#e6edf5; --caw-muted:#9aa9ba;
|
|
11
|
-
--caw-accent:#7896aa; --caw-accent-soft:rgba(120,150,170,.13); --caw-good:#76b69c;
|
|
12
|
-
--caw-warn:#c6a45f; --caw-warn-soft:rgba(198,164,95,.13); --caw-bad:#da7777; --caw-bad-soft:rgba(218,119,119,.12);
|
|
13
|
-
}
|
|
1
|
+
:root {
|
|
2
|
+
--caw-page:#eef2f6; --caw-panel:#fff; --caw-panel-2:#f6f8fb; --caw-panel-3:#e8edf3;
|
|
3
|
+
--caw-line:#d7dfe8; --caw-line-strong:#bcc8d5; --caw-text:#172231; --caw-muted:#687789;
|
|
4
|
+
--caw-accent:#4f6f8f; --caw-accent-soft:rgba(79,111,143,.12); --caw-good:#2f7d64;
|
|
5
|
+
--caw-warn:#9a7428; --caw-warn-soft:rgba(154,116,40,.12); --caw-bad:#af4b4b; --caw-bad-soft:rgba(175,75,75,.11);
|
|
6
|
+
--caw-shadow:0 18px 56px rgba(24,34,48,.12);
|
|
7
|
+
}
|
|
8
|
+
[data-theme="dark"],.theme-dark {
|
|
9
|
+
--caw-page:#090e14; --caw-panel:#151d27; --caw-panel-2:#1a2430; --caw-panel-3:#202c39;
|
|
10
|
+
--caw-line:#2b3949; --caw-line-strong:#3a4a5d; --caw-text:#e6edf5; --caw-muted:#9aa9ba;
|
|
11
|
+
--caw-accent:#7896aa; --caw-accent-soft:rgba(120,150,170,.13); --caw-good:#76b69c;
|
|
12
|
+
--caw-warn:#c6a45f; --caw-warn-soft:rgba(198,164,95,.13); --caw-bad:#da7777; --caw-bad-soft:rgba(218,119,119,.12);
|
|
13
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema": "claude-prompts/v1",
|
|
3
|
-
"prompts": {
|
|
4
|
-
"initial-analysis": {
|
|
5
|
-
"description": "Initial project analysis. Claude runs from the KB workspace and reads the source project via PROJECT_PATH.",
|
|
6
|
-
"model": "sonnet",
|
|
7
|
-
"permissionMode": "bypassPermissions",
|
|
8
|
-
"allowedTools": ["Read", "Grep", "Glob"],
|
|
9
|
-
"systemPrompt": "You are an expert project analyst building a knowledge base. The current working directory is the knowledge-base workspace, not the source project. Read source code from PROJECT_PATH and treat KB_PATH as the write target for knowledge-base drafts and updates. Output STRICT JSON only: no prose and no markdown fences. The JSON must match this shape:\n\n{\n \"goalDraft\": {\n \"oneLine\": \"one-sentence description of what this project is\",\n \"positioning\": \"paragraph describing the project's role and core value\",\n \"coreValue\": [\"bullet points of core value to developers / teams\"],\n \"painPoints\": [{\"pain\": \"...\", \"legacyApproach\": \"...\", \"projectApproach\": \"...\"}]\n },\n \"analysisDraft\": {\n \"framework\": \"primary framework / language / stack\",\n \"modules\": [{\"name\": \"...\", \"responsibility\": \"...\", \"keyFiles\": [\"path/to/file\"]}],\n \"architecture\": \"paragraph describing the architecture and data flow\",\n \"evidence\": [{\"claim\": \"...\", \"file\": \"path:line\"}]\n },\n \"features\": [{\"slug\": \"kebab-case-slug\", \"name\": \"...\", \"summary\": \"...\"}]\n}\n\nUse the Read, Grep, and Glob tools to investigate PROJECT_PATH. Cite real source file paths in evidence. Do not invent modules that do not exist. Do not modify the source project unless the user explicitly asks for source changes.",
|
|
10
|
-
"userPrompt": "Analyze the source project at PROJECT_PATH. The current working directory is KB_PATH.\n\nProject context:\n- Slug: {{SLUG}}\n- Source project path: {{PROJECT_PATH}}\n- Knowledge-base path: {{KB_PATH}}\n- Primary language: {{PRIMARY_LANGUAGE}}\n- Knowledge language: {{KNOWLEDGE_LANGUAGE}}\n\nTasks:\n1. Identify the framework, main tech stack, and language distribution.\n2. List the core modules / top-level directories and their responsibilities.\n3. Describe the architecture and data flow at a high level.\n4. Identify 3-6 user-facing features.\n5. Capture 4-8 evidence-backed claims with file:line citations.\n\nWrite the knowledge-base content in {{KNOWLEDGE_LANGUAGE}} where natural, but keep code identifiers, file paths, and JSON keys in English.\n\nReturn ONLY the JSON object described in the system prompt."
|
|
11
|
-
},
|
|
12
|
-
"commit-analysis": {
|
|
13
|
-
"description": "Batch analysis of one or more commits. Claude runs from the KB workspace and reads the source project via PROJECT_PATH.",
|
|
14
|
-
"model": "sonnet",
|
|
15
|
-
"permissionMode": "bypassPermissions",
|
|
16
|
-
"allowedTools": ["Read", "Grep", "Glob", "Bash"],
|
|
17
|
-
"systemPrompt": "You are a commit analyst building a knowledge base. The current working directory is the knowledge-base workspace, not the source project. Read source code from PROJECT_PATH and treat KB_PATH as the write target for knowledge-base drafts and updates. For each commit provided, output a structured change description. STRICT JSON only: no prose.\n\nOutput shape:\n{\n \"changes\": [\n {\n \"hash\": \"<full-or-short-hash>\",\n \"subject\": \"<original commit subject>\",\n \"type\": \"feat|fix|refactor|docs|test|chore|infra\",\n \"classification\": \"feature|bugfix|infrastructure|documentation|test\",\n \"summary\": \"one-paragraph description of what changed and why\",\n \"files\": [\"paths/touched\"],\n \"evidence\": [{\"claim\": \"...\", \"file\": \"path:line\"}]\n }\n ]\n}\n\nUse git commands against PROJECT_PATH and Read for inspecting changed files. Cite real source file paths. Do not modify the source project unless the user explicitly asks for source changes.",
|
|
18
|
-
"userPrompt": "Analyze the following commits in the source project at PROJECT_PATH. The current working directory is KB_PATH.\n\nProject: {{SLUG}} ({{PROJECT_PATH}})\nKnowledge-base path: {{KB_PATH}}\nCommit range: {{COMMIT_RANGE}}\nCommits to analyze:\n{{COMMIT_HASHES}}\n\nFor each commit, produce one entry in the changes array. Use git commands against {{PROJECT_PATH}} and Read for inspecting changed source files. Summarize in {{KNOWLEDGE_LANGUAGE}}; keep identifiers and paths in English.\n\nReturn ONLY the JSON object."
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"schema": "claude-prompts/v1",
|
|
3
|
+
"prompts": {
|
|
4
|
+
"initial-analysis": {
|
|
5
|
+
"description": "Initial project analysis. Claude runs from the KB workspace and reads the source project via PROJECT_PATH.",
|
|
6
|
+
"model": "sonnet",
|
|
7
|
+
"permissionMode": "bypassPermissions",
|
|
8
|
+
"allowedTools": ["Read", "Grep", "Glob"],
|
|
9
|
+
"systemPrompt": "You are an expert project analyst building a knowledge base. The current working directory is the knowledge-base workspace, not the source project. Read source code from PROJECT_PATH and treat KB_PATH as the write target for knowledge-base drafts and updates. Output STRICT JSON only: no prose and no markdown fences. The JSON must match this shape:\n\n{\n \"goalDraft\": {\n \"oneLine\": \"one-sentence description of what this project is\",\n \"positioning\": \"paragraph describing the project's role and core value\",\n \"coreValue\": [\"bullet points of core value to developers / teams\"],\n \"painPoints\": [{\"pain\": \"...\", \"legacyApproach\": \"...\", \"projectApproach\": \"...\"}]\n },\n \"analysisDraft\": {\n \"framework\": \"primary framework / language / stack\",\n \"modules\": [{\"name\": \"...\", \"responsibility\": \"...\", \"keyFiles\": [\"path/to/file\"]}],\n \"architecture\": \"paragraph describing the architecture and data flow\",\n \"evidence\": [{\"claim\": \"...\", \"file\": \"path:line\"}]\n },\n \"features\": [{\"slug\": \"kebab-case-slug\", \"name\": \"...\", \"summary\": \"...\"}]\n}\n\nUse the Read, Grep, and Glob tools to investigate PROJECT_PATH. Cite real source file paths in evidence. Do not invent modules that do not exist. Do not modify the source project unless the user explicitly asks for source changes.",
|
|
10
|
+
"userPrompt": "Analyze the source project at PROJECT_PATH. The current working directory is KB_PATH.\n\nProject context:\n- Slug: {{SLUG}}\n- Source project path: {{PROJECT_PATH}}\n- Knowledge-base path: {{KB_PATH}}\n- Primary language: {{PRIMARY_LANGUAGE}}\n- Knowledge language: {{KNOWLEDGE_LANGUAGE}}\n\nTasks:\n1. Identify the framework, main tech stack, and language distribution.\n2. List the core modules / top-level directories and their responsibilities.\n3. Describe the architecture and data flow at a high level.\n4. Identify 3-6 user-facing features.\n5. Capture 4-8 evidence-backed claims with file:line citations.\n\nWrite the knowledge-base content in {{KNOWLEDGE_LANGUAGE}} where natural, but keep code identifiers, file paths, and JSON keys in English.\n\nReturn ONLY the JSON object described in the system prompt."
|
|
11
|
+
},
|
|
12
|
+
"commit-analysis": {
|
|
13
|
+
"description": "Batch analysis of one or more commits. Claude runs from the KB workspace and reads the source project via PROJECT_PATH.",
|
|
14
|
+
"model": "sonnet",
|
|
15
|
+
"permissionMode": "bypassPermissions",
|
|
16
|
+
"allowedTools": ["Read", "Grep", "Glob", "Bash"],
|
|
17
|
+
"systemPrompt": "You are a commit analyst building a knowledge base. The current working directory is the knowledge-base workspace, not the source project. Read source code from PROJECT_PATH and treat KB_PATH as the write target for knowledge-base drafts and updates. For each commit provided, output a structured change description. STRICT JSON only: no prose.\n\nOutput shape:\n{\n \"changes\": [\n {\n \"hash\": \"<full-or-short-hash>\",\n \"subject\": \"<original commit subject>\",\n \"type\": \"feat|fix|refactor|docs|test|chore|infra\",\n \"classification\": \"feature|bugfix|infrastructure|documentation|test\",\n \"summary\": \"one-paragraph description of what changed and why\",\n \"files\": [\"paths/touched\"],\n \"evidence\": [{\"claim\": \"...\", \"file\": \"path:line\"}]\n }\n ]\n}\n\nUse git commands against PROJECT_PATH and Read for inspecting changed files. Cite real source file paths. Do not modify the source project unless the user explicitly asks for source changes.",
|
|
18
|
+
"userPrompt": "Analyze the following commits in the source project at PROJECT_PATH. The current working directory is KB_PATH.\n\nProject: {{SLUG}} ({{PROJECT_PATH}})\nKnowledge-base path: {{KB_PATH}}\nCommit range: {{COMMIT_RANGE}}\nCommits to analyze:\n{{COMMIT_HASHES}}\n\nFor each commit, produce one entry in the changes array. Use git commands against {{PROJECT_PATH}} and Read for inspecting changed source files. Summarize in {{KNOWLEDGE_LANGUAGE}}; keep identifiers and paths in English.\n\nReturn ONLY the JSON object."
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
const { AI_VENDOR_PRESETS, listVendorPresetNames } = require('../lib/ai-vendor-presets');
|
|
2
|
-
const { listAdapters } = require('../lib/ai-adapter');
|
|
3
|
-
const { sendJson, readBody } = require('./node-helpers');
|
|
4
|
-
|
|
5
|
-
async function handleAiProfileRoutes(req, res, options = {}) {
|
|
6
|
-
const store = options.store || options.profileStore;
|
|
7
|
-
const basePath = options.basePath || '/api/ai-profiles';
|
|
8
|
-
const vendorPath = options.vendorPath || '/api/ai-vendor-presets';
|
|
9
|
-
const url = options.url || new URL(req.url, 'http://127.0.0.1');
|
|
10
|
-
const pathname = url.pathname;
|
|
11
|
-
if (pathname !== basePath && pathname !== vendorPath && !pathname.startsWith(`${basePath}/`)) return false;
|
|
12
|
-
if (!store) {
|
|
13
|
-
sendJson(res, 503, { ok: false, error: 'AI profile store is not available' });
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
if (req.method === 'GET' && pathname === basePath) {
|
|
19
|
-
const publicConfig = store.publicConfig();
|
|
20
|
-
sendJson(res, 200, {
|
|
21
|
-
ok: true,
|
|
22
|
-
...publicConfig,
|
|
23
|
-
adapters: listAdapters(),
|
|
24
|
-
});
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (req.method === 'GET' && pathname === vendorPath) {
|
|
29
|
-
sendJson(res, 200, { ok: true, presets: AI_VENDOR_PRESETS, names: listVendorPresetNames() });
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (req.method === 'PUT' && pathname === basePath) {
|
|
34
|
-
const body = await readBody(req);
|
|
35
|
-
const saved = store.writeConfig(body);
|
|
36
|
-
sendJson(res, 200, { ok: true, config: saved });
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (req.method === 'POST' && pathname === `${basePath}/test`) {
|
|
41
|
-
const body = await readBody(req);
|
|
42
|
-
const result = await store.testProfile({
|
|
43
|
-
profileId: body.profileId,
|
|
44
|
-
profile: body.profile,
|
|
45
|
-
prompt: body.prompt || 'what model are you?',
|
|
46
|
-
});
|
|
47
|
-
sendJson(res, 200, result);
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
sendJson(res, error.status || 400, { ok: false, error: error.message, errors: error.errors || undefined });
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
sendJson(res, 404, { ok: false, error: 'unknown AI profile route' });
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
module.exports = {
|
|
60
|
-
handleAiProfileRoutes,
|
|
61
|
-
};
|
|
1
|
+
const { AI_VENDOR_PRESETS, listVendorPresetNames } = require('../lib/ai-vendor-presets');
|
|
2
|
+
const { listAdapters } = require('../lib/ai-adapter');
|
|
3
|
+
const { sendJson, readBody } = require('./node-helpers');
|
|
4
|
+
|
|
5
|
+
async function handleAiProfileRoutes(req, res, options = {}) {
|
|
6
|
+
const store = options.store || options.profileStore;
|
|
7
|
+
const basePath = options.basePath || '/api/ai-profiles';
|
|
8
|
+
const vendorPath = options.vendorPath || '/api/ai-vendor-presets';
|
|
9
|
+
const url = options.url || new URL(req.url, 'http://127.0.0.1');
|
|
10
|
+
const pathname = url.pathname;
|
|
11
|
+
if (pathname !== basePath && pathname !== vendorPath && !pathname.startsWith(`${basePath}/`)) return false;
|
|
12
|
+
if (!store) {
|
|
13
|
+
sendJson(res, 503, { ok: false, error: 'AI profile store is not available' });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
if (req.method === 'GET' && pathname === basePath) {
|
|
19
|
+
const publicConfig = store.publicConfig();
|
|
20
|
+
sendJson(res, 200, {
|
|
21
|
+
ok: true,
|
|
22
|
+
...publicConfig,
|
|
23
|
+
adapters: listAdapters(),
|
|
24
|
+
});
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (req.method === 'GET' && pathname === vendorPath) {
|
|
29
|
+
sendJson(res, 200, { ok: true, presets: AI_VENDOR_PRESETS, names: listVendorPresetNames() });
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (req.method === 'PUT' && pathname === basePath) {
|
|
34
|
+
const body = await readBody(req);
|
|
35
|
+
const saved = store.writeConfig(body);
|
|
36
|
+
sendJson(res, 200, { ok: true, config: saved });
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (req.method === 'POST' && pathname === `${basePath}/test`) {
|
|
41
|
+
const body = await readBody(req);
|
|
42
|
+
const result = await store.testProfile({
|
|
43
|
+
profileId: body.profileId,
|
|
44
|
+
profile: body.profile,
|
|
45
|
+
prompt: body.prompt || 'what model are you?',
|
|
46
|
+
});
|
|
47
|
+
sendJson(res, 200, result);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
sendJson(res, error.status || 400, { ok: false, error: error.message, errors: error.errors || undefined });
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
sendJson(res, 404, { ok: false, error: 'unknown AI profile route' });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
handleAiProfileRoutes,
|
|
61
|
+
};
|