reeboot 1.0.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 +361 -0
- package/container/Dockerfile +48 -0
- package/container/entrypoint.sh +8 -0
- package/dist/agent-runner/index.d.ts +9 -0
- package/dist/agent-runner/index.d.ts.map +1 -0
- package/dist/agent-runner/index.js +21 -0
- package/dist/agent-runner/index.js.map +1 -0
- package/dist/agent-runner/interface.d.ts +56 -0
- package/dist/agent-runner/interface.d.ts.map +1 -0
- package/dist/agent-runner/interface.js +5 -0
- package/dist/agent-runner/interface.js.map +1 -0
- package/dist/agent-runner/pi-runner.d.ts +41 -0
- package/dist/agent-runner/pi-runner.d.ts.map +1 -0
- package/dist/agent-runner/pi-runner.js +162 -0
- package/dist/agent-runner/pi-runner.js.map +1 -0
- package/dist/channels/interface.d.ts +63 -0
- package/dist/channels/interface.d.ts.map +1 -0
- package/dist/channels/interface.js +33 -0
- package/dist/channels/interface.js.map +1 -0
- package/dist/channels/registry.d.ts +30 -0
- package/dist/channels/registry.d.ts.map +1 -0
- package/dist/channels/registry.js +71 -0
- package/dist/channels/registry.js.map +1 -0
- package/dist/channels/signal.d.ts +51 -0
- package/dist/channels/signal.d.ts.map +1 -0
- package/dist/channels/signal.js +263 -0
- package/dist/channels/signal.js.map +1 -0
- package/dist/channels/web.d.ts +35 -0
- package/dist/channels/web.d.ts.map +1 -0
- package/dist/channels/web.js +65 -0
- package/dist/channels/web.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +25 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +150 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/config.d.ts +366 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +140 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +69 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +166 -0
- package/dist/context.js.map +1 -0
- package/dist/credential-proxy.d.ts +25 -0
- package/dist/credential-proxy.d.ts.map +1 -0
- package/dist/credential-proxy.js +96 -0
- package/dist/credential-proxy.js.map +1 -0
- package/dist/daemon.d.ts +25 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +138 -0
- package/dist/daemon.js.map +1 -0
- package/dist/db/index.d.ts +23 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +113 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +408 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +55 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/doctor.d.ts +23 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +217 -0
- package/dist/doctor.js.map +1 -0
- package/dist/extensions/loader.d.ts +19 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +124 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +60 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +313 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/packages.d.ts +21 -0
- package/dist/packages.d.ts.map +1 -0
- package/dist/packages.js +116 -0
- package/dist/packages.js.map +1 -0
- package/dist/scheduler-registry.d.ts +8 -0
- package/dist/scheduler-registry.d.ts.map +1 -0
- package/dist/scheduler-registry.js +14 -0
- package/dist/scheduler-registry.js.map +1 -0
- package/dist/scheduler.d.ts +60 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +143 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +489 -0
- package/dist/server.js.map +1 -0
- package/dist/setup-wizard.d.ts +12 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +163 -0
- package/dist/setup-wizard.js.map +1 -0
- package/extensions/confirm-destructive.ts +59 -0
- package/extensions/custom-compaction.ts +114 -0
- package/extensions/protected-paths.ts +30 -0
- package/extensions/sandbox/index.ts +317 -0
- package/extensions/sandbox/package-lock.json +92 -0
- package/extensions/sandbox/package.json +19 -0
- package/extensions/scheduler-tool.ts +65 -0
- package/extensions/session-name.ts +27 -0
- package/extensions/token-meter.ts +55 -0
- package/package.json +68 -0
- package/skills/send-message/SKILL.md +27 -0
- package/skills/web-search/SKILL.md +32 -0
- package/templates/global-agents.md +23 -0
- package/templates/main-agents.md +28 -0
- package/webchat/index.html +421 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-search
|
|
3
|
+
description: Search the web using SearXNG. Use when you need to find current information, research a topic, look up facts, or gather information from the internet.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Web Search Skill
|
|
7
|
+
|
|
8
|
+
Use this skill when you need to search the web for information.
|
|
9
|
+
|
|
10
|
+
## Instructions
|
|
11
|
+
|
|
12
|
+
1. Formulate a clear, concise search query for the topic
|
|
13
|
+
2. Use the `web_search` tool with your query
|
|
14
|
+
3. Review the results and extract relevant information
|
|
15
|
+
4. Cite your sources when presenting findings
|
|
16
|
+
|
|
17
|
+
## When to Use
|
|
18
|
+
|
|
19
|
+
- Looking up current events or news
|
|
20
|
+
- Researching a technical topic
|
|
21
|
+
- Finding documentation or tutorials
|
|
22
|
+
- Verifying facts or statistics
|
|
23
|
+
- Discovering resources on a subject
|
|
24
|
+
|
|
25
|
+
## Example
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
User: What is the current version of Node.js LTS?
|
|
29
|
+
→ web_search("Node.js LTS version 2025")
|
|
30
|
+
→ Review results
|
|
31
|
+
→ Report: "The current Node.js LTS version is X.Y.Z (sources: ...)"
|
|
32
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Global Agent Memory
|
|
2
|
+
|
|
3
|
+
This file is shared across all contexts. Use it for persistent facts, preferences, and knowledge that should be available everywhere.
|
|
4
|
+
|
|
5
|
+
## About Me
|
|
6
|
+
|
|
7
|
+
<!-- Add facts about yourself here: name, location, occupation, preferences, etc. -->
|
|
8
|
+
|
|
9
|
+
## Persistent Preferences
|
|
10
|
+
|
|
11
|
+
<!-- Communication style, language preferences, formatting preferences -->
|
|
12
|
+
|
|
13
|
+
## Important People & Relationships
|
|
14
|
+
|
|
15
|
+
<!-- Key people, their roles, contact preferences -->
|
|
16
|
+
|
|
17
|
+
## Projects & Responsibilities
|
|
18
|
+
|
|
19
|
+
<!-- Ongoing projects, responsibilities, goals -->
|
|
20
|
+
|
|
21
|
+
## Knowledge Base
|
|
22
|
+
|
|
23
|
+
<!-- Important domain knowledge, frequently referenced facts -->
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Reeboot — Personal Assistant
|
|
2
|
+
|
|
3
|
+
You are Reeboot, a personal AI assistant running locally on this machine.
|
|
4
|
+
|
|
5
|
+
## Persona
|
|
6
|
+
|
|
7
|
+
- You are helpful, concise, and direct
|
|
8
|
+
- You remember context across conversations
|
|
9
|
+
- You have access to scheduled tasks and can act proactively
|
|
10
|
+
- You communicate via the configured channels (WhatsApp, Signal, WebChat)
|
|
11
|
+
|
|
12
|
+
## Capabilities
|
|
13
|
+
|
|
14
|
+
- Answer questions and help with tasks
|
|
15
|
+
- Access and manage contexts and sessions
|
|
16
|
+
- Execute scheduled prompts and tasks
|
|
17
|
+
- Coordinate across channels
|
|
18
|
+
|
|
19
|
+
## Instructions
|
|
20
|
+
|
|
21
|
+
- Always be brief unless detail is requested
|
|
22
|
+
- Reference global memory (AGENTS.md in contexts/global/) for persistent facts
|
|
23
|
+
- Use workspace/ for files and artifacts
|
|
24
|
+
- Escalate to the user when in doubt
|
|
25
|
+
|
|
26
|
+
## Workspace
|
|
27
|
+
|
|
28
|
+
All files created during sessions are stored in `workspace/`.
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Reeboot WebChat</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--bg: #0f0f0f;
|
|
12
|
+
--surface: #1a1a1a;
|
|
13
|
+
--border: #2a2a2a;
|
|
14
|
+
--text: #e0e0e0;
|
|
15
|
+
--text-muted: #888;
|
|
16
|
+
--accent: #4f9cf9;
|
|
17
|
+
--user-bg: #1e3a5f;
|
|
18
|
+
--assistant-bg: #1a1a1a;
|
|
19
|
+
--tool-bg: #1e2a1e;
|
|
20
|
+
--error: #e05252;
|
|
21
|
+
--radius: 8px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
26
|
+
background: var(--bg);
|
|
27
|
+
color: var(--text);
|
|
28
|
+
height: 100dvh;
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
header {
|
|
34
|
+
padding: 12px 16px;
|
|
35
|
+
border-bottom: 1px solid var(--border);
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 10px;
|
|
39
|
+
background: var(--surface);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
header h1 { font-size: 1rem; font-weight: 600; }
|
|
43
|
+
|
|
44
|
+
#status {
|
|
45
|
+
margin-left: auto;
|
|
46
|
+
font-size: 0.75rem;
|
|
47
|
+
color: var(--text-muted);
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 6px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#status-dot {
|
|
54
|
+
width: 8px; height: 8px;
|
|
55
|
+
border-radius: 50%;
|
|
56
|
+
background: #555;
|
|
57
|
+
}
|
|
58
|
+
#status-dot.connected { background: #4caf50; }
|
|
59
|
+
#status-dot.connecting { background: #ff9800; }
|
|
60
|
+
#status-dot.error { background: var(--error); }
|
|
61
|
+
|
|
62
|
+
#messages {
|
|
63
|
+
flex: 1;
|
|
64
|
+
overflow-y: auto;
|
|
65
|
+
padding: 16px;
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
gap: 12px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.message {
|
|
72
|
+
max-width: 80%;
|
|
73
|
+
padding: 10px 14px;
|
|
74
|
+
border-radius: var(--radius);
|
|
75
|
+
line-height: 1.5;
|
|
76
|
+
font-size: 0.9rem;
|
|
77
|
+
white-space: pre-wrap;
|
|
78
|
+
word-break: break-word;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.message.user {
|
|
82
|
+
background: var(--user-bg);
|
|
83
|
+
align-self: flex-end;
|
|
84
|
+
border-bottom-right-radius: 2px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.message.assistant {
|
|
88
|
+
background: var(--assistant-bg);
|
|
89
|
+
border: 1px solid var(--border);
|
|
90
|
+
align-self: flex-start;
|
|
91
|
+
border-bottom-left-radius: 2px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.message.assistant.streaming::after {
|
|
95
|
+
content: '▋';
|
|
96
|
+
animation: blink 1s step-end infinite;
|
|
97
|
+
color: var(--accent);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes blink { 50% { opacity: 0; } }
|
|
101
|
+
|
|
102
|
+
.message.error-msg {
|
|
103
|
+
background: #2a1515;
|
|
104
|
+
border: 1px solid var(--error);
|
|
105
|
+
color: var(--error);
|
|
106
|
+
align-self: center;
|
|
107
|
+
font-size: 0.8rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.tool-indicator {
|
|
111
|
+
background: var(--tool-bg);
|
|
112
|
+
border: 1px solid #2d4a2d;
|
|
113
|
+
border-radius: 4px;
|
|
114
|
+
padding: 4px 10px;
|
|
115
|
+
font-size: 0.75rem;
|
|
116
|
+
color: #7db87d;
|
|
117
|
+
align-self: flex-start;
|
|
118
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
user-select: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.tool-indicator summary { list-style: none; }
|
|
124
|
+
.tool-indicator summary::-webkit-details-marker { display: none; }
|
|
125
|
+
|
|
126
|
+
.tool-details {
|
|
127
|
+
margin-top: 6px;
|
|
128
|
+
color: var(--text-muted);
|
|
129
|
+
font-size: 0.72rem;
|
|
130
|
+
white-space: pre-wrap;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
footer {
|
|
134
|
+
padding: 12px 16px;
|
|
135
|
+
border-top: 1px solid var(--border);
|
|
136
|
+
background: var(--surface);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#input-row {
|
|
140
|
+
display: flex;
|
|
141
|
+
gap: 8px;
|
|
142
|
+
align-items: flex-end;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#input {
|
|
146
|
+
flex: 1;
|
|
147
|
+
background: #111;
|
|
148
|
+
border: 1px solid var(--border);
|
|
149
|
+
border-radius: var(--radius);
|
|
150
|
+
color: var(--text);
|
|
151
|
+
padding: 10px 12px;
|
|
152
|
+
font-size: 0.9rem;
|
|
153
|
+
font-family: inherit;
|
|
154
|
+
resize: none;
|
|
155
|
+
min-height: 42px;
|
|
156
|
+
max-height: 160px;
|
|
157
|
+
outline: none;
|
|
158
|
+
transition: border-color 0.15s;
|
|
159
|
+
}
|
|
160
|
+
#input:focus { border-color: var(--accent); }
|
|
161
|
+
#input:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
162
|
+
|
|
163
|
+
button {
|
|
164
|
+
border: none;
|
|
165
|
+
border-radius: var(--radius);
|
|
166
|
+
padding: 10px 16px;
|
|
167
|
+
font-size: 0.85rem;
|
|
168
|
+
font-family: inherit;
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
transition: opacity 0.15s;
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
}
|
|
173
|
+
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
174
|
+
|
|
175
|
+
#send-btn {
|
|
176
|
+
background: var(--accent);
|
|
177
|
+
color: #fff;
|
|
178
|
+
font-weight: 600;
|
|
179
|
+
}
|
|
180
|
+
#send-btn:hover:not(:disabled) { opacity: 0.85; }
|
|
181
|
+
|
|
182
|
+
#cancel-btn {
|
|
183
|
+
background: #3a1515;
|
|
184
|
+
color: var(--error);
|
|
185
|
+
display: none;
|
|
186
|
+
}
|
|
187
|
+
#cancel-btn.visible { display: block; }
|
|
188
|
+
#cancel-btn:hover:not(:disabled) { opacity: 0.85; }
|
|
189
|
+
|
|
190
|
+
.hint {
|
|
191
|
+
font-size: 0.72rem;
|
|
192
|
+
color: var(--text-muted);
|
|
193
|
+
margin-top: 6px;
|
|
194
|
+
text-align: right;
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
197
|
+
</head>
|
|
198
|
+
<body>
|
|
199
|
+
|
|
200
|
+
<header>
|
|
201
|
+
<h1>🤖 Reeboot</h1>
|
|
202
|
+
<div id="status">
|
|
203
|
+
<div id="status-dot" class="connecting"></div>
|
|
204
|
+
<span id="status-text">Connecting…</span>
|
|
205
|
+
</div>
|
|
206
|
+
</header>
|
|
207
|
+
|
|
208
|
+
<div id="messages"></div>
|
|
209
|
+
|
|
210
|
+
<footer>
|
|
211
|
+
<div id="input-row">
|
|
212
|
+
<textarea
|
|
213
|
+
id="input"
|
|
214
|
+
placeholder="Send a message…"
|
|
215
|
+
rows="1"
|
|
216
|
+
autocomplete="off"
|
|
217
|
+
spellcheck="true"
|
|
218
|
+
></textarea>
|
|
219
|
+
<button id="send-btn" disabled>Send</button>
|
|
220
|
+
<button id="cancel-btn">Cancel</button>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="hint">Enter to send · Shift+Enter for newline</div>
|
|
223
|
+
</footer>
|
|
224
|
+
|
|
225
|
+
<script>
|
|
226
|
+
(function () {
|
|
227
|
+
'use strict';
|
|
228
|
+
|
|
229
|
+
// ── Config ─────────────────────────────────────────────────────────────────
|
|
230
|
+
const contextId = 'main';
|
|
231
|
+
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
232
|
+
const wsUrl = `${wsProtocol}//${location.host}/ws/chat/${contextId}`;
|
|
233
|
+
|
|
234
|
+
// ── State ──────────────────────────────────────────────────────────────────
|
|
235
|
+
let ws = null;
|
|
236
|
+
let isBusy = false;
|
|
237
|
+
let currentAssistantEl = null;
|
|
238
|
+
let reconnectDelay = 1000;
|
|
239
|
+
|
|
240
|
+
// ── Elements ───────────────────────────────────────────────────────────────
|
|
241
|
+
const messagesEl = document.getElementById('messages');
|
|
242
|
+
const inputEl = document.getElementById('input');
|
|
243
|
+
const sendBtn = document.getElementById('send-btn');
|
|
244
|
+
const cancelBtn = document.getElementById('cancel-btn');
|
|
245
|
+
const statusDot = document.getElementById('status-dot');
|
|
246
|
+
const statusText = document.getElementById('status-text');
|
|
247
|
+
|
|
248
|
+
// ── Status helpers ─────────────────────────────────────────────────────────
|
|
249
|
+
function setStatus(state, text) {
|
|
250
|
+
statusDot.className = state;
|
|
251
|
+
statusText.textContent = text;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── UI helpers ─────────────────────────────────────────────────────────────
|
|
255
|
+
function appendMessage(role, text) {
|
|
256
|
+
const el = document.createElement('div');
|
|
257
|
+
el.className = `message ${role}`;
|
|
258
|
+
el.textContent = text;
|
|
259
|
+
messagesEl.appendChild(el);
|
|
260
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
261
|
+
return el;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function appendError(text) {
|
|
265
|
+
const el = document.createElement('div');
|
|
266
|
+
el.className = 'message error-msg';
|
|
267
|
+
el.textContent = `⚠ ${text}`;
|
|
268
|
+
messagesEl.appendChild(el);
|
|
269
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function appendToolIndicator(toolName) {
|
|
273
|
+
const el = document.createElement('details');
|
|
274
|
+
el.className = 'tool-indicator';
|
|
275
|
+
const summary = document.createElement('summary');
|
|
276
|
+
summary.textContent = `⚙ ${toolName}`;
|
|
277
|
+
el.appendChild(summary);
|
|
278
|
+
messagesEl.appendChild(el);
|
|
279
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
280
|
+
return el;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function setBusy(busy) {
|
|
284
|
+
isBusy = busy;
|
|
285
|
+
inputEl.disabled = busy;
|
|
286
|
+
sendBtn.disabled = busy || ws?.readyState !== WebSocket.OPEN;
|
|
287
|
+
if (busy) {
|
|
288
|
+
cancelBtn.classList.add('visible');
|
|
289
|
+
} else {
|
|
290
|
+
cancelBtn.classList.remove('visible');
|
|
291
|
+
currentAssistantEl?.classList.remove('streaming');
|
|
292
|
+
currentAssistantEl = null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ── Auto-resize textarea ───────────────────────────────────────────────────
|
|
297
|
+
inputEl.addEventListener('input', () => {
|
|
298
|
+
inputEl.style.height = 'auto';
|
|
299
|
+
inputEl.style.height = Math.min(inputEl.scrollHeight, 160) + 'px';
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// ── Send ───────────────────────────────────────────────────────────────────
|
|
303
|
+
function sendMessage() {
|
|
304
|
+
const content = inputEl.value.trim();
|
|
305
|
+
if (!content || isBusy || ws?.readyState !== WebSocket.OPEN) return;
|
|
306
|
+
|
|
307
|
+
appendMessage('user', content);
|
|
308
|
+
inputEl.value = '';
|
|
309
|
+
inputEl.style.height = 'auto';
|
|
310
|
+
|
|
311
|
+
ws.send(JSON.stringify({ type: 'message', content }));
|
|
312
|
+
setBusy(true);
|
|
313
|
+
|
|
314
|
+
// Prepare streaming assistant bubble
|
|
315
|
+
currentAssistantEl = appendMessage('assistant', '');
|
|
316
|
+
currentAssistantEl.classList.add('streaming');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
inputEl.addEventListener('keydown', (e) => {
|
|
320
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
sendMessage();
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
sendBtn.addEventListener('click', sendMessage);
|
|
327
|
+
|
|
328
|
+
cancelBtn.addEventListener('click', () => {
|
|
329
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
330
|
+
ws.send(JSON.stringify({ type: 'cancel' }));
|
|
331
|
+
}
|
|
332
|
+
setBusy(false);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// ── WebSocket ──────────────────────────────────────────────────────────────
|
|
336
|
+
function connect() {
|
|
337
|
+
setStatus('connecting', 'Connecting…');
|
|
338
|
+
ws = new WebSocket(wsUrl);
|
|
339
|
+
|
|
340
|
+
ws.onopen = () => {
|
|
341
|
+
reconnectDelay = 1000;
|
|
342
|
+
// Wait for connected message before enabling input
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
ws.onmessage = (e) => {
|
|
346
|
+
let msg;
|
|
347
|
+
try { msg = JSON.parse(e.data); } catch { return; }
|
|
348
|
+
|
|
349
|
+
switch (msg.type) {
|
|
350
|
+
case 'connected':
|
|
351
|
+
setStatus('connected', `Connected · ${contextId}`);
|
|
352
|
+
sendBtn.disabled = false;
|
|
353
|
+
break;
|
|
354
|
+
|
|
355
|
+
case 'text_delta':
|
|
356
|
+
if (currentAssistantEl) {
|
|
357
|
+
currentAssistantEl.textContent += msg.delta;
|
|
358
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
|
|
362
|
+
case 'tool_call_start': {
|
|
363
|
+
const toolEl = appendToolIndicator(msg.toolName);
|
|
364
|
+
toolEl.dataset.toolCallId = msg.toolCallId;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
case 'tool_call_end': {
|
|
369
|
+
// Find the open tool indicator and add result
|
|
370
|
+
const open = messagesEl.querySelector(
|
|
371
|
+
`.tool-indicator[data-tool-call-id="${msg.toolCallId}"]`
|
|
372
|
+
);
|
|
373
|
+
if (open) {
|
|
374
|
+
const details = document.createElement('div');
|
|
375
|
+
details.className = 'tool-details';
|
|
376
|
+
const result = typeof msg.result === 'string' ? msg.result : JSON.stringify(msg.result, null, 2);
|
|
377
|
+
details.textContent = result.slice(0, 500) + (result.length > 500 ? '…' : '');
|
|
378
|
+
open.appendChild(details);
|
|
379
|
+
if (msg.isError) open.style.borderColor = '#5a2a2a';
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
case 'message_end':
|
|
385
|
+
setBusy(false);
|
|
386
|
+
break;
|
|
387
|
+
|
|
388
|
+
case 'cancelled':
|
|
389
|
+
setBusy(false);
|
|
390
|
+
appendError('Turn cancelled.');
|
|
391
|
+
break;
|
|
392
|
+
|
|
393
|
+
case 'error':
|
|
394
|
+
setBusy(false);
|
|
395
|
+
appendError(msg.message || 'Unknown error');
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
ws.onclose = (e) => {
|
|
401
|
+
setStatus('error', `Disconnected (${e.code})`);
|
|
402
|
+
sendBtn.disabled = true;
|
|
403
|
+
setBusy(false);
|
|
404
|
+
|
|
405
|
+
// Reconnect unless intentionally closed
|
|
406
|
+
if (e.code !== 1000) {
|
|
407
|
+
setTimeout(connect, reconnectDelay);
|
|
408
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
ws.onerror = () => {
|
|
413
|
+
setStatus('error', 'Connection error');
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
connect();
|
|
418
|
+
})();
|
|
419
|
+
</script>
|
|
420
|
+
</body>
|
|
421
|
+
</html>
|