@agentproto/apps 0.10.0 → 0.11.0

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/dist/index.mjs CHANGED
@@ -2888,6 +2888,10 @@ function panelBridgeScript(appName) {
2888
2888
  // JSON-RPC 2.0 over window.parent.postMessage \xB7 spec 2026-01-26
2889
2889
  var _nextId = 1, _pending = {}, _notifyHandlers = [];
2890
2890
  var _hostContext = null, _hostContextHandlers = [];
2891
+ var _standaloneApp = null;
2892
+ function _isStandalone(){
2893
+ return window.parent === window && !!window.McpApp && typeof window.McpApp.connect === 'function';
2894
+ }
2891
2895
  function post(msg){ window.parent.postMessage(msg, '*'); }
2892
2896
  function getHostContext(){ return _hostContext; }
2893
2897
  function onHostContext(cb){
@@ -2934,6 +2938,16 @@ window.addEventListener('message', function(evt){
2934
2938
  }
2935
2939
  });
2936
2940
  function initBridge(){
2941
+ if (_isStandalone()){
2942
+ return window.McpApp.connect().then(function(conn){
2943
+ _standaloneApp = conn;
2944
+ // No host to advertise a hostContext \u2014 default to inline with no
2945
+ // other modes available, which keeps the #dm/#pin toggle buttons
2946
+ // hidden (they only show for modes hostContext.availableDisplayModes
2947
+ // lists) instead of rendering two buttons with nothing to switch to.
2948
+ _setHostContext({displayMode: 'inline', availableDisplayModes: []});
2949
+ });
2950
+ }
2937
2951
  return rpcRequest('ui/initialize', {
2938
2952
  appInfo: {name: ${JSON.stringify(appName)}, version: '0.1.0'},
2939
2953
  appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},
@@ -2949,7 +2963,10 @@ function requestDisplayMode(mode){
2949
2963
  return rpcRequest('ui/request-display-mode', {mode: mode});
2950
2964
  }
2951
2965
  function callTool(name, args){
2952
- return rpcRequest('tools/call', {name: name, arguments: args || {}}).then(function(result){
2966
+ var raw = _standaloneApp
2967
+ ? _standaloneApp.callTool(name, args || {})
2968
+ : rpcRequest('tools/call', {name: name, arguments: args || {}});
2969
+ return raw.then(function(result){
2953
2970
  if (result.isError){
2954
2971
  var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';
2955
2972
  throw new Error(e);
@@ -5591,317 +5608,634 @@ var sessionChatApp = defineApp({
5591
5608
  }
5592
5609
  });
5593
5610
 
5594
- // src/work-board/panel.ts
5595
- var WORK_BOARD_HTML = `<!DOCTYPE html>
5611
+ // src/work-board/panel.generated.ts
5612
+ var WORK_BOARD_HTML_GENERATED = `<!DOCTYPE html>
5596
5613
  <html lang="en">
5597
5614
  <head>
5598
- <meta charset="UTF-8">
5599
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
5615
+ <meta charset="UTF-8" />
5616
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
5600
5617
  <title>agentproto work board</title>
5601
- <style>
5602
- *{box-sizing:border-box;margin:0;padding:0}
5603
- :root{
5604
- --bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;
5605
- --text:#e6edf3;--text2:#8b949e;
5606
- --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;
5607
- }
5608
- html,body{height:100%;font-family:Menlo,Monaco,'Courier New',monospace;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}
5609
- #app{display:flex;flex-direction:column;height:100%}
5610
- #toolbar{padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2);flex-shrink:0}
5611
- #board-label{font-size:11px;color:var(--text2)}
5612
- #board-id{font-weight:600;color:var(--text);font-size:12px}
5613
- #board-input{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:4px 8px;border-radius:4px;font-size:12px;font-family:inherit;width:220px}
5614
- .tbtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:4px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}
5615
- .tbtn:hover{background:var(--border)}
5616
- #new-title{flex:1;min-width:120px;background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:4px 8px;border-radius:4px;font-size:12px;font-family:inherit}
5617
- #columns{flex:1;display:flex;overflow-x:auto;overflow-y:hidden}
5618
- .col{width:260px;min-width:220px;flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--border)}
5619
- .col-hdr{padding:8px 10px;border-bottom:1px solid var(--border);background:var(--bg2);font-size:11px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.05em;display:flex;justify-content:space-between}
5620
- .col-body{flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:8px}
5621
- .card{background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:8px 10px}
5622
- .c-title{font-size:12px;font-weight:500;margin-bottom:4px;word-break:break-word}
5623
- .c-id{font-size:10px;color:var(--text2);margin-bottom:6px}
5624
- .c-row{display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin-bottom:6px}
5625
- .tag{display:inline-block;padding:1px 6px;border-radius:10px;font-size:10px;font-weight:600;white-space:nowrap}
5626
- .t-unclaimed{background:rgba(139,148,158,.15);color:var(--text2);border:1px dashed var(--text2)}
5627
- .t-owner{background:rgba(88,166,255,.15);color:var(--blue)}
5628
- .t-gate{background:rgba(63,185,80,.18);color:var(--green)}
5629
- .t-self{background:rgba(210,153,34,.18);color:var(--yellow)}
5630
- .t-human{background:rgba(88,166,255,.18);color:var(--blue)}
5631
- .t-gated{background:rgba(139,148,158,.12);color:var(--text2)}
5632
- .t-cancelled{background:rgba(139,148,158,.12);color:var(--text2)}
5633
- .c-err{font-size:10px;color:var(--red);margin-bottom:6px;word-break:break-word}
5634
- .c-actions{display:flex;flex-wrap:wrap;gap:4px}
5635
- .abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:3px 8px;border-radius:4px;cursor:pointer;font-size:11px;font-family:inherit}
5636
- .abtn:hover{background:var(--border)}
5637
- .abtn.danger{border-color:var(--red);color:var(--red)}
5638
- .abtn.primary{border-color:var(--green);color:var(--green)}
5639
- #statusbar{padding:4px 12px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}
5640
- .empty{padding:16px 8px;color:var(--text2);font-size:11px;text-align:center}
5641
- </style>
5642
- </head>
5643
- <body>
5644
- <div id="app">
5645
- <div id="toolbar">
5646
- <span id="board-label">Board:</span>
5647
- <span id="board-id">\u2014</span>
5648
- <input id="board-input" type="text" placeholder="tree:&lt;sessionId&gt; or ws:&lt;slug&gt;">
5649
- <button class="tbtn" id="go-btn">Go</button>
5650
- <button class="tbtn" id="refresh-btn" title="Refresh">&#8635;</button>
5651
- <input id="new-title" type="text" placeholder="New task title&#8230;">
5652
- <button class="tbtn" id="add-btn">+ Add</button>
5653
- </div>
5654
- <div id="columns"></div>
5655
- <div id="statusbar">Connecting to bridge&#8230;</div>
5656
- </div>
5657
- <script>
5658
- ${panelBridgeScript("agentproto-work-board-panel")}
5659
-
5660
- // ============================================================
5661
- // State
5662
- // ============================================================
5663
-
5664
- var COLUMNS = [
5665
- {key: 'pending', label: 'Pending'},
5666
- {key: 'in_progress', label: 'In Progress'},
5667
- {key: 'done', label: 'Done'},
5668
- {key: 'failed', label: 'Failed'}
5669
- ];
5670
-
5671
- var boardId = null;
5672
- var tasks = [];
5673
- var pollTimer = null;
5674
- var pollActive = false;
5675
-
5676
- function setStatus(msg) {
5677
- document.getElementById('statusbar').textContent = msg;
5678
- }
5679
-
5680
- function esc(s) {
5681
- return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
5618
+ <!-- PANEL_BRIDGE: replaced with the inline panelBridgeScript() output by
5619
+ vite.config.ts's injectPanelBridge plugin \u2014 a classic (non-module)
5620
+ script so its \`initBridge\`/\`callTool\` land as globals before the
5621
+ deferred module script below runs. -->
5622
+ <script>// \u2500\u2500 MCP Apps bridge (shared: panel-bridge.ts) \u2500\u2500
5623
+ // JSON-RPC 2.0 over window.parent.postMessage \xB7 spec 2026-01-26
5624
+ var _nextId = 1, _pending = {}, _notifyHandlers = [];
5625
+ var _hostContext = null, _hostContextHandlers = [];
5626
+ var _standaloneApp = null;
5627
+ function _isStandalone(){
5628
+ return window.parent === window && !!window.McpApp && typeof window.McpApp.connect === 'function';
5682
5629
  }
5683
-
5684
- // A cancelled task folds into the Failed column, tagged distinctly \u2014 v1
5685
- // treats both as terminal scrap, and a fifth column for a rarely-used
5686
- // status isn't worth the width.
5687
- function columnOf(status) {
5688
- if (status === 'cancelled') return 'failed';
5689
- return status;
5630
+ function post(msg){ window.parent.postMessage(msg, '*'); }
5631
+ function getHostContext(){ return _hostContext; }
5632
+ function onHostContext(cb){
5633
+ _hostContextHandlers.push(cb);
5634
+ // Replay the last context so a late subscriber isn't stuck blind.
5635
+ if (_hostContext){ try { cb(_hostContext); } catch(_) {} }
5690
5636
  }
5691
-
5692
- // ============================================================
5693
- // Verification tell \u2014 never render an unverified done as gate-passed.
5694
- // ============================================================
5695
-
5696
- function verificationTag(t) {
5697
- var v = t.verification;
5698
- if (v) {
5699
- if (v.kind === 'gate') return '<span class="tag t-gate">&#10003; gate</span>';
5700
- if (v.kind === 'self-report') return '<span class="tag t-self">self-report</span>';
5701
- if (v.kind === 'human') return '<span class="tag t-human">human</span>';
5702
- }
5703
- if (t.status === 'in_progress' && t.verify) {
5704
- return '<span class="tag t-gated">gated</span>';
5637
+ function _setHostContext(ctx){
5638
+ if (!ctx || typeof ctx !== 'object') return;
5639
+ // ui/notifications/host-context-changed carries only the changed keys \u2014
5640
+ // merge, matching the official ext-apps App behaviour.
5641
+ _hostContext = Object.assign({}, _hostContext || {}, ctx);
5642
+ for (var i = 0; i < _hostContextHandlers.length; i++){
5643
+ try { _hostContextHandlers[i](_hostContext); } catch(_) {}
5705
5644
  }
5706
- return '';
5707
5645
  }
5708
-
5709
- function ownerTag(t) {
5710
- if (!t.owner) return '<span class="tag t-unclaimed">Unclaimed</span>';
5711
- return '<span class="tag t-owner">' + esc(t.owner) + '</span>';
5646
+ function rpcRequest(method, params){
5647
+ return new Promise(function(resolve, reject){
5648
+ var id = _nextId++;
5649
+ _pending[id] = {resolve: resolve, reject: reject};
5650
+ post({jsonrpc: '2.0', id: id, method: method, params: params || {}});
5651
+ });
5712
5652
  }
5713
-
5714
- // ============================================================
5715
- // Card actions \u2014 explicit status moves, no drag-and-drop (read-only-safe
5716
- // first: see module docblock).
5717
- // ============================================================
5718
-
5719
- function actionsFor(t) {
5720
- var a = [];
5721
- if (t.status === 'pending') {
5722
- if (!t.owner) a.push({label: 'Claim', action: 'claim', cls: 'primary'});
5723
- else a.push({label: 'Start', action: 'start', cls: 'primary'});
5724
- a.push({label: 'Cancel', action: 'cancel', cls: 'danger'});
5725
- } else if (t.status === 'in_progress') {
5726
- a.push({label: 'Done', action: 'done', cls: 'primary'});
5727
- a.push({label: 'Fail', action: 'fail', cls: 'danger'});
5728
- a.push({label: 'Release', action: 'release', cls: ''});
5729
- a.push({label: 'Cancel', action: 'cancel', cls: 'danger'});
5730
- } else if (t.status === 'done') {
5731
- a.push({label: 'Reopen', action: 'reopen', cls: ''});
5732
- }
5733
- return a;
5734
- }
5735
-
5736
- function applyAction(taskId, action) {
5737
- var t = null;
5738
- for (var i = 0; i < tasks.length; i++) {
5739
- if (tasks[i].taskId === taskId) { t = tasks[i]; break; }
5740
- }
5741
- if (!t) return;
5742
- var p;
5743
- if (action === 'claim') {
5744
- p = callTool('task_claim', {taskId: t.taskId, rev: t.rev});
5745
- } else if (action === 'start') {
5746
- p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'in_progress'});
5747
- } else if (action === 'done') {
5748
- p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'done'});
5749
- } else if (action === 'fail') {
5750
- p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'failed'});
5751
- } else if (action === 'release') {
5752
- p = callTool('task_update', {taskId: t.taskId, rev: t.rev, owner: null});
5753
- } else if (action === 'cancel') {
5754
- p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'cancelled'});
5755
- } else if (action === 'reopen') {
5756
- p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'pending'});
5757
- } else {
5653
+ function rpcNotify(method, params){ post({jsonrpc: '2.0', method: method, params: params || {}}); }
5654
+ function onHostNotification(cb){ _notifyHandlers.push(cb); }
5655
+ window.addEventListener('message', function(evt){
5656
+ var msg = evt.data;
5657
+ if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0') return;
5658
+ if (msg.id != null && msg.method == null){
5659
+ var p = _pending[msg.id];
5660
+ if (!p) return;
5661
+ delete _pending[msg.id];
5662
+ if (msg.error) p.reject(new Error(msg.error.message || ('rpc error ' + msg.error.code)));
5663
+ else p.resolve(msg.result);
5758
5664
  return;
5759
5665
  }
5760
- p.then(function (result) {
5761
- if (result && result.conflict) {
5762
- setStatus('Someone else moved "' + t.title + '" first \u2014 refreshed.');
5763
- } else if (result && result.error) {
5764
- setStatus('Action failed: ' + result.error);
5765
- } else {
5766
- setStatus('Updated "' + t.title + '".');
5666
+ if (msg.method){
5667
+ if (msg.method === 'ui/notifications/host-context-changed'){
5668
+ _setHostContext(msg.params || {});
5767
5669
  }
5768
- return loadBoard();
5769
- }).catch(function (e) {
5770
- setStatus('Action failed: ' + e.message);
5771
- });
5772
- }
5773
-
5774
- // ============================================================
5775
- // Render
5776
- // ============================================================
5777
-
5778
- function renderCard(t) {
5779
- var html = '<div class="card" data-taskid="' + esc(t.taskId) + '">';
5780
- html += '<div class="c-title">' + esc(t.title) + '</div>';
5781
- html += '<div class="c-id">#' + esc(t.taskId) + '</div>';
5782
- html += '<div class="c-row">' + ownerTag(t) + verificationTag(t);
5783
- if (t.status === 'cancelled') html += '<span class="tag t-cancelled">cancelled</span>';
5784
- html += '</div>';
5785
- if (t.lastVerifyError) {
5786
- html += '<div class="c-err">verify failed: ' + esc(t.lastVerifyError) + '</div>';
5787
- }
5788
- var actions = actionsFor(t);
5789
- if (actions.length > 0) {
5790
- html += '<div class="c-actions">';
5791
- for (var i = 0; i < actions.length; i++) {
5792
- var a = actions[i];
5793
- html += '<button class="abtn ' + a.cls + '" data-taskid="' + esc(t.taskId) + '" data-action="' + a.action + '">' + a.label + '</button>';
5670
+ for (var i = 0; i < _notifyHandlers.length; i++){
5671
+ try { _notifyHandlers[i](msg.method, msg.params || {}); } catch(_) {}
5794
5672
  }
5795
- html += '</div>';
5796
- }
5797
- html += '</div>';
5798
- return html;
5799
- }
5800
-
5801
- function render() {
5802
- document.getElementById('board-id').textContent = boardId || '\u2014';
5803
- if (!document.getElementById('board-input').value) {
5804
- document.getElementById('board-input').value = boardId || '';
5805
5673
  }
5806
- var el = document.getElementById('columns');
5807
- var html = '';
5808
- for (var c = 0; c < COLUMNS.length; c++) {
5809
- var col = COLUMNS[c];
5810
- var rows = tasks.filter(function (t) { return columnOf(t.status) === col.key; });
5811
- html += '<div class="col"><div class="col-hdr"><span>' + col.label + '</span><span>' + rows.length + '</span></div><div class="col-body">';
5812
- if (rows.length === 0) {
5813
- html += '<div class="empty">No tasks</div>';
5814
- } else {
5815
- for (var i = 0; i < rows.length; i++) html += renderCard(rows[i]);
5816
- }
5817
- html += '</div></div>';
5674
+ });
5675
+ function initBridge(){
5676
+ if (_isStandalone()){
5677
+ return window.McpApp.connect().then(function(conn){
5678
+ _standaloneApp = conn;
5679
+ // No host to advertise a hostContext \u2014 default to inline with no
5680
+ // other modes available, which keeps the #dm/#pin toggle buttons
5681
+ // hidden (they only show for modes hostContext.availableDisplayModes
5682
+ // lists) instead of rendering two buttons with nothing to switch to.
5683
+ _setHostContext({displayMode: 'inline', availableDisplayModes: []});
5684
+ });
5818
5685
  }
5819
- el.innerHTML = html;
5820
- }
5821
-
5822
- // ============================================================
5823
- // Data
5824
- // ============================================================
5825
-
5826
- function loadBoard() {
5827
- var args = {includeClosed: true, full: true};
5828
- if (boardId) args.boardId = boardId;
5829
- return callTool('task_list', args).then(function (data) {
5830
- boardId = data.boardId || boardId;
5831
- tasks = data.tasks || [];
5832
- render();
5833
- setStatus(tasks.length + ' task' + (tasks.length === 1 ? '' : 's') + ' \xB7 ' + new Date().toLocaleTimeString());
5834
- }).catch(function (e) {
5835
- setStatus('Error: ' + e.message);
5686
+ return rpcRequest('ui/initialize', {
5687
+ appInfo: {name: "agentproto-work-board-panel", version: '0.1.0'},
5688
+ appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},
5689
+ protocolVersion: '2026-01-26'
5690
+ }).then(function(result){
5691
+ // The initialize result carries the initial hostContext (displayMode +
5692
+ // availableDisplayModes) \u2014 capture it before notifying the host.
5693
+ if (result && result.hostContext) _setHostContext(result.hostContext);
5694
+ rpcNotify('ui/notifications/initialized', {});
5836
5695
  });
5837
5696
  }
5838
-
5839
- function doPoll() {
5840
- if (pollActive) return;
5841
- pollActive = true;
5842
- loadBoard().then(function () {
5843
- pollActive = false;
5844
- pollTimer = setTimeout(doPoll, 4000);
5845
- }).catch(function () {
5846
- pollActive = false;
5847
- pollTimer = setTimeout(doPoll, 4000);
5697
+ function requestDisplayMode(mode){
5698
+ return rpcRequest('ui/request-display-mode', {mode: mode});
5699
+ }
5700
+ function callTool(name, args){
5701
+ var raw = _standaloneApp
5702
+ ? _standaloneApp.callTool(name, args || {})
5703
+ : rpcRequest('tools/call', {name: name, arguments: args || {}});
5704
+ return raw.then(function(result){
5705
+ if (result.isError){
5706
+ var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';
5707
+ throw new Error(e);
5708
+ }
5709
+ var text = (result.content && result.content[0] && result.content[0].text) || '{}';
5710
+ return JSON.parse(text);
5848
5711
  });
5849
5712
  }
5850
5713
 
5851
- // ============================================================
5852
- // Toolbar wiring
5853
- // ============================================================
5714
+ // \u2500\u2500 Display-mode toggle buttons (NO auto-request) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5715
+ // Injected by the shared bridge so every panel gets them without touching
5716
+ // its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the
5717
+ // panel stays inline by default; the user expands on demand. Buttons only
5718
+ // appear for modes the host advertises in hostContext.availableDisplayModes.
5719
+ (function(){
5720
+ function mount(){
5721
+ var style = document.createElement('style');
5722
+ style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'
5723
+ + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'
5724
+ + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'
5725
+ + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'
5726
+ + '#dm{right:8px}#pin{right:118px}'
5727
+ + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'
5728
+ + '@media (prefers-color-scheme:dark){'
5729
+ + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'
5730
+ + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';
5731
+ document.head.appendChild(style);
5854
5732
 
5855
- document.getElementById('columns').addEventListener('click', function (evt) {
5856
- var btn = evt.target.closest ? evt.target.closest('button[data-action]') : null;
5857
- if (!btn) return;
5858
- applyAction(btn.getAttribute('data-taskid'), btn.getAttribute('data-action'));
5859
- });
5733
+ var btn = document.createElement('button');
5734
+ btn.id = 'dm'; btn.type = 'button'; btn.title = "Basculer l'affichage";
5735
+ var pin = document.createElement('button');
5736
+ pin.id = 'pin'; pin.type = 'button'; pin.title = '\xC9pingler sur le c\xF4t\xE9 (pip)';
5737
+ document.body.appendChild(pin);
5738
+ document.body.appendChild(btn);
5860
5739
 
5861
- document.getElementById('refresh-btn').addEventListener('click', function () {
5862
- loadBoard();
5863
- });
5740
+ function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }
5864
5741
 
5865
- document.getElementById('go-btn').addEventListener('click', function () {
5866
- var v = document.getElementById('board-input').value.trim();
5867
- if (!v) return;
5868
- boardId = v;
5869
- loadBoard();
5870
- });
5742
+ // Re-sync button visibility + label from the current host context.
5743
+ function syncBtn(ctx){
5744
+ ctx = ctx || {};
5745
+ var avail = ctx.availableDisplayModes;
5746
+ // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)
5747
+ console.log('[mcp-app] displayMode=', ctx.displayMode,
5748
+ 'availableDisplayModes=', avail);
5871
5749
 
5872
- document.getElementById('add-btn').addEventListener('click', function () {
5873
- var input = document.getElementById('new-title');
5874
- var title = input.value.trim();
5875
- if (!title) return;
5876
- var args = {title: title};
5877
- if (boardId) args.boardId = boardId;
5878
- callTool('task_create', args).then(function (result) {
5879
- if (result && result.error) {
5880
- setStatus('Create failed: ' + result.error);
5881
- return;
5750
+ // Fullscreen toggle button.
5751
+ if (has(avail, 'fullscreen')){
5752
+ btn.style.display = 'block';
5753
+ btn.textContent = (ctx.displayMode === 'fullscreen') ? '\u2921 R\xE9duire' : '\u2922 Agrandir';
5754
+ } else { btn.style.display = 'none'; }
5755
+
5756
+ // Dedicated pip ("pinned on side") button \u2014 only if the host advertises pip.
5757
+ if (has(avail, 'pip')){
5758
+ pin.style.display = 'block';
5759
+ pin.textContent = (ctx.displayMode === 'pip') ? '\u2921 D\xE9tacher' : '\u{1F4CC} \xC9pingler';
5760
+ } else { pin.style.display = 'none'; }
5882
5761
  }
5883
- input.value = '';
5884
- setStatus('Created "' + title + '".');
5885
- return loadBoard();
5886
- }).catch(function (e) {
5887
- setStatus('Create failed: ' + e.message);
5888
- });
5889
- });
5890
5762
 
5891
- // ============================================================
5892
- // Boot
5893
- // ============================================================
5763
+ onHostContext(syncBtn);
5894
5764
 
5895
- initBridge().then(function () {
5896
- return loadBoard();
5897
- }).then(function () {
5898
- pollTimer = setTimeout(doPoll, 4000);
5899
- }).catch(function (e) {
5900
- setStatus('Bridge error: ' + e.message);
5901
- });
5902
- </script>
5765
+ btn.addEventListener('click', function(){
5766
+ var ctx = getHostContext() || {};
5767
+ var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');
5768
+ requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});
5769
+ });
5770
+
5771
+ pin.addEventListener('click', function(){
5772
+ var ctx = getHostContext() || {};
5773
+ requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});
5774
+ });
5775
+ }
5776
+ if (document.body) mount();
5777
+ else document.addEventListener('DOMContentLoaded', mount);
5778
+ })();</script>
5779
+ <script>(function() {
5780
+ "use strict";
5781
+ const COLUMN_STATUSES = ["pending", "in_progress", "done", "failed"];
5782
+ const COLUMN_LABELS = {
5783
+ pending: "Pending",
5784
+ in_progress: "In Progress",
5785
+ done: "Done",
5786
+ failed: "Failed"
5787
+ };
5788
+ function esc(value) {
5789
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5790
+ }
5791
+ function columnOf(status) {
5792
+ return status === "cancelled" ? "failed" : status;
5793
+ }
5794
+ function verificationTag(task) {
5795
+ const v = task.verification;
5796
+ if (v) {
5797
+ if (v.kind === "gate") return '<span class="tag t-gate">&#10003; gate</span>';
5798
+ if (v.kind === "self-report") return '<span class="tag t-self">self-report</span>';
5799
+ if (v.kind === "human") return '<span class="tag t-human">human</span>';
5800
+ }
5801
+ if (task.status === "in_progress" && task.verify) {
5802
+ return '<span class="tag t-gated">gated</span>';
5803
+ }
5804
+ return "";
5805
+ }
5806
+ function ownerTag(task) {
5807
+ if (!task.owner) return '<span class="tag t-unclaimed">Unclaimed</span>';
5808
+ return \`<span class="tag t-owner">\${esc(task.owner)}</span>\`;
5809
+ }
5810
+ function actionsFor(task) {
5811
+ if (task.status === "pending") {
5812
+ return [
5813
+ task.owner ? { label: "Start", action: "start", cls: "primary" } : { label: "Claim", action: "claim", cls: "primary" },
5814
+ { label: "Cancel", action: "cancel", cls: "danger" }
5815
+ ];
5816
+ }
5817
+ if (task.status === "in_progress") {
5818
+ return [
5819
+ { label: "Done", action: "done", cls: "primary" },
5820
+ { label: "Fail", action: "fail", cls: "danger" },
5821
+ { label: "Release", action: "release", cls: "" },
5822
+ { label: "Cancel", action: "cancel", cls: "danger" }
5823
+ ];
5824
+ }
5825
+ if (task.status === "done") {
5826
+ return [{ label: "Reopen", action: "reopen", cls: "" }];
5827
+ }
5828
+ return [];
5829
+ }
5830
+ function renderCard(task) {
5831
+ let html = \`<div class="card" data-taskid="\${esc(task.taskId)}">\`;
5832
+ html += \`<div class="c-title">\${esc(task.title)}</div>\`;
5833
+ html += \`<div class="c-id">#\${esc(task.taskId)}</div>\`;
5834
+ html += \`<div class="c-row">\${ownerTag(task)}\${verificationTag(task)}\`;
5835
+ if (task.status === "cancelled") html += '<span class="tag t-cancelled">cancelled</span>';
5836
+ html += "</div>";
5837
+ if (task.lastVerifyError) {
5838
+ html += \`<div class="c-err">verify failed: \${esc(task.lastVerifyError)}</div>\`;
5839
+ }
5840
+ const actions = actionsFor(task);
5841
+ if (actions.length > 0) {
5842
+ html += '<div class="c-actions">';
5843
+ for (const a of actions) {
5844
+ html += \`<button class="abtn \${a.cls}" data-taskid="\${esc(task.taskId)}" data-action="\${a.action}">\${a.label}</button>\`;
5845
+ }
5846
+ html += "</div>";
5847
+ }
5848
+ html += "</div>";
5849
+ return html;
5850
+ }
5851
+ function renderColumns(tasks2) {
5852
+ let html = "";
5853
+ for (const status of COLUMN_STATUSES) {
5854
+ const rows = tasks2.filter((t) => columnOf(t.status) === status);
5855
+ html += \`<div class="col"><div class="col-hdr"><span>\${COLUMN_LABELS[status]}</span><span>\${rows.length}</span></div><div class="col-body">\`;
5856
+ html += rows.length === 0 ? '<div class="empty">No tasks</div>' : rows.map(renderCard).join("");
5857
+ html += "</div></div>";
5858
+ }
5859
+ return html;
5860
+ }
5861
+ function getEl(id) {
5862
+ const el = document.getElementById(id);
5863
+ if (!el) throw new Error(\`work board: missing #\${id}\`);
5864
+ return el;
5865
+ }
5866
+ function getInput(id) {
5867
+ const el = document.getElementById(id);
5868
+ if (!(el instanceof HTMLInputElement)) throw new Error(\`work board: missing input #\${id}\`);
5869
+ return el;
5870
+ }
5871
+ function isCardAction(value) {
5872
+ switch (value) {
5873
+ case "claim":
5874
+ case "start":
5875
+ case "done":
5876
+ case "fail":
5877
+ case "release":
5878
+ case "cancel":
5879
+ case "reopen":
5880
+ return true;
5881
+ default:
5882
+ return false;
5883
+ }
5884
+ }
5885
+ let boardId = null;
5886
+ let tasks = [];
5887
+ let pollActive = false;
5888
+ function boot() {
5889
+ const columnsEl = getEl("columns");
5890
+ const statusbarEl = getEl("statusbar");
5891
+ const boardIdEl = getEl("board-id");
5892
+ const boardInputEl = getInput("board-input");
5893
+ const newTitleEl = getInput("new-title");
5894
+ function setStatus(msg) {
5895
+ statusbarEl.textContent = msg;
5896
+ }
5897
+ function render() {
5898
+ boardIdEl.textContent = boardId ?? "\u2014";
5899
+ if (!boardInputEl.value) boardInputEl.value = boardId ?? "";
5900
+ columnsEl.innerHTML = renderColumns(tasks);
5901
+ }
5902
+ function findTask(taskId) {
5903
+ return tasks.find((t) => t.taskId === taskId);
5904
+ }
5905
+ function loadBoard() {
5906
+ const args = {
5907
+ includeClosed: true,
5908
+ full: true,
5909
+ ...boardId ? { boardId } : {}
5910
+ };
5911
+ return callTool("task_list", args).then((data) => {
5912
+ boardId = data.boardId ?? boardId;
5913
+ tasks = data.tasks ?? [];
5914
+ render();
5915
+ setStatus(\`\${tasks.length} task\${tasks.length === 1 ? "" : "s"} \xB7 \${(/* @__PURE__ */ new Date()).toLocaleTimeString()}\`);
5916
+ }).catch((e) => {
5917
+ setStatus(\`Error: \${e.message}\`);
5918
+ });
5919
+ }
5920
+ function applyAction(taskId, action) {
5921
+ const task = findTask(taskId);
5922
+ if (!task) return;
5923
+ const base = { taskId: task.taskId, rev: task.rev };
5924
+ let promise;
5925
+ if (action === "claim") {
5926
+ promise = callTool("task_claim", base);
5927
+ } else if (action === "start") {
5928
+ promise = callTool("task_update", { ...base, status: "in_progress" });
5929
+ } else if (action === "done") {
5930
+ promise = callTool("task_update", { ...base, status: "done" });
5931
+ } else if (action === "fail") {
5932
+ promise = callTool("task_update", { ...base, status: "failed" });
5933
+ } else if (action === "release") {
5934
+ promise = callTool("task_update", { ...base, owner: null });
5935
+ } else if (action === "cancel") {
5936
+ promise = callTool("task_update", { ...base, status: "cancelled" });
5937
+ } else {
5938
+ promise = callTool("task_update", { ...base, status: "pending" });
5939
+ }
5940
+ promise.then((result) => {
5941
+ if ("conflict" in result) {
5942
+ setStatus(\`Someone else moved "\${task.title}" first \u2014 refreshed.\`);
5943
+ } else if ("error" in result) {
5944
+ setStatus(\`Action failed: \${result.error}\`);
5945
+ } else {
5946
+ setStatus(\`Updated "\${task.title}".\`);
5947
+ }
5948
+ return loadBoard();
5949
+ }).catch((e) => {
5950
+ setStatus(\`Action failed: \${e.message}\`);
5951
+ });
5952
+ }
5953
+ function doPoll() {
5954
+ if (pollActive) return;
5955
+ pollActive = true;
5956
+ loadBoard().finally(() => {
5957
+ pollActive = false;
5958
+ setTimeout(doPoll, 4e3);
5959
+ });
5960
+ }
5961
+ columnsEl.addEventListener("click", (evt) => {
5962
+ if (!(evt.target instanceof Element)) return;
5963
+ const btn = evt.target.closest("button[data-action]");
5964
+ if (!(btn instanceof HTMLButtonElement)) return;
5965
+ const taskId = btn.getAttribute("data-taskid");
5966
+ const action = btn.getAttribute("data-action");
5967
+ if (!taskId || !action || !isCardAction(action)) return;
5968
+ applyAction(taskId, action);
5969
+ });
5970
+ getEl("refresh-btn").addEventListener("click", () => {
5971
+ void loadBoard();
5972
+ });
5973
+ getEl("go-btn").addEventListener("click", () => {
5974
+ const value = boardInputEl.value.trim();
5975
+ if (!value) return;
5976
+ boardId = value;
5977
+ void loadBoard();
5978
+ });
5979
+ getEl("add-btn").addEventListener("click", () => {
5980
+ const title = newTitleEl.value.trim();
5981
+ if (!title) return;
5982
+ const args = boardId ? { title, boardId } : { title };
5983
+ callTool("task_create", args).then((result) => {
5984
+ if (result.error) {
5985
+ setStatus(\`Create failed: \${result.error}\`);
5986
+ return void 0;
5987
+ }
5988
+ newTitleEl.value = "";
5989
+ setStatus(\`Created "\${title}".\`);
5990
+ return loadBoard();
5991
+ }).catch((e) => {
5992
+ setStatus(\`Create failed: \${e.message}\`);
5993
+ });
5994
+ });
5995
+ initBridge().then(() => loadBoard()).then(() => {
5996
+ setTimeout(doPoll, 4e3);
5997
+ }).catch((e) => {
5998
+ setStatus(\`Bridge error: \${e.message}\`);
5999
+ });
6000
+ }
6001
+ if (document.readyState === "loading") {
6002
+ document.addEventListener("DOMContentLoaded", boot);
6003
+ } else {
6004
+ boot();
6005
+ }
6006
+ })();</script>
6007
+ <style rel="stylesheet" crossorigin>/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
6008
+ Semantic tokens + runtime mapping sheets.
6009
+
6010
+ Every rule below the next section reads ONLY the --wb-* custom
6011
+ properties. Each one resolves through TWO mapping sheets, chosen at
6012
+ PAINT time via plain CSS var() fallback \u2014 no build step, no JS, so a
6013
+ host that swaps its theme live (VS Code) is never frozen at whatever
6014
+ theme happened to be active when this bundle was built:
6015
+
6016
+ 1. embedded \u2014 VS Code injects a live --vscode-* custom-property set
6017
+ into the webview and swaps it on theme change. Whenever the
6018
+ relevant --vscode-* variable exists, it wins \u2014 in a VS Code
6019
+ \`srcdoc\` relay panel AND (structurally identically) any other host
6020
+ that happened to expose the same names, though none is known to.
6021
+ 2. standalone \u2014 the agentproto brand tokens (see
6022
+ projects/agentproto/site/src/app/brand.css), used whenever no
6023
+ --vscode-* variable is set \u2014 a plain MCP-Apps host or the daemon's
6024
+ own \`/apps/.../ui\` tab. \`--phos\`/\`--amber\`/\`--crimson\` are
6025
+ THEME-INVARIANT in brand.css (the phosphor terminal palette), so
6026
+ they're identical in both blocks below; the rest follow brand.css's
6027
+ light/dark split via \`prefers-color-scheme\` \u2014 a standalone tab has
6028
+ no host to pick a theme for it, unlike VS Code's own.
6029
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
6030
+
6031
+ :root {
6032
+ --wb-bg: var(--vscode-editor-background, #f5f6f3);
6033
+ --wb-surface: var(--vscode-sideBar-background, #ffffff);
6034
+ --wb-ink: var(--vscode-editor-foreground, #171a1e);
6035
+ --wb-ink-muted: var(--vscode-descriptionForeground, #5c6470);
6036
+ --wb-border: var(--vscode-panel-border, #dde0da);
6037
+ --wb-accent: var(--vscode-button-background, #0f62fe);
6038
+
6039
+ /* Status colors \u2014 phos/amber/crimson are the theme-invariant terminal
6040
+ palette in brand.css, so light and dark standalone fallbacks match. */
6041
+ --wb-status-pending: var(--vscode-descriptionForeground, #5c6470);
6042
+ --wb-status-in-progress: var(--vscode-charts-yellow, #f0b429);
6043
+ --wb-status-done: var(--vscode-charts-green, #3edc81);
6044
+ --wb-status-failed: var(--vscode-charts-red, #fa4d56);
6045
+ --wb-status-cancelled: var(--vscode-descriptionForeground, #5c6470);
6046
+ --wb-status-cancelled-weak: color-mix(in srgb, var(--wb-status-cancelled) 55%, transparent);
6047
+ }
6048
+
6049
+ @media (prefers-color-scheme: dark) {
6050
+ :root {
6051
+ --wb-bg: var(--vscode-editor-background, #101214);
6052
+ --wb-surface: var(--vscode-sideBar-background, #16191d);
6053
+ --wb-ink: var(--vscode-editor-foreground, #e8eaed);
6054
+ --wb-ink-muted: var(--vscode-descriptionForeground, #99a2ad);
6055
+ --wb-border: var(--vscode-panel-border, #272c33);
6056
+ --wb-accent: var(--vscode-button-background, #4589ff);
6057
+
6058
+ --wb-status-pending: var(--vscode-descriptionForeground, #99a2ad);
6059
+ --wb-status-in-progress: var(--vscode-charts-yellow, #f0b429);
6060
+ --wb-status-done: var(--vscode-charts-green, #3edc81);
6061
+ --wb-status-failed: var(--vscode-charts-red, #fa4d56);
6062
+ --wb-status-cancelled: var(--vscode-descriptionForeground, #99a2ad);
6063
+ --wb-status-cancelled-weak: color-mix(in srgb, var(--wb-status-cancelled) 55%, transparent);
6064
+ }
6065
+ }
6066
+
6067
+ /* \u2500\u2500 Base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
6068
+
6069
+ * { box-sizing: border-box; margin: 0; padding: 0; }
6070
+
6071
+ html, body {
6072
+ height: 100%;
6073
+ font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
6074
+ font-size: 13px;
6075
+ background: var(--wb-bg);
6076
+ color: var(--wb-ink);
6077
+ overflow: hidden;
6078
+ }
6079
+
6080
+ .mono {
6081
+ font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, "Courier New", monospace;
6082
+ }
6083
+
6084
+ #app { display: flex; flex-direction: column; height: 100%; }
6085
+
6086
+ #toolbar {
6087
+ padding: 8px 12px;
6088
+ border-bottom: 1px solid var(--wb-border);
6089
+ display: flex;
6090
+ align-items: center;
6091
+ gap: 8px;
6092
+ background: var(--wb-surface);
6093
+ flex-shrink: 0;
6094
+ }
6095
+ #board-label { font-size: 11px; color: var(--wb-ink-muted); }
6096
+ #board-id { font-weight: 600; color: var(--wb-ink); font-size: 12px; font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, "Courier New", monospace; }
6097
+ #board-input {
6098
+ background: var(--wb-bg);
6099
+ border: 1px solid var(--wb-border);
6100
+ color: var(--wb-ink);
6101
+ padding: 4px 8px;
6102
+ border-radius: 4px;
6103
+ font-size: 12px;
6104
+ font-family: inherit;
6105
+ width: 220px;
6106
+ }
6107
+ .tbtn {
6108
+ background: var(--wb-bg);
6109
+ border: 1px solid var(--wb-border);
6110
+ color: var(--wb-ink);
6111
+ padding: 4px 10px;
6112
+ border-radius: 4px;
6113
+ cursor: pointer;
6114
+ font-size: 12px;
6115
+ font-family: inherit;
6116
+ }
6117
+ .tbtn:hover { background: var(--wb-border); }
6118
+ #new-title {
6119
+ flex: 1;
6120
+ min-width: 120px;
6121
+ background: var(--wb-bg);
6122
+ border: 1px solid var(--wb-border);
6123
+ color: var(--wb-ink);
6124
+ padding: 4px 8px;
6125
+ border-radius: 4px;
6126
+ font-size: 12px;
6127
+ font-family: inherit;
6128
+ }
6129
+
6130
+ #columns { flex: 1; display: flex; overflow-x: auto; overflow-y: hidden; }
6131
+ .col {
6132
+ width: 260px;
6133
+ min-width: 220px;
6134
+ flex-shrink: 0;
6135
+ display: flex;
6136
+ flex-direction: column;
6137
+ border-right: 1px solid var(--wb-border);
6138
+ }
6139
+ .col-hdr {
6140
+ padding: 8px 10px;
6141
+ border-bottom: 1px solid var(--wb-border);
6142
+ background: var(--wb-surface);
6143
+ font-size: 11px;
6144
+ font-weight: 600;
6145
+ color: var(--wb-ink-muted);
6146
+ text-transform: uppercase;
6147
+ letter-spacing: 0.05em;
6148
+ display: flex;
6149
+ justify-content: space-between;
6150
+ }
6151
+ .col-body { flex: 1; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 8px; }
6152
+
6153
+ .card {
6154
+ background: var(--wb-surface);
6155
+ border: 1px solid var(--wb-border);
6156
+ border-radius: 6px;
6157
+ padding: 8px 10px;
6158
+ }
6159
+ .c-title { font-size: 12px; font-weight: 500; margin-bottom: 4px; word-break: break-word; }
6160
+ .c-id {
6161
+ font-size: 10px;
6162
+ color: var(--wb-ink-muted);
6163
+ margin-bottom: 6px;
6164
+ font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, "Courier New", monospace;
6165
+ }
6166
+ .c-row { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin-bottom: 6px; }
6167
+
6168
+ .tag { display: inline-block; padding: 1px 6px; border-radius: 10px; font-size: 10px; font-weight: 600; white-space: nowrap; }
6169
+ .t-unclaimed { background: color-mix(in srgb, var(--wb-ink-muted) 15%, transparent); color: var(--wb-ink-muted); border: 1px dashed var(--wb-ink-muted); }
6170
+ .t-owner { background: color-mix(in srgb, var(--wb-accent) 15%, transparent); color: var(--wb-accent); }
6171
+
6172
+ /* Verification tell: gate solid green, self-report amber outline, human
6173
+ blue, a declared verify with no verification yet grey "gated" \u2014 never
6174
+ render an unverified done as gate-passed (see render.ts's verificationTag). */
6175
+ .t-gate { background: color-mix(in srgb, var(--wb-status-done) 18%, transparent); color: var(--wb-status-done); }
6176
+ .t-self { background: transparent; color: var(--wb-status-in-progress); border: 1px solid var(--wb-status-in-progress); }
6177
+ .t-human { background: color-mix(in srgb, var(--wb-accent) 18%, transparent); color: var(--wb-accent); }
6178
+ .t-gated { background: color-mix(in srgb, var(--wb-ink-muted) 12%, transparent); color: var(--wb-ink-muted); }
6179
+ .t-cancelled {
6180
+ background: color-mix(in srgb, var(--wb-status-cancelled) 10%, transparent);
6181
+ color: var(--wb-status-cancelled-weak);
6182
+ border: 1px dashed color-mix(in srgb, var(--wb-status-cancelled) 40%, transparent);
6183
+ }
6184
+
6185
+ .c-err { font-size: 10px; color: var(--wb-status-failed); margin-bottom: 6px; word-break: break-word; }
6186
+ .c-actions { display: flex; flex-wrap: wrap; gap: 4px; }
6187
+ .abtn {
6188
+ background: var(--wb-bg);
6189
+ border: 1px solid var(--wb-border);
6190
+ color: var(--wb-ink);
6191
+ padding: 3px 8px;
6192
+ border-radius: 4px;
6193
+ cursor: pointer;
6194
+ font-size: 11px;
6195
+ font-family: inherit;
6196
+ }
6197
+ .abtn:hover { background: var(--wb-border); }
6198
+ .abtn.danger { border-color: var(--wb-status-failed); color: var(--wb-status-failed); }
6199
+ .abtn.primary { border-color: var(--wb-status-done); color: var(--wb-status-done); }
6200
+
6201
+ #statusbar {
6202
+ padding: 4px 12px;
6203
+ font-size: 11px;
6204
+ color: var(--wb-ink-muted);
6205
+ border-top: 1px solid var(--wb-border);
6206
+ background: var(--wb-surface);
6207
+ flex-shrink: 0;
6208
+ }
6209
+ .empty { padding: 16px 8px; color: var(--wb-ink-muted); font-size: 11px; text-align: center; }</style>
6210
+ </head>
6211
+ <body>
6212
+ <div id="app">
6213
+ <div id="toolbar">
6214
+ <span id="board-label">Board:</span>
6215
+ <span id="board-id">\u2014</span>
6216
+ <input id="board-input" type="text" placeholder="tree:&lt;sessionId&gt; or ws:&lt;slug&gt;">
6217
+ <button class="tbtn" id="go-btn">Go</button>
6218
+ <button class="tbtn" id="refresh-btn" title="Refresh">&#8635;</button>
6219
+ <input id="new-title" type="text" placeholder="New task title&#8230;">
6220
+ <button class="tbtn" id="add-btn">+ Add</button>
6221
+ </div>
6222
+ <div id="columns"></div>
6223
+ <div id="statusbar">Connecting to bridge&#8230;</div>
6224
+ </div>
5903
6225
  </body>
5904
- </html>`;
6226
+ </html>
6227
+ `;
6228
+
6229
+ // src/work-board/panel.ts
6230
+ var WORK_BOARD_APP_ID = "@agentproto/work-board";
6231
+ var WORK_BOARD_TOOL_ID = "agentproto_work_board";
6232
+ var WORK_BOARD_UI_TOOLS = [
6233
+ "task_list",
6234
+ "task_claim",
6235
+ "task_update",
6236
+ "task_create"
6237
+ ];
6238
+ var WORK_BOARD_HTML = WORK_BOARD_HTML_GENERATED;
5905
6239
 
5906
6240
  // src/work-board/index.ts
5907
6241
  var workBoardInputSchema = z.object({
@@ -5911,7 +6245,7 @@ var workBoardInputSchema = z.object({
5911
6245
  });
5912
6246
  function makeWorkBoardApp(ops) {
5913
6247
  return {
5914
- id: "agentproto_work_board",
6248
+ id: WORK_BOARD_TOOL_ID,
5915
6249
  title: "Work Board",
5916
6250
  description: "Open the agentproto work board \u2014 a kanban over the daemon's Task ledger (columns: pending/in_progress/done/failed, cancelled folded into failed). Swimlane is the ledger's own board id (`tree:<root>` or `ws:<slug>`). Each card shows its owner (or \"Unclaimed\") and the verification tell \u2014 a gate-passed done reads visually distinct from a self-reported one. Pass `boardId` to open a specific board.",
5917
6251
  inputSchema: workBoardInputSchema,
@@ -5920,14 +6254,14 @@ function makeWorkBoardApp(ops) {
5920
6254
  };
5921
6255
  }
5922
6256
  var workBoardApp = defineApp({
5923
- id: "@agentproto/work-board",
6257
+ id: WORK_BOARD_APP_ID,
5924
6258
  name: "Work Board",
5925
6259
  description: "Open the agentproto work board \u2014 a kanban over the daemon's Task ledger, scoped by board id (`tree:<rootSessionId>` or `ws:<slug>`), with an explicit status action per card.",
5926
6260
  agents: [],
5927
6261
  ui: {
5928
6262
  html: WORK_BOARD_HTML,
5929
6263
  title: "Work Board",
5930
- tools: ["task_list", "task_claim", "task_update", "task_create"]
6264
+ tools: [...WORK_BOARD_UI_TOOLS]
5931
6265
  }
5932
6266
  });
5933
6267