@agentproto/apps 0.8.2 → 0.9.1
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/README.md +28 -1
- package/dist/agents-overview/index.d.ts +54 -0
- package/dist/agents-overview/index.d.ts.map +1 -0
- package/dist/agents-overview/panel.d.ts +14 -0
- package/dist/agents-overview/panel.d.ts.map +1 -0
- package/dist/agents-overview/panel.mjs +306 -0
- package/dist/agents-overview/panel.mjs.map +1 -0
- package/dist/agents-overview.mjs +336 -0
- package/dist/agents-overview.mjs.map +1 -0
- package/dist/bin/sync.mjs +25 -11
- package/dist/bin/sync.mjs.map +1 -1
- package/dist/bureau-sessions/index.d.ts +52 -0
- package/dist/bureau-sessions/index.d.ts.map +1 -0
- package/dist/bureau-sessions/panel.d.ts +10 -0
- package/dist/bureau-sessions/panel.d.ts.map +1 -0
- package/dist/bureau-sessions/panel.mjs +297 -0
- package/dist/bureau-sessions/panel.mjs.map +1 -0
- package/dist/bureau-sessions.mjs +329 -0
- package/dist/bureau-sessions.mjs.map +1 -0
- package/dist/content-team/workflows/produce-content.d.ts +14 -2
- package/dist/content-team/workflows/produce-content.d.ts.map +1 -1
- package/dist/content-team/workflows/produce-cover.d.ts +13 -1
- package/dist/content-team/workflows/produce-cover.d.ts.map +1 -1
- package/dist/content-team.mjs +25 -11
- package/dist/content-team.mjs.map +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +2635 -12
- package/dist/index.mjs.map +1 -1
- package/dist/live-session/index.d.ts +46 -0
- package/dist/live-session/index.d.ts.map +1 -0
- package/dist/live-session/logic.d.ts +125 -0
- package/dist/live-session/logic.d.ts.map +1 -0
- package/dist/live-session/panel.d.ts +61 -0
- package/dist/live-session/panel.d.ts.map +1 -0
- package/dist/live-session/panel.mjs +993 -0
- package/dist/live-session/panel.mjs.map +1 -0
- package/dist/live-session.mjs +1032 -0
- package/dist/live-session.mjs.map +1 -0
- package/dist/mcp-app-types.d.ts +33 -0
- package/dist/mcp-app-types.d.ts.map +1 -0
- package/dist/panel-bridge.d.ts +38 -0
- package/dist/panel-bridge.d.ts.map +1 -0
- package/dist/session-story/index.d.ts +66 -0
- package/dist/session-story/index.d.ts.map +1 -0
- package/dist/session-story/panel.d.ts +31 -0
- package/dist/session-story/panel.d.ts.map +1 -0
- package/dist/session-story/panel.mjs +970 -0
- package/dist/session-story/panel.mjs.map +1 -0
- package/dist/session-story.mjs +1002 -0
- package/dist/session-story.mjs.map +1 -0
- package/dist/sessions-panel/index.d.ts +51 -0
- package/dist/sessions-panel/index.d.ts.map +1 -0
- package/dist/sessions-panel/panel.d.ts +20 -0
- package/dist/sessions-panel/panel.d.ts.map +1 -0
- package/dist/sessions-panel/panel.mjs +511 -0
- package/dist/sessions-panel/panel.mjs.map +1 -0
- package/dist/sessions-panel.mjs +545 -0
- package/dist/sessions-panel.mjs.map +1 -0
- package/package.json +56 -5
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineApp } from '@agentproto/app-kit';
|
|
2
2
|
import { defineAgent } from '@agentproto/agent';
|
|
3
3
|
import { defineWorkflow } from '@agentproto/workflow';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @agentproto/apps v0.1.0
|
|
@@ -134,17 +135,29 @@ var illustrator = {
|
|
|
134
135
|
var produceContent = defineWorkflow({
|
|
135
136
|
id: "produce-content",
|
|
136
137
|
name: "Produce a piece of content",
|
|
137
|
-
description: "
|
|
138
|
+
description: "Research the sources, draft the piece, then edit it.",
|
|
138
139
|
version: "0.1.0",
|
|
139
140
|
inputs: {},
|
|
140
141
|
outputs: {},
|
|
141
|
-
// Concrete workspace tools (fs + exec), so the workflow describes a process
|
|
142
|
-
// a standard runtime can actually run — not abstract tools nothing provides.
|
|
143
142
|
steps: [
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
{
|
|
144
|
+
id: "research",
|
|
145
|
+
kind: "agent",
|
|
146
|
+
agent: { ref: "@agentproto/researcher" },
|
|
147
|
+
prompt: "Read the source material already in the workspace and hand off a tight brief with a citation for every claim."
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "draft",
|
|
151
|
+
kind: "agent",
|
|
152
|
+
agent: { ref: "@agentproto/writer" },
|
|
153
|
+
prompt: "Draft the piece from the researcher's brief and its sources."
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "edit",
|
|
157
|
+
kind: "agent",
|
|
158
|
+
agent: { ref: "@agentproto/editor" },
|
|
159
|
+
prompt: "Edit the draft for voice, clarity, and length, then finalize it."
|
|
160
|
+
}
|
|
148
161
|
]
|
|
149
162
|
});
|
|
150
163
|
var produceCover = defineWorkflow({
|
|
@@ -155,10 +168,12 @@ var produceCover = defineWorkflow({
|
|
|
155
168
|
inputs: {},
|
|
156
169
|
outputs: {},
|
|
157
170
|
steps: [
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
{
|
|
172
|
+
id: "illustrate",
|
|
173
|
+
kind: "agent",
|
|
174
|
+
agent: { ref: "@agentproto/illustrator" },
|
|
175
|
+
prompt: "Produce the cover for this article in one pass: (1) read the article and derive the single visual concept that best represents it, (2) write the art direction as a clean, text-free image prompt and generate the base image from it, (3) clean up the generated image (crop, color, artifacts), then (4) upload the final cover to the workspace's output location."
|
|
176
|
+
}
|
|
162
177
|
]
|
|
163
178
|
});
|
|
164
179
|
|
|
@@ -2867,6 +2882,2614 @@ var sessionViewer = defineApp({
|
|
|
2867
2882
|
}
|
|
2868
2883
|
});
|
|
2869
2884
|
|
|
2870
|
-
|
|
2885
|
+
// src/panel-bridge.ts
|
|
2886
|
+
function panelBridgeScript(appName) {
|
|
2887
|
+
return `// \u2500\u2500 MCP Apps bridge (shared: panel-bridge.ts) \u2500\u2500
|
|
2888
|
+
// JSON-RPC 2.0 over window.parent.postMessage \xB7 spec 2026-01-26
|
|
2889
|
+
var _nextId = 1, _pending = {}, _notifyHandlers = [];
|
|
2890
|
+
var _hostContext = null, _hostContextHandlers = [];
|
|
2891
|
+
function post(msg){ window.parent.postMessage(msg, '*'); }
|
|
2892
|
+
function getHostContext(){ return _hostContext; }
|
|
2893
|
+
function onHostContext(cb){
|
|
2894
|
+
_hostContextHandlers.push(cb);
|
|
2895
|
+
// Replay the last context so a late subscriber isn't stuck blind.
|
|
2896
|
+
if (_hostContext){ try { cb(_hostContext); } catch(_) {} }
|
|
2897
|
+
}
|
|
2898
|
+
function _setHostContext(ctx){
|
|
2899
|
+
if (!ctx || typeof ctx !== 'object') return;
|
|
2900
|
+
// ui/notifications/host-context-changed carries only the changed keys \u2014
|
|
2901
|
+
// merge, matching the official ext-apps App behaviour.
|
|
2902
|
+
_hostContext = Object.assign({}, _hostContext || {}, ctx);
|
|
2903
|
+
for (var i = 0; i < _hostContextHandlers.length; i++){
|
|
2904
|
+
try { _hostContextHandlers[i](_hostContext); } catch(_) {}
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
function rpcRequest(method, params){
|
|
2908
|
+
return new Promise(function(resolve, reject){
|
|
2909
|
+
var id = _nextId++;
|
|
2910
|
+
_pending[id] = {resolve: resolve, reject: reject};
|
|
2911
|
+
post({jsonrpc: '2.0', id: id, method: method, params: params || {}});
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
2914
|
+
function rpcNotify(method, params){ post({jsonrpc: '2.0', method: method, params: params || {}}); }
|
|
2915
|
+
function onHostNotification(cb){ _notifyHandlers.push(cb); }
|
|
2916
|
+
window.addEventListener('message', function(evt){
|
|
2917
|
+
var msg = evt.data;
|
|
2918
|
+
if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0') return;
|
|
2919
|
+
if (msg.id != null && msg.method == null){
|
|
2920
|
+
var p = _pending[msg.id];
|
|
2921
|
+
if (!p) return;
|
|
2922
|
+
delete _pending[msg.id];
|
|
2923
|
+
if (msg.error) p.reject(new Error(msg.error.message || ('rpc error ' + msg.error.code)));
|
|
2924
|
+
else p.resolve(msg.result);
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
if (msg.method){
|
|
2928
|
+
if (msg.method === 'ui/notifications/host-context-changed'){
|
|
2929
|
+
_setHostContext(msg.params || {});
|
|
2930
|
+
}
|
|
2931
|
+
for (var i = 0; i < _notifyHandlers.length; i++){
|
|
2932
|
+
try { _notifyHandlers[i](msg.method, msg.params || {}); } catch(_) {}
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
});
|
|
2936
|
+
function initBridge(){
|
|
2937
|
+
return rpcRequest('ui/initialize', {
|
|
2938
|
+
appInfo: {name: ${JSON.stringify(appName)}, version: '0.1.0'},
|
|
2939
|
+
appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},
|
|
2940
|
+
protocolVersion: '2026-01-26'
|
|
2941
|
+
}).then(function(result){
|
|
2942
|
+
// The initialize result carries the initial hostContext (displayMode +
|
|
2943
|
+
// availableDisplayModes) \u2014 capture it before notifying the host.
|
|
2944
|
+
if (result && result.hostContext) _setHostContext(result.hostContext);
|
|
2945
|
+
rpcNotify('ui/notifications/initialized', {});
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
function requestDisplayMode(mode){
|
|
2949
|
+
return rpcRequest('ui/request-display-mode', {mode: mode});
|
|
2950
|
+
}
|
|
2951
|
+
function callTool(name, args){
|
|
2952
|
+
return rpcRequest('tools/call', {name: name, arguments: args || {}}).then(function(result){
|
|
2953
|
+
if (result.isError){
|
|
2954
|
+
var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';
|
|
2955
|
+
throw new Error(e);
|
|
2956
|
+
}
|
|
2957
|
+
var text = (result.content && result.content[0] && result.content[0].text) || '{}';
|
|
2958
|
+
return JSON.parse(text);
|
|
2959
|
+
});
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
// \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
|
|
2963
|
+
// Injected by the shared bridge so every panel gets them without touching
|
|
2964
|
+
// its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the
|
|
2965
|
+
// panel stays inline by default; the user expands on demand. Buttons only
|
|
2966
|
+
// appear for modes the host advertises in hostContext.availableDisplayModes.
|
|
2967
|
+
(function(){
|
|
2968
|
+
function mount(){
|
|
2969
|
+
var style = document.createElement('style');
|
|
2970
|
+
style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'
|
|
2971
|
+
+ 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'
|
|
2972
|
+
+ 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'
|
|
2973
|
+
+ 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'
|
|
2974
|
+
+ '#dm{right:8px}#pin{right:118px}'
|
|
2975
|
+
+ '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'
|
|
2976
|
+
+ '@media (prefers-color-scheme:dark){'
|
|
2977
|
+
+ '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'
|
|
2978
|
+
+ '#dm:hover,#pin:hover{background:#333;border-color:#777}}';
|
|
2979
|
+
document.head.appendChild(style);
|
|
2980
|
+
|
|
2981
|
+
var btn = document.createElement('button');
|
|
2982
|
+
btn.id = 'dm'; btn.type = 'button'; btn.title = "Basculer l'affichage";
|
|
2983
|
+
var pin = document.createElement('button');
|
|
2984
|
+
pin.id = 'pin'; pin.type = 'button'; pin.title = '\xC9pingler sur le c\xF4t\xE9 (pip)';
|
|
2985
|
+
document.body.appendChild(pin);
|
|
2986
|
+
document.body.appendChild(btn);
|
|
2987
|
+
|
|
2988
|
+
function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }
|
|
2989
|
+
|
|
2990
|
+
// Re-sync button visibility + label from the current host context.
|
|
2991
|
+
function syncBtn(ctx){
|
|
2992
|
+
ctx = ctx || {};
|
|
2993
|
+
var avail = ctx.availableDisplayModes;
|
|
2994
|
+
// Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)
|
|
2995
|
+
console.log('[mcp-app] displayMode=', ctx.displayMode,
|
|
2996
|
+
'availableDisplayModes=', avail);
|
|
2997
|
+
|
|
2998
|
+
// Fullscreen toggle button.
|
|
2999
|
+
if (has(avail, 'fullscreen')){
|
|
3000
|
+
btn.style.display = 'block';
|
|
3001
|
+
btn.textContent = (ctx.displayMode === 'fullscreen') ? '\u2921 R\xE9duire' : '\u2922 Agrandir';
|
|
3002
|
+
} else { btn.style.display = 'none'; }
|
|
3003
|
+
|
|
3004
|
+
// Dedicated pip ("pinned on side") button \u2014 only if the host advertises pip.
|
|
3005
|
+
if (has(avail, 'pip')){
|
|
3006
|
+
pin.style.display = 'block';
|
|
3007
|
+
pin.textContent = (ctx.displayMode === 'pip') ? '\u2921 D\xE9tacher' : '\u{1F4CC} \xC9pingler';
|
|
3008
|
+
} else { pin.style.display = 'none'; }
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
onHostContext(syncBtn);
|
|
3012
|
+
|
|
3013
|
+
btn.addEventListener('click', function(){
|
|
3014
|
+
var ctx = getHostContext() || {};
|
|
3015
|
+
var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');
|
|
3016
|
+
requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});
|
|
3017
|
+
});
|
|
3018
|
+
|
|
3019
|
+
pin.addEventListener('click', function(){
|
|
3020
|
+
var ctx = getHostContext() || {};
|
|
3021
|
+
requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3024
|
+
if (document.body) mount();
|
|
3025
|
+
else document.addEventListener('DOMContentLoaded', mount);
|
|
3026
|
+
})();`;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
// src/sessions-panel/panel.ts
|
|
3030
|
+
var PANEL_HTML = `<!DOCTYPE html>
|
|
3031
|
+
<html lang="en">
|
|
3032
|
+
<head>
|
|
3033
|
+
<meta charset="UTF-8">
|
|
3034
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3035
|
+
<title>agentproto sessions</title>
|
|
3036
|
+
<style>
|
|
3037
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
3038
|
+
:root{
|
|
3039
|
+
--bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;
|
|
3040
|
+
--text:#e6edf3;--text2:#8b949e;
|
|
3041
|
+
--green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;
|
|
3042
|
+
}
|
|
3043
|
+
html,body{height:100%;font-family:Menlo,Monaco,'Courier New',monospace;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}
|
|
3044
|
+
#app{display:flex;height:100%}
|
|
3045
|
+
#sidebar{width:220px;min-width:180px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
|
|
3046
|
+
#sidebar-hdr{padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}
|
|
3047
|
+
#sidebar-hdr h1{font-size:11px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.06em}
|
|
3048
|
+
#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:15px;line-height:1;padding:2px 4px;border-radius:4px;font-family:inherit}
|
|
3049
|
+
#refresh-btn:hover{color:var(--text);background:var(--bg3)}
|
|
3050
|
+
#session-list{flex:1;overflow-y:auto;padding:4px 0}
|
|
3051
|
+
.si{padding:8px 12px;cursor:pointer;border-left:2px solid transparent}
|
|
3052
|
+
.si:hover{background:var(--bg3)}
|
|
3053
|
+
.si.active{background:var(--bg3);border-left-color:var(--blue)}
|
|
3054
|
+
.sn{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
3055
|
+
.sm{font-size:11px;color:var(--text2);margin-top:2px;display:flex;gap:6px;align-items:center}
|
|
3056
|
+
.badge{display:inline-block;padding:1px 5px;border-radius:10px;font-size:10px;font-weight:600}
|
|
3057
|
+
.br{background:rgba(63,185,80,.15);color:var(--green)}
|
|
3058
|
+
.bs{background:rgba(88,166,255,.15);color:var(--blue)}
|
|
3059
|
+
.be{background:rgba(139,148,158,.1);color:var(--text2)}
|
|
3060
|
+
.bk{background:rgba(248,81,73,.1);color:var(--red)}
|
|
3061
|
+
.berr{background:rgba(248,81,73,.2);color:var(--red)}
|
|
3062
|
+
#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
|
|
3063
|
+
#toolbar{padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2)}
|
|
3064
|
+
#session-title{font-size:12px;font-weight:500;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
3065
|
+
.abtn{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}
|
|
3066
|
+
.abtn:hover{background:var(--border)}
|
|
3067
|
+
.abtn:disabled{opacity:.4;cursor:default}
|
|
3068
|
+
.abtn.danger{border-color:var(--red);color:var(--red)}
|
|
3069
|
+
.abtn.danger:hover{background:rgba(248,81,73,.1)}
|
|
3070
|
+
#output{flex:1;overflow-y:auto;padding:8px 12px;background:var(--bg);font-size:12px;line-height:1.6}
|
|
3071
|
+
.line{white-space:pre-wrap;word-break:break-all}
|
|
3072
|
+
#empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text2);gap:8px;text-align:center;padding:24px}
|
|
3073
|
+
#empty .ico{font-size:32px}
|
|
3074
|
+
#statusbar{padding:4px 12px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}
|
|
3075
|
+
.errmsg{color:var(--red);padding:12px}
|
|
3076
|
+
/* ANSI SGR */
|
|
3077
|
+
.ab{font-weight:bold}.ad{opacity:.6}.ai{font-style:italic}.au{text-decoration:underline}
|
|
3078
|
+
.f0{color:#21262d}.f1{color:#f85149}.f2{color:#3fb950}.f3{color:#d29922}
|
|
3079
|
+
.f4{color:#58a6ff}.f5{color:#bc8cff}.f6{color:#39d353}.f7{color:#e6edf3}
|
|
3080
|
+
.f8{color:#8b949e}.f9{color:#ff7b72}.f10{color:#56d364}.f11{color:#e3b341}
|
|
3081
|
+
.f12{color:#79c0ff}.f13{color:#d2a8ff}.f14{color:#56d364}.f15{color:#f0f6fc}
|
|
3082
|
+
</style>
|
|
3083
|
+
</head>
|
|
3084
|
+
<body>
|
|
3085
|
+
<div id="app">
|
|
3086
|
+
<div id="sidebar">
|
|
3087
|
+
<div id="sidebar-hdr">
|
|
3088
|
+
<h1>Sessions</h1>
|
|
3089
|
+
<button id="refresh-btn" title="Refresh" onclick="doRefresh()">↻</button>
|
|
3090
|
+
</div>
|
|
3091
|
+
<div id="session-list"></div>
|
|
3092
|
+
</div>
|
|
3093
|
+
<div id="main">
|
|
3094
|
+
<div id="toolbar" style="display:none">
|
|
3095
|
+
<span id="session-title"></span>
|
|
3096
|
+
<button class="abtn danger" id="kill-btn" onclick="doKill()">Kill</button>
|
|
3097
|
+
</div>
|
|
3098
|
+
<div id="output">
|
|
3099
|
+
<div id="empty"><div class="ico">⚡</div><div>Select a session</div></div>
|
|
3100
|
+
</div>
|
|
3101
|
+
<div id="statusbar">Connecting to bridge…</div>
|
|
3102
|
+
</div>
|
|
3103
|
+
</div>
|
|
3104
|
+
<script>
|
|
3105
|
+
${panelBridgeScript("agentproto-sessions-panel")}
|
|
3106
|
+
|
|
3107
|
+
// ============================================================
|
|
3108
|
+
// ANSI-to-HTML renderer (SGR codes: colors 0-15, bold/dim/italic/underline)
|
|
3109
|
+
// ============================================================
|
|
3110
|
+
|
|
3111
|
+
function escHtml(s) {
|
|
3112
|
+
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
function ansiToHtml(raw) {
|
|
3116
|
+
// Strip CR overwrite sequences (progress bars: CR without LF)
|
|
3117
|
+
var text = raw.replace(/[^\\n]*\\r([^\\n])/g, '$1').replace(/\\r/g, '');
|
|
3118
|
+
var bold = false, dim = false, italic = false, underline = false, fg = -1;
|
|
3119
|
+
var out = '';
|
|
3120
|
+
// Split on ESC [ ... m (SGR) sequences; keep delimiters
|
|
3121
|
+
var ESC = '\x1B';
|
|
3122
|
+
var parts = text.split(/([\x1B]\\[[0-9;]*m)/);
|
|
3123
|
+
for (var pi = 0; pi < parts.length; pi++) {
|
|
3124
|
+
var part = parts[pi];
|
|
3125
|
+
if (part.length === 0) continue;
|
|
3126
|
+
if (part.charCodeAt(0) === 0x1b && part[1] === '[') {
|
|
3127
|
+
// Parse SGR parameters
|
|
3128
|
+
var inner = part.slice(2, part.length - 1); // strip ESC[ and m
|
|
3129
|
+
var codes = inner === '' ? [0] : inner.split(';').map(Number);
|
|
3130
|
+
for (var ci = 0; ci < codes.length; ci++) {
|
|
3131
|
+
var c = codes[ci];
|
|
3132
|
+
if (c === 0) { bold = false; dim = false; italic = false; underline = false; fg = -1; }
|
|
3133
|
+
else if (c === 1) bold = true;
|
|
3134
|
+
else if (c === 2) dim = true;
|
|
3135
|
+
else if (c === 3) italic = true;
|
|
3136
|
+
else if (c === 4) underline = true;
|
|
3137
|
+
else if (c === 22) { bold = false; dim = false; }
|
|
3138
|
+
else if (c === 23) italic = false;
|
|
3139
|
+
else if (c === 24) underline = false;
|
|
3140
|
+
else if (c >= 30 && c <= 37) fg = c - 30;
|
|
3141
|
+
else if (c === 39) fg = -1;
|
|
3142
|
+
else if (c >= 90 && c <= 97) fg = c - 90 + 8;
|
|
3143
|
+
}
|
|
3144
|
+
} else {
|
|
3145
|
+
// Text segment \u2014 strip any remaining non-printable ESC sequences
|
|
3146
|
+
var safe = escHtml(part).replace(/\x1B\\[[^m]*[A-Za-z]/g, '');
|
|
3147
|
+
if (safe === '') continue;
|
|
3148
|
+
var cls = '';
|
|
3149
|
+
if (bold) cls += ' ab';
|
|
3150
|
+
if (dim) cls += ' ad';
|
|
3151
|
+
if (italic) cls += ' ai';
|
|
3152
|
+
if (underline) cls += ' au';
|
|
3153
|
+
if (fg >= 0 && fg <= 15) cls += ' f' + fg;
|
|
3154
|
+
cls = cls.trim();
|
|
3155
|
+
out += cls ? '<span class="' + cls + '">' + safe + '</span>' : safe;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
return out;
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
// ============================================================
|
|
3162
|
+
// App state
|
|
3163
|
+
// ============================================================
|
|
3164
|
+
|
|
3165
|
+
var sessions = [];
|
|
3166
|
+
var activeId = null;
|
|
3167
|
+
var outputLines = [];
|
|
3168
|
+
var nextCursor = 0;
|
|
3169
|
+
var pollTimer = null;
|
|
3170
|
+
var pollActive = false;
|
|
3171
|
+
|
|
3172
|
+
function setStatus(msg) {
|
|
3173
|
+
document.getElementById('statusbar').textContent = msg;
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
// ============================================================
|
|
3177
|
+
// Session list
|
|
3178
|
+
// ============================================================
|
|
3179
|
+
|
|
3180
|
+
function badgeClass(status) {
|
|
3181
|
+
if (status === 'running') return 'br';
|
|
3182
|
+
if (status === 'starting') return 'bs';
|
|
3183
|
+
if (status === 'killed') return 'bk';
|
|
3184
|
+
if (status === 'error') return 'berr';
|
|
3185
|
+
return 'be'; // exited
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
// Derive a turn-aware display badge. A session's 'status' only tracks process
|
|
3189
|
+
// liveness, so an agent-cli process stays "running" while idle between turns.
|
|
3190
|
+
// Read 'busy' / 'awaitingInput' to show real activity instead:
|
|
3191
|
+
// working \u2014 a turn is in flight (busy)
|
|
3192
|
+
// waiting \u2014 turn ended on awaiting-input (needs a reply)
|
|
3193
|
+
// idle \u2014 process alive, no turn running (last turn done)
|
|
3194
|
+
function displayBadge(s) {
|
|
3195
|
+
if (s.status === 'running' && s.kind === 'agent-cli') {
|
|
3196
|
+
if (s.busy) return { label: 'working', cls: 'br' };
|
|
3197
|
+
if (s.awaitingInput) return { label: 'waiting', cls: 'bs' };
|
|
3198
|
+
return { label: 'idle', cls: 'be' };
|
|
3199
|
+
}
|
|
3200
|
+
return { label: s.status, cls: badgeClass(s.status) };
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
function renderSidebar() {
|
|
3204
|
+
var el = document.getElementById('session-list');
|
|
3205
|
+
if (sessions.length === 0) {
|
|
3206
|
+
el.innerHTML = '<div style="padding:12px;color:var(--text2);font-size:11px">No sessions</div>';
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
var html = '';
|
|
3210
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
3211
|
+
var s = sessions[i];
|
|
3212
|
+
var label = s.label || s.name || (s.command ? s.command.split('/').pop() : null) || s.id.slice(0, 8);
|
|
3213
|
+
var db = displayBadge(s);
|
|
3214
|
+
var active = s.id === activeId ? ' active' : '';
|
|
3215
|
+
// blockedOn (set while the turn waits on a spawned sub-agent or a
|
|
3216
|
+
// shell command) rides next to the status badge; waiting-on-user is
|
|
3217
|
+
// NOT here \u2014 that's awaitingInput, a different signal.
|
|
3218
|
+
var blocked = '';
|
|
3219
|
+
if (s.blockedOn === 'subagent') blocked = '<span class="badge bs">🧩 sous-agent</span>';
|
|
3220
|
+
else if (s.blockedOn === 'command') blocked = '<span class="badge bs">⏳ commande</span>';
|
|
3221
|
+
html += '<div class="si' + active + '" onclick="selectSession(\\'' + s.id + '\\')">'
|
|
3222
|
+
+ '<div class="sn">' + escHtml(label) + '</div>'
|
|
3223
|
+
+ '<div class="sm">'
|
|
3224
|
+
+ '<span class="badge ' + db.cls + '">' + db.label + '</span>'
|
|
3225
|
+
+ blocked
|
|
3226
|
+
+ '<span>' + escHtml(s.kind || '') + '</span>'
|
|
3227
|
+
+ '</div></div>';
|
|
3228
|
+
}
|
|
3229
|
+
el.innerHTML = html;
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
function loadSessions() {
|
|
3233
|
+
// {kind:'all'} is session_list's live-able default \u2014 agent-CLI + terminal/
|
|
3234
|
+
// PTY only. kind:"command" rows (a shell-execution log, not a resumable
|
|
3235
|
+
// session) are excluded unless includeCommands is explicitly passed.
|
|
3236
|
+
return callTool('session_list', {kind: 'all'}).then(function(data) {
|
|
3237
|
+
sessions = data.sessions || [];
|
|
3238
|
+
renderSidebar();
|
|
3239
|
+
setStatus(sessions.length + ' session' + (sessions.length === 1 ? '' : 's') + ' \xB7 ' + new Date().toLocaleTimeString());
|
|
3240
|
+
}).catch(function(e) {
|
|
3241
|
+
setStatus('Error: ' + e.message);
|
|
3242
|
+
});
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
// ============================================================
|
|
3246
|
+
// Output panel
|
|
3247
|
+
// ============================================================
|
|
3248
|
+
|
|
3249
|
+
function renderOutputFull() {
|
|
3250
|
+
var el = document.getElementById('output');
|
|
3251
|
+
if (outputLines.length === 0) {
|
|
3252
|
+
el.innerHTML = '<div id="empty"><div class="ico">⚡</div><div>No output yet</div></div>';
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
var html = '';
|
|
3256
|
+
for (var i = 0; i < outputLines.length; i++) {
|
|
3257
|
+
html += '<div class="line">' + ansiToHtml(outputLines[i]) + '</div>';
|
|
3258
|
+
}
|
|
3259
|
+
el.innerHTML = html;
|
|
3260
|
+
el.scrollTop = el.scrollHeight;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
function appendLines(lines) {
|
|
3264
|
+
var el = document.getElementById('output');
|
|
3265
|
+
// Remove empty-state placeholder if present
|
|
3266
|
+
var empty = el.querySelector('#empty');
|
|
3267
|
+
if (empty) empty.parentNode.removeChild(empty);
|
|
3268
|
+
var frag = document.createDocumentFragment();
|
|
3269
|
+
for (var i = 0; i < lines.length; i++) {
|
|
3270
|
+
var div = document.createElement('div');
|
|
3271
|
+
div.className = 'line';
|
|
3272
|
+
div.innerHTML = ansiToHtml(lines[i]);
|
|
3273
|
+
frag.appendChild(div);
|
|
3274
|
+
}
|
|
3275
|
+
el.appendChild(frag);
|
|
3276
|
+
el.scrollTop = el.scrollHeight;
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
function selectSession(id) {
|
|
3280
|
+
activeId = id;
|
|
3281
|
+
outputLines = [];
|
|
3282
|
+
nextCursor = 0;
|
|
3283
|
+
renderSidebar();
|
|
3284
|
+
|
|
3285
|
+
var s = null;
|
|
3286
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
3287
|
+
if (sessions[i].id === id) { s = sessions[i]; break; }
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
var toolbar = document.getElementById('toolbar');
|
|
3291
|
+
toolbar.style.display = 'flex';
|
|
3292
|
+
document.getElementById('session-title').textContent = (s && (s.label || s.name)) || id.slice(0, 12);
|
|
3293
|
+
|
|
3294
|
+
var killBtn = document.getElementById('kill-btn');
|
|
3295
|
+
killBtn.disabled = !s || (s.status !== 'running' && s.status !== 'starting');
|
|
3296
|
+
|
|
3297
|
+
document.getElementById('output').innerHTML = '<div style="padding:12px;color:var(--text2)">Loading…</div>';
|
|
3298
|
+
|
|
3299
|
+
// Initial load
|
|
3300
|
+
callTool('agent_output', {sessionId: id, lastN: 200}).then(function(data) {
|
|
3301
|
+
outputLines = data.lines || [];
|
|
3302
|
+
nextCursor = data.nextCursor || 0;
|
|
3303
|
+
renderOutputFull();
|
|
3304
|
+
}).catch(function(e) {
|
|
3305
|
+
document.getElementById('output').innerHTML = '<div class="errmsg">Error: ' + escHtml(e.message) + '</div>';
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
// ============================================================
|
|
3310
|
+
// Kill
|
|
3311
|
+
// ============================================================
|
|
3312
|
+
|
|
3313
|
+
function doKill() {
|
|
3314
|
+
if (!activeId) return;
|
|
3315
|
+
var s = null;
|
|
3316
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
3317
|
+
if (sessions[i].id === activeId) { s = sessions[i]; break; }
|
|
3318
|
+
}
|
|
3319
|
+
var toolName = (s && s.pty) ? 'terminal_kill' : 'agent_kill';
|
|
3320
|
+
callTool(toolName, {sessionId: activeId}).then(function() {
|
|
3321
|
+
return doRefresh();
|
|
3322
|
+
}).catch(function(e) {
|
|
3323
|
+
setStatus('Kill failed: ' + e.message);
|
|
3324
|
+
});
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
// ============================================================
|
|
3328
|
+
// Poll
|
|
3329
|
+
// ============================================================
|
|
3330
|
+
|
|
3331
|
+
function doPoll() {
|
|
3332
|
+
if (pollActive) return;
|
|
3333
|
+
pollActive = true;
|
|
3334
|
+
var p = loadSessions();
|
|
3335
|
+
if (activeId) {
|
|
3336
|
+
var capturedId = activeId;
|
|
3337
|
+
var capturedCursor = nextCursor;
|
|
3338
|
+
p = p.then(function() {
|
|
3339
|
+
return callTool('agent_output', {sessionId: capturedId, since: capturedCursor});
|
|
3340
|
+
}).then(function(data) {
|
|
3341
|
+
if (capturedId !== activeId) return; // user switched sessions
|
|
3342
|
+
var newLines = data.lines || [];
|
|
3343
|
+
if (newLines.length > 0) {
|
|
3344
|
+
outputLines = outputLines.concat(newLines);
|
|
3345
|
+
if (outputLines.length > 2000) outputLines = outputLines.slice(-2000);
|
|
3346
|
+
nextCursor = data.nextCursor || nextCursor;
|
|
3347
|
+
appendLines(newLines);
|
|
3348
|
+
}
|
|
3349
|
+
}).catch(function() {});
|
|
3350
|
+
}
|
|
3351
|
+
p.then(function() {
|
|
3352
|
+
pollActive = false;
|
|
3353
|
+
pollTimer = setTimeout(doPoll, 3000);
|
|
3354
|
+
}).catch(function() {
|
|
3355
|
+
pollActive = false;
|
|
3356
|
+
pollTimer = setTimeout(doPoll, 3000);
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
function doRefresh() {
|
|
3361
|
+
return loadSessions().then(function() {
|
|
3362
|
+
if (activeId) {
|
|
3363
|
+
return callTool('agent_output', {sessionId: activeId, lastN: 200}).then(function(data) {
|
|
3364
|
+
outputLines = data.lines || [];
|
|
3365
|
+
nextCursor = data.nextCursor || 0;
|
|
3366
|
+
renderOutputFull();
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
// ============================================================
|
|
3373
|
+
// Boot
|
|
3374
|
+
// ============================================================
|
|
3375
|
+
|
|
3376
|
+
initBridge().then(function() {
|
|
3377
|
+
return loadSessions();
|
|
3378
|
+
}).then(function() {
|
|
3379
|
+
pollTimer = setTimeout(doPoll, 3000);
|
|
3380
|
+
}).catch(function(e) {
|
|
3381
|
+
setStatus('Bridge error: ' + e.message);
|
|
3382
|
+
document.getElementById('output').innerHTML = '<div class="errmsg">Failed to connect to MCP bridge: ' + escHtml(e.message) + '</div>';
|
|
3383
|
+
});
|
|
3384
|
+
</script>
|
|
3385
|
+
</body>
|
|
3386
|
+
</html>`;
|
|
3387
|
+
|
|
3388
|
+
// src/sessions-panel/index.ts
|
|
3389
|
+
var sessionsPanelInputSchema = z.object({
|
|
3390
|
+
filter: z.enum(["running", "all"]).optional().describe(
|
|
3391
|
+
"Which sessions to return. `running` = only alive; `all` = running + recent (default)."
|
|
3392
|
+
)
|
|
3393
|
+
});
|
|
3394
|
+
function makeSessionsPanelApp(ops) {
|
|
3395
|
+
return {
|
|
3396
|
+
id: "agentproto_sessions",
|
|
3397
|
+
title: "Agent Sessions",
|
|
3398
|
+
description: "Open the agentproto sessions panel \u2014 an interactive UI that shows all running and recent agent-CLI and terminal/PTY sessions. Raw shell-command runs are a log, not a resumable session, and don't appear here \u2014 see `command_list`. The panel polls live data and lets you inspect output or kill sessions.",
|
|
3399
|
+
inputSchema: sessionsPanelInputSchema,
|
|
3400
|
+
execute: async (input) => ({
|
|
3401
|
+
sessions: ops.listSessions(input.filter)
|
|
3402
|
+
}),
|
|
3403
|
+
html: PANEL_HTML
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
var sessionsPanelApp = defineApp({
|
|
3407
|
+
id: "@agentproto/sessions-panel",
|
|
3408
|
+
name: "Agent Sessions",
|
|
3409
|
+
description: "Open the agentproto sessions panel \u2014 an interactive UI that shows all running and recent agent-CLI and terminal/PTY sessions.",
|
|
3410
|
+
agents: [],
|
|
3411
|
+
ui: {
|
|
3412
|
+
html: PANEL_HTML,
|
|
3413
|
+
title: "Agent Sessions",
|
|
3414
|
+
tools: ["session_list", "agent_output"]
|
|
3415
|
+
}
|
|
3416
|
+
});
|
|
3417
|
+
|
|
3418
|
+
// src/agents-overview/panel.ts
|
|
3419
|
+
var AGENTS_OVERVIEW_HTML = `<!DOCTYPE html>
|
|
3420
|
+
<html lang="fr">
|
|
3421
|
+
<head>
|
|
3422
|
+
<meta charset="UTF-8">
|
|
3423
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3424
|
+
<title>agents \u2014 vue claire</title>
|
|
3425
|
+
<style>
|
|
3426
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
3427
|
+
:root{
|
|
3428
|
+
--bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;
|
|
3429
|
+
--text:#e6edf3;--text2:#8b949e;
|
|
3430
|
+
--green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;
|
|
3431
|
+
}
|
|
3432
|
+
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}
|
|
3433
|
+
#app{display:flex;flex-direction:column;height:100%}
|
|
3434
|
+
#hdr{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:var(--bg2);flex-shrink:0}
|
|
3435
|
+
#hdr h1{font-size:13px;font-weight:600;letter-spacing:.02em}
|
|
3436
|
+
#hdr .sub{font-size:11px;color:var(--text2);margin-top:2px}
|
|
3437
|
+
#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:16px;line-height:1;padding:4px 6px;border-radius:6px}
|
|
3438
|
+
#refresh-btn:hover{color:var(--text);background:var(--bg3)}
|
|
3439
|
+
#grid{flex:1;overflow-y:auto;padding:14px 16px;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;align-content:start}
|
|
3440
|
+
.card{background:var(--bg2);border:1px solid var(--border);border-left:3px solid var(--border);border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;min-height:96px}
|
|
3441
|
+
.card.s-todo{border-left-color:var(--purple)}
|
|
3442
|
+
.card.s-work{border-left-color:var(--green)}
|
|
3443
|
+
.card.s-wait{border-left-color:var(--yellow)}
|
|
3444
|
+
.card.s-done{border-left-color:var(--text2)}
|
|
3445
|
+
.card-top{display:flex;align-items:center;justify-content:space-between;gap:8px}
|
|
3446
|
+
.title{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}
|
|
3447
|
+
.state{font-size:10px;font-weight:700;padding:2px 8px;border-radius:11px;white-space:nowrap;letter-spacing:.02em}
|
|
3448
|
+
.state.s-todo{background:rgba(188,140,255,.16);color:var(--purple)}
|
|
3449
|
+
.state.s-work{background:rgba(63,185,80,.16);color:var(--green)}
|
|
3450
|
+
.state.s-wait{background:rgba(210,153,34,.16);color:var(--yellow)}
|
|
3451
|
+
.state.s-done{background:rgba(139,148,158,.14);color:var(--text2)}
|
|
3452
|
+
.summary{font-size:12.5px;line-height:1.5;color:var(--text);word-break:break-word}
|
|
3453
|
+
.summary.muted{color:var(--text2);font-style:italic}
|
|
3454
|
+
.meta{font-size:10.5px;color:var(--text2);display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:auto}
|
|
3455
|
+
.dot{width:5px;height:5px;border-radius:50%;background:var(--text2);display:inline-block}
|
|
3456
|
+
#empty{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;padding:48px;text-align:center}
|
|
3457
|
+
#empty .ico{font-size:30px}
|
|
3458
|
+
#statusbar{padding:5px 16px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}
|
|
3459
|
+
</style>
|
|
3460
|
+
</head>
|
|
3461
|
+
<body>
|
|
3462
|
+
<div id="app">
|
|
3463
|
+
<div id="hdr">
|
|
3464
|
+
<div>
|
|
3465
|
+
<h1>Agents \u2014 vue claire</h1>
|
|
3466
|
+
<div class="sub">une carte par agent \xB7 r\xE9sum\xE9 + \xE9tat</div>
|
|
3467
|
+
</div>
|
|
3468
|
+
<button id="refresh-btn" title="Rafra\xEEchir" onclick="doRefresh()">↻</button>
|
|
3469
|
+
</div>
|
|
3470
|
+
<div id="grid"><div id="empty"><div class="ico">⚡</div><div>Connexion au bridge…</div></div></div>
|
|
3471
|
+
<div id="statusbar">Connexion…</div>
|
|
3472
|
+
</div>
|
|
3473
|
+
<script>
|
|
3474
|
+
${panelBridgeScript("agentproto-agents-overview")}
|
|
3475
|
+
|
|
3476
|
+
// \u2500\u2500 State \u2500\u2500
|
|
3477
|
+
var REFRESH_MS = 12000;
|
|
3478
|
+
var pollTimer = null, polling = false;
|
|
3479
|
+
function esc(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
3480
|
+
function setStatus(m){ document.getElementById('statusbar').textContent = m; }
|
|
3481
|
+
|
|
3482
|
+
var STATE_CLASS = {
|
|
3483
|
+
'\xE0 traiter':'s-todo', 'au travail':'s-work', 'en attente':'s-wait', 'termin\xE9':'s-done'
|
|
3484
|
+
};
|
|
3485
|
+
function stateClass(st){ return STATE_CLASS[st] || 's-wait'; }
|
|
3486
|
+
|
|
3487
|
+
function fmtAgo(iso){
|
|
3488
|
+
if (!iso) return '';
|
|
3489
|
+
var t = Date.parse(iso);
|
|
3490
|
+
if (isNaN(t)) return '';
|
|
3491
|
+
var s = Math.max(0, Math.round((Date.now() - t) / 1000));
|
|
3492
|
+
if (s < 60) return 'il y a ' + s + 's';
|
|
3493
|
+
var m = Math.round(s/60);
|
|
3494
|
+
if (m < 60) return 'il y a ' + m + 'min';
|
|
3495
|
+
var h = Math.round(m/60);
|
|
3496
|
+
return 'il y a ' + h + 'h';
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
function titleOf(s){
|
|
3500
|
+
return s.label || s.name || (s.command ? s.command.split(/\\s+/)[0].split('/').pop() : null) || s.id.slice(0,8);
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
function render(sessions, summaries){
|
|
3504
|
+
var grid = document.getElementById('grid');
|
|
3505
|
+
if (!sessions.length){
|
|
3506
|
+
grid.innerHTML = '<div id="empty"><div class="ico">😴</div><div>Aucune session d\\'agent</div></div>';
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3509
|
+
var html = '';
|
|
3510
|
+
for (var i=0;i<sessions.length;i++){
|
|
3511
|
+
var s = sessions[i];
|
|
3512
|
+
var sum = summaries[s.id] || {};
|
|
3513
|
+
var st = sum.state || 'en attente';
|
|
3514
|
+
var cls = stateClass(st);
|
|
3515
|
+
var summary = sum.summary || 'R\xE9sum\xE9 indisponible.';
|
|
3516
|
+
var muted = !sum.summary ? ' muted' : '';
|
|
3517
|
+
html += '<div class="card ' + cls + '">'
|
|
3518
|
+
+ '<div class="card-top">'
|
|
3519
|
+
+ '<span class="title">' + esc(titleOf(s)) + '</span>'
|
|
3520
|
+
+ '<span class="state ' + cls + '">' + esc(st) + '</span>'
|
|
3521
|
+
+ '</div>'
|
|
3522
|
+
+ '<div class="summary' + muted + '">' + esc(summary) + '</div>'
|
|
3523
|
+
+ '<div class="meta">'
|
|
3524
|
+
+ '<span>' + esc(s.kind || '') + '</span><span class="dot"></span>'
|
|
3525
|
+
+ '<span>' + esc(s.status || '') + '</span>'
|
|
3526
|
+
+ (s.lastOutputAt ? '<span class="dot"></span><span>' + esc(fmtAgo(s.lastOutputAt)) + '</span>' : '')
|
|
3527
|
+
+ '</div>'
|
|
3528
|
+
+ '</div>';
|
|
3529
|
+
}
|
|
3530
|
+
grid.innerHTML = html;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
function loadAndRender(){
|
|
3534
|
+
return callTool('session_list', {kind:'all'}).then(function(data){
|
|
3535
|
+
var all = data.sessions || [];
|
|
3536
|
+
var agents = all.filter(function(s){ return s.kind === 'agent-cli'; });
|
|
3537
|
+
// Render shells immediately, then fill summaries as they arrive.
|
|
3538
|
+
var summaries = {};
|
|
3539
|
+
render(agents, summaries);
|
|
3540
|
+
setStatus(agents.length + ' agent' + (agents.length===1?'':'s') + ' \xB7 ' + new Date().toLocaleTimeString('fr-FR'));
|
|
3541
|
+
return Promise.all(agents.map(function(s){
|
|
3542
|
+
return callTool('summarize_session', {sessionId: s.id}).then(function(sum){
|
|
3543
|
+
summaries[s.id] = sum;
|
|
3544
|
+
}).catch(function(){ /* leave shell */ });
|
|
3545
|
+
})).then(function(){ render(agents, summaries); });
|
|
3546
|
+
}).catch(function(e){ setStatus('Erreur : ' + e.message); });
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
function doPoll(){
|
|
3550
|
+
if (polling) return;
|
|
3551
|
+
polling = true;
|
|
3552
|
+
loadAndRender().then(function(){
|
|
3553
|
+
polling = false;
|
|
3554
|
+
pollTimer = setTimeout(doPoll, REFRESH_MS);
|
|
3555
|
+
}).catch(function(){
|
|
3556
|
+
polling = false;
|
|
3557
|
+
pollTimer = setTimeout(doPoll, REFRESH_MS);
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
function doRefresh(){ if (pollTimer) clearTimeout(pollTimer); return loadAndRender().then(function(){ pollTimer = setTimeout(doPoll, REFRESH_MS); }); }
|
|
3561
|
+
|
|
3562
|
+
initBridge().then(loadAndRender).then(function(){
|
|
3563
|
+
pollTimer = setTimeout(doPoll, REFRESH_MS);
|
|
3564
|
+
}).catch(function(e){
|
|
3565
|
+
setStatus('Bridge : ' + e.message);
|
|
3566
|
+
document.getElementById('grid').innerHTML = '<div id="empty"><div class="ico">⚠</div><div>\xC9chec connexion bridge : ' + esc(e.message) + '</div></div>';
|
|
3567
|
+
});
|
|
3568
|
+
</script>
|
|
3569
|
+
</body>
|
|
3570
|
+
</html>`;
|
|
3571
|
+
|
|
3572
|
+
// src/agents-overview/index.ts
|
|
3573
|
+
var agentsOverviewInputSchema = z.object({
|
|
3574
|
+
filter: z.enum(["running", "all"]).optional().describe("`running` = only alive agent sessions; `all` = running + recent (default).")
|
|
3575
|
+
});
|
|
3576
|
+
function makeAgentsOverviewApp(ops) {
|
|
3577
|
+
return {
|
|
3578
|
+
id: "agentproto_agents_overview",
|
|
3579
|
+
title: "Agents \u2014 vue claire",
|
|
3580
|
+
description: "Open the agents overview \u2014 a plain-language card per agent session with one human sentence (what it's doing / last said) and a coarse state (\xE0 traiter / au travail / en attente / termin\xE9). Polls live and asks the server to summarise each session.",
|
|
3581
|
+
inputSchema: agentsOverviewInputSchema,
|
|
3582
|
+
execute: async (input) => ({ sessions: ops.listSessions(input.filter) }),
|
|
3583
|
+
html: AGENTS_OVERVIEW_HTML
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
var agentsOverviewApp = defineApp({
|
|
3587
|
+
id: "@agentproto/agents-overview",
|
|
3588
|
+
name: "Agents \u2014 vue claire",
|
|
3589
|
+
description: "Open the agents overview \u2014 a plain-language card per agent session with one human sentence and a coarse state (\xE0 traiter / au travail / en attente / termin\xE9).",
|
|
3590
|
+
agents: [],
|
|
3591
|
+
ui: {
|
|
3592
|
+
html: AGENTS_OVERVIEW_HTML,
|
|
3593
|
+
title: "Agents \u2014 vue claire",
|
|
3594
|
+
tools: ["session_list", "summarize_session"]
|
|
3595
|
+
}
|
|
3596
|
+
});
|
|
3597
|
+
|
|
3598
|
+
// src/bureau-sessions/panel.ts
|
|
3599
|
+
var BUREAU_SESSIONS_HTML = `<!DOCTYPE html>
|
|
3600
|
+
<html lang="fr">
|
|
3601
|
+
<head>
|
|
3602
|
+
<meta charset="UTF-8">
|
|
3603
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3604
|
+
<title>bureau \u2014 sessions navigateur</title>
|
|
3605
|
+
<style>
|
|
3606
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
3607
|
+
:root{
|
|
3608
|
+
--bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;
|
|
3609
|
+
--text:#e6edf3;--text2:#8b949e;
|
|
3610
|
+
--green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;
|
|
3611
|
+
}
|
|
3612
|
+
*{box-sizing:border-box}
|
|
3613
|
+
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}
|
|
3614
|
+
#app{display:flex;flex-direction:column;height:100%}
|
|
3615
|
+
#hdr{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:var(--bg2);flex-shrink:0}
|
|
3616
|
+
#hdr h1{font-size:13px;font-weight:600}
|
|
3617
|
+
#hdr .sub{font-size:11px;color:var(--text2);margin-top:2px}
|
|
3618
|
+
#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:16px;line-height:1;padding:4px 6px;border-radius:6px}
|
|
3619
|
+
#refresh-btn:hover{color:var(--text);background:var(--bg3)}
|
|
3620
|
+
#list{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:10px}
|
|
3621
|
+
.row{background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:11px 14px;display:flex;align-items:center;gap:14px}
|
|
3622
|
+
.icon{font-size:20px;flex-shrink:0}
|
|
3623
|
+
.body{flex:1;min-width:0}
|
|
3624
|
+
.r1{display:flex;align-items:center;gap:8px}
|
|
3625
|
+
.adapter{font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
3626
|
+
.url{font-family:Menlo,Monaco,'Courier New',monospace;font-size:11.5px;color:var(--blue);margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
3627
|
+
.meta{font-size:10.5px;color:var(--text2);margin-top:3px;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
|
3628
|
+
.dot{width:5px;height:5px;border-radius:50%;background:var(--text2);display:inline-block}
|
|
3629
|
+
.badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:11px;white-space:nowrap}
|
|
3630
|
+
.br{background:rgba(63,185,80,.16);color:var(--green)}
|
|
3631
|
+
.bs{background:rgba(88,166,255,.16);color:var(--blue)}
|
|
3632
|
+
.bk{background:rgba(248,81,73,.14);color:var(--red)}
|
|
3633
|
+
.be{background:rgba(139,148,158,.14);color:var(--text2)}
|
|
3634
|
+
.berr{background:rgba(248,81,73,.22);color:var(--red)}
|
|
3635
|
+
.port{font-family:Menlo,Monaco,'Courier New',monospace}
|
|
3636
|
+
#empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;padding:48px;text-align:center}
|
|
3637
|
+
#empty .ico{font-size:30px}
|
|
3638
|
+
#statusbar{padding:5px 16px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}
|
|
3639
|
+
</style>
|
|
3640
|
+
</head>
|
|
3641
|
+
<body>
|
|
3642
|
+
<div id="app">
|
|
3643
|
+
<div id="hdr">
|
|
3644
|
+
<div>
|
|
3645
|
+
<h1>Bureau \u2014 sessions navigateur</h1>
|
|
3646
|
+
<div class="sub">services navigateur actifs \xB7 URL / port / statut</div>
|
|
3647
|
+
</div>
|
|
3648
|
+
<button id="refresh-btn" title="Rafra\xEEchir" onclick="doRefresh()">↻</button>
|
|
3649
|
+
</div>
|
|
3650
|
+
<div id="list"><div id="empty"><div class="ico">🌐</div><div>Connexion au bridge…</div></div></div>
|
|
3651
|
+
<div id="statusbar">Connexion…</div>
|
|
3652
|
+
</div>
|
|
3653
|
+
<script>
|
|
3654
|
+
${panelBridgeScript("agentproto-bureau-sessions")}
|
|
3655
|
+
|
|
3656
|
+
// \u2500\u2500 State \u2500\u2500
|
|
3657
|
+
var REFRESH_MS = 5000;
|
|
3658
|
+
var pollTimer = null, polling = false;
|
|
3659
|
+
function esc(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
3660
|
+
function setStatus(m){ document.getElementById('statusbar').textContent = m; }
|
|
3661
|
+
|
|
3662
|
+
function badgeClass(st){
|
|
3663
|
+
if (st === 'running') return 'br';
|
|
3664
|
+
if (st === 'starting') return 'bs';
|
|
3665
|
+
if (st === 'killed') return 'bk';
|
|
3666
|
+
if (st === 'error') return 'berr';
|
|
3667
|
+
return 'be';
|
|
3668
|
+
}
|
|
3669
|
+
function fmtUptime(iso, end){
|
|
3670
|
+
if (!iso) return '';
|
|
3671
|
+
var start = Date.parse(iso);
|
|
3672
|
+
if (isNaN(start)) return '';
|
|
3673
|
+
var ref = end ? Date.parse(end) : Date.now();
|
|
3674
|
+
var s = Math.max(0, Math.round((ref - start) / 1000));
|
|
3675
|
+
if (s < 60) return s + 's';
|
|
3676
|
+
var m = Math.floor(s/60), rs = s%60;
|
|
3677
|
+
if (m < 60) return m + 'm ' + rs + 's';
|
|
3678
|
+
var h = Math.floor(m/60), rm = m%60;
|
|
3679
|
+
return h + 'h ' + rm + 'm';
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
function render(rows){
|
|
3683
|
+
var list = document.getElementById('list');
|
|
3684
|
+
if (!rows.length){
|
|
3685
|
+
list.innerHTML = '<div id="empty"><div class="ico">🌐</div><div>Aucune session navigateur</div></div>';
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
var html = '';
|
|
3689
|
+
for (var i=0;i<rows.length;i++){
|
|
3690
|
+
var s = rows[i];
|
|
3691
|
+
var bc = badgeClass(s.status);
|
|
3692
|
+
var url = s.browserBaseUrl || (s.browserPort ? ('http://127.0.0.1:' + s.browserPort) : '');
|
|
3693
|
+
var adapter = s.browserAdapterId || s.label || s.name || 'navigateur';
|
|
3694
|
+
var up = fmtUptime(s.startedAt, s.endedAt);
|
|
3695
|
+
html += '<div class="row">'
|
|
3696
|
+
+ '<div class="icon">🌐</div>'
|
|
3697
|
+
+ '<div class="body">'
|
|
3698
|
+
+ '<div class="r1"><span class="adapter">' + esc(adapter) + '</span>'
|
|
3699
|
+
+ '<span class="badge ' + bc + '">' + esc(s.status || '') + '</span></div>'
|
|
3700
|
+
+ (url ? '<div class="url">' + esc(url) + '</div>' : '')
|
|
3701
|
+
+ '<div class="meta">'
|
|
3702
|
+
+ (s.browserPort ? '<span>port <span class="port">' + esc(s.browserPort) + '</span></span><span class="dot"></span>' : '')
|
|
3703
|
+
+ '<span>' + (up ? 'uptime ' + esc(up) : '') + '</span>'
|
|
3704
|
+
+ '</div>'
|
|
3705
|
+
+ '</div>'
|
|
3706
|
+
+ '</div>';
|
|
3707
|
+
}
|
|
3708
|
+
list.innerHTML = html;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
function loadAndRender(){
|
|
3712
|
+
return callTool('session_list', {kind:'all'}).then(function(data){
|
|
3713
|
+
var all = data.sessions || [];
|
|
3714
|
+
var browsers = all.filter(function(s){ return s.kind === 'browser'; });
|
|
3715
|
+
render(browsers);
|
|
3716
|
+
setStatus(browsers.length + ' session' + (browsers.length===1?'':'s') + ' \xB7 ' + new Date().toLocaleTimeString('fr-FR'));
|
|
3717
|
+
}).catch(function(e){ setStatus('Erreur : ' + e.message); });
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
function doPoll(){
|
|
3721
|
+
if (polling) return;
|
|
3722
|
+
polling = true;
|
|
3723
|
+
loadAndRender().then(function(){
|
|
3724
|
+
polling = false;
|
|
3725
|
+
pollTimer = setTimeout(doPoll, REFRESH_MS);
|
|
3726
|
+
}).catch(function(){
|
|
3727
|
+
polling = false;
|
|
3728
|
+
pollTimer = setTimeout(doPoll, REFRESH_MS);
|
|
3729
|
+
});
|
|
3730
|
+
}
|
|
3731
|
+
function doRefresh(){ if (pollTimer) clearTimeout(pollTimer); return loadAndRender().then(function(){ pollTimer = setTimeout(doPoll, REFRESH_MS); }); }
|
|
3732
|
+
|
|
3733
|
+
initBridge().then(loadAndRender).then(function(){
|
|
3734
|
+
pollTimer = setTimeout(doPoll, REFRESH_MS);
|
|
3735
|
+
}).catch(function(e){
|
|
3736
|
+
setStatus('Bridge : ' + e.message);
|
|
3737
|
+
document.getElementById('list').innerHTML = '<div id="empty"><div class="ico">⚠</div><div>\xC9chec connexion bridge : ' + esc(e.message) + '</div></div>';
|
|
3738
|
+
});
|
|
3739
|
+
</script>
|
|
3740
|
+
</body>
|
|
3741
|
+
</html>`;
|
|
3742
|
+
|
|
3743
|
+
// src/bureau-sessions/index.ts
|
|
3744
|
+
var bureauSessionsInputSchema = z.object({
|
|
3745
|
+
filter: z.enum(["running", "all"]).optional().describe("`running` = only alive browser sessions; `all` = running + recent (default).")
|
|
3746
|
+
});
|
|
3747
|
+
function makeBureauSessionsApp(ops) {
|
|
3748
|
+
return {
|
|
3749
|
+
id: "agentproto_bureau_sessions",
|
|
3750
|
+
title: "Bureau \u2014 sessions navigateur",
|
|
3751
|
+
description: "Open the browser-sessions panel \u2014 one row per browser service (adapter, base URL, port, status, uptime). Polls live every ~5 s.",
|
|
3752
|
+
inputSchema: bureauSessionsInputSchema,
|
|
3753
|
+
execute: async (input) => ({
|
|
3754
|
+
sessions: ops.listSessions(input.filter).filter((s) => s.kind === "browser")
|
|
3755
|
+
}),
|
|
3756
|
+
html: BUREAU_SESSIONS_HTML
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
var bureauSessionsApp = defineApp({
|
|
3760
|
+
id: "@agentproto/bureau-sessions",
|
|
3761
|
+
name: "Bureau \u2014 sessions navigateur",
|
|
3762
|
+
description: "Open the browser-sessions panel \u2014 one row per browser service (adapter, base URL, port, status, uptime).",
|
|
3763
|
+
agents: [],
|
|
3764
|
+
ui: {
|
|
3765
|
+
html: BUREAU_SESSIONS_HTML,
|
|
3766
|
+
title: "Bureau \u2014 sessions navigateur",
|
|
3767
|
+
tools: ["session_list"]
|
|
3768
|
+
}
|
|
3769
|
+
});
|
|
3770
|
+
|
|
3771
|
+
// src/session-story/panel.ts
|
|
3772
|
+
var SESSION_STORY_PANEL_HTML = `<!doctype html>
|
|
3773
|
+
<html lang="fr">
|
|
3774
|
+
<head>
|
|
3775
|
+
<meta charset="utf-8" />
|
|
3776
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3777
|
+
<title>session story</title>
|
|
3778
|
+
<style>
|
|
3779
|
+
:root {
|
|
3780
|
+
color-scheme: light;
|
|
3781
|
+
--bg:#faf8f5; --panel:#fffdfa; --line:#ece5da; --line-soft:#f2ede3;
|
|
3782
|
+
--ink:#241f1a; --ink-mute:#7d7060; --ink-faint:#a9997f; --ink-ghost:#b3a893;
|
|
3783
|
+
--accent:#0d7a4f; --accent-soft:#e8f4ec;
|
|
3784
|
+
--gold:#a6701b; --gold-soft:#fff2dc;
|
|
3785
|
+
--blue:#1d4e80; --blue-soft:#e9f1fb;
|
|
3786
|
+
--violet:#5b3fa6; --violet-soft:#ede9ff;
|
|
3787
|
+
--sel:#fbeccd; --red:#b3261e; --red-soft:#fbeae8;
|
|
3788
|
+
}
|
|
3789
|
+
* { box-sizing:border-box; }
|
|
3790
|
+
html,body { height:100%; }
|
|
3791
|
+
body { margin:0; font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif; background:var(--bg); color:var(--ink); -webkit-font-smoothing:antialiased; }
|
|
3792
|
+
.app { height:100vh; display:flex; flex-direction:column; }
|
|
3793
|
+
.hidden { display:none !important; }
|
|
3794
|
+
|
|
3795
|
+
/* \u2500\u2500 picker screen \u2500\u2500 */
|
|
3796
|
+
#pickerScreen { height:100vh; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; padding:24px; }
|
|
3797
|
+
#pickerScreen h1 { font-size:15px; font-weight:700; }
|
|
3798
|
+
#pickerList { width:min(520px,90vw); max-height:60vh; overflow-y:auto; border:1px solid var(--line); border-radius:12px; background:var(--panel); }
|
|
3799
|
+
.pk-item { padding:10px 14px; border-bottom:1px solid var(--line-soft); cursor:pointer; display:flex; align-items:center; gap:10px; }
|
|
3800
|
+
.pk-item:last-child { border-bottom:none; }
|
|
3801
|
+
.pk-item:hover { background:var(--line-soft); }
|
|
3802
|
+
.pk-name { flex:1; min-width:0; font-size:13px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
3803
|
+
.pk-meta { flex:none; font-size:10.5px; color:var(--ink-faint); font-weight:600; }
|
|
3804
|
+
.pk-empty { padding:24px; text-align:center; color:var(--ink-mute); font-size:12.5px; }
|
|
3805
|
+
.badge { display:inline-block; padding:1px 8px; border-radius:999px; font-size:10px; font-weight:700; }
|
|
3806
|
+
.badge.running { background:var(--accent-soft); color:var(--accent); }
|
|
3807
|
+
.badge.starting { background:var(--blue-soft); color:var(--blue); }
|
|
3808
|
+
.badge.exited { background:var(--line-soft); color:var(--ink-faint); }
|
|
3809
|
+
.badge.killed, .badge.error { background:var(--red-soft); color:var(--red); }
|
|
3810
|
+
|
|
3811
|
+
/* \u2500\u2500 big picture : mission + plan de sous-t\xE2ches \u2500\u2500 */
|
|
3812
|
+
.hero { flex:none; padding:13px 20px 0; border-bottom:1px solid var(--line); background:var(--panel); }
|
|
3813
|
+
.hero-top { display:flex; align-items:center; gap:13px; }
|
|
3814
|
+
.pulse { width:10px; height:10px; border-radius:50%; background:var(--accent); flex:none;
|
|
3815
|
+
box-shadow:0 0 0 0 rgba(13,122,79,.35); animation:pulse 2.4s infinite; }
|
|
3816
|
+
.pulse.off { background:var(--ink-ghost); animation:none; }
|
|
3817
|
+
@keyframes pulse { 70% { box-shadow:0 0 0 9px rgba(13,122,79,0); } 100% { box-shadow:0 0 0 0 rgba(13,122,79,0); } }
|
|
3818
|
+
.who { min-width:0; flex:1; }
|
|
3819
|
+
.who .h1 { font-size:14.5px; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
3820
|
+
.who .h2 { font-size:12px; color:var(--ink-mute); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
3821
|
+
.modewrap { display:flex; border:1px solid var(--line); border-radius:9px; overflow:hidden; flex:none; }
|
|
3822
|
+
.modewrap button { border:none; background:var(--panel); color:var(--ink-mute); font-size:11px; font-weight:700; padding:6px 11px; cursor:pointer; }
|
|
3823
|
+
.modewrap button.on { background:var(--ink); color:#fdf9f2; }
|
|
3824
|
+
button.sim, a.sim { border:1px solid var(--line); background:var(--panel); color:var(--ink-mute); font-weight:700;
|
|
3825
|
+
font-size:11.5px; border-radius:8px; padding:6px 12px; cursor:pointer; flex:none; }
|
|
3826
|
+
button.sim.on { background:var(--ink); border-color:var(--ink); color:#fdf9f2; }
|
|
3827
|
+
a.sim { display:inline-flex; align-items:center; text-decoration:none; }
|
|
3828
|
+
a.sim:hover { border-color:var(--ink-ghost); color:var(--ink); }
|
|
3829
|
+
|
|
3830
|
+
/* plan strip : les sous-t\xE2ches, l'avancement d'un coup d'\u0153il */
|
|
3831
|
+
.plan { display:flex; gap:6px; overflow-x:auto; padding:11px 0 12px; scrollbar-width:none; }
|
|
3832
|
+
.plan::-webkit-scrollbar { display:none; }
|
|
3833
|
+
.pt { flex:none; display:flex; align-items:center; gap:6px; font-size:11.5px; font-weight:700; padding:5px 11px;
|
|
3834
|
+
border-radius:999px; border:1px solid var(--line); background:var(--bg); color:var(--ink-mute); cursor:pointer; white-space:nowrap; }
|
|
3835
|
+
.pt:hover { border-color:var(--ink-ghost); }
|
|
3836
|
+
.pt .st { font-size:10px; }
|
|
3837
|
+
.pt.done { color:var(--accent); background:var(--accent-soft); border-color:transparent; }
|
|
3838
|
+
.pt.cur { color:var(--gold); background:var(--gold-soft); border-color:transparent; }
|
|
3839
|
+
.pt.cur .st { animation:blink 1.6s infinite; }
|
|
3840
|
+
@keyframes blink { 50% { opacity:.35; } }
|
|
3841
|
+
|
|
3842
|
+
/* \u2500\u2500 corps \u2500\u2500 */
|
|
3843
|
+
.body { flex:1; display:flex; min-height:0; }
|
|
3844
|
+
.feedcol { flex:1; min-width:320px; display:flex; flex-direction:column; }
|
|
3845
|
+
.feed { flex:1; overflow-y:auto; padding:4px 14px 10px; display:flex; flex-direction:column; scroll-behavior:smooth; }
|
|
3846
|
+
.fspacer { flex:1; }
|
|
3847
|
+
|
|
3848
|
+
/* chapitres */
|
|
3849
|
+
.chap { flex:none; position:sticky; top:0; z-index:5; margin:8px -4px 2px; padding:7px 12px; display:flex; align-items:center; gap:9px;
|
|
3850
|
+
background:color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter:blur(6px);
|
|
3851
|
+
border-radius:9px; cursor:pointer; font-size:11.5px; font-weight:800; letter-spacing:.03em; color:var(--ink-mute); }
|
|
3852
|
+
.chap:hover { color:var(--ink); }
|
|
3853
|
+
.chap .cst { flex:none; width:17px; height:17px; border-radius:50%; display:grid; place-items:center; font-size:9.5px; font-weight:900; }
|
|
3854
|
+
.chap.done .cst { background:var(--accent-soft); color:var(--accent); }
|
|
3855
|
+
.chap.cur .cst { background:var(--gold-soft); color:var(--gold); }
|
|
3856
|
+
.chap .cnum { color:var(--ink-ghost); font-weight:700; }
|
|
3857
|
+
.chap .csum { flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
3858
|
+
.chap .cmeta { flex:none; font-size:10.5px; color:var(--ink-ghost); font-weight:600; }
|
|
3859
|
+
.chap .cchev { flex:none; color:var(--ink-ghost); transition:transform .15s; }
|
|
3860
|
+
.chap.open .cchev { transform:rotate(90deg); }
|
|
3861
|
+
|
|
3862
|
+
.row { flex:none; min-height:44px; margin:1px 0 1px 10px; padding:5px 12px 5px 10px; display:flex; align-items:center; gap:11px;
|
|
3863
|
+
cursor:pointer; border-radius:10px; border-left:3px solid transparent; transition:background .12s; }
|
|
3864
|
+
.row:hover { background:var(--line-soft); }
|
|
3865
|
+
.row[aria-selected="true"] { background:var(--sel); border-left-color:var(--gold); }
|
|
3866
|
+
.row .ico { width:24px; height:24px; border-radius:8px; flex:none; display:grid; place-items:center; font-size:11.5px; font-weight:800; }
|
|
3867
|
+
.ico.k-text { background:var(--blue-soft); color:var(--blue); }
|
|
3868
|
+
.ico.k-edit { background:var(--gold-soft); color:var(--gold); }
|
|
3869
|
+
.ico.k-bash { background:var(--accent-soft); color:var(--accent); }
|
|
3870
|
+
.ico.k-read { background:var(--violet-soft); color:var(--violet); }
|
|
3871
|
+
.ico.k-user { background:var(--ink); color:#fdf9f2; }
|
|
3872
|
+
.row .mid { flex:1; min-width:0; }
|
|
3873
|
+
.row .sum { display:block; font-size:13.5px; line-height:1.35; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
3874
|
+
.row .raw1 { display:block; font-size:10.5px; color:var(--ink-faint); font-family:ui-monospace,Menlo,monospace;
|
|
3875
|
+
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-top:1px; }
|
|
3876
|
+
body:not(.tech) .row .raw1 { display:none; }
|
|
3877
|
+
.row .route { display:inline-block; font-size:10px; font-weight:800; padding:1px 8px; border-radius:999px; margin-top:2px; }
|
|
3878
|
+
.route.cont { background:var(--blue-soft); color:var(--blue); }
|
|
3879
|
+
.route.newt { background:var(--gold-soft); color:var(--gold); }
|
|
3880
|
+
.row .cnt { flex:none; font-size:10px; font-weight:800; color:var(--gold); background:var(--gold-soft); padding:2px 7px; border-radius:999px; }
|
|
3881
|
+
.row .ts { flex:none; font-size:10.5px; color:var(--ink-ghost); font-variant-numeric:tabular-nums; }
|
|
3882
|
+
@keyframes slidein { from { opacity:0; transform:translateY(6px); } }
|
|
3883
|
+
.row.new { animation:slidein .25s ease-out; }
|
|
3884
|
+
|
|
3885
|
+
/* \u2500\u2500 panneau ancr\xE9 \u2500\u2500 */
|
|
3886
|
+
.panel { flex:none; width:0; overflow:hidden; border-left:1px solid transparent; background:var(--panel);
|
|
3887
|
+
display:flex; flex-direction:column; transition:width .22s ease, border-color .22s; }
|
|
3888
|
+
.panel.open { width:min(430px,46vw); border-left-color:var(--line); }
|
|
3889
|
+
.panel-inner { width:min(430px,46vw); flex:1; display:flex; flex-direction:column; min-height:0; }
|
|
3890
|
+
.phead { flex:none; padding:14px 16px 12px; border-bottom:1px solid var(--line); display:flex; align-items:flex-start; gap:11px; }
|
|
3891
|
+
.phead .ico { width:28px; height:28px; font-size:13px; border-radius:9px; }
|
|
3892
|
+
.phead .tt { min-width:0; flex:1; }
|
|
3893
|
+
.phead .t { font-size:14px; font-weight:700; line-height:1.4; }
|
|
3894
|
+
.phead .s { font-size:11px; color:var(--ink-faint); margin-top:3px; font-variant-numeric:tabular-nums; }
|
|
3895
|
+
.pnav { display:flex; gap:4px; flex:none; }
|
|
3896
|
+
.pnav button { width:26px; height:26px; border:1px solid var(--line); background:var(--panel); border-radius:8px;
|
|
3897
|
+
color:var(--ink-mute); font-size:12px; cursor:pointer; display:grid; place-items:center; }
|
|
3898
|
+
.pnav button:disabled { opacity:.3; cursor:default; }
|
|
3899
|
+
.pbody { flex:1; overflow-y:auto; padding:16px; display:flex; flex-direction:column; gap:14px; }
|
|
3900
|
+
.plain { font-size:14px; line-height:1.7; }
|
|
3901
|
+
.plain .why { margin-top:8px; font-size:12.5px; color:var(--ink-mute); line-height:1.6; }
|
|
3902
|
+
.facts { display:flex; flex-wrap:wrap; gap:6px; }
|
|
3903
|
+
.fact { font-size:11px; font-weight:700; background:var(--bg); border:1px solid var(--line); color:var(--ink-mute); padding:4px 10px; border-radius:999px; }
|
|
3904
|
+
.fact.ok { background:var(--accent-soft); border-color:transparent; color:var(--accent); }
|
|
3905
|
+
details.techbox { border:1px solid var(--line); border-radius:12px; background:var(--bg); overflow:hidden; }
|
|
3906
|
+
details.techbox summary { list-style:none; cursor:pointer; padding:10px 14px; font-size:11.5px; font-weight:800;
|
|
3907
|
+
letter-spacing:.04em; text-transform:uppercase; color:var(--ink-faint); display:flex; align-items:center; gap:8px; }
|
|
3908
|
+
details.techbox summary::-webkit-details-marker { display:none; }
|
|
3909
|
+
details.techbox summary::after { content:"\u25B8"; margin-left:auto; transition:transform .15s; }
|
|
3910
|
+
details.techbox[open] summary::after { transform:rotate(90deg); }
|
|
3911
|
+
.techlist { padding:0 12px 12px; display:flex; flex-direction:column; gap:8px; }
|
|
3912
|
+
.titem { border:1px solid var(--line); border-radius:10px; background:var(--panel); overflow:hidden; }
|
|
3913
|
+
.titem .th { padding:8px 12px; font-size:11.5px; font-weight:700; color:var(--ink-mute); display:flex; align-items:center; gap:8px; }
|
|
3914
|
+
.titem .th .copy { margin-left:auto; border:none; background:none; color:var(--ink-ghost); font-size:11px; cursor:pointer; padding:2px 4px; border-radius:5px; }
|
|
3915
|
+
.titem .th .copy:hover { color:var(--ink); background:var(--line-soft); }
|
|
3916
|
+
.titem pre { margin:0; border-top:1px solid var(--line-soft); font-family:ui-monospace,Menlo,monospace; font-size:11.5px;
|
|
3917
|
+
line-height:1.55; color:#4a4236; padding:9px 12px; overflow:auto; max-height:240px; white-space:pre-wrap; word-break:break-word; }
|
|
3918
|
+
.d-text { font-size:13.5px; line-height:1.7; }
|
|
3919
|
+
.d-text p { margin:0 0 8px; }
|
|
3920
|
+
.d-text p:last-child { margin-bottom:0; }
|
|
3921
|
+
.d-text h1, .d-text h2, .d-text h3, .d-text h4, .d-text h5, .d-text h6 { margin:12px 0 6px; line-height:1.3; }
|
|
3922
|
+
.d-text h1:first-child, .d-text h2:first-child, .d-text h3:first-child { margin-top:0; }
|
|
3923
|
+
.d-text ul, .d-text ol { margin:0 0 8px; padding-left:20px; }
|
|
3924
|
+
.d-text code { font-family:ui-monospace,Menlo,monospace; font-size:12.5px; background:var(--bg); border-radius:4px; padding:1px 5px; }
|
|
3925
|
+
.d-text pre { margin:0 0 8px; background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:9px 12px; overflow:auto; }
|
|
3926
|
+
.d-text pre code { background:none; border-radius:0; padding:0; }
|
|
3927
|
+
.d-text table { border-collapse:collapse; margin:0 0 8px; font-size:12.5px; }
|
|
3928
|
+
.d-text th, .d-text td { border:1px solid var(--line); padding:4px 8px; text-align:left; }
|
|
3929
|
+
.d-text a { color:var(--blue); }
|
|
3930
|
+
.pfoot { flex:none; border-top:1px solid var(--line); padding:9px 16px; font-size:11px; color:var(--ink-ghost); display:flex; gap:10px; }
|
|
3931
|
+
.kbd { font-family:ui-monospace,Menlo,monospace; font-size:10px; border:1px solid var(--line); border-bottom-width:2px;
|
|
3932
|
+
border-radius:5px; padding:1px 5px; background:var(--panel); color:var(--ink-mute); }
|
|
3933
|
+
|
|
3934
|
+
/* \u2500\u2500 bo\xEEte d'envoi + routage IA \u2500\u2500 */
|
|
3935
|
+
.composer { flex:none; border-top:1px solid var(--line); background:var(--panel); padding:10px 14px; }
|
|
3936
|
+
.composer .cbar { display:flex; gap:8px; }
|
|
3937
|
+
.composer textarea { flex:1; resize:none; border:1px solid var(--line); border-radius:10px; padding:9px 12px; font:inherit; font-size:13px; max-height:110px; background:var(--bg); }
|
|
3938
|
+
.composer textarea:focus { outline:2px solid #241f1a22; }
|
|
3939
|
+
.composer textarea:disabled { opacity:.5; cursor:not-allowed; }
|
|
3940
|
+
.composer button { border:none; border-radius:10px; padding:0 16px; background:var(--ink); color:#fdf9f2; font-weight:700; font-size:13px; cursor:pointer; }
|
|
3941
|
+
.composer button:disabled { opacity:.4; cursor:not-allowed; }
|
|
3942
|
+
.composer .routing { font-size:11px; color:var(--ink-faint); padding:6px 2px 0; min-height:22px; }
|
|
3943
|
+
.composer .routing .r-cont { color:var(--blue); font-weight:700; }
|
|
3944
|
+
.composer .routing .r-newt { color:var(--gold); font-weight:700; }
|
|
3945
|
+
#statusbar { flex:none; padding:4px 20px; font-size:10.5px; color:var(--ink-ghost); border-top:1px solid var(--line-soft); }
|
|
3946
|
+
</style>
|
|
3947
|
+
</head>
|
|
3948
|
+
<body>
|
|
3949
|
+
<div id="pickerScreen">
|
|
3950
|
+
<h1>Choisis une session</h1>
|
|
3951
|
+
<div id="pickerList"><div class="pk-empty">Connexion…</div></div>
|
|
3952
|
+
</div>
|
|
3953
|
+
|
|
3954
|
+
<div class="app hidden" id="storyScreen">
|
|
3955
|
+
<div class="hero">
|
|
3956
|
+
<div class="hero-top">
|
|
3957
|
+
<span class="pulse" id="pulse"></span>
|
|
3958
|
+
<div class="who">
|
|
3959
|
+
<div class="h1" id="heroTitle"></div>
|
|
3960
|
+
<div class="h2" id="heroSub"></div>
|
|
3961
|
+
</div>
|
|
3962
|
+
<div class="modewrap"><button id="modeSimple" class="on" type="button">Simple</button><button id="modeTech" type="button">Tech</button></div>
|
|
3963
|
+
<a class="sim" id="fullPanelLink" href="#" target="_blank" rel="noopener" title="Ouvrir le panneau complet (Terminal/Chat/JSON/TTY)">↗ panneau complet</a>
|
|
3964
|
+
<button class="sim" id="switchBtn" type="button">⇆ changer</button>
|
|
3965
|
+
</div>
|
|
3966
|
+
<div class="plan" id="plan"></div>
|
|
3967
|
+
</div>
|
|
3968
|
+
|
|
3969
|
+
<div class="body">
|
|
3970
|
+
<div class="feedcol">
|
|
3971
|
+
<div class="feed" id="feed"><div class="fspacer"></div><div id="rows"></div></div>
|
|
3972
|
+
<div class="composer">
|
|
3973
|
+
<div class="cbar">
|
|
3974
|
+
<textarea id="msgBox" rows="1" placeholder="\xC9cris \xE0 l'agent\u2026 (la surcouche classe ton message : suite de la sous-t\xE2che ou nouvelle sous-t\xE2che)"></textarea>
|
|
3975
|
+
<button id="sendBtn" type="button">Envoyer</button>
|
|
3976
|
+
</div>
|
|
3977
|
+
<div class="routing" id="routing"></div>
|
|
3978
|
+
</div>
|
|
3979
|
+
</div>
|
|
3980
|
+
|
|
3981
|
+
<aside class="panel" id="panel" aria-label="D\xE9tail de l'\xE9tape">
|
|
3982
|
+
<div class="panel-inner">
|
|
3983
|
+
<div class="phead">
|
|
3984
|
+
<span class="ico" id="pIco"></span>
|
|
3985
|
+
<div class="tt"><div class="t" id="pTitle"></div><div class="s" id="pSub"></div></div>
|
|
3986
|
+
<div class="pnav"><button id="pPrev" type="button">\u2191</button><button id="pNext" type="button">\u2193</button><button id="pClose" type="button">\u2715</button></div>
|
|
3987
|
+
</div>
|
|
3988
|
+
<div class="pbody" id="pBody"></div>
|
|
3989
|
+
<div class="pfoot"><span class="kbd">\u2191</span><span class="kbd">\u2193</span> naviguer \xB7 <span class="kbd">Esc</span> fermer</div>
|
|
3990
|
+
</div>
|
|
3991
|
+
</aside>
|
|
3992
|
+
</div>
|
|
3993
|
+
<div id="statusbar"></div>
|
|
3994
|
+
</div>
|
|
3995
|
+
|
|
3996
|
+
<script>
|
|
3997
|
+
var $=function(id){ return document.getElementById(id); };
|
|
3998
|
+
var esc=function(s){ return String(s==null?"":s).replace(/[&<>]/g,function(c){ return {"&":"&","<":"<",">":">"}[c]; }); };
|
|
3999
|
+
|
|
4000
|
+
// ============================================================
|
|
4001
|
+
// renderMd \u2014 vanilla-JS port of markdown-lite.ts. Kept
|
|
4002
|
+
// function-for-function identical so the two are easy to diff (same
|
|
4003
|
+
// self-contained-panel constraint as buildStoryJs below): headers,
|
|
4004
|
+
// bold/italic, inline/fenced code, bullet/numbered lists, pipe tables and
|
|
4005
|
+
// links, with every raw text run HTML-escaped before any generated tag
|
|
4006
|
+
// wraps it.
|
|
4007
|
+
// ============================================================
|
|
4008
|
+
function escHtmlMd(s){ return String(s).replace(/[&<>"]/g,function(c){ if(c==='&') return '&'; if(c==='<') return '<'; if(c==='>') return '>'; return '"'; }); }
|
|
4009
|
+
function renderInlineMd(text){
|
|
4010
|
+
var out=escHtmlMd(text);
|
|
4011
|
+
out=out.replace(/\`([^\`]+)\`/g,function(_m,code){ return '<code>'+code+'</code>'; });
|
|
4012
|
+
out=out.replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g,function(_m,label,url){ return '<a href="'+url+'" target="_blank" rel="noopener noreferrer">'+label+'</a>'; });
|
|
4013
|
+
out=out.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
|
4014
|
+
out=out.replace(/(^|[^*])\\*([^*]+)\\*(?!\\*)/g,'$1<em>$2</em>');
|
|
4015
|
+
return out;
|
|
4016
|
+
}
|
|
4017
|
+
function isTableSepMd(line){ return /^\\s*\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)+\\|?\\s*$/.test(line); }
|
|
4018
|
+
function splitRowMd(line){ return line.trim().replace(/^\\|/,'').replace(/\\|$/,'').split('|').map(function(c){ return c.trim(); }); }
|
|
4019
|
+
function renderMd(md){
|
|
4020
|
+
var lines=String(md==null?'':md).replace(/\\r\\n?/g,'\\n').split('\\n');
|
|
4021
|
+
var out=[], para=[], list=null;
|
|
4022
|
+
function flushPara(){ if(para.length){ out.push('<p>'+para.map(renderInlineMd).join('<br>')+'</p>'); para=[]; } }
|
|
4023
|
+
function flushList(){ if(list){ var tag=list.ordered?'ol':'ul'; out.push('<'+tag+'>'+list.items.map(function(i){ return '<li>'+renderInlineMd(i)+'</li>'; }).join('')+'</'+tag+'>'); list=null; } }
|
|
4024
|
+
function flushAll(){ flushPara(); flushList(); }
|
|
4025
|
+
var i=0;
|
|
4026
|
+
while(i<lines.length){
|
|
4027
|
+
var line=lines[i];
|
|
4028
|
+
if(/^\\s*\`\`\`/.test(line)){
|
|
4029
|
+
flushAll();
|
|
4030
|
+
var code=[]; i+=1;
|
|
4031
|
+
while(i<lines.length && !/^\\s*\`\`\`/.test(lines[i])){ code.push(lines[i]); i+=1; }
|
|
4032
|
+
i+=1;
|
|
4033
|
+
out.push('<pre><code>'+escHtmlMd(code.join('\\n'))+'</code></pre>');
|
|
4034
|
+
continue;
|
|
4035
|
+
}
|
|
4036
|
+
var header=line.match(/^(#{1,6})\\s+(.*)$/);
|
|
4037
|
+
if(header){
|
|
4038
|
+
flushAll();
|
|
4039
|
+
var level=header[1].length;
|
|
4040
|
+
out.push('<h'+level+'>'+renderInlineMd(header[2].trim())+'</h'+level+'>');
|
|
4041
|
+
i+=1;
|
|
4042
|
+
continue;
|
|
4043
|
+
}
|
|
4044
|
+
if(/^\\s*\\|/.test(line) && i+1<lines.length && isTableSepMd(lines[i+1])){
|
|
4045
|
+
flushAll();
|
|
4046
|
+
var headCells=splitRowMd(line);
|
|
4047
|
+
i+=2;
|
|
4048
|
+
var bodyRows=[];
|
|
4049
|
+
while(i<lines.length && /^\\s*\\|/.test(lines[i])){ bodyRows.push(splitRowMd(lines[i])); i+=1; }
|
|
4050
|
+
out.push('<table><thead><tr>'+headCells.map(function(c){ return '<th>'+renderInlineMd(c)+'</th>'; }).join('')+'</tr></thead><tbody>'
|
|
4051
|
+
+bodyRows.map(function(r){ return '<tr>'+r.map(function(c){ return '<td>'+renderInlineMd(c)+'</td>'; }).join('')+'</tr>'; }).join('')+'</tbody></table>');
|
|
4052
|
+
continue;
|
|
4053
|
+
}
|
|
4054
|
+
var bullet=line.match(/^\\s*[-*+]\\s+(.*)$/);
|
|
4055
|
+
var numbered=line.match(/^\\s*\\d+\\.\\s+(.*)$/);
|
|
4056
|
+
if(bullet || numbered){
|
|
4057
|
+
flushPara();
|
|
4058
|
+
var ordered=!!numbered;
|
|
4059
|
+
var item=(bullet||numbered)[1];
|
|
4060
|
+
if(!list || list.ordered!==ordered){ flushList(); list={ordered:ordered,items:[]}; }
|
|
4061
|
+
list.items.push(item);
|
|
4062
|
+
i+=1;
|
|
4063
|
+
continue;
|
|
4064
|
+
}
|
|
4065
|
+
if(line.trim()===''){ flushAll(); i+=1; continue; }
|
|
4066
|
+
flushList();
|
|
4067
|
+
para.push(line);
|
|
4068
|
+
i+=1;
|
|
4069
|
+
}
|
|
4070
|
+
flushAll();
|
|
4071
|
+
return out.join('');
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
${panelBridgeScript("agentproto-session-story-panel")}
|
|
4075
|
+
// Best-effort: some hosts forward the triggering tool call's arguments as
|
|
4076
|
+
// a notification so the panel can auto-open the right session. Purely
|
|
4077
|
+
// additive \u2014 the session picker is the reliable path when this never
|
|
4078
|
+
// arrives.
|
|
4079
|
+
var pendingSessionId=null;
|
|
4080
|
+
onHostNotification(function(method, params){
|
|
4081
|
+
if(/tool-input|tool-call/.test(method)){
|
|
4082
|
+
var args=(params && (params.arguments || params.input)) || {};
|
|
4083
|
+
if(args && args.sessionId) pendingSessionId=args.sessionId;
|
|
4084
|
+
}
|
|
4085
|
+
});
|
|
4086
|
+
|
|
4087
|
+
// ============================================================
|
|
4088
|
+
// buildStory \u2014 vanilla-JS port of session-story.ts. Kept
|
|
4089
|
+
// function-for-function identical so the two are easy to diff; the panel
|
|
4090
|
+
// resource must be fully self-contained (no bundler/dynamic import), so it
|
|
4091
|
+
// cannot import the TS module directly.
|
|
4092
|
+
// ============================================================
|
|
4093
|
+
|
|
4094
|
+
var SALIENT_KEYS=["file_path","path","filePath","file","command","pattern","query","q","url","todos","description","prompt"];
|
|
4095
|
+
function truncateStr(v,max){ var o=String(v).replace(/\\s+/g,' ').trim(); return o.length>max? o.slice(0,max-1)+'\u2026':o; }
|
|
4096
|
+
function formatArgValue(v){
|
|
4097
|
+
if(typeof v==='string') return v;
|
|
4098
|
+
if(Array.isArray(v)) return v.length+' item'+(v.length===1?'':'s');
|
|
4099
|
+
if(v && typeof v==='object') return JSON.stringify(v);
|
|
4100
|
+
return String(v);
|
|
4101
|
+
}
|
|
4102
|
+
function pickSalient(args){
|
|
4103
|
+
for(var i=0;i<SALIENT_KEYS.length;i++){
|
|
4104
|
+
var k=SALIENT_KEYS[i], v=args[k];
|
|
4105
|
+
if(v!==undefined && v!==null && v!=='') return formatArgValue(v);
|
|
4106
|
+
}
|
|
4107
|
+
return null;
|
|
4108
|
+
}
|
|
4109
|
+
function formatToolCall(name,args){
|
|
4110
|
+
name=name||'tool';
|
|
4111
|
+
args=(args && typeof args==='object' && !Array.isArray(args)) ? args : {};
|
|
4112
|
+
var salient=pickSalient(args);
|
|
4113
|
+
if(salient!==null){
|
|
4114
|
+
if(name.toLowerCase().indexOf(salient.toLowerCase())>=0) return truncateStr(name,120);
|
|
4115
|
+
return truncateStr(name+' '+salient,120);
|
|
4116
|
+
}
|
|
4117
|
+
if(Object.keys(args).length===0) return name;
|
|
4118
|
+
return truncateStr(name+' '+JSON.stringify(args),120);
|
|
4119
|
+
}
|
|
4120
|
+
function extractText(v){
|
|
4121
|
+
if(v==null) return null;
|
|
4122
|
+
if(typeof v==='string') return v;
|
|
4123
|
+
if(Array.isArray(v)){
|
|
4124
|
+
var parts=v.map(extractText).filter(function(x){ return x!=null; });
|
|
4125
|
+
return parts.length? parts.join('\\n') : null;
|
|
4126
|
+
}
|
|
4127
|
+
if(typeof v==='object'){
|
|
4128
|
+
if(typeof v.text==='string') return v.text;
|
|
4129
|
+
if(typeof v.message==='string') return v.message;
|
|
4130
|
+
if(Array.isArray(v.content)) return extractText(v.content);
|
|
4131
|
+
if(typeof v.error==='string') return v.error;
|
|
4132
|
+
if(v.error && typeof v.error==='object' && typeof v.error.message==='string') return v.error.message;
|
|
4133
|
+
return null;
|
|
4134
|
+
}
|
|
4135
|
+
return null;
|
|
4136
|
+
}
|
|
4137
|
+
function formatToolResult(toolName,result,isError){
|
|
4138
|
+
var text=extractText(result);
|
|
4139
|
+
if(isError){
|
|
4140
|
+
var message=text!=null? text : (result!=null? JSON.stringify(result) : 'failed');
|
|
4141
|
+
var firstLine=String(message).split(/\\r?\\n/)[0] || message;
|
|
4142
|
+
return truncateStr(firstLine,160);
|
|
4143
|
+
}
|
|
4144
|
+
if(text==null) return null;
|
|
4145
|
+
var trimmed=text.trim();
|
|
4146
|
+
if(!trimmed) return null;
|
|
4147
|
+
var lines=trimmed.split(/\\r?\\n/);
|
|
4148
|
+
if(lines.length>1){
|
|
4149
|
+
var bytes=new TextEncoder().encode(trimmed).length;
|
|
4150
|
+
return lines.length+' lines, '+bytes+'B';
|
|
4151
|
+
}
|
|
4152
|
+
return truncateStr(lines[0],160);
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
function classifyKind(toolCalls){
|
|
4156
|
+
if(!toolCalls || toolCalls.length===0) return 'text';
|
|
4157
|
+
var names=toolCalls.map(function(t){ return t.name.toLowerCase(); });
|
|
4158
|
+
if(names.some(function(n){ return /edit|write/.test(n); })) return 'edit';
|
|
4159
|
+
if(names.some(function(n){ return /bash|terminal|command/.test(n); })) return 'bash';
|
|
4160
|
+
if(names.some(function(n){ return /read|grep|glob/.test(n); })) return 'read';
|
|
4161
|
+
return 'text';
|
|
4162
|
+
}
|
|
4163
|
+
var NEW_CHAPTER_RE=/\\b(aussi|autre|ensuite|nouveau|nouvelle|plut[o\xF4]t|maintenant|apr[e\xE8]s \xE7a|il faudrait|peux[- ]tu|on pourrait|ajoute|g[e\xE8]re)\\b/iu;
|
|
4164
|
+
function classifyRoute(text){
|
|
4165
|
+
var newt=NEW_CHAPTER_RE.test(text);
|
|
4166
|
+
if(!newt) return {route:'cont'};
|
|
4167
|
+
var title=text.replace(/[.?!].*$/,'').slice(0,42);
|
|
4168
|
+
return {route:'newt', title:title};
|
|
4169
|
+
}
|
|
4170
|
+
function formatTsJs(ts){
|
|
4171
|
+
if(ts===undefined || ts===null || isNaN(ts)) return '';
|
|
4172
|
+
return new Date(ts).toISOString().slice(11,19);
|
|
4173
|
+
}
|
|
4174
|
+
function firstMeaningfulLine(text){
|
|
4175
|
+
if(!text) return undefined;
|
|
4176
|
+
var lines=text.split('\\n').map(function(l){ return l.trim(); }).filter(function(l){ return l.length>0; });
|
|
4177
|
+
return lines[0];
|
|
4178
|
+
}
|
|
4179
|
+
function lineCountOf(text){
|
|
4180
|
+
var n=(text||'').split('\\n').filter(function(l){ return l.trim().length>0; }).length;
|
|
4181
|
+
return n||1;
|
|
4182
|
+
}
|
|
4183
|
+
function parseArgsJson(s){ try{ return JSON.parse(s); }catch(e){ return {}; } }
|
|
4184
|
+
|
|
4185
|
+
function foldToolStep(assistant,toolResults){
|
|
4186
|
+
var toolCalls=assistant.toolCalls||[];
|
|
4187
|
+
var kind=classifyKind(toolCalls);
|
|
4188
|
+
var count=toolCalls.length||1;
|
|
4189
|
+
var items=[], facts=[];
|
|
4190
|
+
if(assistant.text && assistant.text.trim()) items.push({text:assistant.text.trim()});
|
|
4191
|
+
toolCalls.forEach(function(tc,i){
|
|
4192
|
+
var args=parseArgsJson(tc.args);
|
|
4193
|
+
var h=formatToolCall(tc.name,args);
|
|
4194
|
+
var resultMsg=toolResults[i];
|
|
4195
|
+
var resultText=(resultMsg && resultMsg.text) || '';
|
|
4196
|
+
var isError=resultText.indexOf('[error]')===0;
|
|
4197
|
+
var r=isError? resultText.slice(7).trim() : resultText;
|
|
4198
|
+
items.push({h:h,r:r});
|
|
4199
|
+
var fact=formatToolResult(tc.name,r,isError);
|
|
4200
|
+
if(fact) facts.push(fact);
|
|
4201
|
+
});
|
|
4202
|
+
var firstLine=firstMeaningfulLine(assistant.text);
|
|
4203
|
+
var firstToolCall=toolCalls[0];
|
|
4204
|
+
var sum=firstLine!==undefined? firstLine : (firstToolCall? formatToolCall(firstToolCall.name,parseArgsJson(firstToolCall.args)) : '\u2026');
|
|
4205
|
+
var raw1;
|
|
4206
|
+
if(toolCalls.length===0) raw1='assistant \xB7 '+lineCountOf(assistant.text)+' ligne(s)';
|
|
4207
|
+
else if(toolCalls.length===1) raw1=formatToolCall(firstToolCall.name,parseArgsJson(firstToolCall.args));
|
|
4208
|
+
else raw1=(firstToolCall? firstToolCall.name : 'tool')+' \xD7'+toolCalls.length;
|
|
4209
|
+
return {kind:kind, ts:formatTsJs(assistant.ts), sum:sum, raw1:raw1, count:count, facts:facts, items:items};
|
|
4210
|
+
}
|
|
4211
|
+
function foldUserStep(msg){
|
|
4212
|
+
var text=msg.text||'';
|
|
4213
|
+
return {kind:'user', ts:formatTsJs(msg.ts), sum:'\xAB '+truncateStr(text,80)+' \xBB', raw1:'user \xB7 '+lineCountOf(text)+' ligne(s)', count:1, facts:[], items:[{text:text}], userText:text};
|
|
4214
|
+
}
|
|
4215
|
+
function foldOrphanToolStep(msg){
|
|
4216
|
+
var text=msg.text||'';
|
|
4217
|
+
var isError=text.indexOf('[error]')===0;
|
|
4218
|
+
var r=isError? text.slice(7).trim() : text;
|
|
4219
|
+
var name=msg.toolName||'tool';
|
|
4220
|
+
var fact=formatToolResult(name,r,isError);
|
|
4221
|
+
return {kind:classifyKind([{name:name}]), ts:formatTsJs(msg.ts), sum: msg.toolName? (msg.toolName+' \xB7 r\xE9sultat') : "R\xE9sultat d'outil", raw1: msg.toolName||'tool', count:1, facts: fact?[fact]:[], items:[{h:name,r:r}]};
|
|
4222
|
+
}
|
|
4223
|
+
function foldSystemStep(msg){
|
|
4224
|
+
var text=msg.text||'';
|
|
4225
|
+
var line=firstMeaningfulLine(text);
|
|
4226
|
+
return {kind:'text', ts:formatTsJs(msg.ts), sum: line!==undefined? line : text, raw1:'system', count:1, facts:[], items: text?[{text:text}]:[]};
|
|
4227
|
+
}
|
|
4228
|
+
function foldMessages(messages){
|
|
4229
|
+
var steps=[], i=0;
|
|
4230
|
+
while(i<messages.length){
|
|
4231
|
+
var msg=messages[i];
|
|
4232
|
+
if(msg.role==='user'){ steps.push(foldUserStep(msg)); i+=1; continue; }
|
|
4233
|
+
if(msg.role==='assistant'){
|
|
4234
|
+
var j=i+1, toolResults=[];
|
|
4235
|
+
while(j<messages.length && messages[j].role==='tool'){ toolResults.push(messages[j]); j+=1; }
|
|
4236
|
+
steps.push(foldToolStep(msg,toolResults)); i=j; continue;
|
|
4237
|
+
}
|
|
4238
|
+
if(msg.role==='tool'){ steps.push(foldOrphanToolStep(msg)); i+=1; continue; }
|
|
4239
|
+
steps.push(foldSystemStep(msg)); i+=1;
|
|
4240
|
+
}
|
|
4241
|
+
return steps;
|
|
4242
|
+
}
|
|
4243
|
+
function buildStoryJs(messages){
|
|
4244
|
+
var folded=foldMessages(messages||[]);
|
|
4245
|
+
var chapters=[], steps=[];
|
|
4246
|
+
var currentChapterId, sawFirstUser=false;
|
|
4247
|
+
function closeCurrent(){ var cur=chapters.filter(function(c){ return c.id===currentChapterId; })[0]; if(cur) cur.status='done'; }
|
|
4248
|
+
function openChapter(title){ var id='c'+(chapters.length+1); chapters.push({id:id,title:title,status:'cur'}); return id; }
|
|
4249
|
+
folded.forEach(function(step){
|
|
4250
|
+
var route;
|
|
4251
|
+
if(step.kind==='user' && step.userText!==undefined){
|
|
4252
|
+
if(!sawFirstUser){ sawFirstUser=true; currentChapterId=openChapter('Cadrage'); }
|
|
4253
|
+
else {
|
|
4254
|
+
var verdict=classifyRoute(step.userText);
|
|
4255
|
+
route=verdict.route;
|
|
4256
|
+
if(verdict.route==='newt'){ closeCurrent(); currentChapterId=openChapter(verdict.title||'Nouvelle sous-t\xE2che'); }
|
|
4257
|
+
}
|
|
4258
|
+
} else if(currentChapterId===undefined){ currentChapterId=openChapter('Cadrage'); }
|
|
4259
|
+
var out={chap:currentChapterId, kind:step.kind, ts:step.ts, sum:step.sum, raw1:step.raw1, count:step.count, facts:step.facts, items:step.items};
|
|
4260
|
+
if(route) out.route=route;
|
|
4261
|
+
steps.push(out);
|
|
4262
|
+
});
|
|
4263
|
+
return {chapters:chapters, steps:steps};
|
|
4264
|
+
}
|
|
4265
|
+
|
|
4266
|
+
// ============================================================
|
|
4267
|
+
// App state
|
|
4268
|
+
// ============================================================
|
|
4269
|
+
var sessions=[];
|
|
4270
|
+
var activeSessionId=null;
|
|
4271
|
+
var story={chapters:[], steps:[]};
|
|
4272
|
+
var open={};
|
|
4273
|
+
var selected=-1;
|
|
4274
|
+
var lastSeenOutputAt=null;
|
|
4275
|
+
var pollTimer=null, polling=false;
|
|
4276
|
+
var ICONS={text:["k-text","A"],edit:["k-edit","\u270E"],bash:["k-bash","\u25B8"],read:["k-read","\u2315"],user:["k-user","T"]};
|
|
4277
|
+
var icoSpec=function(k){ return ICONS[k]||ICONS.text; };
|
|
4278
|
+
var chapOf=function(id){ return story.chapters.filter(function(c){ return c.id===id; })[0]; };
|
|
4279
|
+
var curChap=function(){ return story.chapters.filter(function(c){ return c.status==='cur'; })[0] || story.chapters[story.chapters.length-1]; };
|
|
4280
|
+
|
|
4281
|
+
function setStatus(msg){ $('statusbar').textContent=msg; }
|
|
4282
|
+
function nowTs(){ return new Date().toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit',second:'2-digit'}); }
|
|
4283
|
+
|
|
4284
|
+
function titleOf(s){
|
|
4285
|
+
return s.label || s.name || (s.command? s.command.split(/\\s+/)[0].split('/').pop() : null) || s.id.slice(0,8);
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
// ============================================================
|
|
4289
|
+
// Picker screen
|
|
4290
|
+
// ============================================================
|
|
4291
|
+
function renderPicker(){
|
|
4292
|
+
var el=$('pickerList');
|
|
4293
|
+
if(!sessions.length){ el.innerHTML='<div class="pk-empty">Aucune session</div>'; return; }
|
|
4294
|
+
var html='';
|
|
4295
|
+
sessions.forEach(function(s){
|
|
4296
|
+
html+='<div class="pk-item" data-id="'+esc(s.id)+'">'
|
|
4297
|
+
+ '<span class="pk-name">'+esc(titleOf(s))+'</span>'
|
|
4298
|
+
+ '<span class="badge '+esc(s.status)+'">'+esc(s.status)+'</span>'
|
|
4299
|
+
+ '<span class="pk-meta">'+esc(s.kind||'')+'</span>'
|
|
4300
|
+
+ '</div>';
|
|
4301
|
+
});
|
|
4302
|
+
el.innerHTML=html;
|
|
4303
|
+
Array.prototype.forEach.call(el.querySelectorAll('.pk-item'), function(row){
|
|
4304
|
+
row.onclick=function(){ openSession(row.getAttribute('data-id')); };
|
|
4305
|
+
});
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
function showPicker(){
|
|
4309
|
+
activeSessionId=null;
|
|
4310
|
+
$('pickerScreen').classList.remove('hidden');
|
|
4311
|
+
$('storyScreen').classList.add('hidden');
|
|
4312
|
+
renderPicker();
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
function openSession(id){
|
|
4316
|
+
activeSessionId=id;
|
|
4317
|
+
story={chapters:[], steps:[]};
|
|
4318
|
+
open={};
|
|
4319
|
+
selected=-1;
|
|
4320
|
+
lastSeenOutputAt=null;
|
|
4321
|
+
$('pickerScreen').classList.add('hidden');
|
|
4322
|
+
$('storyScreen').classList.remove('hidden');
|
|
4323
|
+
$('fullPanelLink').href='https://cli.agentproto.sh/panel?session='+encodeURIComponent(id);
|
|
4324
|
+
closePanel();
|
|
4325
|
+
loadStory().then(renderAll);
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
// ============================================================
|
|
4329
|
+
// Story loading
|
|
4330
|
+
// ============================================================
|
|
4331
|
+
function activeSession(){ return sessions.filter(function(s){ return s.id===activeSessionId; })[0]; }
|
|
4332
|
+
|
|
4333
|
+
function loadStory(){
|
|
4334
|
+
return callTool('agent_export', {sessionId:activeSessionId, format:'json'}).then(function(data){
|
|
4335
|
+
var messages=(data && data.messages) || [];
|
|
4336
|
+
story=buildStoryJs(messages);
|
|
4337
|
+
// Default open state: only the last (current) chapter is expanded.
|
|
4338
|
+
var last=story.chapters[story.chapters.length-1];
|
|
4339
|
+
if(last && !(last.id in open)) open[last.id]=true;
|
|
4340
|
+
}).catch(function(e){
|
|
4341
|
+
setStatus('Erreur export : '+e.message);
|
|
4342
|
+
});
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
function canSend(){
|
|
4346
|
+
var s=activeSession();
|
|
4347
|
+
return !!s && s.kind==='agent-cli' && s.status==='running';
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
function renderComposer(){
|
|
4351
|
+
var s=activeSession();
|
|
4352
|
+
var box=$('msgBox'), btn=$('sendBtn');
|
|
4353
|
+
var enabled=canSend();
|
|
4354
|
+
box.disabled=!enabled;
|
|
4355
|
+
btn.disabled=!enabled;
|
|
4356
|
+
if(!s){ box.placeholder='Session introuvable.'; }
|
|
4357
|
+
else if(!enabled) box.placeholder='Lecture seule \u2014 session '+esc(s.status)+'.';
|
|
4358
|
+
else box.placeholder="\xC9cris \xE0 l'agent\u2026 (la surcouche classe ton message : suite de la sous-t\xE2che ou nouvelle sous-t\xE2che)";
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4361
|
+
function renderHero(){
|
|
4362
|
+
var s=activeSession();
|
|
4363
|
+
$('heroTitle').textContent=s? titleOf(s) : (activeSessionId||'');
|
|
4364
|
+
var firstUser=story.steps.filter(function(st){ return st.kind==='user'; })[0];
|
|
4365
|
+
var mission=firstUser? firstUser.userText || (firstUser.items[0] && firstUser.items[0].text) : null;
|
|
4366
|
+
$('heroSub').textContent=mission? truncateStr(mission,200) : 'Aucun message pour le moment.';
|
|
4367
|
+
var p=$('pulse');
|
|
4368
|
+
p.classList.toggle('off', !(s && (s.status==='running' || s.status==='starting')));
|
|
4369
|
+
renderComposer();
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
function renderAll(){
|
|
4373
|
+
renderHero();
|
|
4374
|
+
renderPlan();
|
|
4375
|
+
renderRows('bottom');
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4378
|
+
// ============================================================
|
|
4379
|
+
// big picture strip
|
|
4380
|
+
// ============================================================
|
|
4381
|
+
function renderPlan(){
|
|
4382
|
+
var done=story.chapters.filter(function(c){ return c.status==='done'; }).length;
|
|
4383
|
+
$('plan').innerHTML=story.chapters.map(function(c,i){
|
|
4384
|
+
return '<span class="pt '+c.status+'" data-c="'+esc(c.id)+'"><span class="st">'+(c.status==='done'?'\u2713':'\u25CF')+'</span>'+(i+1)+'. '+esc(c.title)+'</span>';
|
|
4385
|
+
}).join('') + '<span class="pt" style="cursor:default"><b>'+done+'/'+story.chapters.length+'</b> faites</span>';
|
|
4386
|
+
Array.prototype.forEach.call($('plan').querySelectorAll('.pt[data-c]'), function(el){
|
|
4387
|
+
el.onclick=function(){ open[el.getAttribute('data-c')]=true; renderRows(); jumpToChap(el.getAttribute('data-c')); };
|
|
4388
|
+
});
|
|
4389
|
+
}
|
|
4390
|
+
function jumpToChap(cid){
|
|
4391
|
+
var el=document.querySelector('.chap[data-c="'+cid+'"]');
|
|
4392
|
+
if(el) el.scrollIntoView({block:'start',behavior:'smooth'});
|
|
4393
|
+
}
|
|
4394
|
+
|
|
4395
|
+
// ============================================================
|
|
4396
|
+
// feed segment\xE9 par chapitres
|
|
4397
|
+
// ============================================================
|
|
4398
|
+
function rowHtml(s,i,isNew){
|
|
4399
|
+
var spec=icoSpec(s.kind), cls=spec[0], ch=spec[1];
|
|
4400
|
+
var route=s.route? '<span class="route '+(s.route==='newt'?'newt':'cont')+'">'+(s.route==='newt'?'\u2605 nouvelle sous-t\xE2che':'\u21B3 suite')+'</span>' : '';
|
|
4401
|
+
return '<div class="row '+(isNew?'new':'')+'" aria-selected="'+(i===selected)+'" data-i="'+i+'">'
|
|
4402
|
+
+ '<span class="ico '+cls+'">'+ch+'</span>'
|
|
4403
|
+
+ '<span class="mid"><span class="sum">'+esc(s.sum)+'</span><span class="raw1">'+esc(s.raw1||'')+'</span>'+route+'</span>'
|
|
4404
|
+
+ (s.count>1? '<span class="cnt">\xD7'+s.count+'</span>':'') + '<span class="ts">'+esc(s.ts||'')+'</span>'
|
|
4405
|
+
+ '</div>';
|
|
4406
|
+
}
|
|
4407
|
+
function renderRows(keepScroll,newIdx){
|
|
4408
|
+
var feed=$('feed');
|
|
4409
|
+
var prevH=feed.scrollHeight, prevTop=feed.scrollTop;
|
|
4410
|
+
var html='';
|
|
4411
|
+
story.chapters.forEach(function(c,ci){
|
|
4412
|
+
var chapSteps=[];
|
|
4413
|
+
story.steps.forEach(function(s,i){ if(s.chap===c.id) chapSteps.push({s:s,i:i}); });
|
|
4414
|
+
if(!chapSteps.length) return;
|
|
4415
|
+
var isOpen=!!open[c.id];
|
|
4416
|
+
html+='<div class="chap '+c.status+' '+(isOpen?'open':'')+'" data-c="'+esc(c.id)+'">'
|
|
4417
|
+
+ '<span class="cst">'+(c.status==='done'?'\u2713':'\u25CF')+'</span><span class="cnum">'+(ci+1)+'.</span>'
|
|
4418
|
+
+ '<span class="csum">'+esc(c.title)+'</span>'
|
|
4419
|
+
+ '<span class="cmeta">'+chapSteps.length+' \xE9tape'+(chapSteps.length>1?'s':'')+'</span><span class="cchev">\u25B8</span>'
|
|
4420
|
+
+ '</div>';
|
|
4421
|
+
if(isOpen) html += chapSteps.map(function(x){ return rowHtml(x.s,x.i,x.i===newIdx); }).join('');
|
|
4422
|
+
});
|
|
4423
|
+
$('rows').innerHTML=html;
|
|
4424
|
+
Array.prototype.forEach.call(document.querySelectorAll('.row'), function(el){
|
|
4425
|
+
el.onclick=function(){ selectStep(Number(el.getAttribute('data-i'))); };
|
|
4426
|
+
});
|
|
4427
|
+
Array.prototype.forEach.call(document.querySelectorAll('.chap'), function(el){
|
|
4428
|
+
el.onclick=function(){ var c=el.getAttribute('data-c'); open[c]=!open[c]; renderRows(); };
|
|
4429
|
+
});
|
|
4430
|
+
if(keepScroll==='bottom') feed.scrollTop=feed.scrollHeight;
|
|
4431
|
+
else if(keepScroll==='preserve') feed.scrollTop=feed.scrollHeight-prevH+prevTop;
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
// ============================================================
|
|
4435
|
+
// panneau ancr\xE9
|
|
4436
|
+
// ============================================================
|
|
4437
|
+
function selectStep(i){
|
|
4438
|
+
selected=i;
|
|
4439
|
+
var s=story.steps[i]; if(!s) return;
|
|
4440
|
+
open[s.chap]=true;
|
|
4441
|
+
$('panel').classList.add('open');
|
|
4442
|
+
var spec=icoSpec(s.kind), cls=spec[0], ch=spec[1];
|
|
4443
|
+
var ico=$('pIco'); ico.className='ico '+cls; ico.textContent=ch;
|
|
4444
|
+
$('pTitle').textContent=s.sum;
|
|
4445
|
+
var c=chapOf(s.chap);
|
|
4446
|
+
$('pSub').textContent=(s.ts? s.ts+' \xB7 ':'')+(c? ('sous-t\xE2che : '+c.title) : '');
|
|
4447
|
+
var facts=(s.facts||[]).map(function(f){
|
|
4448
|
+
return '<span class="fact '+(/\u2713|exit 0|passed|0 match/.test(f)?'ok':'')+'">'+esc(f)+'</span>';
|
|
4449
|
+
}).join('');
|
|
4450
|
+
var tech=(s.items||[]).map(function(it,k){
|
|
4451
|
+
return it.text!==undefined
|
|
4452
|
+
? '<div class="d-text">'+renderMd(it.text)+'</div>'
|
|
4453
|
+
: '<div class="titem"><div class="th">'+esc(it.h)+'<button class="copy" data-k="'+k+'" type="button">\u29C9</button></div><pre>'+esc(it.r)+'</pre></div>';
|
|
4454
|
+
}).join('');
|
|
4455
|
+
$('pBody').innerHTML=''
|
|
4456
|
+
+ '<div class="plain"><div>'+esc(s.sum)+'.</div><div class="why">'+esc(s.why||'')+'</div></div>'
|
|
4457
|
+
+ (facts? '<div class="facts">'+facts+'</div>':'')
|
|
4458
|
+
+ '<details class="techbox" '+(document.body.classList.contains('tech')?'open':'')+'>'
|
|
4459
|
+
+ '<summary>D\xE9tail technique \xB7 '+(s.items||[]).length+'</summary><div class="techlist">'+tech+'</div>'
|
|
4460
|
+
+ '</details>';
|
|
4461
|
+
Array.prototype.forEach.call($('pBody').querySelectorAll('.copy'), function(btn){
|
|
4462
|
+
btn.onclick=function(e){
|
|
4463
|
+
e.stopPropagation();
|
|
4464
|
+
var it=(s.items||[])[Number(btn.getAttribute('data-k'))];
|
|
4465
|
+
var payload=(it.h||'')+'\\n'+(it.r||it.text||'');
|
|
4466
|
+
if(navigator.clipboard) navigator.clipboard.writeText(payload).catch(function(){});
|
|
4467
|
+
btn.textContent='\u2713'; setTimeout(function(){ btn.textContent='\u29C9'; },900);
|
|
4468
|
+
};
|
|
4469
|
+
});
|
|
4470
|
+
$('pPrev').disabled=i<=0; $('pNext').disabled=i>=story.steps.length-1;
|
|
4471
|
+
renderRows();
|
|
4472
|
+
var el=document.querySelector('.row[data-i="'+i+'"]');
|
|
4473
|
+
if(el) el.scrollIntoView({block:'nearest',behavior:'smooth'});
|
|
4474
|
+
}
|
|
4475
|
+
function closePanel(){ selected=-1; var p=$('panel'); if(p) p.classList.remove('open'); renderRows(); }
|
|
4476
|
+
$('pClose').addEventListener('click',closePanel);
|
|
4477
|
+
$('pPrev').addEventListener('click',function(){ if(selected>0) selectStep(selected-1); });
|
|
4478
|
+
$('pNext').addEventListener('click',function(){ if(selected<story.steps.length-1) selectStep(selected+1); });
|
|
4479
|
+
document.addEventListener('keydown',function(e){
|
|
4480
|
+
if(e.key==='Escape'){ closePanel(); return; }
|
|
4481
|
+
if($('storyScreen').classList.contains('hidden')) return;
|
|
4482
|
+
if(selected<0 || e.target.tagName==='TEXTAREA') return;
|
|
4483
|
+
if(e.key==='ArrowUp'){ e.preventDefault(); if(selected>0) selectStep(selected-1); }
|
|
4484
|
+
if(e.key==='ArrowDown'){ e.preventDefault(); if(selected<story.steps.length-1) selectStep(selected+1); }
|
|
4485
|
+
});
|
|
4486
|
+
|
|
4487
|
+
// ============================================================
|
|
4488
|
+
// Simple / Tech modes
|
|
4489
|
+
// ============================================================
|
|
4490
|
+
function setMode(tech){
|
|
4491
|
+
document.body.classList.toggle('tech',tech);
|
|
4492
|
+
$('modeTech').classList.toggle('on',tech);
|
|
4493
|
+
$('modeSimple').classList.toggle('on',!tech);
|
|
4494
|
+
if(selected>=0) selectStep(selected);
|
|
4495
|
+
}
|
|
4496
|
+
$('modeSimple').addEventListener('click',function(){ setMode(false); });
|
|
4497
|
+
$('modeTech').addEventListener('click',function(){ setMode(true); });
|
|
4498
|
+
$('switchBtn').addEventListener('click', function(){ if(pollTimer) clearTimeout(pollTimer); showPicker(); doPoll(); });
|
|
4499
|
+
|
|
4500
|
+
// ============================================================
|
|
4501
|
+
// composer \u2014 local chapter-routing classification + agent_prompt
|
|
4502
|
+
// ============================================================
|
|
4503
|
+
$('sendBtn').addEventListener('click', sendMsg);
|
|
4504
|
+
$('msgBox').addEventListener('keydown', function(e){
|
|
4505
|
+
if(e.key==='Enter' && !e.shiftKey){ e.preventDefault(); sendMsg(); }
|
|
4506
|
+
});
|
|
4507
|
+
function sendMsg(){
|
|
4508
|
+
if(!canSend()) return;
|
|
4509
|
+
var box=$('msgBox'), text=box.value.trim();
|
|
4510
|
+
if(!text) return;
|
|
4511
|
+
box.value='';
|
|
4512
|
+
var cur=curChap();
|
|
4513
|
+
var r=cur? classifyRoute(text) : {route:'cont'};
|
|
4514
|
+
var chapId=cur? cur.id : null;
|
|
4515
|
+
if(r.route==='newt' && cur){
|
|
4516
|
+
cur.status='done';
|
|
4517
|
+
var id='c'+(story.chapters.length+1);
|
|
4518
|
+
story.chapters.push({id:id, title:r.title||'Nouvelle sous-t\xE2che', status:'cur'});
|
|
4519
|
+
chapId=id; open[id]=true;
|
|
4520
|
+
$('routing').innerHTML='\u2726 class\xE9 : <span class="r-newt">\u2605 nouvelle sous-t\xE2che \xAB '+esc(r.title||'')+' \xBB</span>';
|
|
4521
|
+
} else {
|
|
4522
|
+
$('routing').innerHTML='\u2726 class\xE9 : <span class="r-cont">\u21B3 suite de \xAB '+esc(cur? cur.title : '')+' \xBB</span>';
|
|
4523
|
+
}
|
|
4524
|
+
setTimeout(function(){ $('routing').textContent=''; },5000);
|
|
4525
|
+
story.steps.push({
|
|
4526
|
+
chap:chapId, kind:'user', ts:nowTs(),
|
|
4527
|
+
sum:'\xAB '+text.slice(0,80)+(text.length>80?'\u2026':'')+' \xBB',
|
|
4528
|
+
raw1:'user \xB7 '+text.split('\\n').length+' ligne(s)',
|
|
4529
|
+
route:r.route, facts:[], items:[{text:text}], userText:text,
|
|
4530
|
+
});
|
|
4531
|
+
renderPlan(); renderRows('bottom', story.steps.length-1);
|
|
4532
|
+
callTool('agent_prompt', {sessionId:activeSessionId, prompt:text}).catch(function(e){
|
|
4533
|
+
setStatus('Envoi \xE9chou\xE9 : '+e.message);
|
|
4534
|
+
});
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
// ============================================================
|
|
4538
|
+
// Poll \u2014 session_list every ~5s; re-fetch agent_export only on turn
|
|
4539
|
+
// boundaries (lastOutputAt changed for the active session).
|
|
4540
|
+
// ============================================================
|
|
4541
|
+
var POLL_MS=5000;
|
|
4542
|
+
function loadSessions(){
|
|
4543
|
+
return callTool('session_list', {kind:'all'}).then(function(data){
|
|
4544
|
+
sessions=data.sessions||[];
|
|
4545
|
+
if($('pickerScreen') && !$('pickerScreen').classList.contains('hidden')) renderPicker();
|
|
4546
|
+
}).catch(function(e){ setStatus('Erreur : '+e.message); });
|
|
4547
|
+
}
|
|
4548
|
+
function doPoll(){
|
|
4549
|
+
if(polling) return;
|
|
4550
|
+
polling=true;
|
|
4551
|
+
loadSessions().then(function(){
|
|
4552
|
+
if(!activeSessionId){ polling=false; pollTimer=setTimeout(doPoll,POLL_MS); return; }
|
|
4553
|
+
var s=activeSession();
|
|
4554
|
+
if(!s){ polling=false; pollTimer=setTimeout(doPoll,POLL_MS); return; }
|
|
4555
|
+
renderHero();
|
|
4556
|
+
var changed=s.lastOutputAt && s.lastOutputAt!==lastSeenOutputAt;
|
|
4557
|
+
if(changed){
|
|
4558
|
+
lastSeenOutputAt=s.lastOutputAt;
|
|
4559
|
+
loadStory().then(function(){ renderPlan(); renderRows('bottom'); polling=false; pollTimer=setTimeout(doPoll,POLL_MS); });
|
|
4560
|
+
} else {
|
|
4561
|
+
polling=false; pollTimer=setTimeout(doPoll,POLL_MS);
|
|
4562
|
+
}
|
|
4563
|
+
}).catch(function(){ polling=false; pollTimer=setTimeout(doPoll,POLL_MS); });
|
|
4564
|
+
}
|
|
4565
|
+
|
|
4566
|
+
// ============================================================
|
|
4567
|
+
// Boot
|
|
4568
|
+
// ============================================================
|
|
4569
|
+
initBridge().then(loadSessions).then(function(){
|
|
4570
|
+
setTimeout(function(){
|
|
4571
|
+
var target=pendingSessionId && sessions.some(function(s){ return s.id===pendingSessionId; })
|
|
4572
|
+
? pendingSessionId
|
|
4573
|
+
: null;
|
|
4574
|
+
if(!target){
|
|
4575
|
+
var agentSessions=sessions.filter(function(s){ return s.kind==='agent-cli'; });
|
|
4576
|
+
if(agentSessions.length===1) target=agentSessions[0].id;
|
|
4577
|
+
}
|
|
4578
|
+
if(target) openSession(target); else showPicker();
|
|
4579
|
+
pollTimer=setTimeout(doPoll,POLL_MS);
|
|
4580
|
+
}, 50);
|
|
4581
|
+
}).catch(function(e){
|
|
4582
|
+
setStatus('Bridge : '+e.message);
|
|
4583
|
+
$('pickerList').innerHTML='<div class="pk-empty">\xC9chec connexion bridge : '+esc(e.message)+'</div>';
|
|
4584
|
+
});
|
|
4585
|
+
</script>
|
|
4586
|
+
</body>
|
|
4587
|
+
</html>`;
|
|
4588
|
+
|
|
4589
|
+
// src/session-story/index.ts
|
|
4590
|
+
var sessionStoryInputSchema = z.object({
|
|
4591
|
+
sessionId: z.string().optional().describe(
|
|
4592
|
+
"Session id to open directly. When omitted, the panel shows a session picker (built from session_list) and lets the user choose."
|
|
4593
|
+
)
|
|
4594
|
+
});
|
|
4595
|
+
function makeSessionStoryPanelApp(ops) {
|
|
4596
|
+
return {
|
|
4597
|
+
id: "agentproto_session_story",
|
|
4598
|
+
title: "Session Story",
|
|
4599
|
+
description: "Open the session story panel \u2014 a readable, per-session timeline for two audiences at once: a plain-language summary of every step for beginners, expandable to raw tool-call detail for technical users. Shows a one-sentence mission, a plan strip of inferred sub-task chapters, a chapter-segmented feed, and a composer to keep driving the session. Polls live data and lets you jump to any step.",
|
|
4600
|
+
inputSchema: sessionStoryInputSchema,
|
|
4601
|
+
execute: async (input) => input.sessionId ? { sessionId: input.sessionId } : { sessions: ops.listSessions("all") },
|
|
4602
|
+
html: SESSION_STORY_PANEL_HTML
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
var sessionStoryApp = defineApp({
|
|
4606
|
+
id: "@agentproto/session-story",
|
|
4607
|
+
name: "Session Story",
|
|
4608
|
+
description: "Open the session story panel \u2014 a readable, per-session timeline for two audiences at once: a plain-language summary of every step for beginners, expandable to raw tool-call detail for technical users.",
|
|
4609
|
+
agents: [],
|
|
4610
|
+
ui: {
|
|
4611
|
+
html: SESSION_STORY_PANEL_HTML,
|
|
4612
|
+
title: "Session Story",
|
|
4613
|
+
tools: ["session_list", "agent_export", "agent_prompt"]
|
|
4614
|
+
}
|
|
4615
|
+
});
|
|
4616
|
+
|
|
4617
|
+
// src/live-session/panel.ts
|
|
4618
|
+
function LIVE_SESSION_HTML(initData) {
|
|
4619
|
+
return `<!DOCTYPE html>
|
|
4620
|
+
<html lang="en">
|
|
4621
|
+
<head>
|
|
4622
|
+
<meta charset="UTF-8">
|
|
4623
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4624
|
+
<title>agentproto live session</title>
|
|
4625
|
+
<style>
|
|
4626
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
4627
|
+
:root{
|
|
4628
|
+
--bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;
|
|
4629
|
+
--text:#e6edf3;--text2:#8b949e;--text3:#6e7681;
|
|
4630
|
+
--green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;
|
|
4631
|
+
}
|
|
4632
|
+
html,body{height:100%;font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}
|
|
4633
|
+
#app{display:flex;height:100%}
|
|
4634
|
+
#tree-pane{width:280px;flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--border);background:var(--bg2)}
|
|
4635
|
+
#tree-head{padding:10px 12px;border-bottom:1px solid var(--border);font-weight:600;font-size:12px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;flex-shrink:0}
|
|
4636
|
+
#tree-body{flex:1;overflow-y:auto;padding:6px}
|
|
4637
|
+
#timeline-pane{flex:1;display:flex;flex-direction:column;min-width:0;position:relative}
|
|
4638
|
+
#timeline-head{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
4639
|
+
#timeline-head .focus-id{font-weight:600;font-size:13px;font-family:Menlo,Monaco,monospace}
|
|
4640
|
+
#head-summary{margin-left:auto;font-size:11px;color:var(--text2);display:flex;align-items:center;gap:4px;white-space:nowrap;overflow:hidden;min-width:0}
|
|
4641
|
+
#head-summary .sdot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:3px;vertical-align:1px}
|
|
4642
|
+
#head-summary .sdot.running{background:var(--green)}
|
|
4643
|
+
#head-summary .sdot.grey{background:var(--text3)}
|
|
4644
|
+
#head-summary .sdot.error{background:var(--red)}
|
|
4645
|
+
#usage-chip{font-size:10.5px;font-family:Menlo,Monaco,monospace;background:var(--bg3);border-radius:4px;padding:1px 6px;color:var(--text)}
|
|
4646
|
+
#status-line{margin-left:8px;font-size:11px;color:var(--text2);flex-shrink:0}
|
|
4647
|
+
#timeline-body{flex:1;overflow-y:auto;padding:10px 14px;display:flex;flex-direction:column;gap:8px}
|
|
4648
|
+
#head-selector{display:none;max-width:42%;font:inherit;font-size:12px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:5px;padding:2px 4px}
|
|
4649
|
+
#new-pill{display:none;position:absolute;right:14px;bottom:12px;z-index:6;background:var(--blue);color:#0d1117;border:none;font-size:11px;font-weight:700;padding:5px 11px;border-radius:12px;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.4)}
|
|
4650
|
+
#new-pill.show{display:block}
|
|
4651
|
+
|
|
4652
|
+
/* WP3 compact mode \u2014 colour rail + tighter rows; only under body.compact-mode
|
|
4653
|
+
(displayMode 'inline' or narrow viewport). fullscreen/pip keep the cards. */
|
|
4654
|
+
body.compact-mode #tree-pane{display:none}
|
|
4655
|
+
body.compact-mode #head-selector{display:inline-block}
|
|
4656
|
+
body.compact-mode #focus-id-label{display:none}
|
|
4657
|
+
body.compact-mode .row{border-radius:6px;padding:5px 8px;border-left-width:3px}
|
|
4658
|
+
body.compact-mode .row.text{border-left-color:var(--blue)}
|
|
4659
|
+
body.compact-mode .row.tool-call{border-left-color:var(--yellow)}
|
|
4660
|
+
body.compact-mode .row.turn-end{border-left-color:var(--purple)}
|
|
4661
|
+
body.compact-mode .row .body{font-size:12px;margin-top:3px}
|
|
4662
|
+
body.compact-mode .row .rhead{font-size:10px}
|
|
4663
|
+
details.tool-group{border:1px solid var(--border);border-radius:8px;background:var(--bg2);padding:5px 8px;border-left:3px solid var(--yellow)}
|
|
4664
|
+
details.tool-group>summary{cursor:pointer;font-size:11px;color:var(--text2);font-weight:600;list-style:none}
|
|
4665
|
+
details.tool-group[open]>summary{margin-bottom:6px}
|
|
4666
|
+
details.tool-group .row{margin-top:5px}
|
|
4667
|
+
|
|
4668
|
+
.tnode{border-radius:6px;cursor:pointer;padding:5px 8px;margin:1px 0;display:flex;align-items:center;gap:7px;font-size:12px}
|
|
4669
|
+
.tnode:hover{background:var(--bg3)}
|
|
4670
|
+
.tnode.focus{background:var(--bg3);outline:1px solid var(--blue)}
|
|
4671
|
+
.tnode .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
4672
|
+
.dot.running{background:var(--green)}
|
|
4673
|
+
.dot.grey{background:var(--text3)}
|
|
4674
|
+
.dot.error{background:var(--red)}
|
|
4675
|
+
.tnode .label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Menlo,Monaco,monospace}
|
|
4676
|
+
.tnode .badge{font-size:9px;font-weight:700;color:var(--purple);border:1px solid var(--purple);border-radius:3px;padding:0 4px;flex-shrink:0}
|
|
4677
|
+
.tchildren{margin-left:14px;border-left:1px solid var(--border);padding-left:4px}
|
|
4678
|
+
#tree-empty{padding:12px;color:var(--text3);font-size:12px}
|
|
4679
|
+
|
|
4680
|
+
.row{border:1px solid var(--border);border-radius:8px;background:var(--bg2);padding:8px 10px}
|
|
4681
|
+
.row .rhead{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text2)}
|
|
4682
|
+
.row .kind{font-weight:700;text-transform:uppercase;letter-spacing:.03em;font-size:10px}
|
|
4683
|
+
.row.text .kind{color:var(--blue)}
|
|
4684
|
+
.row.tool-call .kind{color:var(--yellow)}
|
|
4685
|
+
.row.turn-end .kind{color:var(--purple)}
|
|
4686
|
+
.row .body{margin-top:5px;font-size:12.5px;line-height:1.5;white-space:pre-wrap;word-break:break-word}
|
|
4687
|
+
.row.text .body{font-family:inherit}
|
|
4688
|
+
.row.tool-call .toolname{font-family:Menlo,Monaco,monospace;font-weight:600}
|
|
4689
|
+
.status-badge{font-size:9px;font-weight:700;border-radius:3px;padding:1px 5px;margin-left:6px}
|
|
4690
|
+
.status-badge.pending{background:var(--bg3);color:var(--text2)}
|
|
4691
|
+
.status-badge.ok{background:#0f3d20;color:var(--green)}
|
|
4692
|
+
.status-badge.error{background:#3d1418;color:var(--red)}
|
|
4693
|
+
.row details{margin-top:6px}
|
|
4694
|
+
.row summary{cursor:pointer;font-size:11px;color:var(--text2)}
|
|
4695
|
+
.row pre{margin-top:4px;font-family:Menlo,Monaco,monospace;font-size:11px;white-space:pre-wrap;word-break:break-word;color:var(--text2);background:var(--bg);border-radius:5px;padding:6px 8px;max-height:200px;overflow:auto}
|
|
4696
|
+
.chip{display:inline-block;font-size:10.5px;font-family:Menlo,Monaco,monospace;background:var(--bg3);border-radius:4px;padding:2px 6px}
|
|
4697
|
+
#timeline-empty{color:var(--text3);font-size:12px;padding:8px 2px}
|
|
4698
|
+
</style>
|
|
4699
|
+
</head>
|
|
4700
|
+
<body>
|
|
4701
|
+
<div id="app">
|
|
4702
|
+
<div id="tree-pane">
|
|
4703
|
+
<div id="tree-head">Sessions</div>
|
|
4704
|
+
<div id="tree-body"><div id="tree-empty">Loading\u2026</div></div>
|
|
4705
|
+
</div>
|
|
4706
|
+
<div id="timeline-pane">
|
|
4707
|
+
<div id="timeline-head">
|
|
4708
|
+
<span class="focus-id" id="focus-id-label">\u2014</span>
|
|
4709
|
+
<select id="head-selector" title="Sessions"></select>
|
|
4710
|
+
<span id="head-summary"></span>
|
|
4711
|
+
<span id="status-line">connecting\u2026</span>
|
|
4712
|
+
</div>
|
|
4713
|
+
<div id="timeline-body"><div id="timeline-empty">No session focused yet.</div></div>
|
|
4714
|
+
<button id="new-pill" type="button">\u2193 New messages</button>
|
|
4715
|
+
</div>
|
|
4716
|
+
</div>
|
|
4717
|
+
<script>
|
|
4718
|
+
window.__APP_INIT__ = ${JSON.stringify(initData)};
|
|
4719
|
+
|
|
4720
|
+
${panelBridgeScript("agentproto-live-session")}
|
|
4721
|
+
|
|
4722
|
+
// ============================================================
|
|
4723
|
+
// INLINED REDUCER COPY \u2014 hand-kept mirror of live-session/logic.ts.
|
|
4724
|
+
// Plain JS, same semantics: coalesce consecutive text-delta of the same
|
|
4725
|
+
// session (and rejoin an unterminated mid-line fragment split by an
|
|
4726
|
+
// interleaved record \u2014 see the TS module's text-delta arm), pair
|
|
4727
|
+
// tool-call/tool-result by toolCallId, pass through turn-end, keep usage
|
|
4728
|
+
// as STATE (SPEC \xA71: usage leaves the timeline \u2014 a usage_update record
|
|
4729
|
+
// never produces a row), ignore unknown kinds. Keep in sync with the TS
|
|
4730
|
+
// module; the TS module is the one the test suite imports.
|
|
4731
|
+
// ============================================================
|
|
4732
|
+
|
|
4733
|
+
function initialTimelineState() {
|
|
4734
|
+
return { rows: [], usage: null };
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4737
|
+
function rowId(record, rows) {
|
|
4738
|
+
return record.seq != null ? (record.kind + '-' + record.seq) : (record.kind + '-' + rows.length);
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4741
|
+
// Fold a text-delta record into an existing row (fresh object), keeping the
|
|
4742
|
+
// row's "partial" hint in step with the latest record \u2014 see mergeTextDelta in
|
|
4743
|
+
// the TS module.
|
|
4744
|
+
function mergeTextDelta(row, record) {
|
|
4745
|
+
var merged = Object.assign({}, row, {
|
|
4746
|
+
text: row.text + (record.text || ''),
|
|
4747
|
+
seq: record.seq,
|
|
4748
|
+
ts: record.ts,
|
|
4749
|
+
});
|
|
4750
|
+
if (record.partial === true) merged.partial = true;
|
|
4751
|
+
else delete merged.partial;
|
|
4752
|
+
return merged;
|
|
4753
|
+
}
|
|
4754
|
+
|
|
4755
|
+
function reduceEvent(state, record) {
|
|
4756
|
+
switch (record.kind) {
|
|
4757
|
+
case 'text-delta': {
|
|
4758
|
+
var last = state.rows[state.rows.length - 1];
|
|
4759
|
+
if (last && last.kind === 'text' && last.sessionId === record.sessionId) {
|
|
4760
|
+
return { rows: state.rows.slice(0, -1).concat([mergeTextDelta(last, record)]), usage: state.usage };
|
|
4761
|
+
}
|
|
4762
|
+
// Debounce can flush an unterminated mid-word fragment (flagged
|
|
4763
|
+
// partial), let a tool-call land, then flush the continuation \u2014 look
|
|
4764
|
+
// back within the same turn (bounded by this session's last turn-end)
|
|
4765
|
+
// for that session's most recent text row and continue it in place.
|
|
4766
|
+
// Only the explicit partial flag glues: a non-partial record with no
|
|
4767
|
+
// trailing newline is the writer's normal end-of-text-block shape.
|
|
4768
|
+
for (var i = state.rows.length - 1; i >= 0; i--) {
|
|
4769
|
+
var prior = state.rows[i];
|
|
4770
|
+
if (prior.sessionId !== record.sessionId) continue;
|
|
4771
|
+
if (prior.kind === 'turn-end') break;
|
|
4772
|
+
if (prior.kind !== 'text') continue;
|
|
4773
|
+
if (prior.partial === true) {
|
|
4774
|
+
var patched = state.rows.slice();
|
|
4775
|
+
patched[i] = mergeTextDelta(prior, record);
|
|
4776
|
+
return { rows: patched, usage: state.usage };
|
|
4777
|
+
}
|
|
4778
|
+
break;
|
|
4779
|
+
}
|
|
4780
|
+
var row = {
|
|
4781
|
+
kind: 'text', id: rowId(record, state.rows), seq: record.seq, ts: record.ts,
|
|
4782
|
+
sessionId: record.sessionId, text: record.text || '',
|
|
4783
|
+
};
|
|
4784
|
+
if (record.partial === true) row.partial = true;
|
|
4785
|
+
return { rows: state.rows.concat([row]), usage: state.usage };
|
|
4786
|
+
}
|
|
4787
|
+
case 'tool-call': {
|
|
4788
|
+
var row = {
|
|
4789
|
+
kind: 'tool-call', id: rowId(record, state.rows), seq: record.seq, ts: record.ts,
|
|
4790
|
+
sessionId: record.sessionId, toolCallId: record.toolCallId || '',
|
|
4791
|
+
toolName: record.toolName || 'unknown', arguments: record.arguments, status: 'pending',
|
|
4792
|
+
};
|
|
4793
|
+
return { rows: state.rows.concat([row]), usage: state.usage };
|
|
4794
|
+
}
|
|
4795
|
+
case 'tool-result': {
|
|
4796
|
+
var idx = -1;
|
|
4797
|
+
for (var i = 0; i < state.rows.length; i++) {
|
|
4798
|
+
if (state.rows[i].kind === 'tool-call' && state.rows[i].toolCallId === record.toolCallId) idx = i;
|
|
4799
|
+
}
|
|
4800
|
+
if (idx === -1) {
|
|
4801
|
+
var row = {
|
|
4802
|
+
kind: 'tool-call', id: rowId(record, state.rows), seq: record.seq, ts: record.ts,
|
|
4803
|
+
sessionId: record.sessionId, toolCallId: record.toolCallId || '', toolName: 'unknown',
|
|
4804
|
+
status: record.isError ? 'error' : 'ok', result: record.result,
|
|
4805
|
+
};
|
|
4806
|
+
return { rows: state.rows.concat([row]), usage: state.usage };
|
|
4807
|
+
}
|
|
4808
|
+
var updated = Object.assign({}, state.rows[idx], {
|
|
4809
|
+
status: record.isError ? 'error' : 'ok', result: record.result,
|
|
4810
|
+
});
|
|
4811
|
+
var rows = state.rows.slice();
|
|
4812
|
+
rows[idx] = updated;
|
|
4813
|
+
return { rows: rows, usage: state.usage };
|
|
4814
|
+
}
|
|
4815
|
+
case 'turn-end': {
|
|
4816
|
+
var row = {
|
|
4817
|
+
kind: 'turn-end', id: rowId(record, state.rows), seq: record.seq, ts: record.ts,
|
|
4818
|
+
sessionId: record.sessionId, reason: record.reason,
|
|
4819
|
+
};
|
|
4820
|
+
return { rows: state.rows.concat([row]), usage: state.usage };
|
|
4821
|
+
}
|
|
4822
|
+
case 'usage_update': {
|
|
4823
|
+
// Usage is state, not a row (SPEC \xA71) \u2014 last-write-wins, no merge with
|
|
4824
|
+
// the prior snapshot. state.rows is reused as-is (it didn't change).
|
|
4825
|
+
return {
|
|
4826
|
+
rows: state.rows,
|
|
4827
|
+
usage: {
|
|
4828
|
+
size: record.size, used: record.used, cost: record.cost,
|
|
4829
|
+
tokensIn: record.tokensIn, tokensOut: record.tokensOut,
|
|
4830
|
+
seq: record.seq, ts: record.ts,
|
|
4831
|
+
},
|
|
4832
|
+
};
|
|
4833
|
+
}
|
|
4834
|
+
default:
|
|
4835
|
+
return state;
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
// ============================================================
|
|
4840
|
+
// INLINED PURE HELPERS \u2014 exact copies of live-session/logic.ts's
|
|
4841
|
+
// isNearBottom (SPEC \xA72) and groupAdjacentToolCalls (SPEC \xA73). Same names,
|
|
4842
|
+
// same signatures, same default thresholds; plain JS because the widget
|
|
4843
|
+
// has no import step.
|
|
4844
|
+
// ============================================================
|
|
4845
|
+
|
|
4846
|
+
var SCROLL_STICK_THRESHOLD_PX = 24;
|
|
4847
|
+
|
|
4848
|
+
function isNearBottom(scrollHeight, scrollTop, clientHeight, threshold) {
|
|
4849
|
+
if (threshold == null) threshold = SCROLL_STICK_THRESHOLD_PX;
|
|
4850
|
+
return scrollHeight - scrollTop - clientHeight <= threshold;
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4853
|
+
var TOOL_CALL_GROUP_THRESHOLD = 2;
|
|
4854
|
+
|
|
4855
|
+
// Collapse runs of \`threshold\`+ adjacent tool-call rows into one
|
|
4856
|
+
// {kind:'tool-group', rows:[...]} entry; everything else passes through as
|
|
4857
|
+
// individual {kind:'row', row} entries, same order as the input.
|
|
4858
|
+
function groupAdjacentToolCalls(rows, threshold) {
|
|
4859
|
+
if (threshold == null) threshold = TOOL_CALL_GROUP_THRESHOLD;
|
|
4860
|
+
var out = [];
|
|
4861
|
+
var run = [];
|
|
4862
|
+
function flushRun() {
|
|
4863
|
+
if (!run.length) return;
|
|
4864
|
+
if (run.length >= threshold) out.push({ kind: 'tool-group', rows: run });
|
|
4865
|
+
else for (var j = 0; j < run.length; j++) out.push({ kind: 'row', row: run[j] });
|
|
4866
|
+
run = [];
|
|
4867
|
+
}
|
|
4868
|
+
for (var i = 0; i < rows.length; i++) {
|
|
4869
|
+
if (rows[i].kind === 'tool-call') { run.push(rows[i]); continue; }
|
|
4870
|
+
flushRun();
|
|
4871
|
+
out.push({ kind: 'row', row: rows[i] });
|
|
4872
|
+
}
|
|
4873
|
+
flushRun();
|
|
4874
|
+
return out;
|
|
4875
|
+
}
|
|
4876
|
+
|
|
4877
|
+
// ============================================================
|
|
4878
|
+
// Rendering helpers
|
|
4879
|
+
// ============================================================
|
|
4880
|
+
|
|
4881
|
+
function escHtml(s) {
|
|
4882
|
+
return String(s == null ? '' : s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
4883
|
+
}
|
|
4884
|
+
|
|
4885
|
+
function safeJson(v) {
|
|
4886
|
+
try { return JSON.stringify(v, null, 2); } catch (e) { return String(v); }
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4889
|
+
function statusDotClass(status) {
|
|
4890
|
+
if (status === 'running' || status === 'starting') return 'running';
|
|
4891
|
+
if (status === 'error' || status === 'killed') return 'error';
|
|
4892
|
+
return 'grey';
|
|
4893
|
+
}
|
|
4894
|
+
|
|
4895
|
+
// SPEC \xA74 (non-contract guidance): 52524 \u2192 "52.5k", >=1e6 \u2192 "M" suffix,
|
|
4896
|
+
// one decimal, trailing .0 vanishes because the value stays a number.
|
|
4897
|
+
// cost \u2192 "$" + toFixed(2). Read from timelineState.usage.
|
|
4898
|
+
function fmtCompactNum(n) {
|
|
4899
|
+
if (typeof n !== 'number' || !isFinite(n)) return null;
|
|
4900
|
+
if (n >= 1000000) return (Math.round(n / 100000) / 10) + 'M';
|
|
4901
|
+
if (n >= 1000) return (Math.round(n / 100) / 10) + 'k';
|
|
4902
|
+
return String(n);
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
function usageChipText(usage) {
|
|
4906
|
+
if (!usage) return '';
|
|
4907
|
+
var bits = [];
|
|
4908
|
+
if (usage.used != null && usage.size != null) {
|
|
4909
|
+
bits.push(fmtCompactNum(usage.used) + '/' + fmtCompactNum(usage.size));
|
|
4910
|
+
} else if (usage.used != null) {
|
|
4911
|
+
bits.push(fmtCompactNum(usage.used));
|
|
4912
|
+
}
|
|
4913
|
+
if (typeof usage.cost === 'number') bits.push('$' + usage.cost.toFixed(2));
|
|
4914
|
+
return bits.join(' \xB7 ');
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
// WP5: elapsed seconds since the timeline's first row (running only).
|
|
4918
|
+
function elapsedText(rows, status) {
|
|
4919
|
+
if (status !== 'running' && status !== 'starting') return null;
|
|
4920
|
+
if (!rows.length || rows[0].ts == null) return null;
|
|
4921
|
+
var ms = new Date(rows[0].ts).getTime();
|
|
4922
|
+
if (!isFinite(ms)) return null;
|
|
4923
|
+
return Math.max(0, Math.round((Date.now() - ms) / 1000)) + 's';
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
// ============================================================
|
|
4927
|
+
// LEFT pane \u2014 live tree
|
|
4928
|
+
// ============================================================
|
|
4929
|
+
|
|
4930
|
+
var currentTree = [];
|
|
4931
|
+
var focusId = (window.__APP_INIT__ && window.__APP_INIT__.sessionId) || null;
|
|
4932
|
+
// Where the current focus came from: 'pinned' (the tool result that mounted
|
|
4933
|
+
// this widget named a session \u2014 never auto-switched away), 'auto' (self-
|
|
4934
|
+
// discovered newest running session, a guess the host may still correct),
|
|
4935
|
+
// 'user' (explicit click/selector choice \u2014 outranks everything).
|
|
4936
|
+
var focusSource = focusId ? 'pinned' : null;
|
|
4937
|
+
var bootDone = false;
|
|
4938
|
+
var treeTimer = null;
|
|
4939
|
+
|
|
4940
|
+
// INLINED COPY of live-session/logic.ts extractToolResultSessionId \u2014
|
|
4941
|
+
// keep in sync (same convention as the reducer copy below).
|
|
4942
|
+
function extractToolResultSessionId(params) {
|
|
4943
|
+
if (!params || typeof params !== 'object') return null;
|
|
4944
|
+
var res = (params.result && typeof params.result === 'object') ? params.result : params;
|
|
4945
|
+
if (res.isError) return null;
|
|
4946
|
+
var content = Array.isArray(res.content) ? res.content : [];
|
|
4947
|
+
var item = content[0];
|
|
4948
|
+
if (!item || item.type !== 'text' || typeof item.text !== 'string') return null;
|
|
4949
|
+
try {
|
|
4950
|
+
var body = JSON.parse(item.text);
|
|
4951
|
+
if (body && typeof body.sessionId === 'string' && body.sessionId) return body.sessionId;
|
|
4952
|
+
if (body && typeof body.id === 'string' && body.id) return body.id;
|
|
4953
|
+
} catch (_) {}
|
|
4954
|
+
return null;
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
// The host pushes the triggering tool call's result (ext-apps
|
|
4958
|
+
// ui/notifications/tool-result). For agent_start that result IS the spawned
|
|
4959
|
+
// session's descriptor \u2014 pin the widget to THAT session instead of leaving
|
|
4960
|
+
// pollTree() to self-discover the newest running one, which made every
|
|
4961
|
+
// agent_start card show the latest started session rather than its own.
|
|
4962
|
+
onHostNotification(function(method, params) {
|
|
4963
|
+
if (method !== 'ui/notifications/tool-result') return;
|
|
4964
|
+
var id = extractToolResultSessionId(params);
|
|
4965
|
+
if (!id || focusSource === 'user' || id === focusId) return;
|
|
4966
|
+
focusSource = 'pinned';
|
|
4967
|
+
if (bootDone) setFocus(id);
|
|
4968
|
+
else focusId = id; // boot's own startTimeline(focusId) picks it up
|
|
4969
|
+
});
|
|
4970
|
+
|
|
4971
|
+
function findNode(nodes, id) {
|
|
4972
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
4973
|
+
if (nodes[i].id === id) return nodes[i];
|
|
4974
|
+
var found = findNode(nodes[i].children || [], id);
|
|
4975
|
+
if (found) return found;
|
|
4976
|
+
}
|
|
4977
|
+
return null;
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4980
|
+
function flattenDfs(nodes, out) {
|
|
4981
|
+
out = out || [];
|
|
4982
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
4983
|
+
out.push(nodes[i]);
|
|
4984
|
+
flattenDfs(nodes[i].children || [], out);
|
|
4985
|
+
}
|
|
4986
|
+
return out;
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
function pickInitialFocus(tree) {
|
|
4990
|
+
var flat = flattenDfs(tree);
|
|
4991
|
+
var alive = flat.filter(function(n) { return n.status === 'running' || n.status === 'starting'; });
|
|
4992
|
+
if (alive.length) return alive[alive.length - 1].id;
|
|
4993
|
+
return tree.length ? tree[0].id : null;
|
|
4994
|
+
}
|
|
4995
|
+
|
|
4996
|
+
function renderTreeNode(node, depth) {
|
|
4997
|
+
var childrenHtml = (node.children || []).map(function(c) { return renderTreeNode(c, depth + 1); }).join('');
|
|
4998
|
+
var badge = node.isOrchestrator ? '<span class="badge">orch</span>' : '';
|
|
4999
|
+
var cls = 'tnode' + (node.id === focusId ? ' focus' : '');
|
|
5000
|
+
return '<div class="' + cls + '" data-id="' + escHtml(node.id) + '">' +
|
|
5001
|
+
'<span class="dot ' + statusDotClass(node.status) + '"></span>' +
|
|
5002
|
+
'<span class="label">' + escHtml(node.label || node.id) + '</span>' + badge +
|
|
5003
|
+
'</div>' +
|
|
5004
|
+
(childrenHtml ? '<div class="tchildren">' + childrenHtml + '</div>' : '');
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
// WP4: compact header <select> mirrors the tree; still routes through
|
|
5008
|
+
// setFocus() \u2014 the only session-switch entry point.
|
|
5009
|
+
function renderHeadSelector() {
|
|
5010
|
+
var sel = document.getElementById('head-selector');
|
|
5011
|
+
var flat = flattenDfs(currentTree);
|
|
5012
|
+
while (sel.firstChild) sel.removeChild(sel.firstChild);
|
|
5013
|
+
if (!flat.length) return;
|
|
5014
|
+
for (var i = 0; i < flat.length; i++) {
|
|
5015
|
+
var n = flat[i];
|
|
5016
|
+
var o = document.createElement('option');
|
|
5017
|
+
o.value = n.id;
|
|
5018
|
+
o.textContent = (n.id === focusId ? '\u25CF ' : '') + (n.label || n.id);
|
|
5019
|
+
sel.appendChild(o);
|
|
5020
|
+
}
|
|
5021
|
+
sel.value = focusId || '';
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
function renderTree() {
|
|
5025
|
+
var body = document.getElementById('tree-body');
|
|
5026
|
+
var root = focusId ? findNode(currentTree, focusId) : null;
|
|
5027
|
+
var renderNodes = root ? [root] : currentTree;
|
|
5028
|
+
if (!renderNodes.length) {
|
|
5029
|
+
body.innerHTML = '<div id="tree-empty">No sessions.</div>';
|
|
5030
|
+
renderHeadSelector();
|
|
5031
|
+
return;
|
|
5032
|
+
}
|
|
5033
|
+
body.innerHTML = renderNodes.map(function(n) { return renderTreeNode(n, 0); }).join('');
|
|
5034
|
+
var els = body.querySelectorAll('.tnode');
|
|
5035
|
+
els.forEach(function(el) {
|
|
5036
|
+
el.addEventListener('click', function() {
|
|
5037
|
+
var id = el.getAttribute('data-id');
|
|
5038
|
+
if (id === focusId) return;
|
|
5039
|
+
focusSource = 'user';
|
|
5040
|
+
setFocus(id);
|
|
5041
|
+
});
|
|
5042
|
+
});
|
|
5043
|
+
renderHeadSelector();
|
|
5044
|
+
}
|
|
5045
|
+
|
|
5046
|
+
function pollTree() {
|
|
5047
|
+
callTool('app_session_tree', {}).then(function(res) {
|
|
5048
|
+
currentTree = res.tree || [];
|
|
5049
|
+
if (!focusId) {
|
|
5050
|
+
focusId = pickInitialFocus(currentTree);
|
|
5051
|
+
if (focusId) { focusSource = 'auto'; startTimeline(focusId); }
|
|
5052
|
+
}
|
|
5053
|
+
renderTree();
|
|
5054
|
+
updateHeader();
|
|
5055
|
+
}).catch(function() {
|
|
5056
|
+
// Leave the last-known tree rendered; the next poll may recover.
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
|
|
5060
|
+
// ============================================================
|
|
5061
|
+
// RIGHT pane \u2014 timeline for focusId
|
|
5062
|
+
// ============================================================
|
|
5063
|
+
|
|
5064
|
+
var timelineState = initialTimelineState();
|
|
5065
|
+
var sincePtr = 0;
|
|
5066
|
+
var activeSource = null; // {type:'sse', es} | {type:'poll', timer}
|
|
5067
|
+
var compactMode = false; // WP3: hostContext.displayMode === 'inline' (or narrow)
|
|
5068
|
+
|
|
5069
|
+
function setStatus(msg) {
|
|
5070
|
+
document.getElementById('status-line').textContent = msg;
|
|
5071
|
+
}
|
|
5072
|
+
|
|
5073
|
+
function isCompact() {
|
|
5074
|
+
return (getHostContext() && getHostContext().displayMode === 'inline') ||
|
|
5075
|
+
(typeof window !== 'undefined' && window.innerWidth < 640);
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
// WP3/WP4: apply the compact/expanded split. Only re-renders the timeline
|
|
5079
|
+
// when the mode actually flips (scroll + <details> state survive otherwise).
|
|
5080
|
+
function applyDisplayMode() {
|
|
5081
|
+
var c = isCompact();
|
|
5082
|
+
document.body.classList.toggle('compact-mode', c);
|
|
5083
|
+
if (c !== compactMode) {
|
|
5084
|
+
compactMode = c;
|
|
5085
|
+
renderTimelineFull();
|
|
5086
|
+
renderTree();
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
|
|
5090
|
+
// WP5 + WP2: one header line \u2014 \`\u25CF running \xB7 3 tools \xB7 52.5k/200k \xB7 $0.04 \xB7 12s\`
|
|
5091
|
+
// (status dot from the focus node, tool count from the rows, usage from
|
|
5092
|
+
// timelineState.usage \u2014 no usage_update row anymore) + transport status.
|
|
5093
|
+
function updateHeader() {
|
|
5094
|
+
document.getElementById('focus-id-label').textContent = focusId || '\u2014';
|
|
5095
|
+
var rows = timelineState.rows || [];
|
|
5096
|
+
var usage = timelineState.usage;
|
|
5097
|
+
var tools = 0;
|
|
5098
|
+
for (var i = 0; i < rows.length; i++) if (rows[i].kind === 'tool-call') tools++;
|
|
5099
|
+
var node = focusId ? findNode(currentTree, focusId) : null;
|
|
5100
|
+
var st = node ? node.status : null;
|
|
5101
|
+
var parts = [];
|
|
5102
|
+
if (st) parts.push('<span class="sdot ' + statusDotClass(st) + '"></span>' + escHtml(st));
|
|
5103
|
+
if (tools) parts.push(tools + (tools === 1 ? ' tool' : ' tools'));
|
|
5104
|
+
var chip = usageChipText(usage);
|
|
5105
|
+
if (chip) parts.push('<span id="usage-chip" class="chip">' + escHtml(chip) + '</span>');
|
|
5106
|
+
var el = elapsedText(rows, st);
|
|
5107
|
+
if (el) parts.push(escHtml(el));
|
|
5108
|
+
document.getElementById('head-summary').innerHTML = parts.length ? parts.join(' \xB7 ') : '';
|
|
5109
|
+
}
|
|
5110
|
+
|
|
5111
|
+
function captureDetailsOpenFlags(body) {
|
|
5112
|
+
var dets = body.querySelectorAll('details');
|
|
5113
|
+
var flags = [];
|
|
5114
|
+
for (var i = 0; i < dets.length; i++) flags.push(dets[i].open);
|
|
5115
|
+
return flags;
|
|
5116
|
+
}
|
|
5117
|
+
|
|
5118
|
+
function restoreDetailsOpenFlags(body, flags) {
|
|
5119
|
+
var dets = body.querySelectorAll('details');
|
|
5120
|
+
for (var i = 0; i < dets.length && i < flags.length; i++) {
|
|
5121
|
+
if (flags[i]) dets[i].open = true;
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
|
|
5125
|
+
// Full rebuild \u2014 used ONLY for the first paint of a session, a setFocus()
|
|
5126
|
+
// session switch (SPEC \xA72: full reset is correct there), a display-mode
|
|
5127
|
+
// flip, and as the fallback for shapes the incremental patcher can't patch.
|
|
5128
|
+
// A display-mode flip re-renders the SAME content (WP3: "r\xE9actif... sans
|
|
5129
|
+
// perdre le scroll ni l'\xE9tat des <details>"), so open <details> and the
|
|
5130
|
+
// exact scroll offset (not just the at-bottom decision) survive the rebuild.
|
|
5131
|
+
function renderTimelineFull() {
|
|
5132
|
+
var body = document.getElementById('timeline-body');
|
|
5133
|
+
var wasAtBottom = isNearBottom(body.scrollHeight, body.scrollTop, body.clientHeight);
|
|
5134
|
+
var savedScrollTop = body.scrollTop;
|
|
5135
|
+
var flags = captureDetailsOpenFlags(body);
|
|
5136
|
+
body.innerHTML = buildTimelineHtml();
|
|
5137
|
+
restoreDetailsOpenFlags(body, flags);
|
|
5138
|
+
body.scrollTop = wasAtBottom ? body.scrollHeight : savedScrollTop;
|
|
5139
|
+
updateHeader();
|
|
5140
|
+
}
|
|
5141
|
+
|
|
5142
|
+
function buildTimelineHtml() {
|
|
5143
|
+
var rows = timelineState.rows;
|
|
5144
|
+
if (!rows.length) return '<div id="timeline-empty">No events yet.</div>';
|
|
5145
|
+
if (compactMode) {
|
|
5146
|
+
var entries = groupAdjacentToolCalls(rows);
|
|
5147
|
+
var html = '';
|
|
5148
|
+
for (var i = 0; i < entries.length; i++) {
|
|
5149
|
+
var e = entries[i];
|
|
5150
|
+
if (e.kind === 'tool-group') {
|
|
5151
|
+
html += '<details class="tool-group"><summary>\u25B8 ' + e.rows.length + ' tool calls</summary>' +
|
|
5152
|
+
e.rows.map(renderRow).join('') + '</details>';
|
|
5153
|
+
} else {
|
|
5154
|
+
html += renderRow(e.row);
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
return html;
|
|
5158
|
+
}
|
|
5159
|
+
return rows.map(renderRow).join('');
|
|
5160
|
+
}
|
|
5161
|
+
|
|
5162
|
+
// \u2500\u2500 WP1 incremental patch (dense mode) \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
|
|
5163
|
+
// One record, one minimal DOM mutation. The common case (text-delta merged
|
|
5164
|
+
// into the LAST rendered row) patches just that row's text node. Anything
|
|
5165
|
+
// else appends via insertAdjacentHTML. Only rare in-place replacements
|
|
5166
|
+
// (tool-result merge, partial-lookback re-merge) swap ONE element's HTML.
|
|
5167
|
+
function applyRecordToDom(prevRows) {
|
|
5168
|
+
var body = document.getElementById('timeline-body');
|
|
5169
|
+
// Capture "stuck to bottom?" BEFORE any mutation (SPEC \xA72).
|
|
5170
|
+
var wasAtBottom = isNearBottom(body.scrollHeight, body.scrollTop, body.clientHeight);
|
|
5171
|
+
var rows = timelineState.rows;
|
|
5172
|
+
|
|
5173
|
+
if (compactMode) {
|
|
5174
|
+
// Inline mode: smaller rows \u2014 full grouped rebuild, preserving
|
|
5175
|
+
// wasAtBottom/scrollTop and <details> open-state. Only touch the DOM
|
|
5176
|
+
// (and the scroll/pill decision) when the rows actually changed \u2014 a
|
|
5177
|
+
// usage_update leaves rows === prevRows, so it must fall through to the
|
|
5178
|
+
// header-only path below, same as the dense-mode branch.
|
|
5179
|
+
if (rows !== prevRows) {
|
|
5180
|
+
var flags = captureDetailsOpenFlags(body);
|
|
5181
|
+
body.innerHTML = buildTimelineHtml();
|
|
5182
|
+
restoreDetailsOpenFlags(body, flags);
|
|
5183
|
+
if (wasAtBottom) body.scrollTop = body.scrollHeight;
|
|
5184
|
+
else showNewPill();
|
|
5185
|
+
}
|
|
5186
|
+
updateHeader();
|
|
5187
|
+
return;
|
|
5188
|
+
}
|
|
5189
|
+
|
|
5190
|
+
var appended = rows.length > prevRows.length;
|
|
5191
|
+
if (prevRows.length === 0 || (appended && !samePrefix(prevRows, rows))) {
|
|
5192
|
+
// First real paint of this session (empty placeholder present) or an
|
|
5193
|
+
// unexpected shape \u2014 full reset is correct/cheapest.
|
|
5194
|
+
body.innerHTML = buildTimelineHtml();
|
|
5195
|
+
body.scrollTop = body.scrollHeight;
|
|
5196
|
+
updateHeader();
|
|
5197
|
+
return;
|
|
5198
|
+
}
|
|
5199
|
+
|
|
5200
|
+
if (appended) {
|
|
5201
|
+
body.insertAdjacentHTML('beforeend', renderRow(rows[rows.length - 1]));
|
|
5202
|
+
} else if (rows.length === prevRows.length && rows.length > 0) {
|
|
5203
|
+
// Exactly one row object replaced (tool-result merge, partial-text
|
|
5204
|
+
// re-merge via the reducer's lookback). Patch that one element only.
|
|
5205
|
+
// Scan from the tail: the hot path (a text-delta merged into the LAST
|
|
5206
|
+
// row, the dominant streaming case) is found in O(1) this way instead
|
|
5207
|
+
// of walking the whole array \u2014 the rare mid-array lookback re-merge
|
|
5208
|
+
// still resolves correctly, just slower.
|
|
5209
|
+
var idx = -1;
|
|
5210
|
+
for (var i = rows.length - 1; i >= 0; i--) {
|
|
5211
|
+
if (rows[i] !== prevRows[i]) { idx = i; break; }
|
|
5212
|
+
}
|
|
5213
|
+
var patched = false;
|
|
5214
|
+
if (idx >= 0) {
|
|
5215
|
+
var row = rows[idx];
|
|
5216
|
+
var el = body.querySelector('[data-row-id="' + escHtml(row.id) + '"]');
|
|
5217
|
+
if (el) {
|
|
5218
|
+
if (row.kind === 'text' && idx === rows.length - 1) {
|
|
5219
|
+
// High-frequency case: patch the existing text node in place.
|
|
5220
|
+
var b = el.querySelector('.body');
|
|
5221
|
+
if (b) { b.textContent = row.text; patched = true; }
|
|
5222
|
+
}
|
|
5223
|
+
if (!patched) { el.outerHTML = renderRow(row); patched = true; }
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
if (!patched) {
|
|
5227
|
+
// Multiple rows changed at once (shouldn't happen per-record) \u2014 rebuild.
|
|
5228
|
+
body.innerHTML = buildTimelineHtml();
|
|
5229
|
+
if (wasAtBottom) body.scrollTop = body.scrollHeight;
|
|
5230
|
+
updateHeader();
|
|
5231
|
+
return;
|
|
5232
|
+
}
|
|
5233
|
+
} else {
|
|
5234
|
+
// rows unchanged (usage_update) \u2014 header only.
|
|
5235
|
+
updateHeader();
|
|
5236
|
+
return;
|
|
5237
|
+
}
|
|
5238
|
+
|
|
5239
|
+
// Scroll decision AFTER the mutation: stick or leave the position alone.
|
|
5240
|
+
if (wasAtBottom) body.scrollTop = body.scrollHeight;
|
|
5241
|
+
else showNewPill();
|
|
5242
|
+
updateHeader();
|
|
5243
|
+
}
|
|
5244
|
+
|
|
5245
|
+
function samePrefix(prevRows, rows) {
|
|
5246
|
+
for (var i = 0; i < prevRows.length; i++) {
|
|
5247
|
+
if (prevRows[i] !== rows[i]) return false;
|
|
5248
|
+
}
|
|
5249
|
+
return true;
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
function showNewPill() {
|
|
5253
|
+
document.getElementById('new-pill').classList.add('show');
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
function hideNewPill() {
|
|
5257
|
+
document.getElementById('new-pill').classList.remove('show');
|
|
5258
|
+
}
|
|
5259
|
+
|
|
5260
|
+
function renderRow(row) {
|
|
5261
|
+
if (row.kind === 'text') {
|
|
5262
|
+
return '<div class="row text" data-row-id="' + escHtml(row.id) + '"><div class="rhead"><span class="kind">text</span></div>' +
|
|
5263
|
+
'<div class="body">' + escHtml(row.text) + '</div></div>';
|
|
5264
|
+
}
|
|
5265
|
+
if (row.kind === 'tool-call') {
|
|
5266
|
+
var badgeCls = row.status === 'pending' ? 'pending' : row.status;
|
|
5267
|
+
var badgeText = row.status === 'pending' ? 'pending' : (row.status === 'ok' ? 'ok' : 'error');
|
|
5268
|
+
var resultHtml = row.status !== 'pending'
|
|
5269
|
+
? '<details><summary>result</summary><pre>' + escHtml(safeJson(row.result)) + '</pre></details>'
|
|
5270
|
+
: '';
|
|
5271
|
+
return '<div class="row tool-call" data-row-id="' + escHtml(row.id) + '"><div class="rhead"><span class="kind">tool</span>' +
|
|
5272
|
+
'<span class="toolname">' + escHtml(row.toolName) + '</span>' +
|
|
5273
|
+
'<span class="status-badge ' + badgeCls + '">' + badgeText + '</span></div>' +
|
|
5274
|
+
'<details><summary>arguments</summary><pre>' + escHtml(safeJson(row.arguments)) + '</pre></details>' +
|
|
5275
|
+
resultHtml + '</div>';
|
|
5276
|
+
}
|
|
5277
|
+
if (row.kind === 'turn-end') {
|
|
5278
|
+
return '<div class="row turn-end" data-row-id="' + escHtml(row.id) + '"><div class="rhead"><span class="kind">turn end</span>' +
|
|
5279
|
+
'<span class="chip">' + escHtml(row.reason || '\u2014') + '</span></div></div>';
|
|
5280
|
+
}
|
|
5281
|
+
return '';
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
function teardownTimeline() {
|
|
5285
|
+
if (activeSource) {
|
|
5286
|
+
if (activeSource.type === 'sse' && activeSource.es) {
|
|
5287
|
+
try { activeSource.es.close(); } catch (e) {}
|
|
5288
|
+
}
|
|
5289
|
+
// Both the poll re-arm timer AND the SSE open/first-message fallback
|
|
5290
|
+
// timer must be cleared, or a focus switch within the 2.5s window leaves
|
|
5291
|
+
// a stale timer that fires startPolling for the abandoned session.
|
|
5292
|
+
if (activeSource.timer) clearTimeout(activeSource.timer);
|
|
5293
|
+
if (activeSource.fallbackTimer) clearTimeout(activeSource.fallbackTimer);
|
|
5294
|
+
}
|
|
5295
|
+
activeSource = null;
|
|
5296
|
+
}
|
|
5297
|
+
|
|
5298
|
+
function attemptSSE(id) {
|
|
5299
|
+
var settled = false;
|
|
5300
|
+
var es;
|
|
5301
|
+
try {
|
|
5302
|
+
es = new EventSource(window.__APP_INIT__.httpBaseUrl + '/sessions/' + encodeURIComponent(id) + '/events/stream');
|
|
5303
|
+
} catch (e) {
|
|
5304
|
+
startPolling(id);
|
|
5305
|
+
return;
|
|
5306
|
+
}
|
|
5307
|
+
var src = { type: 'sse', es: es, fallbackTimer: null };
|
|
5308
|
+
activeSource = src;
|
|
5309
|
+
// Every callback below re-checks focusId===id AND that src is still the
|
|
5310
|
+
// live source: a focus switch tears down src and starts a new one, so a
|
|
5311
|
+
// late open/message/error from this abandoned EventSource must be a no-op
|
|
5312
|
+
// rather than mutating the new focus's timeline or clobbering activeSource.
|
|
5313
|
+
function stale() { return activeSource !== src || focusId !== id; }
|
|
5314
|
+
src.fallbackTimer = setTimeout(function() {
|
|
5315
|
+
if (settled || stale()) return;
|
|
5316
|
+
settled = true;
|
|
5317
|
+
try { es.close(); } catch (e) {}
|
|
5318
|
+
startPolling(id);
|
|
5319
|
+
}, 2500);
|
|
5320
|
+
es.addEventListener('open', function() {
|
|
5321
|
+
if (settled || stale()) return;
|
|
5322
|
+
settled = true;
|
|
5323
|
+
clearTimeout(src.fallbackTimer);
|
|
5324
|
+
setStatus('streaming via SSE');
|
|
5325
|
+
});
|
|
5326
|
+
es.onmessage = function(evt) {
|
|
5327
|
+
if (stale()) return;
|
|
5328
|
+
if (!settled) {
|
|
5329
|
+
settled = true;
|
|
5330
|
+
clearTimeout(src.fallbackTimer);
|
|
5331
|
+
setStatus('streaming via SSE');
|
|
5332
|
+
}
|
|
5333
|
+
var rec;
|
|
5334
|
+
try { rec = JSON.parse(evt.data); } catch (e) { return; }
|
|
5335
|
+
var prev = timelineState;
|
|
5336
|
+
timelineState = reduceEvent(timelineState, rec);
|
|
5337
|
+
if (typeof rec.seq === 'number' && rec.seq > sincePtr) sincePtr = rec.seq;
|
|
5338
|
+
if (timelineState !== prev) applyRecordToDom(prev.rows);
|
|
5339
|
+
};
|
|
5340
|
+
es.onerror = function() {
|
|
5341
|
+
if (stale()) return;
|
|
5342
|
+
if (!settled) {
|
|
5343
|
+
settled = true;
|
|
5344
|
+
clearTimeout(src.fallbackTimer);
|
|
5345
|
+
try { es.close(); } catch (e) {}
|
|
5346
|
+
startPolling(id);
|
|
5347
|
+
} else {
|
|
5348
|
+
// Do not leave EventSource to perform its native reconnect: this
|
|
5349
|
+
// endpoint replays from since=0 when no cursor is provided, so a
|
|
5350
|
+
// transparent reconnect would duplicate every row already reduced.
|
|
5351
|
+
// The bridge poll resumes from sincePtr instead and preserves the
|
|
5352
|
+
// exactly-once cursor contract.
|
|
5353
|
+
try { es.close(); } catch (e) {}
|
|
5354
|
+
startPolling(id);
|
|
5355
|
+
}
|
|
5356
|
+
};
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
function startPolling(id) {
|
|
5360
|
+
activeSource = { type: 'poll', timer: null };
|
|
5361
|
+
setStatus('polling');
|
|
5362
|
+
function tick() {
|
|
5363
|
+
if (!activeSource || activeSource.type !== 'poll' || focusId !== id) return;
|
|
5364
|
+
callTool('app_session_events', { sessionId: id, since: sincePtr }).then(function(res) {
|
|
5365
|
+
var events = res.events || [];
|
|
5366
|
+
for (var i = 0; i < events.length; i++) {
|
|
5367
|
+
var prev = timelineState;
|
|
5368
|
+
timelineState = reduceEvent(timelineState, events[i]);
|
|
5369
|
+
if (timelineState !== prev) applyRecordToDom(prev.rows);
|
|
5370
|
+
}
|
|
5371
|
+
if (typeof res.nextSeq === 'number') sincePtr = res.nextSeq;
|
|
5372
|
+
setStatus('polling');
|
|
5373
|
+
if (activeSource) activeSource.timer = setTimeout(tick, 1500);
|
|
5374
|
+
}).catch(function() {
|
|
5375
|
+
setStatus('disconnected');
|
|
5376
|
+
if (activeSource) activeSource.timer = setTimeout(tick, 1500);
|
|
5377
|
+
});
|
|
5378
|
+
}
|
|
5379
|
+
tick();
|
|
5380
|
+
}
|
|
5381
|
+
|
|
5382
|
+
function startTimeline(id) {
|
|
5383
|
+
teardownTimeline();
|
|
5384
|
+
timelineState = initialTimelineState();
|
|
5385
|
+
sincePtr = 0;
|
|
5386
|
+
hideNewPill();
|
|
5387
|
+
renderTimelineFull();
|
|
5388
|
+
setStatus('connecting\u2026');
|
|
5389
|
+
attemptSSE(id);
|
|
5390
|
+
}
|
|
5391
|
+
|
|
5392
|
+
function setFocus(id) {
|
|
5393
|
+
focusId = id;
|
|
5394
|
+
renderTree();
|
|
5395
|
+
startTimeline(id);
|
|
5396
|
+
}
|
|
5397
|
+
|
|
5398
|
+
// ============================================================
|
|
5399
|
+
// Boot
|
|
5400
|
+
// ============================================================
|
|
5401
|
+
|
|
5402
|
+
initBridge().then(function() {
|
|
5403
|
+
var init = window.__APP_INIT__ || {};
|
|
5404
|
+
if (init.httpBaseUrl) return init;
|
|
5405
|
+
// Static ui:// resources render once at server-registration time with
|
|
5406
|
+
// EMPTY initData (mcp-apps-adapter.ts registerMcpApps) \u2014 fall back to
|
|
5407
|
+
// calling the tool ourselves over the bridge, same as the other panels.
|
|
5408
|
+
return callTool('live_session', {}).then(function(result) {
|
|
5409
|
+
window.__APP_INIT__ = result;
|
|
5410
|
+
return result;
|
|
5411
|
+
});
|
|
5412
|
+
}).then(function(init) {
|
|
5413
|
+
// Only override the focus when live_session itself named a session AND a
|
|
5414
|
+
// tool-result notification didn't already pin one while the fallback
|
|
5415
|
+
// tools/call above was in flight \u2014 the host's push names the exact
|
|
5416
|
+
// session this widget was mounted for, so it outranks the fallback's
|
|
5417
|
+
// (argument-less, hence session-less in practice) self-call.
|
|
5418
|
+
if (init.sessionId && focusSource !== 'pinned') {
|
|
5419
|
+
focusId = init.sessionId;
|
|
5420
|
+
focusSource = 'pinned';
|
|
5421
|
+
}
|
|
5422
|
+
onHostContext(function() {
|
|
5423
|
+
applyDisplayMode();
|
|
5424
|
+
updateHeader();
|
|
5425
|
+
});
|
|
5426
|
+
document.getElementById('head-selector').addEventListener('change', function(evt) {
|
|
5427
|
+
var id = evt.target.value;
|
|
5428
|
+
if (id && id !== focusId) { focusSource = 'user'; setFocus(id); }
|
|
5429
|
+
});
|
|
5430
|
+
document.getElementById('new-pill').addEventListener('click', function() {
|
|
5431
|
+
var body = document.getElementById('timeline-body');
|
|
5432
|
+
body.scrollTop = body.scrollHeight;
|
|
5433
|
+
hideNewPill();
|
|
5434
|
+
});
|
|
5435
|
+
document.getElementById('timeline-body').addEventListener('scroll', function() {
|
|
5436
|
+
// User scrolled back near the bottom \u2014 the pill is stale, hide it.
|
|
5437
|
+
var el = document.getElementById('timeline-body');
|
|
5438
|
+
if (isNearBottom(el.scrollHeight, el.scrollTop, el.clientHeight)) hideNewPill();
|
|
5439
|
+
});
|
|
5440
|
+
window.addEventListener('resize', function() {
|
|
5441
|
+
var c = isCompact();
|
|
5442
|
+
if (c !== compactMode) { applyDisplayMode(); }
|
|
5443
|
+
});
|
|
5444
|
+
applyDisplayMode();
|
|
5445
|
+
bootDone = true;
|
|
5446
|
+
pollTree();
|
|
5447
|
+
treeTimer = setInterval(pollTree, 2000);
|
|
5448
|
+
setInterval(updateHeader, 1000); // keep the WP5 elapsed clock fresh
|
|
5449
|
+
if (focusId) startTimeline(focusId);
|
|
5450
|
+
}).catch(function(e) {
|
|
5451
|
+
setStatus('Bridge error: ' + e.message);
|
|
5452
|
+
});
|
|
5453
|
+
</script>
|
|
5454
|
+
</body>
|
|
5455
|
+
</html>`;
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
// src/live-session/index.ts
|
|
5459
|
+
var liveSessionInputSchema = z.object({
|
|
5460
|
+
sessionId: z.string().optional().describe(
|
|
5461
|
+
"Attach the widget to an existing session by id or name. Omit to self-discover the newest running session from the tree."
|
|
5462
|
+
)
|
|
5463
|
+
});
|
|
5464
|
+
function makeLiveSessionApp(ops) {
|
|
5465
|
+
const httpBaseUrl = ops?.httpBaseUrl ?? "http://127.0.0.1:18790";
|
|
5466
|
+
const httpOrigin = new URL(httpBaseUrl).origin;
|
|
5467
|
+
return {
|
|
5468
|
+
id: "live_session",
|
|
5469
|
+
title: "Live Session",
|
|
5470
|
+
description: "Open the live session widget \u2014 a two-pane view of a running agent session: a live tree on the left, a streaming timeline (text, tool calls/results, turn-end) on the right. Omit `sessionId` to attach to the newest running session; pass one to attach directly.",
|
|
5471
|
+
inputSchema: liveSessionInputSchema,
|
|
5472
|
+
execute: async (input) => ({
|
|
5473
|
+
sessionId: input.sessionId,
|
|
5474
|
+
httpBaseUrl
|
|
5475
|
+
}),
|
|
5476
|
+
html: (initData) => LIVE_SESSION_HTML(initData),
|
|
5477
|
+
csp: { connectDomains: [httpOrigin] }
|
|
5478
|
+
};
|
|
5479
|
+
}
|
|
5480
|
+
var DEFAULT_HTTP_BASE_URL = "http://127.0.0.1:18790";
|
|
5481
|
+
var liveSessionApp = defineApp({
|
|
5482
|
+
id: "@agentproto/live-session",
|
|
5483
|
+
name: "Live Session",
|
|
5484
|
+
description: "Open the live session widget \u2014 a two-pane view of a running agent session: a live tree on the left, a streaming timeline (text, tool calls/results, turn-end) on the right.",
|
|
5485
|
+
agents: [],
|
|
5486
|
+
ui: {
|
|
5487
|
+
html: LIVE_SESSION_HTML({ httpBaseUrl: DEFAULT_HTTP_BASE_URL }),
|
|
5488
|
+
title: "Live Session",
|
|
5489
|
+
tools: ["app_session_tree", "app_session_events", "live_session"]
|
|
5490
|
+
}
|
|
5491
|
+
});
|
|
5492
|
+
|
|
5493
|
+
export { agentsOverviewApp, bureauSessionsApp, codeTeam, contentTeam, liveSessionApp, mailTriage, makeAgentsOverviewApp, makeBureauSessionsApp, makeLiveSessionApp, makeSessionStoryPanelApp, makeSessionsPanelApp, mediaViewer, opsPanel, panelBridgeScript, sessionStoryApp, sessionViewer, sessionsPanelApp };
|
|
2871
5494
|
//# sourceMappingURL=index.mjs.map
|
|
2872
5495
|
//# sourceMappingURL=index.mjs.map
|