@lelouchhe/webagent 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.html +4 -4
- package/dist/js/app.KFQAFHA3.js +4 -0
- package/dist/js/{chunk.IJM5DBCO.js → chunk.3CCHZCG7.js} +10 -10
- package/dist/js/chunk.UMQMOGWO.js +1 -0
- package/dist/js/{login.PYIK52HN.js → login.2WA6DTGM.js} +1 -1
- package/dist/js/viewer.T32M5AZZ.js +1 -0
- package/dist/login.html +2 -2
- package/dist/share-viewer.html +4 -4
- package/dist/{styles.00xfh3e6.css → styles.01ky3cex.css} +8 -0
- package/lib/agent-commands.js +27 -0
- package/lib/bridge.js +20 -7
- package/lib/event-handler.js +18 -1
- package/lib/http-status.js +17 -0
- package/lib/model-picker.js +1 -1
- package/lib/push-service.js +2 -1
- package/lib/routes.js +294 -168
- package/lib/session-manager.js +44 -2
- package/lib/share/routes.js +106 -72
- package/lib/share/sanitize.js +2 -1
- package/package.json +2 -2
- package/dist/js/app.3OEVQXHK.js +0 -4
- package/dist/js/chunk.D4ZYHJAM.js +0 -1
- package/dist/js/viewer.FCSSTUVY.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var s=Object.freeze({OK:200,CREATED:201,ACCEPTED:202,NO_CONTENT:204,BAD_REQUEST:400,UNAUTHORIZED:401,FORBIDDEN:403,NOT_FOUND:404,CONFLICT:409,GONE:410,PAYLOAD_TOO_LARGE:413,UNPROCESSABLE_CONTENT:422,INTERNAL_SERVER_ERROR:500,NOT_IMPLEMENTED:501,SERVICE_UNAVAILABLE:503});var c="wa_token";async function p(n,a={}){let i=a.fetch??fetch,o=n.trim();if(!o)return{ok:!1,error:"Token is required"};let r;try{r=await i("/api/v1/auth/verify",{method:"GET",headers:{Authorization:`Bearer ${o}`}})}catch(e){return{ok:!1,error:`Network error: ${e instanceof Error?e.message:String(e)}`}}if(r.status===s.OK){let e;try{e=await r.json()}catch{return{ok:!1,error:"Server returned invalid response"}}if(!e.ok||typeof e.name!="string"||typeof e.scope!="string")return{ok:!1,error:"Server returned malformed response"};try{localStorage.setItem(c,o)}catch(t){return{ok:!1,error:`Could not store token: ${t instanceof Error?t.message:String(t)}`}}return{ok:!0,name:e.name,scope:e.scope}}return r.status===s.UNAUTHORIZED?{ok:!1,error:"Invalid or rejected token"}:{ok:!1,error:`Unexpected response (status ${r.status})`}}export{s as a,c as b,p as c};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{b as r,c as i}from"./chunk.UMQMOGWO.js";import"./chunk.AJZBJBMO.js";localStorage.getItem(r)&&location.replace("/");var s=document.getElementById("login-form"),n=document.getElementById("token-input"),o=document.getElementById("error"),t=document.getElementById("submit-btn");function u(e){o&&(o.textContent=e,o.style.display=e?"block":"none")}s&&n&&t&&(s.addEventListener("submit",e=>{e.preventDefault(),u(""),t.disabled=!0,t.textContent="Verifying\u2026",(async()=>{try{let l=await i(n.value);l.ok?location.replace("/"):u(l.error)}finally{t.disabled=!1,t.textContent="Sign in"}})()}),n.focus());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as f,k as g,l as h}from"./chunk.3CCHZCG7.js";import{a as u}from"./chunk.UMQMOGWO.js";import"./chunk.AJZBJBMO.js";var E=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function m(t){return String(t).padStart(2,"0")}function p(t,r){if(!t)return"";let s=new Date(t),a=s.getTime();if(isNaN(a))return"";let l=Math.floor((r.getTime()-a)/1e3);if(l<60)return"just now";let i=Math.floor(l/60);if(i<60)return`${i}m ago`;let o=Math.floor(i/60);if(o<24)return`${o}h ago`;let e=Math.floor(o/24);if(e<7)return`${e}d ago`;let n=s.getUTCFullYear()===r.getUTCFullYear(),c=E[s.getUTCMonth()],d=s.getUTCDate();return n?`${c} ${d}`:`${c} ${d}, ${s.getUTCFullYear()}`}function y(t){if(!t)return"";let r=new Date(t);return isNaN(r.getTime())?"":`${r.getUTCFullYear()}-${m(r.getUTCMonth()+1)}-${m(r.getUTCDate())} ${m(r.getUTCHours())}:${m(r.getUTCMinutes())} UTC`}function T(t){let r=/^\/api\/v1\/sessions\/[^/]+\/attachments\/([A-Za-z0-9._-]+)(?:\?.*)?$/;return s=>{let a=r.exec(s);return a?`/s/${encodeURIComponent(t)}/attachments/${a[1]}`:s}}function C(t){if(typeof t.data=="string")try{return JSON.parse(t.data)}catch{return{}}return t.data}function v(t,r,s){let a=new Set;for(let n of t)if(n.type==="permission_response")try{let c=JSON.parse(n.data);typeof c.requestId=="string"&&a.add(c.requestId)}catch{}let l=new Map,i=new Map,o=null,e={rewriteAttachmentSrc:T(s),enhanceMarkdown:f,findToolCallEl:n=>l.get(n)??null,findPermissionEl:n=>i.get(n)??null,findBashEl:()=>o,isPermissionResolved:n=>a.has(n)};for(let n of t){if(!g(n.type))continue;let c=C(n),d=h(n.type,c,e);d?(r.appendChild(d),n.type==="tool_call"&&typeof c.id=="string"?l.set(c.id,d):n.type==="permission_request"&&typeof c.requestId=="string"?i.set(c.requestId,d):n.type==="bash_command"&&(o=d)):n.type==="bash_result"&&(o=null)}}async function S(){try{let e=localStorage.getItem("theme")??"auto";document.documentElement.setAttribute("data-theme",e)}catch{}let t=/^\/s\/([A-Za-z0-9_-]{24})(?:[/?#]|$)/.exec(location.pathname);if(!t){document.body.textContent="invalid share URL";return}let r=t[1],s=document.getElementById("session-info"),a=document.getElementById("messages"),l=document.querySelector(".share-footer-author"),i=document.querySelector(".share-footer-meta");if(!a)return;let o;try{let e=await fetch(`/api/v1/shared/${encodeURIComponent(r)}/events`,{headers:{Accept:"application/json"},credentials:"omit"});if(e.status===u.GONE){document.body.innerHTML="<div class='share-gone'>\u6B64\u94FE\u63A5\u5DF2\u64A4\u9500\u6216\u8FC7\u671F\u3002</div>";return}if(!e.ok){document.body.textContent=`error ${e.status}`;return}o=await e.json()}catch(e){document.body.textContent=`failed to load: ${String(e)}`;return}if(document.title=o.share.session_title?`${o.share.session_title} \u2014 shared`:"shared session",s&&(s.textContent=o.share.session_title??"(untitled)"),l){let e=o.share.display_name??"";l.textContent=e?`by ${e}`:""}if(v(o.events,a,r),i){let e=o.share.shared_at??o.share.created_at;i.textContent=p(e,new Date),i instanceof HTMLElement&&(i.title=y(e))}}S();
|
package/dist/login.html
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
media="(prefers-color-scheme: light)"
|
|
18
18
|
/>
|
|
19
19
|
<script src="/theme-init.js"></script>
|
|
20
|
-
<link rel="stylesheet" href="/styles.
|
|
20
|
+
<link rel="stylesheet" href="/styles.01ky3cex.css" />
|
|
21
21
|
</head>
|
|
22
22
|
<body class="login-page">
|
|
23
23
|
<div class="login-card">
|
|
@@ -44,6 +44,6 @@
|
|
|
44
44
|
<code>webagent --create-token <name></code> on the server.
|
|
45
45
|
</p>
|
|
46
46
|
</div>
|
|
47
|
-
<script type="module" src="/js/login.
|
|
47
|
+
<script type="module" src="/js/login.2WA6DTGM.js"></script>
|
|
48
48
|
</body>
|
|
49
49
|
</html>
|
package/dist/share-viewer.html
CHANGED
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
media="(prefers-color-scheme: light)"
|
|
17
17
|
/>
|
|
18
18
|
<meta name="robots" content="noindex, nofollow" />
|
|
19
|
-
<link rel="stylesheet" href="/s/_/styles.
|
|
19
|
+
<link rel="stylesheet" href="/s/_/styles.01ky3cex.css" />
|
|
20
20
|
<link rel="stylesheet" href="/s/_/share-viewer.00gubshk.css" />
|
|
21
|
-
<link rel="modulepreload" href="/s/_/chunk.
|
|
22
|
-
<link rel="modulepreload" href="/s/_/chunk.
|
|
21
|
+
<link rel="modulepreload" href="/s/_/chunk.3CCHZCG7.js">
|
|
22
|
+
<link rel="modulepreload" href="/s/_/chunk.UMQMOGWO.js">
|
|
23
23
|
<link rel="modulepreload" href="/s/_/chunk.AJZBJBMO.js">
|
|
24
24
|
</head>
|
|
25
25
|
<body class="share-viewer">
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
>
|
|
50
50
|
</div>
|
|
51
51
|
</footer>
|
|
52
|
-
<script type="module" src="/s/_/viewer.
|
|
52
|
+
<script type="module" src="/s/_/viewer.T32M5AZZ.js"></script>
|
|
53
53
|
</body>
|
|
54
54
|
</html>
|
|
@@ -177,6 +177,7 @@ body {
|
|
|
177
177
|
display: flex;
|
|
178
178
|
flex-direction: column;
|
|
179
179
|
gap: 4px;
|
|
180
|
+
overflow-anchor: none;
|
|
180
181
|
}
|
|
181
182
|
#messages > * {
|
|
182
183
|
min-width: 0;
|
|
@@ -1005,10 +1006,17 @@ body {
|
|
|
1005
1006
|
|
|
1006
1007
|
/* History pagination sentinel */
|
|
1007
1008
|
.history-sentinel {
|
|
1009
|
+
height: 1px;
|
|
1010
|
+
padding: 0;
|
|
1011
|
+
opacity: 0;
|
|
1012
|
+
pointer-events: none;
|
|
1013
|
+
}
|
|
1014
|
+
.history-loading {
|
|
1008
1015
|
text-align: center;
|
|
1009
1016
|
padding: 8px;
|
|
1010
1017
|
opacity: 0.5;
|
|
1011
1018
|
font-size: 0.85em;
|
|
1019
|
+
pointer-events: none;
|
|
1012
1020
|
}
|
|
1013
1021
|
|
|
1014
1022
|
/* Utility: dim out cancelled / settled / past states. Used in dynamic innerHTML
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function splitFirstToken(text) {
|
|
2
|
+
const boundary = text.search(/\s/);
|
|
3
|
+
if (boundary < 0)
|
|
4
|
+
return { token: text, remainder: "" };
|
|
5
|
+
return {
|
|
6
|
+
token: text.slice(0, boundary),
|
|
7
|
+
remainder: text.slice(boundary),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function resolveAgentCommand(text, commands) {
|
|
11
|
+
if (!text.startsWith("//"))
|
|
12
|
+
return null;
|
|
13
|
+
const { token, remainder } = splitFirstToken(text);
|
|
14
|
+
const inputName = token.slice(2);
|
|
15
|
+
if (!inputName)
|
|
16
|
+
return null;
|
|
17
|
+
const command = commands.find((candidate) => candidate.name.toLowerCase() === inputName.toLowerCase());
|
|
18
|
+
if (!command)
|
|
19
|
+
return null;
|
|
20
|
+
return {
|
|
21
|
+
command: token,
|
|
22
|
+
agentText: `/${command.name}${remainder}`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function agentCommandToken(text) {
|
|
26
|
+
return splitFirstToken(text).token || text;
|
|
27
|
+
}
|
package/lib/bridge.js
CHANGED
|
@@ -95,10 +95,10 @@ export class AgentBridge extends EventEmitter {
|
|
|
95
95
|
async newSession(cwd, opts) {
|
|
96
96
|
if (!this.conn)
|
|
97
97
|
throw new Error("Not connected");
|
|
98
|
-
const session =
|
|
98
|
+
const session = await this.conn.newSession({
|
|
99
99
|
cwd,
|
|
100
100
|
mcpServers: [],
|
|
101
|
-
})
|
|
101
|
+
});
|
|
102
102
|
const configOptions = (session.configOptions ??
|
|
103
103
|
[]);
|
|
104
104
|
if (!opts?.silent) {
|
|
@@ -116,11 +116,11 @@ export class AgentBridge extends EventEmitter {
|
|
|
116
116
|
throw new Error("Not connected");
|
|
117
117
|
let session;
|
|
118
118
|
try {
|
|
119
|
-
session =
|
|
119
|
+
session = await this.conn.loadSession({
|
|
120
120
|
sessionId,
|
|
121
121
|
cwd,
|
|
122
122
|
mcpServers: [],
|
|
123
|
-
})
|
|
123
|
+
});
|
|
124
124
|
}
|
|
125
125
|
catch (err) {
|
|
126
126
|
// -32002 = Resource not found. Some agents (e.g. claude-agent-acp) don't
|
|
@@ -147,11 +147,13 @@ export class AgentBridge extends EventEmitter {
|
|
|
147
147
|
async setConfigOption(sessionId, configId, value) {
|
|
148
148
|
if (!this.conn)
|
|
149
149
|
throw new Error("Not connected");
|
|
150
|
-
const result =
|
|
150
|
+
const result = await this.conn.setSessionConfigOption({
|
|
151
151
|
sessionId,
|
|
152
152
|
configId,
|
|
153
|
-
value
|
|
154
|
-
|
|
153
|
+
...(typeof value === "boolean"
|
|
154
|
+
? { type: "boolean", value }
|
|
155
|
+
: { value }),
|
|
156
|
+
});
|
|
155
157
|
return result.configOptions;
|
|
156
158
|
}
|
|
157
159
|
async prompt(sessionId, text, attachments) {
|
|
@@ -257,6 +259,7 @@ export class AgentBridge extends EventEmitter {
|
|
|
257
259
|
});
|
|
258
260
|
abort(new Error(reason));
|
|
259
261
|
}
|
|
262
|
+
this.emit("event", { type: "agent_disconnected" });
|
|
260
263
|
this.conn = null;
|
|
261
264
|
}
|
|
262
265
|
/** Send a prompt and collect the full text response without emitting events. */
|
|
@@ -497,6 +500,16 @@ export class AgentBridge extends EventEmitter {
|
|
|
497
500
|
configOptions: update
|
|
498
501
|
.configOptions ?? [],
|
|
499
502
|
};
|
|
503
|
+
case "available_commands_update":
|
|
504
|
+
return {
|
|
505
|
+
type: "available_commands_update",
|
|
506
|
+
sessionId,
|
|
507
|
+
commands: update.availableCommands.map((command) => ({
|
|
508
|
+
name: command.name,
|
|
509
|
+
description: command.description,
|
|
510
|
+
...(command.input ? { input: { hint: command.input.hint } } : {}),
|
|
511
|
+
})),
|
|
512
|
+
};
|
|
500
513
|
default:
|
|
501
514
|
return null;
|
|
502
515
|
}
|
package/lib/event-handler.js
CHANGED
|
@@ -14,7 +14,9 @@ function handleConfigLikeEvent(event, sessions, store) {
|
|
|
14
14
|
if (event.configOptions.length)
|
|
15
15
|
sessions.cachedConfigOptions = event.configOptions;
|
|
16
16
|
for (const opt of event.configOptions) {
|
|
17
|
-
|
|
17
|
+
if (typeof opt.currentValue === "string") {
|
|
18
|
+
store.updateSessionConfig(event.sessionId, opt.id, opt.currentValue);
|
|
19
|
+
}
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
function handleMessageChunk(event, sessions) {
|
|
@@ -211,6 +213,21 @@ function maybePushNotify(event, pushService) {
|
|
|
211
213
|
});
|
|
212
214
|
}
|
|
213
215
|
export function handleAgentEvent(event, sessions, store, bridge, config, sseManager, pushService, _clientRegistry) {
|
|
216
|
+
if (event.type === "available_commands_update") {
|
|
217
|
+
const snapshot = sessions.updateAgentCommands(event.sessionId, event.commands);
|
|
218
|
+
if (sessions.restoringSessions.has(event.sessionId))
|
|
219
|
+
return;
|
|
220
|
+
sseManager.broadcast({ ...event, ...snapshot });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (event.type === "agent_reloading" || event.type === "agent_disconnected") {
|
|
224
|
+
for (const snapshot of sessions.clearAgentCommands()) {
|
|
225
|
+
sseManager.broadcast({
|
|
226
|
+
type: "available_commands_update",
|
|
227
|
+
...snapshot,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
214
231
|
if ("sessionId" in event &&
|
|
215
232
|
event.sessionId &&
|
|
216
233
|
sessions.restoringSessions.has(event.sessionId))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const HTTP_STATUS = Object.freeze({
|
|
2
|
+
OK: 200,
|
|
3
|
+
CREATED: 201,
|
|
4
|
+
ACCEPTED: 202,
|
|
5
|
+
NO_CONTENT: 204,
|
|
6
|
+
BAD_REQUEST: 400,
|
|
7
|
+
UNAUTHORIZED: 401,
|
|
8
|
+
FORBIDDEN: 403,
|
|
9
|
+
NOT_FOUND: 404,
|
|
10
|
+
CONFLICT: 409,
|
|
11
|
+
GONE: 410,
|
|
12
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
13
|
+
UNPROCESSABLE_CONTENT: 422,
|
|
14
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
15
|
+
NOT_IMPLEMENTED: 501,
|
|
16
|
+
SERVICE_UNAVAILABLE: 503,
|
|
17
|
+
});
|
package/lib/model-picker.js
CHANGED
|
@@ -5,7 +5,7 @@ export function pickModelByPatterns(configOptions, patterns) {
|
|
|
5
5
|
.filter((p) => p.length > 0);
|
|
6
6
|
if (normalized.length === 0)
|
|
7
7
|
return null;
|
|
8
|
-
const modelOpt = configOptions.find((c) => c.id === "model");
|
|
8
|
+
const modelOpt = configOptions.find((c) => c.id === "model" && "options" in c);
|
|
9
9
|
if (!modelOpt || modelOpt.options.length === 0)
|
|
10
10
|
return null;
|
|
11
11
|
for (const pattern of normalized) {
|
package/lib/push-service.js
CHANGED
|
@@ -3,6 +3,7 @@ import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { log } from "./log.js";
|
|
6
|
+
import { HTTP_STATUS } from "./http-status.js";
|
|
6
7
|
const slog = log.scope("push");
|
|
7
8
|
const elog = log.scope("egress");
|
|
8
9
|
const VAPID_FILE = "vapid.json";
|
|
@@ -267,7 +268,7 @@ export class PushService {
|
|
|
267
268
|
else {
|
|
268
269
|
fail++;
|
|
269
270
|
const err = result.reason;
|
|
270
|
-
if (err.statusCode ===
|
|
271
|
+
if (err.statusCode === HTTP_STATUS.GONE) {
|
|
271
272
|
fail410++;
|
|
272
273
|
this.store.removeSubscription(endpoint);
|
|
273
274
|
this.failureCounts.delete(endpoint);
|