@lelouchhe/webagent 0.3.0 → 0.4.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/README.md +58 -23
- package/bin/webagent.mjs +119 -8
- package/config.toml +96 -3
- package/dist/index.html +64 -41
- package/dist/js/app.GSAIYHML.js +4 -0
- package/dist/js/chunk.AJZBJBMO.js +1 -0
- package/dist/js/chunk.CGWFHJI2.js +76 -0
- package/dist/js/chunk.D4ZYHJAM.js +1 -0
- package/dist/js/chunk.VZXGXFNN.js +5 -0
- package/dist/js/login.PYIK52HN.js +1 -0
- package/dist/js/viewer.6DT53STL.js +1 -0
- package/dist/login.html +49 -0
- package/dist/share-viewer.00gubshk.css +114 -0
- package/dist/share-viewer.html +53 -0
- package/dist/styles.012p32dz.css +1443 -0
- package/dist/sw.js +79 -27
- package/dist/theme-init.js +6 -0
- package/lib/agent-detect.js +110 -0
- package/lib/atomic-write.js +50 -0
- package/lib/attachment-dispatch.js +86 -0
- package/lib/attachment-interceptor.js +130 -0
- package/lib/attachment-labels.js +139 -0
- package/lib/attachments.js +154 -0
- package/lib/auth-middleware.js +102 -0
- package/lib/auth-store.js +269 -0
- package/lib/auth.js +89 -0
- package/lib/bootstrap.js +70 -0
- package/lib/bridge.js +244 -93
- package/lib/client-registry.js +60 -0
- package/lib/config.js +123 -9
- package/lib/daemon.js +175 -41
- package/lib/event-handler.js +209 -91
- package/lib/log-fmt.js +67 -0
- package/lib/log.js +83 -0
- package/lib/message-cleanup.js +48 -0
- package/lib/mode-bucket.js +62 -0
- package/lib/preflight.js +195 -0
- package/lib/push-service.js +338 -45
- package/lib/routes.js +1202 -144
- package/lib/server.js +149 -33
- package/lib/session-manager.js +164 -18
- package/lib/session-state.js +160 -0
- package/lib/sessions-anchor.js +28 -0
- package/lib/share/cleanup.js +45 -0
- package/lib/share/routes.js +972 -0
- package/lib/share/sanitize.js +179 -0
- package/lib/sse-manager.js +94 -8
- package/lib/sse-ticket.js +45 -0
- package/lib/startup-checks.js +94 -0
- package/lib/store.js +624 -30
- package/lib/title-service.js +42 -9
- package/lib/tokens.js +50 -0
- package/lib/types.js +23 -0
- package/package.json +38 -4
- package/dist/js/app.2562YGRO.js +0 -10
- package/dist/styles.008ve1hx.css +0 -669
- package/lib/shared/constants.js +0 -17
package/lib/title-service.js
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
import { log } from "./log.js";
|
|
2
|
+
const tlog = log.scope("title");
|
|
2
3
|
export class TitleService {
|
|
3
4
|
titleSessionId = null;
|
|
4
5
|
activeSourceSessions = new Set();
|
|
5
6
|
cancelledSourceSessions = new Set();
|
|
6
7
|
defaultCwd;
|
|
8
|
+
modelPatterns;
|
|
7
9
|
store;
|
|
8
10
|
sessions;
|
|
9
|
-
constructor(store, sessions, defaultCwd) {
|
|
11
|
+
constructor(store, sessions, defaultCwd, modelPatterns = []) {
|
|
10
12
|
this.store = store;
|
|
11
13
|
this.sessions = sessions;
|
|
12
14
|
this.defaultCwd = defaultCwd;
|
|
15
|
+
// Lowercase + drop empty strings for case-insensitive substring match.
|
|
16
|
+
this.modelPatterns = modelPatterns
|
|
17
|
+
.map((p) => p.trim().toLowerCase())
|
|
18
|
+
.filter((p) => p.length > 0);
|
|
13
19
|
}
|
|
14
20
|
/** Generate a title for the session (non-blocking, fire-and-forget). */
|
|
15
21
|
generate(bridge, userMessage, sessionId, onTitle) {
|
|
16
|
-
if (this.sessions.sessionHasTitle.has(sessionId) ||
|
|
22
|
+
if (this.sessions.sessionHasTitle.has(sessionId) ||
|
|
23
|
+
this.activeSourceSessions.has(sessionId))
|
|
17
24
|
return;
|
|
18
|
-
this._generate(bridge, userMessage, sessionId)
|
|
25
|
+
this._generate(bridge, userMessage, sessionId)
|
|
26
|
+
.then((title) => {
|
|
19
27
|
if (title && onTitle)
|
|
20
28
|
onTitle(title);
|
|
21
|
-
})
|
|
22
|
-
|
|
29
|
+
})
|
|
30
|
+
.catch((err) => {
|
|
31
|
+
tlog.error("generation failed", { error: err });
|
|
23
32
|
});
|
|
24
33
|
}
|
|
25
34
|
async _generate(bridge, userMessage, sessionId) {
|
|
@@ -38,7 +47,10 @@ export class TitleService {
|
|
|
38
47
|
// User may have set a title while generation was in flight
|
|
39
48
|
if (this.sessions.sessionHasTitle.has(sessionId))
|
|
40
49
|
return;
|
|
41
|
-
const cleaned = title
|
|
50
|
+
const cleaned = title
|
|
51
|
+
.replace(/^["']|["']$/g, "")
|
|
52
|
+
.trim()
|
|
53
|
+
.slice(0, 30);
|
|
42
54
|
if (!cleaned)
|
|
43
55
|
return;
|
|
44
56
|
this.store.updateSessionTitle(sessionId, cleaned);
|
|
@@ -65,9 +77,16 @@ export class TitleService {
|
|
|
65
77
|
if (this.titleSessionId)
|
|
66
78
|
return this.titleSessionId;
|
|
67
79
|
try {
|
|
68
|
-
const id = await bridge.newSession(this.defaultCwd, { silent: true });
|
|
80
|
+
const { sessionId: id, configOptions } = await bridge.newSession(this.defaultCwd, { silent: true });
|
|
69
81
|
this.sessions.liveSessions.add(id);
|
|
70
|
-
|
|
82
|
+
// Pick the cheapest available model by matching id substrings against
|
|
83
|
+
// the agent's reported availableModels (`configOptions[id=model].options`).
|
|
84
|
+
// Empty pattern list, no model option, or no match → skip the call and
|
|
85
|
+
// inherit the agent's default model (`currentModelId`).
|
|
86
|
+
const picked = this.pickTitleModel(configOptions);
|
|
87
|
+
if (picked) {
|
|
88
|
+
await bridge.setConfigOption(id, "model", picked).catch(() => []);
|
|
89
|
+
}
|
|
71
90
|
this.titleSessionId = id;
|
|
72
91
|
return id;
|
|
73
92
|
}
|
|
@@ -75,4 +94,18 @@ export class TitleService {
|
|
|
75
94
|
return null;
|
|
76
95
|
}
|
|
77
96
|
}
|
|
97
|
+
/** Find the first available model whose id matches any pattern (case-insensitive). */
|
|
98
|
+
pickTitleModel(configOptions) {
|
|
99
|
+
if (this.modelPatterns.length === 0)
|
|
100
|
+
return null;
|
|
101
|
+
const modelOpt = configOptions.find((c) => c.id === "model");
|
|
102
|
+
if (!modelOpt || modelOpt.options.length === 0)
|
|
103
|
+
return null;
|
|
104
|
+
for (const pattern of this.modelPatterns) {
|
|
105
|
+
const hit = modelOpt.options.find((o) => o.value.toLowerCase().includes(pattern));
|
|
106
|
+
if (hit)
|
|
107
|
+
return hit.value;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
78
111
|
}
|
package/lib/tokens.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Auth-bearing token generators (single audit surface).
|
|
4
|
+
*
|
|
5
|
+
* This file centralizes generation of all string tokens that act as
|
|
6
|
+
* credentials — i.e. clients hold them, present them to the server,
|
|
7
|
+
* and the server grants access on that basis. New auth-bearing tokens
|
|
8
|
+
* MUST be added here so the security review surface stays one file.
|
|
9
|
+
*
|
|
10
|
+
* Out of scope (intentionally not here):
|
|
11
|
+
* - Internal IDs that aren't auth-bearing (client IDs, request IDs,
|
|
12
|
+
* error correlation IDs) — see sse-manager.ts, routes.ts, etc.
|
|
13
|
+
* - HMAC signing keys (image URL secret) — see server.ts.
|
|
14
|
+
* - UUIDs used as opaque labels (session IDs, msg IDs) — these
|
|
15
|
+
* are protocol identifiers, not credentials.
|
|
16
|
+
*
|
|
17
|
+
* Repo convention: `node:crypto` only. No `nanoid` / `uuid` deps.
|
|
18
|
+
*/
|
|
19
|
+
const API_TOKEN_PREFIX = "wat_";
|
|
20
|
+
/**
|
|
21
|
+
* Share viewer token. 24 base64url chars / 144 bits of entropy.
|
|
22
|
+
*
|
|
23
|
+
* Base64url (`[A-Za-z0-9_-]`) is the RFC 4648 standard, has Node
|
|
24
|
+
* built-in support, and is 33% shorter than the hex form at the same
|
|
25
|
+
* entropy. Share links are produced as full URLs by the client
|
|
26
|
+
* (copy-link affordance, no manual retyping), so the double-click
|
|
27
|
+
* selection that hex previously enabled is no longer relevant.
|
|
28
|
+
*/
|
|
29
|
+
export function generateShareToken() {
|
|
30
|
+
return randomBytes(18).toString("base64url");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* API auth token. "wat_" prefix + 43 base64url chars / 256 bits.
|
|
34
|
+
*
|
|
35
|
+
* Prefix lets secret scanners (GitHub, Slack) match `wat_[A-Za-z0-9_-]{43}`
|
|
36
|
+
* and gives `grep` an obvious search target in logs.
|
|
37
|
+
*/
|
|
38
|
+
export function generateApiToken() {
|
|
39
|
+
return API_TOKEN_PREFIX + randomBytes(32).toString("base64url");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Single-use SSE ticket. 32 base64url chars / 192 bits.
|
|
43
|
+
*
|
|
44
|
+
* Lifecycle is short (default 60s TTL, deleted on consume), so length
|
|
45
|
+
* sits between share and API tokens — well above brute-force feasibility,
|
|
46
|
+
* comfortably below "user pastes this manually".
|
|
47
|
+
*/
|
|
48
|
+
export function generateSseTicket() {
|
|
49
|
+
return randomBytes(24).toString("base64url");
|
|
50
|
+
}
|
package/lib/types.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// --- Inbox message ingress (Stage B primitive) ---
|
|
3
|
+
const FROM_REF_ALLOWED = /^(cron|external):[A-Za-z0-9._\-+/]{1,120}$/;
|
|
4
|
+
export const MessageIngressSchema = z.object({
|
|
5
|
+
from_ref: z.string().min(1).max(128).regex(FROM_REF_ALLOWED, {
|
|
6
|
+
message: "from_ref must start with 'cron:' or 'external:' (reserved values and 'session:<id>' rejected in MVP)",
|
|
7
|
+
}),
|
|
8
|
+
from_label: z.string().max(64).optional(),
|
|
9
|
+
to: z.union([z.literal("user"), z.string().regex(/^session:[^\s]+$/)]),
|
|
10
|
+
deliver: z.enum(["silent", "inapp", "push"]).default("push"),
|
|
11
|
+
dedup_key: z.string().max(128).optional(),
|
|
12
|
+
title: z.string().min(1).max(256),
|
|
13
|
+
body: z.string().max(64 * 1024),
|
|
14
|
+
cwd: z.string().max(4096).optional(),
|
|
15
|
+
});
|
|
16
|
+
export const MessageEventDataSchema = z.object({
|
|
17
|
+
message_id: z.string().min(1),
|
|
18
|
+
from_ref: z.string().min(1),
|
|
19
|
+
from_label: z.string().nullish(),
|
|
20
|
+
title: z.string(),
|
|
21
|
+
body: z.string(),
|
|
22
|
+
cwd: z.string().nullish(),
|
|
23
|
+
});
|
|
1
24
|
// --- Utility ---
|
|
2
25
|
export function errorMessage(err) {
|
|
3
26
|
if (err instanceof Error)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lelouchhe/webagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A terminal-style web UI for ACP-compatible agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,30 +31,64 @@
|
|
|
31
31
|
],
|
|
32
32
|
"scripts": {
|
|
33
33
|
"compile": "tsc -p tsconfig.build.json",
|
|
34
|
+
"create-token": "node --experimental-strip-types scripts/create-token.ts",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
34
36
|
"prepublishOnly": "npm run compile && npm run build",
|
|
35
37
|
"build": "node scripts/build.js",
|
|
36
38
|
"start": "node --experimental-strip-types src/server.ts --config config.toml",
|
|
39
|
+
"pretest": "test -f .husky/_/pre-push || npx husky",
|
|
37
40
|
"test": "npm run compile && node --experimental-strip-types --test test/*.test.ts",
|
|
38
|
-
"test:e2e": "playwright test",
|
|
41
|
+
"test:e2e": "rm -rf test/e2e-data && playwright test",
|
|
39
42
|
"dev": "node scripts/build.js --watch & node --experimental-strip-types --watch src/server.ts --config config.dev.toml",
|
|
40
43
|
"dev:start": "node scripts/build.js --dev && node --experimental-strip-types src/server.ts --config config.dev.toml &",
|
|
41
44
|
"dev:stop": "lsof -ti:6801 | xargs kill 2>/dev/null || true",
|
|
42
|
-
"screenshots": "playwright test --config scripts/screenshots.config.ts"
|
|
45
|
+
"screenshots": "playwright test --config scripts/screenshots.config.ts",
|
|
46
|
+
"lint": "eslint . --max-warnings 0",
|
|
47
|
+
"lint:fix": "eslint . --fix",
|
|
48
|
+
"format": "prettier --write .",
|
|
49
|
+
"format:check": "prettier --check .",
|
|
50
|
+
"prepare": "husky"
|
|
51
|
+
},
|
|
52
|
+
"lint-staged": {
|
|
53
|
+
"*.{ts,js,mjs,cjs}": [
|
|
54
|
+
"prettier --write",
|
|
55
|
+
"eslint --fix"
|
|
56
|
+
],
|
|
57
|
+
"*.{json,md,css,html,yml,yaml}": [
|
|
58
|
+
"prettier --write"
|
|
59
|
+
]
|
|
43
60
|
},
|
|
44
61
|
"dependencies": {
|
|
45
62
|
"@agentclientprotocol/sdk": "^0.14.1",
|
|
63
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
46
64
|
"@types/web-push": "^3.6.4",
|
|
47
65
|
"better-sqlite3": "^12.6.2",
|
|
66
|
+
"busboy": "^1.6.0",
|
|
67
|
+
"dompurify": "^3.4.1",
|
|
68
|
+
"file-type": "^22.0.1",
|
|
69
|
+
"highlight.js": "^11.11.1",
|
|
70
|
+
"marked": "^18.0.2",
|
|
71
|
+
"proper-lockfile": "^4.1.2",
|
|
48
72
|
"smol-toml": "^1.6.0",
|
|
49
73
|
"web-push": "^3.6.7",
|
|
50
74
|
"zod": "^4.3.6"
|
|
51
75
|
},
|
|
52
76
|
"devDependencies": {
|
|
77
|
+
"@eslint/js": "^10.0.1",
|
|
53
78
|
"@types/better-sqlite3": "^7.6.13",
|
|
79
|
+
"@types/busboy": "^1.5.4",
|
|
80
|
+
"@types/dompurify": "^3.0.5",
|
|
54
81
|
"@types/node": "^25.3.3",
|
|
55
82
|
"esbuild": "^0.27.3",
|
|
83
|
+
"eslint": "^10.2.1",
|
|
84
|
+
"eslint-config-prettier": "^10.1.8",
|
|
85
|
+
"globals": "^17.5.0",
|
|
56
86
|
"happy-dom": "^20.8.3",
|
|
87
|
+
"husky": "^9.1.7",
|
|
88
|
+
"lint-staged": "^16.4.0",
|
|
57
89
|
"playwright": "^1.58.2",
|
|
58
|
-
"
|
|
90
|
+
"prettier": "^3.8.3",
|
|
91
|
+
"typescript": "^5.9.3",
|
|
92
|
+
"typescript-eslint": "^8.59.0"
|
|
59
93
|
}
|
|
60
94
|
}
|
package/dist/js/app.2562YGRO.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
var he=class extends Error{name="ApiError";status;constructor(t,s){super(s),this.status=t}};async function U(e,t){let s=await fetch(e,t);if(!s.ok){let a=`HTTP ${s.status}`;try{let r=await s.json();r.error&&(a=String(r.error))}catch{}throw new he(s.status,a)}let i=await s.text();if(i)return JSON.parse(i)}function _(e,t){return U(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}function je(e){let t={};return e?.cwd&&(t.cwd=e.cwd),e?.inheritFromSessionId&&(t.inheritFromSessionId=e.inheritFromSessionId),_("/api/v1/sessions",t)}function ye(e){return U("/api/v1/sessions/"+e,{method:"DELETE"})}function ie(){return U("/api/v1/sessions")}function M(e){return U("/api/v1/sessions/"+e)}function we(e,t,s){let i={text:t};return s?.length&&(i.images=s),_("/api/v1/sessions/"+e+"/prompt",i)}function Fe(e){return _("/api/v1/sessions/"+e+"/cancel",{})}function oe(e,t,s){return _("/api/v1/sessions/"+e+"/permissions/"+t,{optionId:s})}function re(e,t){return _("/api/v1/sessions/"+e+"/permissions/"+t,{denied:!0})}function G(e,t,s){let i=t.replace(/_/g,"-");return U("/api/v1/sessions/"+e+"/"+i,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:s})})}function Ue(e,t){return U("/api/v1/sessions/"+e+"/title",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})}function We(e,t){return _("/api/v1/sessions/"+e+"/bash",{command:t})}function J(e,t,s){let i={visible:t};return s&&(i.sessionId=s),_("/api/beta/clients/"+e+"/visibility",i)}function Ke(){return _("/api/v1/bridge/reload",{})}var T=e=>document.querySelector(e),o={messages:T("#messages"),input:T("#input"),sendBtn:T("#send-btn"),prompt:T("#input-prompt"),status:T("#status"),sessionInfo:T("#session-info"),attachBtn:T("#attach-btn"),fileInput:T("#file-input"),attachPreview:T("#attach-preview"),themeBtn:T("#theme-btn"),slashMenu:T("#slash-menu"),inputArea:T("#input-area"),statusBar:T("#status-bar")},n={eventSource:null,clientId:null,sessionId:null,sessionSwitchGen:0,sessionCwd:null,sessionTitle:null,awaitingNewSession:!1,configOptions:[],currentAssistantEl:null,currentAssistantText:"",currentThinkingEl:null,currentThinkingText:"",busy:!1,pendingImages:[],currentBashEl:null,followMessages:!0,pendingToolCallIds:new Set,pendingPermissionRequestIds:new Set,pendingPromptDone:!1,turnEnded:!1,newTurnStarted:!1,sentMessageForSession:null,sentBashForSession:null,cancelTimeout:1e4,serverVersion:null,agentName:null,agentVersion:null,_cancelTimerId:null,_onCancelTimeout:null,lastEventSeq:0,oldestLoadedSeq:0,hasMoreHistory:!1,loadingOlderEvents:!1,replayInProgress:!1,replayTarget:null,replayQueue:[],unconfirmedPermissions:new Map,agentReloading:!1,recentPathsLimit:10},Tt={disconnected:"is-disconnected",connecting:"is-connecting",connected:"is-connected"};function O(e,t=e){o.status.textContent="",o.status.className=`status-dot ${Tt[e]}`,o.status.dataset.state=e,o.status.setAttribute("aria-label",t),o.status.setAttribute("title",t)}function x(e){return n.configOptions.find(t=>t.id===e)}function ae(e){return x(e)?.currentValue??null}function Ve(e,t){let s=x(e);s&&(s.currentValue=t)}function ve(e){n.configOptions=e,N(),A()}function N(){o.inputArea.classList.remove("plan-mode","autopilot-mode");let e=ae("mode")||"";e.includes("#plan")?o.inputArea.classList.add("plan-mode"):e.includes("#autopilot")&&o.inputArea.classList.add("autopilot-mode")}function A(){if(!o.statusBar)return;let e=ae("model"),t=n.sessionCwd||"";if(o.statusBar.textContent="",t){e&&o.statusBar.appendChild(document.createTextNode(e+" \xB7 "));let s=document.createElement("span");s.className="status-cwd",s.textContent=t,o.statusBar.appendChild(s)}else e&&(o.statusBar.textContent=e)}function h(e){n.busy=e,e?(o.sendBtn.textContent="^C",o.sendBtn.title="Cancel (Ctrl+C)",o.sendBtn.classList.add("cancel"),o.prompt.classList.add("busy")):(o.sendBtn.textContent="\u21B5",o.sendBtn.title="Send (Enter)",o.sendBtn.classList.remove("cancel"),o.prompt.classList.remove("busy"))}function L({cwd:e,inheritFromSessionId:t=n.sessionId}={}){n.awaitingNewSession=!0,je({cwd:e,inheritFromSessionId:t}).catch(()=>{})}var Ge=[];function Je(e){Ge.push(e)}function w(){for(let e of Ge)e();o.messages.innerHTML="",n.currentAssistantEl=null,n.currentAssistantText="",n.currentThinkingEl=null,n.currentThinkingText="",n.pendingImages.length=0,n.followMessages=!0,n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.unconfirmedPermissions.clear(),n.pendingPromptDone=!1,n.turnEnded=!1,n.newTurnStarted=!1,n._cancelTimerId=null,n.lastEventSeq=0,n.oldestLoadedSeq=0,n.hasMoreHistory=!1,n.loadingOlderEvents=!1,n.replayInProgress=!1,n.replayQueue=[],o.attachPreview.innerHTML="",o.attachPreview.classList.remove("active"),o.input.disabled=!1,o.sendBtn.disabled=!1,o.input.placeholder="Message or ?",h(!1),n.sessionTitle=null,n.sessionCwd=null,n.configOptions=[],ce(null,null),o.statusBar&&(o.statusBar.textContent="")}function Q(){return!n.busy||!n.sessionId?!1:(Fe(n.sessionId).catch(()=>{}),Y(),n.cancelTimeout>0&&(n._cancelTimerId=setTimeout(()=>{n._cancelTimerId=null,n.busy&&(n.turnEnded=!0,h(!1),n._onCancelTimeout?.())},n.cancelTimeout)),!0)}function Y(){n._cancelTimerId!=null&&(clearTimeout(n._cancelTimerId),n._cancelTimerId=null)}function Qe(){return location.hash.slice(1)||null}function le(e){history.replaceState(null,"",`#${e}`)}function ce(e,t){o.sessionInfo.textContent=t||(e?e.slice(0,8)+"\u2026":""),document.title=t||">_"}O("disconnected");var q="idle",Se=[],Et="11.11.1",Ye=`https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@${Et}/build`,kt={dark:"github-dark.min.css",light:"github.min.css",auto:""},$=null;function Ct(){let e=document.documentElement.getAttribute("data-theme")||"auto";return e==="light"?"light":e==="dark"?"dark":window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"}function be(){let e=Ct(),t=kt[e],s=`${Ye}/styles/${t}`;if($){if($.href===s)return;$.href=s;return}$=document.createElement("link"),$.rel="stylesheet",$.href=s,document.head.appendChild($)}function xt(){return new Promise((e,t)=>{let s=document.createElement("script");s.src=`${Ye}/highlight.min.js`,s.onload=()=>e(),s.onerror=()=>t(new Error("Failed to load highlight.js")),document.head.appendChild(s)})}async function Lt(){if(q==="ready")return!0;if(q==="loading")return new Promise(e=>{Se.push(()=>e(q==="ready"))});q="loading";try{return be(),await xt(),q="ready",Se.splice(0).forEach(t=>t()),!0}catch{return q="idle",Se.splice(0).forEach(t=>t()),!1}}function Mt(e){let t=globalThis.hljs;if(t)for(let s of e.querySelectorAll("pre code"))s.dataset.highlighted||t.highlightElement(s)}function $t(e){for(let t of e.classList)if(t.startsWith("language-"))return t.slice(9);return""}function Pt(e){let t=e.querySelectorAll("pre");for(let s of t){let i=s.querySelector("code");if(!i||s.parentElement?.classList.contains("code-block-wrapper"))continue;let a=$t(i),r=document.createElement("div");r.className="code-block-wrapper";let l=document.createElement("div");l.className="code-toolbar";let d=document.createElement("button");d.className="copy-btn",d.textContent="cp",d.type="button",l.appendChild(d),s.replaceWith(r),r.appendChild(l),r.appendChild(s)}}var Ie=new WeakMap;function ze(e){let t=e.target.closest(".copy-btn");if(!t)return;let i=t.closest(".code-block-wrapper")?.querySelector("code");if(!i)return;let a=i.textContent||"";navigator.clipboard.writeText(a).then(()=>{let r=Ie.get(t);r&&clearTimeout(r),t.textContent="\u2713",t.classList.add("copied");let l=setTimeout(()=>{t.textContent="cp",t.classList.remove("copied"),Ie.delete(t)},1500);Ie.set(t,l)}).catch(()=>{let r=document.createRange();r.selectNodeContents(i);let l=window.getSelection();l?.removeAllRanges(),l?.addRange(r)})}async function z(e){if(!e.querySelector("pre code"))return;Pt(e),await Lt()&&Mt(e)}function Ze(){q==="ready"&&$&&be()}typeof window<"u"&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{q==="ready"&&$&&be()});marked.setOptions({breaks:!0,gfm:!0});function de(e){return DOMPurify.sanitize(marked.parse(e))}function D(e,t){let s=document.createElement("div");return s.className=`msg ${e}`,s.innerHTML=e==="user"?p(t).replace(/\n/g,"<br>"):de(t),E(s),s}function c(e){let t=document.createElement("div");return t.className="system-msg",t.textContent=e,E(t),t}function P(){let e=n.currentAssistantEl;n.currentAssistantEl=null,n.currentAssistantText="",e&&typeof e.querySelector=="function"&&z(e)}function k(){if(n.currentThinkingEl){let e=n.currentThinkingEl.querySelector("summary");e.textContent="\u283F thought",e.classList.remove("active"),e.style.animation="none",n.currentThinkingEl=null,n.currentThinkingText=""}}var R=null,Ht=80;function Ee(e){return e.scrollHeight-e.scrollTop-e.clientHeight<Ht}function _t(){n.followMessages=Ee(o.messages)}o.messages.addEventListener("scroll",_t);function Nt(){return n.followMessages||Ee(o.messages)}function E(e,t=!1){if(n.replayTarget)return n.replayTarget.appendChild(e),e;let s=t||Nt();return o.messages.appendChild(e),I(s),e}function et(){j(),R=document.createElement("div"),R.id="waiting",R.innerHTML='<span class="cursor">\u258C</span>',E(R,!0)}function j(){R&&(R.remove(),R=null)}var Te=!1;function I(e){let t=o.messages;if(e||n.followMessages){typeof requestAnimationFrame=="function"?Te||(Te=!0,requestAnimationFrame(()=>{Te=!1,t.scrollTop=t.scrollHeight})):t.scrollTop=t.scrollHeight,n.followMessages=!0;return}n.followMessages=Ee(t)}function p(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function tt(e){if(!e)return"";let t=new Date(e.endsWith("Z")?e:e+"Z"),s=i=>String(i).padStart(2,"0");return`${t.getFullYear()}-${s(t.getMonth()+1)}-${s(t.getDate())} ${s(t.getHours())}:${s(t.getMinutes())}`}function ke(e){if(typeof e=="string"&&e.includes("*** Begin Patch")){let t=e.split(`
|
|
2
|
-
`),s=[];for(let i of t)i.startsWith("*** Begin Patch")||i.startsWith("*** End Patch")||(i.startsWith("*** Update File:")||i.startsWith("*** Add File:")||i.startsWith("*** Delete File:")?s.push(`<span class="diff-file">${p(i)}</span>`):i.startsWith("@@")?s.push(`<span class="diff-hunk">${p(i)}</span>`):i.startsWith("-")?s.push(`<span class="diff-del">${p(i)}</span>`):i.startsWith("+")?s.push(`<span class="diff-add">${p(i)}</span>`):s.push(p(i)));return s.join(`
|
|
3
|
-
`)}if(e&&typeof e=="object"){let t=[];if(e.path&&t.push(`<span class="diff-file">*** ${p(e.path)}</span>`),e.old_str!=null)for(let s of String(e.old_str).split(`
|
|
4
|
-
`))t.push(`<span class="diff-del">- ${p(s)}</span>`);if(e.new_str!=null)for(let s of String(e.new_str).split(`
|
|
5
|
-
`))t.push(`<span class="diff-add">+ ${p(s)}</span>`);if(e.file_text!=null)for(let s of String(e.file_text).split(`
|
|
6
|
-
`))t.push(`<span class="diff-add">+ ${p(s)}</span>`);return t.length>(e.path?1:0)?t.join(`
|
|
7
|
-
`):null}return null}function Z(e,t=!1){let s=document.createElement("div");return s.className="bash-block",s.innerHTML=`<span class="bash-cmd${t?" running":""}">${p(e)}</span><div class="bash-output"></div>`,s.querySelector(".bash-cmd").addEventListener("click",()=>{let i=s.querySelector(".bash-output");i.style.display==="none"?i.style.display="block":i.classList.contains("has-content")&&(i.style.display="none")}),E(s),t&&(n.currentBashEl=s),s}function X(e,t,s){if(!e)return;let i=e.querySelector(".bash-cmd");i.classList.remove("running");let a="";if(s?a=`[signal: ${s}]`:t!==0&&t!=null&&(a=`[exit: ${t}]`),a){let r=document.createElement("span");r.className=`bash-exit ${t===0?"ok":"fail"}`,r.textContent=a,i.after(r)}e===n.currentBashEl&&(n.currentBashEl=null)}var qt={auto:"\u25D1",light:"\u2600",dark:"\u263E"},Xe=["auto","light","dark"];function nt(){return localStorage.getItem("theme")||"auto"}function st(e){document.documentElement.setAttribute("data-theme",e),o.themeBtn.textContent=qt[e],o.themeBtn.title=`Theme: ${e}`,localStorage.setItem("theme",e);for(let t of it)t()}var it=[];function ot(e){it.push(e)}o.themeBtn.onclick=()=>{let e=nt();st(Xe[(Xe.indexOf(e)+1)%3])};st(nt());var Ce={task_complete:"\u2714",read:"cat",edit:"edit",execute:"exec",search:"find",delete:"rm"},xe="run",Le={pending:"\u25CB",in_progress:"\u25C9",completed:"\u25CF"};function rt(e){return n.replayTarget?.querySelector(`[id="${e}"]`)??document.getElementById(e)}function Bt(e){return n.replayTarget?.querySelector(e)??document.querySelector(e)}var at="webagent_notify_tip_shown",lt="webagent_notify_tip_denied_shown";function Ot(){if(typeof Notification>"u"||n.replayInProgress)return;let e=Notification.permission;if(e!=="granted"){if(e==="denied"){if(localStorage.getItem(lt))return;localStorage.setItem(lt,"1"),c("tip: notifications are blocked \u2014 allow in browser site settings to enable");return}localStorage.getItem(at)||(localStorage.setItem(at,"1"),c("tip: use /notify to enable background notifications"))}}function ue(){n.pendingPromptDone&&(n.pendingToolCallIds.size>0||n.pendingPermissionRequestIds.size>0||(j(),k(),P(),h(!1),n.pendingPromptDone=!1,Ot()))}function At(){for(let e of n.pendingToolCallIds){let t=document.getElementById(`tc-${e}`);if(!t)continue;t.className="tool-call failed";let s=t.querySelector(".icon");s&&(s.textContent="\u2717")}for(let e of n.pendingPermissionRequestIds){let t=document.querySelector(`.permission[data-request-id="${e}"]`);if(!t||!t.querySelector("button"))continue;let i=t.querySelector(".title")?.textContent||"\u26BF";t.innerHTML=`<span style="opacity:0.5">${p(i)} \u2014 cancelled</span>`}n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear()}function Rt(){for(let e of n.pendingToolCallIds){let t=document.getElementById(`tc-${e}`);if(!t)continue;t.className="tool-call completed";let s=t.querySelector(".icon");s&&(s.textContent="\u2713")}n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear()}function $e(e){if(Array.isArray(e))return{events:e,streaming:{thinking:!1,assistant:!1}};let t=e;return{events:t.events??[],streaming:t.streaming??{thinking:!1,assistant:!1},total:typeof t.total=="number"?t.total:void 0,hasMore:typeof t.hasMore=="boolean"?t.hasMore:void 0}}var ct=200;async function B(e){n.replayInProgress=!0,n.replayQueue=[];try{let t=await fetch(`/api/v1/sessions/${e}/events?limit=${ct}`);if(!t.ok)return!1;let s=await t.json(),{events:i,streaming:a,hasMore:r}=$e(s),l=document.createDocumentFragment(),d=He(i);n.replayTarget=l;for(let u=0;u<i.length;u++){let f=JSON.parse(i[u].data);_e(i[u].type,f,i,u,d)}return n.replayTarget=null,o.messages.style.display="none",o.messages.appendChild(l),o.messages.style.display="",i.length&&(n.lastEventSeq=i[i.length-1].seq,n.oldestLoadedSeq=i[0].seq),n.hasMoreHistory=r===!0,n.hasMoreHistory&&Dt(),dt(),Me(i,a),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,pt()}}function Me(e,t){if(t.thinking&&e.length){for(let s=e.length-1;s>=0;s--)if(e[s].type==="thinking"){let i=JSON.parse(e[s].data),a=o.messages.querySelectorAll(".thinking"),r=a[a.length-1];if(r){n.currentThinkingEl=r,n.currentThinkingText=i.text;let l=r.querySelector("summary");l&&(l.textContent="\u283F thinking...",l.classList.add("active"))}break}}if(t.assistant&&e.length){for(let s=e.length-1;s>=0;s--)if(e[s].type==="assistant_message"){let i=JSON.parse(e[s].data),a=o.messages.querySelectorAll(".msg.assistant"),r=a[a.length-1];r&&(n.currentAssistantEl=r,n.currentAssistantText=i.text);break}}}function dt(){let e=o.messages.querySelector("[data-sync-boundary]");e&&e.removeAttribute("data-sync-boundary");let t=o.messages.lastElementChild;t&&t.setAttribute("data-sync-boundary","")}async function Pe(e){n.replayInProgress=!0,n.replayQueue=[];try{let t=`/api/v1/sessions/${e}/events?after=${n.lastEventSeq}`,s=await fetch(t);if(!s.ok)return!1;let i=await s.json(),{events:a,streaming:r}=$e(i),l=o.messages.querySelector("[data-sync-boundary]");if(l)for(;l.nextElementSibling;)l.nextElementSibling.remove();if(n.currentAssistantEl=null,n.currentAssistantText="",n.currentThinkingEl=null,n.currentThinkingText="",n.currentBashEl=null,a.length===0)return Me(a,r),!0;let d=document.createDocumentFragment(),u=He(a);n.replayTarget=d;for(let f=0;f<a.length;f++){let g=JSON.parse(a[f].data);_e(a[f].type,g,a,f,u)}return n.replayTarget=null,o.messages.appendChild(d),n.lastEventSeq=a[a.length-1].seq,dt(),Me(a,r),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,pt()}}var ee=null;function Dt(){pe();let e=document.createElement("div");e.id="history-sentinel",e.className="history-sentinel",e.textContent="\u2191 loading\u2026",o.messages.prepend(e),typeof IntersectionObserver=="function"&&(ee=new IntersectionObserver(t=>{t[0]?.isIntersecting&&!n.loadingOlderEvents&&n.hasMoreHistory&&n.sessionId&&jt(n.sessionId)},{root:o.messages,rootMargin:"200px 0px 0px 0px"}),ee.observe(e))}function pe(){ee&&(ee.disconnect(),ee=null),document.getElementById("history-sentinel")?.remove()}Je(pe);async function jt(e){if(n.loadingOlderEvents||!n.hasMoreHistory||n.oldestLoadedSeq<=0)return!1;n.loadingOlderEvents=!0;try{let t=await fetch(`/api/v1/sessions/${e}/events?limit=${ct}&before=${n.oldestLoadedSeq}`);if(!t.ok||e!==n.sessionId)return!1;let s=await t.json(),{events:i,hasMore:a}=$e(s);if(i.length===0)return n.hasMoreHistory=!1,pe(),!0;let r=document.createDocumentFragment(),l=He(i);n.replayTarget=r;for(let g=0;g<i.length;g++){let S=JSON.parse(i[g].data);_e(i[g].type,S,i,g,l)}n.replayTarget=null;let d=o.messages,u=d.scrollHeight,f=document.getElementById("history-sentinel");return f?f.after(r):d.prepend(r),d.scrollTop+=d.scrollHeight-u,n.oldestLoadedSeq=i[0].seq,n.hasMoreHistory=a===!0,n.hasMoreHistory||pe(),!0}catch{return!1}finally{n.loadingOlderEvents=!1}}function ut(){for(let[e,t]of n.unconfirmedPermissions){let s=document.querySelector(`.permission[data-request-id="${e}"]`);if(!s||!s.querySelector("button")){n.unconfirmedPermissions.delete(e);continue}t.denied?re(t.sessionId,e).catch(()=>{}):oe(t.sessionId,e,t.optionId).catch(()=>{});let i=s.dataset.title?`\u26BF ${p(s.dataset.title)}`:"\u26BF";s.innerHTML=`<span style="opacity:0.5">${i} \u2014 ${p(t.optionName)}</span>`,n.unconfirmedPermissions.delete(e)}}function He(e){let t=new Set;for(let s of e)s.type==="permission_response"&&t.add(JSON.parse(s.data).requestId);return{toolCalls:new Map,permissions:new Map,resolvedPermissions:t,currentBashEl:null}}function _e(e,t,s,i,a){switch(e){case"user_message":{let r=D("user",t.text);if(t.images)for(let l of t.images){let d=document.createElement("img");d.className="user-image",d.src=l.path,r.appendChild(d)}break}case"assistant_message":{let l=(n.replayTarget||o.messages).lastElementChild;if(l&&l.classList.contains("msg")&&l.classList.contains("assistant")){let f=(l.getAttribute("data-raw")||"")+t.text;l.setAttribute("data-raw",f),l.innerHTML=de(f),z(l);break}let d=D("assistant",t.text);d.setAttribute("data-raw",t.text),z(d);break}case"thinking":{let l=(n.replayTarget||o.messages).lastElementChild;if(l&&l.classList.contains("thinking")){let u=l.querySelector(".thinking-content");if(u){u.textContent+=`
|
|
8
|
-
`+t.text;break}}let d=document.createElement("details");d.className="thinking",d.innerHTML=`<summary>\u283F thought</summary><div class="thinking-content">${p(t.text)}</div>`,E(d);break}case"tool_call":{let r=Ce[t.kind]||xe,l=document.createElement("div");l.className="tool-call",l.id=`tc-${t.id}`,l.dataset.kind=t.kind;let d=`<span class="icon">${r}</span> ${p(t.title)}`,u=t.rawInput;u&&u.command?d+=`<span class="tc-detail">$ ${p(u.command)}</span>`:u&&u.path&&(d+=`<span class="tc-detail">${p(u.path)}</span>`),l.innerHTML=d;let f=t.kind==="edit"?ke(u):null;if(f){let S=document.createElement("details");S.innerHTML=`<summary>diff</summary><div class="diff-view">${f}</div>`,l.appendChild(S)}let g=l.querySelector(".tc-detail");g&&l.addEventListener("click",S=>{S.target.closest("details")||g.classList.toggle("expanded")}),E(l),a&&a.toolCalls.set(t.id,l);break}case"tool_call_update":{let r=a?a.toolCalls.get(t.id):rt(`tc-${t.id}`);if(r){let l=t.status==="completed"?"\u2713":t.status==="failed"?"\u2717":"\u2026";r.className=`tool-call ${t.status}`;let d=r.querySelector(".icon");if(d&&(d.textContent=l),t.content&&t.content.length&&!r.querySelector("details")&&!r.querySelector(".tc-summary")){let u=t.content.map(f=>f.type==="terminal"?`[terminal ${f.terminalId}]`:f.content?.text?f.content.text:Array.isArray(f.content)?f.content.map(g=>g.text||"").join(""):"").filter(Boolean).join(`
|
|
9
|
-
`);if(u)if(r.dataset.kind==="task_complete"){let f=document.createElement("div");f.className="tc-summary",f.textContent=u,r.appendChild(f)}else{let f=document.createElement("details");f.innerHTML=`<summary>output</summary><div class="tc-content">${p(u)}</div>`,r.appendChild(f)}}}(t.status==="completed"||t.status==="failed")&&n.pendingToolCallIds.delete(t.id);break}case"plan":{let r=document.createElement("div");r.className="plan",r.innerHTML='<div class="plan-title">\u2015 plan</div>'+(t.entries||[]).map(l=>`<div class="plan-entry">${Le[l.status]||"?"} ${p(l.content)}</div>`).join(""),E(r);break}case"permission_request":{let r=document.createElement("div");r.className="permission",r.dataset.requestId=t.requestId,r.dataset.title=t.title||"",r.innerHTML=`<span class="title" style="opacity:0.5">\u26BF ${p(t.title)}</span> `,!(a?a.resolvedPermissions.has(t.requestId):s&&s.slice(i+1).some(d=>d.type==="permission_response"&&JSON.parse(d.data).requestId===t.requestId))&&t.options&&(r.querySelector(".title").style.opacity="1",t.options.forEach(d=>{let u=document.createElement("button"),f=(d.kind||"").includes("allow");u.className=f?"allow":"deny",u.textContent=d.name,u.onclick=()=>{(d.kind||"").includes("reject")||(d.kind||"").includes("deny")?re(n.sessionId,t.requestId).catch(()=>{}):oe(n.sessionId,t.requestId,d.optionId).catch(()=>{}),r.innerHTML=`<span style="opacity:0.5">\u26BF ${p(t.title)} \u2014 ${p(d.name)}</span>`},r.appendChild(u)})),E(r),a&&a.permissions.set(t.requestId,r);break}case"permission_response":{let r=a?a.permissions.get(t.requestId):Bt(`.permission[data-request-id="${t.requestId}"]`);if(r){let l=r.dataset.title?`\u26BF ${r.dataset.title}`:"\u26BF",d=t.optionName||(t.denied?"denied":"allowed");r.innerHTML=`<span style="opacity:0.5">${p(l)} \u2014 ${p(d)}</span>`}break}case"bash_command":{let r=Z(t.command,!1);a?a.currentBashEl=r:r.id="bash-replay-pending";break}case"bash_result":{let r=a?a.currentBashEl:rt("bash-replay-pending");if(r){if(a||r.removeAttribute("id"),t.output){let l=r.querySelector(".bash-output");l&&(l.textContent=t.output,l.classList.add("has-content"))}X(r,t.code,t.signal),a&&(a.currentBashEl=null)}break}case"prompt_done":n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,h(!1);break}}function pt(){let e=n.replayQueue;n.replayQueue=[];for(let t of e)Ft(t)||C(t)}function Ft(e){switch(e.type){case"tool_call":return!!document.getElementById(`tc-${e.id}`);case"permission_request":return!!document.querySelector(`.permission[data-request-id="${e.requestId}"]`);case"thought_chunk":return!!n.currentThinkingEl;case"message_chunk":return!!n.currentAssistantEl;default:return!1}}function C(e){if(n.replayInProgress){console.log("[handleEvent-DEBUG] QUEUED (replayInProgress):",e.type),n.replayQueue.push(e);return}if(!(e.sessionId&&e.type!=="session_created"&&e.type!=="session_deleted"&&(!n.sessionId||e.sessionId!==n.sessionId)))switch(e.type){case"connected":if(e.cancelTimeout!=null&&(n.cancelTimeout=e.cancelTimeout),e.recentPathsLimit!=null&&(n.recentPathsLimit=e.recentPathsLimit),n.agentReloading){n.agentReloading=!1;let t=e.agent?.name??"",s=e.agent?.version??"",i=t&&s?`${t} ${s}`:"Agent";c(`${i} reloaded.`),h(!1)}break;case"session_created":if(!n.awaitingNewSession&&n.sessionId&&e.sessionId!==n.sessionId)break;if(n.awaitingNewSession=!1,n.sessionId=e.sessionId,n.sessionCwd=e.cwd||n.sessionCwd,n.sessionTitle=e.title||null,e.configOptions?.length&&ve(e.configOptions),le(n.sessionId),n.clientId&&J(n.clientId,!document.hidden,n.sessionId).catch(()=>{}),ce(n.sessionId,n.sessionTitle),O("connected","connected"),o.input.disabled=!1,o.sendBtn.disabled=!1,o.input.placeholder="Message or ?",h(!!e.busyKind),n.newTurnStarted=!1,e.busyKind==="bash"){let t=document.getElementById("bash-replay-pending");t&&(t.removeAttribute("id"),t.querySelector(".bash-cmd")?.classList.add("running"),n.currentBashEl=t)}else n.currentBashEl=null;o.messages.children.length===0&&c(`Session created: ${n.sessionTitle||e.sessionId.slice(0,8)+"\u2026"}`),A();break;case"user_message":{if(n.sentMessageForSession===e.sessionId){n.sentMessageForSession=null;break}if(k(),P(),n.newTurnStarted=!0,n.turnEnded=!1,e.sessionId===n.sessionId){let t=D("user",e.text);if(e.images)for(let s of e.images){let i=document.createElement("img");i.className="user-image",i.src=s.path,t.appendChild(i)}}break}case"message_chunk":if(n.turnEnded)break;j(),k(),n.currentAssistantEl||(n.currentAssistantEl=D("assistant",""),n.currentAssistantText=""),n.currentAssistantText+=e.text,n.currentAssistantEl.innerHTML=de(n.currentAssistantText),I();break;case"thought_chunk":if(n.turnEnded)break;j(),n.currentThinkingEl||(n.currentThinkingEl=document.createElement("details"),n.currentThinkingEl.className="thinking",n.currentThinkingEl.innerHTML='<summary class="active">\u283F thinking...</summary><div class="thinking-content"></div>',n.currentThinkingText="",E(n.currentThinkingEl)),n.currentThinkingText+=e.text,n.currentThinkingEl.querySelector(".thinking-content").textContent=n.currentThinkingText,I();break;case"tool_call":{if(n.turnEnded)break;n.pendingToolCallIds.add(e.id),h(!0),j(),k(),P();let t=Ce[e.kind]||xe,s=document.createElement("div");s.className="tool-call",s.id=`tc-${e.id}`,s.dataset.kind=e.kind;let i=`<span class="icon">${t}</span> ${p(e.title)}`,a=e.rawInput;a&&a.command?i+=`<span class="tc-detail">$ ${p(a.command)}</span>`:a&&a.path&&(i+=`<span class="tc-detail">${p(a.path)}</span>`),s.innerHTML=i;let r=e.kind==="edit"?ke(a):null;if(r){let d=document.createElement("details");d.innerHTML=`<summary>diff</summary><div class="diff-view">${r}</div>`,s.appendChild(d)}let l=s.querySelector(".tc-detail");l&&s.addEventListener("click",d=>{d.target.closest("details")||l.classList.toggle("expanded")}),E(s);break}case"tool_call_update":{let t=document.getElementById(`tc-${e.id}`);if((e.status==="completed"||e.status==="failed")&&n.pendingToolCallIds.delete(e.id),t){let s=e.status==="completed"?"\u2713":e.status==="failed"?"\u2717":"\u2026";t.className=`tool-call ${e.status}`;let i=t.querySelector(".icon");if(i&&(i.textContent=s),e.content&&e.content.length&&!t.querySelector("details")&&!t.querySelector(".tc-summary")){let a=e.content.map(r=>r.type==="terminal"?`[terminal ${r.terminalId}]`:r.content?.text?r.content.text:Array.isArray(r.content)?r.content.map(l=>l.text||"").join(""):"").filter(Boolean).join(`
|
|
10
|
-
`);if(a)if(t.dataset.kind==="task_complete"){let r=document.createElement("div");r.className="tc-summary",r.textContent=a,t.appendChild(r)}else{let r=document.createElement("details");r.innerHTML=`<summary>output</summary><div class="tc-content">${p(a)}</div>`,t.appendChild(r)}}}ue(),I();break}case"plan":{k(),P();let t=document.createElement("div");t.className="plan",t.innerHTML='<div class="plan-title">\u2015 plan</div>'+e.entries.map(s=>`<div class="plan-entry">${Le[s.status]||"?"} ${p(s.content)}</div>`).join(""),E(t);break}case"permission_request":{if(n.turnEnded||document.querySelector(`.permission[data-request-id="${e.requestId}"]`))break;n.pendingPermissionRequestIds.add(e.requestId),h(!0),k();let t=document.createElement("div");t.className="permission",t.dataset.requestId=e.requestId,t.dataset.title=e.title||"",t.innerHTML=`<span class="title">\u26BF ${p(e.title)}</span> `,e.options.forEach(s=>{let i=document.createElement("button"),a=(s.kind||"").includes("allow");i.className=a?"allow":"deny",i.textContent=s.name,i.onclick=()=>{let r=(s.kind||"").includes("reject")||(s.kind||"").includes("deny");r?re(n.sessionId,e.requestId).catch(()=>{}):oe(n.sessionId,e.requestId,s.optionId).catch(()=>{}),n.pendingPermissionRequestIds.delete(e.requestId),n.unconfirmedPermissions.set(e.requestId,{sessionId:n.sessionId,optionId:s.optionId,optionName:s.name,denied:r}),t.innerHTML=`<span style="opacity:0.5">\u26BF ${p(e.title)} \u2014 ${p(s.name)}</span>`,ue()},t.appendChild(i)}),E(t);break}case"permission_response":{console.log("[PERM-DEBUG] permission_response received:",e.requestId,"sessionId:",e.sessionId,"state.sessionId:",n.sessionId),n.pendingPermissionRequestIds.delete(e.requestId),n.unconfirmedPermissions.delete(e.requestId);let t=document.querySelector(`.permission[data-request-id="${e.requestId}"]`);if(console.log("[PERM-DEBUG] permTarget found:",!!t,"sessionMatch:",e.sessionId===n.sessionId),e.sessionId===n.sessionId&&t){let s=t.dataset.title?`\u26BF ${t.dataset.title}`:"\u26BF",i=e.optionName||(e.denied?"denied":"allowed");t.innerHTML=`<span style="opacity:0.5">${p(s)} \u2014 ${p(i)}</span>`,console.log("[PERM-DEBUG] DOM updated successfully")}else console.log("[PERM-DEBUG] DOM NOT updated \u2014 check failed");ue();break}case"bash_command":{if(n.sentBashForSession===e.sessionId){n.sentBashForSession=null;break}e.sessionId===n.sessionId&&(Z(e.command,!0),h(!0));break}case"bash_output":{if(e.sessionId!==n.sessionId)break;if(n.currentBashEl){let t=n.currentBashEl.querySelector(".bash-output");if(e.stream==="stderr"){let s=document.createElement("span");s.className="stderr",s.textContent=e.text,t.appendChild(s)}else t.appendChild(document.createTextNode(e.text));t.classList.add("has-content"),t.scrollTop=t.scrollHeight,I()}break}case"bash_done":{if(e.sessionId!==n.sessionId)break;X(n.currentBashEl,e.code,e.signal),e.error&&c(`err: ${e.error}`),h(!1);break}case"prompt_done":{if(Y(),e.stopReason==="cancelled"&&n.newTurnStarted){n.newTurnStarted=!1,k(),P();break}n.newTurnStarted=!1,e.stopReason==="cancelled"?At():Rt(),n.turnEnded=!0,n.pendingPromptDone=!0,ue();break}case"session_deleted":e.sessionId===n.sessionId&&(c("warn: This session has been deleted."),o.input.disabled=!0,o.sendBtn.disabled=!0,o.input.placeholder="Session deleted");break;case"session_expired":{let t=n.sessionCwd;w(),c("warn: Previous session expired, created new one."),L({cwd:t||void 0});break}case"config_set":{Ve(e.configId,e.value);let t=x(e.configId),s=t?.name||e.configId,i=t?.options.find(a=>a.value===e.value)?.name||e.value;c(`ok: ${s}: ${i}`),e.configId==="mode"&&N(),A();break}case"config_option_update":e.configOptions?.length&&ve(e.configOptions);break;case"session_title_updated":e.sessionId===n.sessionId&&(n.sessionTitle=e.title,ce(n.sessionId,n.sessionTitle));break;case"agent_reloading":n.agentReloading=!0,c("Agent reloading\u2026"),h(!0);break;case"agent_reloading_failed":c(`err: Agent reload failed: ${e.error}`),h(!1);break;case"error":n.awaitingNewSession=!1,n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,j(),k(),P(),c(`err: ${e.message}`),h(!1);break}}async function Ut(){try{let e=await navigator.serviceWorker?.ready;if(!e)return;let t=await fetch("/api/beta/push/vapid-key");if(!t.ok)return;let{publicKey:s}=await t.json(),a=(await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:Kt(s)})).toJSON();await fetch("/api/beta/push/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:a.endpoint,keys:a.keys,clientId:n.clientId})})}catch(e){console.error("[push] subscribe failed:",e)}}async function Wt(){try{let e=await navigator.serviceWorker?.ready;if(!e)return;let t=await e.pushManager.getSubscription();if(!t)return;let s=t.endpoint;await t.unsubscribe(),await fetch("/api/beta/push/unsubscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:s})})}catch(e){console.error("[push] unsubscribe failed:",e)}}function Kt(e){let t="=".repeat((4-e.length%4)%4),s=(e+t).replace(/-/g,"+").replace(/_/g,"/"),i=atob(s),a=new Uint8Array(i.length);for(let r=0;r<i.length;r++)a[r]=i.charCodeAt(r);return a}async function Ne(){try{let e=await navigator.serviceWorker?.ready;return e?await e.pushManager.getSubscription()!==null:!1}catch{return!1}}async function Be(e){let t=e.split(/\s+/),s=t[0].toLowerCase(),i=t.slice(1).join(" ").trim();switch(s){case"/new":{let a=i||n.sessionCwd;return w(),c("Creating new session\u2026"),L({cwd:a||void 0}),!0}case"/pwd":return c(`\u{1F4C1} ${n.sessionCwd||"unknown"}`),!0;case"/rename":{if(!n.sessionId)return c("err: No active session"),!0;if(!i)return c(`Current: ${n.sessionTitle||"(untitled)"}`),c("Usage: /rename <new title>"),!0;try{await Ue(n.sessionId,i),c(`Renamed \u2192 ${i}`)}catch{c("err: Failed to rename session")}return!0}case"/sessions":return c("Removed. Use /switch to see all sessions."),!0;case"/exit":{if(!n.sessionId)return c("warn: No active session"),!0;let a=n.sessionId;try{let l=(await ie()).find(d=>d.id!==a);if(n.busy&&Q(),ye(a).catch(()=>{}),K=null,l){n.sessionSwitchGen++;let d=n.sessionSwitchGen;w(),n.sessionId=null,le(l.id);let[u]=await Promise.all([M(l.id),B(l.id)]);if(d!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:u.id,cwd:u.cwd,title:u.title,configOptions:u.configOptions,busyKind:u.busyKind}),I(!0)}else{let d=n.sessionCwd;w(),n.sessionId=null,c("Creating new session\u2026"),L({cwd:d||void 0,inheritFromSessionId:null})}}catch{c("err: Failed to exit session")}return!0}case"/prune":{try{let l=(await(await fetch("/api/v1/sessions")).json()).filter(d=>d.id!==n.sessionId);if(l.length===0)return c("No other sessions to prune."),!0;await Promise.all(l.map(d=>ye(d.id).catch(()=>{}))),K=null,c(`Pruned ${l.length} session(s).`)}catch{c("err: Failed to prune sessions")}return!0}case"/switch":{if(!i)return c("Usage: /switch <title or id prefix>"),!0;try{let r=await(await fetch("/api/v1/sessions")).json(),l=i.toLowerCase(),d=r.find(g=>g.id.startsWith(i)||g.title&&g.title.toLowerCase().includes(l));if(!d)return c(`err: No session matching "${i}"`),!0;n.sessionSwitchGen++;let u=n.sessionSwitchGen;w(),n.sessionId=null;let[f]=await Promise.all([M(d.id),B(d.id)]);if(u!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:f.id,cwd:f.cwd,title:f.title,configOptions:f.configOptions,busyKind:f.busyKind}),I(!0)}catch{w(),n.sessionId=null,c("err: Failed to switch session")}return!0}case"/cancel":return n.busy?(Q(),c("^C")):c("Nothing to cancel."),!0;case"/reload":return c("Reloading agent\u2026"),Ke().then(()=>{}).catch(a=>{c(`err: ${a instanceof Error?a.message:"Failed to reload agent"}`)}),!0;case"/help":case"?":{let a=[];n.serverVersion&&a.push(`WebAgent ${n.serverVersion}`),n.agentName&&n.agentVersion&&a.push(`${n.agentName} ${n.agentVersion}`),a.length&&c(a.join(" \xB7 ")),c("? \u2014 Show help"),c("!<command> \u2014 Run bash command");for(let r of ft){let l=r.args?`${r.cmd} ${r.args}`:r.cmd;c(`${l} \u2014 ${r.desc}`)}c("--- Shortcuts ---");for(let r of Vt)c(`${r.key} \u2014 ${r.desc}`);c("--- Tips ---");for(let r of Gt)c(r.text);return!0}case"/model":case"/mode":case"/think":{let r={"/model":"model","/mode":"mode","/think":"reasoning_effort"}[s],l=x(r);if(!i){let S=l?.options.find(se=>se.value===l.currentValue)?.name||l?.currentValue||"unknown";return c(`${l?.name||r}: ${S}`),c(`Type ${s} + space to pick from list`),!0}if(!l)return c(`err: ${s.slice(1)} is not available.`),!0;let d=i.trim(),u=S=>S.toLowerCase().replace(/[\s_]+/g,"-"),f=u(d),g=l.options.find(S=>u(S.value)===f||u(S.name)===f);if(!g){let S=l.options.filter(se=>u(se.value).includes(f)||u(se.name).includes(f));if(S.length===1)g=S[0];else if(S.length>1)return c(`err: Ambiguous "${i}". Type ${s} + space to see options.`),!0}return g?(l.currentValue=g.value,N(),A(),c(`${l.name} \u2192 ${g.name}`),await G(n.sessionId,r,g.value).catch(()=>{}),!0):(c(`err: Unknown "${i}". Type ${s} + space to see options.`),!0)}case"/notify":{if(typeof Notification>"u")return c("err: notifications not supported in this browser"),!0;let a=i.toLowerCase();if(a==="on"){if(Notification.permission==="denied")return c("notify: blocked \u2014 allow in browser site settings to enable"),!0;if(Notification.permission!=="granted"&&await Notification.requestPermission()!=="granted")return c("notify: blocked \u2014 allow in browser site settings to enable"),!0;let l=await Ne();return await Ut(),c(l?"notify: already enabled":"notify: enabled"),!0}if(a==="off")return await Wt(),c("notify: disabled"),!0;let r=Notification.permission;return r==="denied"?c("notify: blocked \u2014 allow in browser site settings to enable"):r==="granted"&&await Ne()?c("notify: enabled"):c("notify: off \u2014 use /notify on to enable"),!0}default:return!1}}var ft=[{cmd:"/cancel",args:"",desc:"Cancel current response"},{cmd:"/exit",args:"",desc:"Close current session"},{cmd:"/help",args:"",desc:"Show help (or type ?)"},{cmd:"/mode",args:"[name]",desc:"Pick or switch mode"},{cmd:"/model",args:"[name]",desc:"Pick or switch model"},{cmd:"/new",args:"[cwd]",desc:"New session"},{cmd:"/notify",args:"[on|off]",desc:"Toggle background notifications"},{cmd:"/prune",args:"",desc:"Delete all sessions except current"},{cmd:"/pwd",args:"",desc:"Show working directory"},{cmd:"/reload",args:"",desc:"Reload agent CLI"},{cmd:"/rename",args:"<new title>",desc:"Rename current session"},{cmd:"/switch",args:"<title|id>",desc:"Switch to session"},{cmd:"/think",args:"[level]",desc:"Pick or switch reasoning effort"}],Vt=[{key:"Enter",desc:"Send message"},{key:"Shift+Enter",desc:"New line"},{key:"^C",desc:"Cancel current response"},{key:"^M",desc:"Cycle mode (Agent \u2192 Plan \u2192 Autopilot)"},{key:"^U",desc:"Upload image"}],Gt=[{text:"Tap \u276F prompt to cycle mode"}],b=-1,m=[],v="commands",te=null,K=null,V=null,qe=!1;function Oe(){let e=o.input.value;if(V!==null){if(e===V)return;V=null}let t=e.match(/^\/new /);if(t){let l=e.slice(t[0].length).toLowerCase();Qt(l);return}let s=e.match(/^\/switch /);if(s){let l=e.slice(s[0].length).toLowerCase();Jt(l,"switch");return}let i=e.match(/^\/(model|mode|think) /);if(i){let d={model:"model",mode:"mode",think:"reasoning_effort"}[i[1]],u=e.slice(i[0].length).toLowerCase();Yt(d,u);return}let a=e.match(/^\/notify /);if(a){let l=e.slice(a[0].length).toLowerCase();Zt(l);return}if(!e.startsWith("/")||e.includes(" ")){y();return}v="commands";let r=e.toLowerCase();if(m=ft.filter(l=>l.cmd.startsWith(r)),m.length===0){y();return}b=0,F(),o.slashMenu.classList.add("active")}async function Jt(e,t="switch"){if(!K)try{K=await(await fetch("/api/v1/sessions")).json(),setTimeout(()=>{K=null},5e3)}catch{return}if(v=t,m=K.filter(i=>e?i.title&&i.title.toLowerCase().includes(e)||i.id.startsWith(e):!0),m.length===0){y();return}b=0,F(),o.slashMenu.classList.add("active")}var fe=null;async function Qt(e){if(!fe)try{let s=n.recentPathsLimit||0,i=s>0?`/api/v1/recent-paths?limit=${s}`:"/api/v1/recent-paths";fe=(await(await fetch(i)).json()).map(r=>({cwd:r.cwd,time:r.last_used_at})),setTimeout(()=>{fe=null},5e3)}catch{return}v="new";let t=fe;if(e&&(t=t.filter(s=>s.cwd.toLowerCase().includes(e))),m=t,m.length===0){y();return}b=0,F(),o.slashMenu.classList.add("active")}function Yt(e,t){let s=x(e);if(!s){y();return}if(v="config",te=e,m=s.options.filter(i=>t?i.value.toLowerCase().includes(t)||i.name.toLowerCase().includes(t):!0),m.length===0){y();return}b=0,F(),o.slashMenu.classList.add("active")}var zt=[{value:"on",name:"on",desc:"Enable background notifications"},{value:"off",name:"off",desc:"Disable background notifications"}];async function Zt(e){if(v="notify",m=zt.filter(i=>e?i.value.includes(e)||i.name.includes(e):!0),m.length===0){y();return}qe=await Ne();let t=qe?"on":"off",s=m.findIndex(i=>i.value===t);b=s>=0?s:0,F(),o.slashMenu.classList.add("active")}function F(){if(v==="new"){let t=(n.sessionCwd||"").toLowerCase();o.slashMenu.innerHTML=m.map((s,i)=>{let a=s.cwd.toLowerCase()===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${a?' style="color:var(--green)"':""}>${p((a?"* ":" ")+s.cwd)}</span></div>`}).join("")}else if(v==="config"){let t=ae(te)?.toLowerCase()||"";o.slashMenu.innerHTML=m.map((s,i)=>{let a=s.value.toLowerCase()===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${a?' style="color:var(--green)"':""}>${p((a?"* ":" ")+s.name)}</span></div>`}).join("")}else if(v==="notify"){let t=qe?"on":"off";o.slashMenu.innerHTML=m.map((s,i)=>{let a=s.value===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${a?' style="color:var(--green)"':""}>${p((a?"* ":" ")+s.name)}</span><span class="slash-desc">${p(s.desc)}</span></div>`}).join("")}else v==="switch"?o.slashMenu.innerHTML=m.map((t,s)=>{let i=t.id===n.sessionId,a=i?"* ":" ",r=t.title||t.id.slice(0,8)+"\u2026",l=tt(t.last_active_at||t.created_at);return`<div class="slash-item${s===b?" selected":""}" data-idx="${s}"><span class="slash-cmd"${i?' style="color:var(--green)"':""}>${p(a+r)}</span><span class="slash-desc">${p(t.cwd)} (${p(l)})</span></div>`}).join(""):o.slashMenu.innerHTML=m.map((t,s)=>{let i=t.args?`${t.cmd} ${t.args}`:t.cmd;return`<div class="slash-item${s===b?" selected":""}" data-idx="${s}"><span class="slash-cmd">${p(i)}</span><span class="slash-desc">${p(t.desc)}</span></div>`}).join("");let e=o.slashMenu.querySelector(".selected");e&&e.scrollIntoView({block:"nearest"})}function y(){o.slashMenu.classList.remove("active"),b=-1,m=[],v="commands",V=o.input.value}function Xt(e){if(!(e<0||e>=m.length)){if(v==="commands"){let t=m[e];o.input.value=t.cmd+(t.args?" ":""),y(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(V=null,Oe())}else if(v==="config"){let t=m[e],s={model:"/model",mode:"/mode",reasoning_effort:"/think"}[te]||`/${te}`;o.input.value=`${s} ${t.name}`,y(),o.input.focus()}else if(v==="notify"){let t=m[e];o.input.value=`/notify ${t.value}`,y(),o.input.focus()}else if(v==="new"){let t=m[e];o.input.value=`/new ${t.cwd}`,y(),o.input.focus()}else if(v==="switch"){let t=m[e];o.input.value=`/switch ${t.title||t.id}`,y(),o.input.focus()}}}async function en(e){if(!(e<0||e>=m.length))if(v==="new"){let t=m[e];o.input.value="",y(),w(),c("Creating new session\u2026"),L({cwd:t.cwd})}else if(v==="config"){let t=m[e],s=te,i=x(s);o.input.value="",y(),i&&(i.currentValue=t.value),N(),A(),c(`${i?.name||s} \u2192 ${t.name}`),await G(n.sessionId,s,t.value).catch(()=>{})}else if(v==="switch"){let t=m[e];o.input.value="",y(),w(),n.sessionId=null,c("Switching\u2026"),Promise.all([M(t.id),B(t.id)]).then(([s,i])=>{C({type:"session_created",sessionId:s.id,cwd:s.cwd,title:s.title,configOptions:s.configOptions,busyKind:s.busyKind}),i&&I(!0)}).catch(()=>{w(),n.sessionId=null,c("err: Failed to switch session")})}else if(v==="notify"){let t=m[e];o.input.value=`/notify ${t.value}`,y(),Be(o.input.value),o.input.value=""}else{let t=m[e];o.input.value=t.cmd+(t.args?" ":""),y(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(V=null,Oe())}}function mt(e){return o.slashMenu.classList.contains("active")?e.key==="ArrowDown"?(b=(b+1)%m.length,F(),!0):e.key==="ArrowUp"?(b=(b-1+m.length)%m.length,F(),!0):e.key==="Tab"?(Xt(b),!0):!1:!1}o.slashMenu.addEventListener("mousedown",e=>{e.preventDefault();let t=e.target.closest(".slash-item");t&&en(Number(t.dataset.idx))});o.input.addEventListener("input",()=>{Oe(),o.inputArea.classList.toggle("bash-mode",o.input.value.startsWith("!"))});function gt(e){return new Promise(t=>{let s=new FileReader;s.onload=()=>{let i=s.result.split(",")[1];t({data:i,mimeType:e.type,previewUrl:s.result})},s.readAsDataURL(e)})}function ht(e){n.pendingImages.push(e),me(),o.input.focus()}function me(){if(o.attachPreview.innerHTML="",n.pendingImages.length===0){o.attachPreview.classList.remove("active");return}o.attachPreview.classList.add("active"),n.pendingImages.forEach((e,t)=>{let s=document.createElement("span");s.className="attach-thumb",s.innerHTML=`<img src="${e.previewUrl}"><button class="remove">\xD7</button>`,s.querySelector(".remove").addEventListener("click",()=>{n.pendingImages.splice(t,1),me()}),o.attachPreview.appendChild(s)})}o.attachBtn.onclick=()=>o.fileInput.click();o.fileInput.onchange=async()=>{for(let e of o.fileInput.files)e.type.startsWith("image/")&&ht(await gt(e));o.fileInput.value=""};o.input.addEventListener("paste",async e=>{for(let t of e.clipboardData.items)t.type.startsWith("image/")&&(e.preventDefault(),ht(await gt(t.getAsFile())))});var H=document.createElement("div");H.id="lightbox-overlay";var ne=document.createElement("img");H.appendChild(ne);document.body.appendChild(H);var ge=1,tn=.5,nn=5;function sn(){return H.classList.contains("active")}function on(e){ne.src=e,ge=1,ne.style.transform="",H.classList.add("active")}function yt(){H.classList.remove("active"),ne.src=""}document.getElementById("messages").addEventListener("click",e=>{let t=e.target;t.tagName==="IMG"&&t.classList.contains("user-image")&&on(t.src)});H.addEventListener("click",e=>{e.target===H&&yt()});document.addEventListener("keydown",e=>{e.key==="Escape"&&sn()&&yt()});H.addEventListener("wheel",e=>{e.preventDefault();let t=e.deltaY>0?-.15:.15;ge=Math.min(nn,Math.max(tn,ge+t)),ne.style.transform=`scale(${ge})`},{passive:!1});function Ae(){return n.clientId!==null}n._onCancelTimeout=()=>c("warn: Agent not responding to cancel");function wt(){let e=o.input.value.trim();if(!e&&n.pendingImages.length===0)return;if((e.startsWith("/")||e==="?"||e.startsWith("? "))&&n.pendingImages.length===0){o.input.value="",o.input.style.height="auto",It(),Be(e);return}if(e.startsWith("!")&&n.pendingImages.length===0){let i=e.slice(1).trim();if(!i)return;if(!n.sessionId){c("warn: Session not ready yet, please wait\u2026");return}if(!Ae()){c("warn: Not connected, please retry");return}o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),Z(i,!0),n.sentBashForSession=n.sessionId,We(n.sessionId,i).catch(()=>{}),h(!0);return}if(n.busy)return;if(o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),!n.sessionId){c("warn: Session not ready yet, please wait\u2026");return}if(!Ae()){c("warn: Not connected, please retry");return}let t=D("user",e||"(image)");for(let i of n.pendingImages){let a=document.createElement("img");a.className="user-image",a.src=i.previewUrl,t.appendChild(a)}let s=n.pendingImages.slice();n.pendingImages.length=0,me(),s.length>0?Promise.all(s.map(i=>fetch(`/api/v1/sessions/${n.sessionId}/images`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({data:i.data,mimeType:i.mimeType})}).then(a=>a.json()).then(a=>({data:i.data,mimeType:i.mimeType,path:a.url})))).then(i=>{if(!Ae()){t.remove(),c("warn: Not connected, please retry"),h(!1);return}we(n.sessionId,e||"What is in this image?",i.map(a=>({data:a.data,mimeType:a.mimeType,path:a.path}))).catch(()=>{})}):we(n.sessionId,e).catch(()=>{}),n.turnEnded=!1,n.sentMessageForSession=n.sessionId,h(!0),et()}function vt(){Q()&&c("^C")}function St(){let e=o.input.value.trim();return e.startsWith("/")||e.startsWith("!")||e==="?"||e.startsWith("? ")}function It(){n.busy&&(St()?(o.sendBtn.textContent="\u21B5",o.sendBtn.title="Send (Enter)",o.sendBtn.classList.remove("cancel")):(o.sendBtn.textContent="^C",o.sendBtn.title="Cancel (Ctrl+C)",o.sendBtn.classList.add("cancel")))}o.sendBtn.onclick=()=>{n.busy&&!St()?vt():wt()};o.input.addEventListener("keydown",e=>{if(mt(e)){e.preventDefault();return}if(e.key==="Enter"&&!e.shiftKey){e.preventDefault(),y(),wt();return}if(e.key==="u"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey){e.preventDefault(),o.fileInput.click();return}});document.addEventListener("keydown",e=>{if(e.key==="c"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&n.busy&&!(window.getSelection()?.toString()||o.input.selectionStart!==o.input.selectionEnd)){e.preventDefault(),vt();return}e.key==="Escape"&&o.slashMenu.classList.contains("active")&&(e.preventDefault(),y(),o.input.focus())});function bt(){let e=x("mode");if(!e||!e.options.length)return;let t=e.options.findIndex(i=>i.value===e.currentValue),s=e.options[(t+1)%e.options.length];e.currentValue=s.value,G(n.sessionId,"mode",s.value).catch(()=>{}),c(`Mode \u2192 ${s.name}`),N()}document.addEventListener("keydown",e=>{e.key==="m"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&(e.preventDefault(),bt())});o.prompt.addEventListener("click",bt);o.input.addEventListener("input",It);o.input.addEventListener("input",()=>{o.input.style.height="auto",o.input.style.height=Math.min(o.input.scrollHeight,200)+"px"});async function rn(e){try{let t=await navigator.serviceWorker?.ready;if(!t)return;let s=await t.pushManager.getSubscription();if(!s)return;await fetch("/api/beta/push/register-client",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clientId:e,endpoint:s.endpoint})})}catch{}}function De(){O("connecting","connecting");let e=new EventSource("/api/v1/events/stream");n.eventSource=e,e.onmessage=async t=>{let s=JSON.parse(t.data);s.type==="connected"&&s.clientId&&(n.clientId=s.clientId,J(s.clientId,!document.hidden,n.sessionId??void 0).catch(()=>{}),rn(s.clientId),!s.agent)||C(s)},e.onerror=()=>{e.close(),ln(),setTimeout(De,3e3)},an()}async function an(){O("connecting","session loading");let e=n.sessionSwitchGen,t=Qe();if(t&&t===n.sessionId&&n.lastEventSeq>0){if(await Re(t,!0,e),e!==n.sessionSwitchGen)return;ut(),I(!1);return}if(t){if(w(),await Re(t,!1,e),e!==n.sessionSwitchGen)return;I(!0);return}try{let s=await ie();if(e!==n.sessionSwitchGen)return;if(s.length>0){if(w(),await Re(s[0].id,!1,e),e!==n.sessionSwitchGen)return;I(!0);return}}catch{}e===n.sessionSwitchGen&&L()}async function Re(e,t,s){if(t){try{let i=await M(e);if(s!==n.sessionSwitchGen)return;C({type:"session_created",sessionId:i.id,cwd:i.cwd,title:i.title,configOptions:i.configOptions,busyKind:i.busyKind})}catch{if(s!==n.sessionSwitchGen)return;let i=n.sessionCwd;w(),c("warn: Previous session expired, created new one."),L({cwd:i||void 0});return}if(s!==n.sessionSwitchGen)return;await Pe(e)}else{n.sessionId=null;let i=B(e),a;try{let[r,l]=await Promise.all([M(e),i]);if(s!==n.sessionSwitchGen)return;a=r,l||c("warn: Failed to load history.")}catch{if(s!==n.sessionSwitchGen)return;let r=n.sessionCwd;w(),c("warn: Previous session expired, created new one."),L({cwd:r||void 0});return}C({type:"session_created",sessionId:a.id,cwd:a.cwd,title:a.title,configOptions:a.configOptions,busyKind:a.busyKind})}}function ln(){O("disconnected","disconnected"),n.eventSource=null,n.clientId=null,k(),P(),n.currentBashEl&&X(n.currentBashEl,null,"disconnected"),n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,n.turnEnded=!1,Y(),h(!1)}document.addEventListener("visibilitychange",()=>{n.clientId&&J(n.clientId,!document.hidden,n.sessionId??void 0).catch(()=>{}),!document.hidden&&n.sessionId&&n.lastEventSeq>0&&!n.replayInProgress&&Pe(n.sessionId).then(()=>I(!1))});o.messages.addEventListener("click",ze);ot(Ze);De();fetch("/api/v1/version").then(e=>e.json()).then(e=>{typeof e.server=="string"&&(n.serverVersion=e.server);let t=e.agent;t&&(n.agentName=t.name??null,n.agentVersion=t.version??null)}).catch(()=>{});"serviceWorker"in navigator&&(navigator.serviceWorker.register("/sw.js"),navigator.serviceWorker.addEventListener("message",e=>{if(e.data?.type==="navigate"&&e.data.sessionId){let t=e.data.sessionId;if(n.sessionId===t)return;n.sessionSwitchGen++;let s=n.sessionSwitchGen;history.replaceState(null,"",`#${t}`),w(),n.sessionId=null,c("Switching\u2026"),Promise.all([M(t),B(t)]).then(([i,a])=>{s===n.sessionSwitchGen&&(C({type:"session_created",sessionId:i.id,cwd:i.cwd,title:i.title,configOptions:i.configOptions,busyKind:i.busyKind}),a&&I(!0))}).catch(()=>{w(),n.sessionId=null,c("err: Failed to switch session")})}}));
|