@poncho-ai/cli 0.17.0 → 0.19.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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +32 -0
- package/dist/{chunk-GDB5X2OS.js → chunk-7P53QSP5.js} +494 -157
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-B6JJ33SN.js → run-interactive-ink-4KVVPMR3.js} +1 -1
- package/package.json +4 -4
- package/src/index.ts +216 -124
- package/src/web-ui-client.ts +203 -36
- package/src/web-ui-store.ts +68 -1
- package/src/web-ui-styles.ts +51 -5
- package/src/web-ui.ts +6 -3
package/src/web-ui-client.ts
CHANGED
|
@@ -164,6 +164,62 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
164
164
|
}
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
+
// During streaming, incomplete backtick sequences cause marked to
|
|
168
|
+
// swallow all subsequent text into an invisible code element. This
|
|
169
|
+
// helper detects unclosed fences and inline code delimiters and
|
|
170
|
+
// appends the missing closing so marked can render partial text.
|
|
171
|
+
const closeStreamingMarkdown = (text) => {
|
|
172
|
+
const BT = "\\x60";
|
|
173
|
+
let result = text;
|
|
174
|
+
|
|
175
|
+
// 1. Unclosed fenced code blocks (lines starting with 3+ backticks)
|
|
176
|
+
const lines = result.split("\\n");
|
|
177
|
+
let openFenceLen = 0;
|
|
178
|
+
for (let li = 0; li < lines.length; li++) {
|
|
179
|
+
const trimmed = lines[li].trimStart();
|
|
180
|
+
let btCount = 0;
|
|
181
|
+
while (btCount < trimmed.length && trimmed[btCount] === BT) btCount++;
|
|
182
|
+
if (btCount >= 3) {
|
|
183
|
+
if (openFenceLen === 0) {
|
|
184
|
+
openFenceLen = btCount;
|
|
185
|
+
} else if (btCount >= openFenceLen) {
|
|
186
|
+
openFenceLen = 0;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (openFenceLen > 0) {
|
|
191
|
+
let fence = "";
|
|
192
|
+
for (let k = 0; k < openFenceLen; k++) fence += BT;
|
|
193
|
+
return result + "\\n" + fence;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 2. Unclosed inline code delimiters
|
|
197
|
+
let idx = 0;
|
|
198
|
+
let inCode = false;
|
|
199
|
+
let delimLen = 0;
|
|
200
|
+
while (idx < result.length) {
|
|
201
|
+
if (result[idx] === BT) {
|
|
202
|
+
let run = 0;
|
|
203
|
+
while (idx < result.length && result[idx] === BT) { run++; idx++; }
|
|
204
|
+
if (!inCode) {
|
|
205
|
+
inCode = true;
|
|
206
|
+
delimLen = run;
|
|
207
|
+
} else if (run === delimLen) {
|
|
208
|
+
inCode = false;
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
idx++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (inCode) {
|
|
215
|
+
let closing = "";
|
|
216
|
+
for (let k = 0; k < delimLen; k++) closing += BT;
|
|
217
|
+
result += closing;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
};
|
|
222
|
+
|
|
167
223
|
const extractToolActivity = (value) => {
|
|
168
224
|
const source = String(value || "");
|
|
169
225
|
let markerIndex = source.lastIndexOf("\\n### Tool activity\\n");
|
|
@@ -230,8 +286,15 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
230
286
|
);
|
|
231
287
|
})
|
|
232
288
|
.join("");
|
|
289
|
+
const batchButtons = requests.length > 1
|
|
290
|
+
? '<div class="approval-batch-actions">' +
|
|
291
|
+
'<button class="approval-batch-btn approve" data-approval-batch="approve">Approve all (' + requests.length + ')</button>' +
|
|
292
|
+
'<button class="approval-batch-btn deny" data-approval-batch="deny">Deny all (' + requests.length + ')</button>' +
|
|
293
|
+
"</div>"
|
|
294
|
+
: "";
|
|
233
295
|
return (
|
|
234
296
|
'<div class="approval-requests">' +
|
|
297
|
+
batchButtons +
|
|
235
298
|
rows +
|
|
236
299
|
"</div>"
|
|
237
300
|
);
|
|
@@ -761,7 +824,7 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
761
824
|
// Show current text being typed
|
|
762
825
|
if (isStreaming && i === messages.length - 1 && m._currentText) {
|
|
763
826
|
const textDiv = document.createElement("div");
|
|
764
|
-
textDiv.innerHTML = renderAssistantMarkdown(m._currentText);
|
|
827
|
+
textDiv.innerHTML = renderAssistantMarkdown(closeStreamingMarkdown(m._currentText));
|
|
765
828
|
content.appendChild(textDiv);
|
|
766
829
|
}
|
|
767
830
|
} else {
|
|
@@ -1062,8 +1125,32 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
1062
1125
|
updateContextRing();
|
|
1063
1126
|
}
|
|
1064
1127
|
}
|
|
1128
|
+
if (eventName === "tool:generating") {
|
|
1129
|
+
const toolName = payload.tool || "tool";
|
|
1130
|
+
if (!Array.isArray(assistantMessage._activeActivities)) {
|
|
1131
|
+
assistantMessage._activeActivities = [];
|
|
1132
|
+
}
|
|
1133
|
+
assistantMessage._activeActivities.push({
|
|
1134
|
+
kind: "generating",
|
|
1135
|
+
tool: toolName,
|
|
1136
|
+
label: "Preparing " + toolName,
|
|
1137
|
+
});
|
|
1138
|
+
if (assistantMessage._currentText.length > 0) {
|
|
1139
|
+
assistantMessage._sections.push({
|
|
1140
|
+
type: "text",
|
|
1141
|
+
content: assistantMessage._currentText,
|
|
1142
|
+
});
|
|
1143
|
+
assistantMessage._currentText = "";
|
|
1144
|
+
}
|
|
1145
|
+
const prepText =
|
|
1146
|
+
"- preparing \\x60" + toolName + "\\x60";
|
|
1147
|
+
assistantMessage._currentTools.push(prepText);
|
|
1148
|
+
assistantMessage.metadata.toolActivity.push(prepText);
|
|
1149
|
+
renderIfActiveConversation(true);
|
|
1150
|
+
}
|
|
1065
1151
|
if (eventName === "tool:started") {
|
|
1066
1152
|
const toolName = payload.tool || "tool";
|
|
1153
|
+
removeActiveActivityForTool(assistantMessage, toolName);
|
|
1067
1154
|
const startedActivity = addActiveActivityFromToolStart(
|
|
1068
1155
|
assistantMessage,
|
|
1069
1156
|
payload,
|
|
@@ -1075,14 +1162,24 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
1075
1162
|
});
|
|
1076
1163
|
assistantMessage._currentText = "";
|
|
1077
1164
|
}
|
|
1165
|
+
const tick = "\\x60";
|
|
1166
|
+
const prepPrefix = "- preparing " + tick + toolName + tick;
|
|
1167
|
+
const prepToolIdx = assistantMessage._currentTools.indexOf(prepPrefix);
|
|
1168
|
+
if (prepToolIdx >= 0) {
|
|
1169
|
+
assistantMessage._currentTools.splice(prepToolIdx, 1);
|
|
1170
|
+
}
|
|
1171
|
+
const prepMetaIdx = assistantMessage.metadata.toolActivity.indexOf(prepPrefix);
|
|
1172
|
+
if (prepMetaIdx >= 0) {
|
|
1173
|
+
assistantMessage.metadata.toolActivity.splice(prepMetaIdx, 1);
|
|
1174
|
+
}
|
|
1078
1175
|
const detail =
|
|
1079
1176
|
startedActivity && typeof startedActivity.detail === "string"
|
|
1080
1177
|
? startedActivity.detail.trim()
|
|
1081
1178
|
: "";
|
|
1082
1179
|
const toolText =
|
|
1083
|
-
"- start
|
|
1180
|
+
"- start " + tick +
|
|
1084
1181
|
toolName +
|
|
1085
|
-
|
|
1182
|
+
tick +
|
|
1086
1183
|
(detail ? " (" + detail + ")" : "");
|
|
1087
1184
|
assistantMessage._currentTools.push(toolText);
|
|
1088
1185
|
assistantMessage.metadata.toolActivity.push(toolText);
|
|
@@ -1805,26 +1902,57 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
1805
1902
|
updateContextRing();
|
|
1806
1903
|
}
|
|
1807
1904
|
}
|
|
1905
|
+
if (eventName === "tool:generating") {
|
|
1906
|
+
const toolName = payload.tool || "tool";
|
|
1907
|
+
if (!Array.isArray(assistantMessage._activeActivities)) {
|
|
1908
|
+
assistantMessage._activeActivities = [];
|
|
1909
|
+
}
|
|
1910
|
+
assistantMessage._activeActivities.push({
|
|
1911
|
+
kind: "generating",
|
|
1912
|
+
tool: toolName,
|
|
1913
|
+
label: "Preparing " + toolName,
|
|
1914
|
+
});
|
|
1915
|
+
if (assistantMessage._currentText.length > 0) {
|
|
1916
|
+
assistantMessage._sections.push({ type: "text", content: assistantMessage._currentText });
|
|
1917
|
+
assistantMessage._currentText = "";
|
|
1918
|
+
}
|
|
1919
|
+
if (!assistantMessage.metadata) assistantMessage.metadata = {};
|
|
1920
|
+
if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
|
|
1921
|
+
const prepText = "- preparing \\x60" + toolName + "\\x60";
|
|
1922
|
+
assistantMessage._currentTools.push(prepText);
|
|
1923
|
+
assistantMessage.metadata.toolActivity.push(prepText);
|
|
1924
|
+
renderIfActiveConversation(true);
|
|
1925
|
+
}
|
|
1808
1926
|
if (eventName === "tool:started") {
|
|
1809
1927
|
const toolName = payload.tool || "tool";
|
|
1928
|
+
removeActiveActivityForTool(assistantMessage, toolName);
|
|
1810
1929
|
const startedActivity = addActiveActivityFromToolStart(
|
|
1811
1930
|
assistantMessage,
|
|
1812
1931
|
payload,
|
|
1813
1932
|
);
|
|
1814
|
-
// If we have text accumulated, push it as a text section
|
|
1815
1933
|
if (assistantMessage._currentText.length > 0) {
|
|
1816
1934
|
assistantMessage._sections.push({ type: "text", content: assistantMessage._currentText });
|
|
1817
1935
|
assistantMessage._currentText = "";
|
|
1818
1936
|
}
|
|
1937
|
+
if (!assistantMessage.metadata) assistantMessage.metadata = {};
|
|
1938
|
+
if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
|
|
1939
|
+
const tick = "\\x60";
|
|
1940
|
+
const prepPrefix = "- preparing " + tick + toolName + tick;
|
|
1941
|
+
const prepToolIdx = assistantMessage._currentTools.indexOf(prepPrefix);
|
|
1942
|
+
if (prepToolIdx >= 0) {
|
|
1943
|
+
assistantMessage._currentTools.splice(prepToolIdx, 1);
|
|
1944
|
+
}
|
|
1945
|
+
const prepMetaIdx = assistantMessage.metadata.toolActivity.indexOf(prepPrefix);
|
|
1946
|
+
if (prepMetaIdx >= 0) {
|
|
1947
|
+
assistantMessage.metadata.toolActivity.splice(prepMetaIdx, 1);
|
|
1948
|
+
}
|
|
1819
1949
|
const detail =
|
|
1820
1950
|
startedActivity && typeof startedActivity.detail === "string"
|
|
1821
1951
|
? startedActivity.detail.trim()
|
|
1822
1952
|
: "";
|
|
1823
1953
|
const toolText =
|
|
1824
|
-
"- start
|
|
1954
|
+
"- start " + tick + toolName + tick + (detail ? " (" + detail + ")" : "");
|
|
1825
1955
|
assistantMessage._currentTools.push(toolText);
|
|
1826
|
-
if (!assistantMessage.metadata) assistantMessage.metadata = {};
|
|
1827
|
-
if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
|
|
1828
1956
|
assistantMessage.metadata.toolActivity.push(toolText);
|
|
1829
1957
|
renderIfActiveConversation(true);
|
|
1830
1958
|
}
|
|
@@ -2278,45 +2406,20 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
2278
2406
|
openLightbox(img.src);
|
|
2279
2407
|
});
|
|
2280
2408
|
|
|
2281
|
-
|
|
2282
|
-
const
|
|
2283
|
-
if (!(target instanceof Element)) {
|
|
2284
|
-
return;
|
|
2285
|
-
}
|
|
2286
|
-
const button = target.closest(".approval-action-btn");
|
|
2287
|
-
if (!button) {
|
|
2288
|
-
return;
|
|
2289
|
-
}
|
|
2290
|
-
const approvalId = button.getAttribute("data-approval-id") || "";
|
|
2291
|
-
const decision = button.getAttribute("data-approval-decision") || "";
|
|
2292
|
-
if (!approvalId || (decision !== "approve" && decision !== "deny")) {
|
|
2293
|
-
return;
|
|
2294
|
-
}
|
|
2295
|
-
if (state.approvalRequestsInFlight[approvalId]) {
|
|
2296
|
-
return;
|
|
2297
|
-
}
|
|
2409
|
+
const submitApproval = async (approvalId, decision, opts) => {
|
|
2410
|
+
const wasStreaming = opts && opts.wasStreaming;
|
|
2298
2411
|
state.approvalRequestsInFlight[approvalId] = true;
|
|
2299
|
-
const wasStreaming = state.isStreaming;
|
|
2300
|
-
if (!wasStreaming) {
|
|
2301
|
-
setStreaming(true);
|
|
2302
|
-
}
|
|
2303
2412
|
updatePendingApproval(approvalId, (request) => ({
|
|
2304
2413
|
...request,
|
|
2305
2414
|
state: "submitting",
|
|
2306
2415
|
pendingDecision: decision,
|
|
2307
2416
|
}));
|
|
2308
|
-
renderMessages(state.activeMessages, state.isStreaming);
|
|
2309
2417
|
try {
|
|
2310
2418
|
await api("/api/approvals/" + encodeURIComponent(approvalId), {
|
|
2311
2419
|
method: "POST",
|
|
2312
2420
|
body: JSON.stringify({ approved: decision === "approve" }),
|
|
2313
2421
|
});
|
|
2314
2422
|
updatePendingApproval(approvalId, () => null);
|
|
2315
|
-
renderMessages(state.activeMessages, state.isStreaming);
|
|
2316
|
-
loadConversations();
|
|
2317
|
-
if (!wasStreaming && state.activeConversationId) {
|
|
2318
|
-
await streamConversationEvents(state.activeConversationId, { liveOnly: true });
|
|
2319
|
-
}
|
|
2320
2423
|
} catch (error) {
|
|
2321
2424
|
const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
|
|
2322
2425
|
if (isStale) {
|
|
@@ -2330,13 +2433,77 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
2330
2433
|
_error: errMsg,
|
|
2331
2434
|
}));
|
|
2332
2435
|
}
|
|
2333
|
-
renderMessages(state.activeMessages, state.isStreaming);
|
|
2334
2436
|
} finally {
|
|
2437
|
+
delete state.approvalRequestsInFlight[approvalId];
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
|
|
2441
|
+
elements.messages.addEventListener("click", async (event) => {
|
|
2442
|
+
const target = event.target;
|
|
2443
|
+
if (!(target instanceof Element)) {
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
// Batch approve/deny all
|
|
2448
|
+
const batchBtn = target.closest(".approval-batch-btn");
|
|
2449
|
+
if (batchBtn) {
|
|
2450
|
+
const decision = batchBtn.getAttribute("data-approval-batch") || "";
|
|
2451
|
+
if (decision !== "approve" && decision !== "deny") return;
|
|
2452
|
+
const messages = state.activeMessages || [];
|
|
2453
|
+
const pending = [];
|
|
2454
|
+
for (const m of messages) {
|
|
2455
|
+
if (Array.isArray(m._pendingApprovals)) {
|
|
2456
|
+
for (const req of m._pendingApprovals) {
|
|
2457
|
+
if (req.approvalId && req.state !== "submitting" && !state.approvalRequestsInFlight[req.approvalId]) {
|
|
2458
|
+
pending.push(req.approvalId);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
if (pending.length === 0) return;
|
|
2464
|
+
const wasStreaming = state.isStreaming;
|
|
2465
|
+
if (!wasStreaming) setStreaming(true);
|
|
2466
|
+
renderMessages(state.activeMessages, state.isStreaming);
|
|
2467
|
+
await Promise.all(pending.map((aid) => submitApproval(aid, decision, { wasStreaming })));
|
|
2468
|
+
renderMessages(state.activeMessages, state.isStreaming);
|
|
2469
|
+
loadConversations();
|
|
2470
|
+
if (!wasStreaming && state.activeConversationId) {
|
|
2471
|
+
await streamConversationEvents(state.activeConversationId, { liveOnly: true });
|
|
2472
|
+
}
|
|
2335
2473
|
if (!wasStreaming) {
|
|
2336
2474
|
setStreaming(false);
|
|
2337
2475
|
renderMessages(state.activeMessages, false);
|
|
2338
2476
|
}
|
|
2339
|
-
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// Individual approve/deny
|
|
2481
|
+
const button = target.closest(".approval-action-btn");
|
|
2482
|
+
if (!button) {
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
const approvalId = button.getAttribute("data-approval-id") || "";
|
|
2486
|
+
const decision = button.getAttribute("data-approval-decision") || "";
|
|
2487
|
+
if (!approvalId || (decision !== "approve" && decision !== "deny")) {
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
if (state.approvalRequestsInFlight[approvalId]) {
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
const wasStreaming = state.isStreaming;
|
|
2494
|
+
if (!wasStreaming) {
|
|
2495
|
+
setStreaming(true);
|
|
2496
|
+
}
|
|
2497
|
+
renderMessages(state.activeMessages, state.isStreaming);
|
|
2498
|
+
await submitApproval(approvalId, decision, { wasStreaming });
|
|
2499
|
+
renderMessages(state.activeMessages, state.isStreaming);
|
|
2500
|
+
loadConversations();
|
|
2501
|
+
if (!wasStreaming && state.activeConversationId) {
|
|
2502
|
+
await streamConversationEvents(state.activeConversationId, { liveOnly: true });
|
|
2503
|
+
}
|
|
2504
|
+
if (!wasStreaming) {
|
|
2505
|
+
setStreaming(false);
|
|
2506
|
+
renderMessages(state.activeMessages, false);
|
|
2340
2507
|
}
|
|
2341
2508
|
});
|
|
2342
2509
|
|
package/src/web-ui-store.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
1
|
+
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, resolve } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
@@ -165,11 +165,16 @@ type SessionRecord = {
|
|
|
165
165
|
export class SessionStore {
|
|
166
166
|
private readonly sessions = new Map<string, SessionRecord>();
|
|
167
167
|
private readonly ttlMs: number;
|
|
168
|
+
private signingKey: string | undefined;
|
|
168
169
|
|
|
169
170
|
constructor(ttlMs = 1000 * 60 * 60 * 8) {
|
|
170
171
|
this.ttlMs = ttlMs;
|
|
171
172
|
}
|
|
172
173
|
|
|
174
|
+
setSigningKey(key: string): void {
|
|
175
|
+
if (key) this.signingKey = key;
|
|
176
|
+
}
|
|
177
|
+
|
|
173
178
|
create(ownerId = DEFAULT_OWNER): SessionRecord {
|
|
174
179
|
const now = Date.now();
|
|
175
180
|
const session: SessionRecord = {
|
|
@@ -200,6 +205,68 @@ export class SessionStore {
|
|
|
200
205
|
delete(sessionId: string): void {
|
|
201
206
|
this.sessions.delete(sessionId);
|
|
202
207
|
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Encode a session into a signed cookie value that survives serverless
|
|
211
|
+
* cold starts. Format: `base64url(payload).signature`
|
|
212
|
+
*/
|
|
213
|
+
signSession(session: SessionRecord): string | undefined {
|
|
214
|
+
if (!this.signingKey) return undefined;
|
|
215
|
+
const payload = Buffer.from(
|
|
216
|
+
JSON.stringify({
|
|
217
|
+
sid: session.sessionId,
|
|
218
|
+
o: session.ownerId,
|
|
219
|
+
csrf: session.csrfToken,
|
|
220
|
+
exp: session.expiresAt,
|
|
221
|
+
}),
|
|
222
|
+
).toString("base64url");
|
|
223
|
+
const sig = createHmac("sha256", this.signingKey)
|
|
224
|
+
.update(payload)
|
|
225
|
+
.digest("base64url");
|
|
226
|
+
return `${payload}.${sig}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Restore a session from a signed cookie value. Returns the session
|
|
231
|
+
* (also added to the in-memory store) or undefined if invalid/expired.
|
|
232
|
+
*/
|
|
233
|
+
restoreFromSigned(cookieValue: string): SessionRecord | undefined {
|
|
234
|
+
if (!this.signingKey) return undefined;
|
|
235
|
+
const dotIdx = cookieValue.lastIndexOf(".");
|
|
236
|
+
if (dotIdx <= 0) return undefined;
|
|
237
|
+
|
|
238
|
+
const payload = cookieValue.slice(0, dotIdx);
|
|
239
|
+
const sig = cookieValue.slice(dotIdx + 1);
|
|
240
|
+
const expected = createHmac("sha256", this.signingKey)
|
|
241
|
+
.update(payload)
|
|
242
|
+
.digest("base64url");
|
|
243
|
+
if (sig.length !== expected.length) return undefined;
|
|
244
|
+
if (
|
|
245
|
+
!timingSafeEqual(Buffer.from(sig, "utf8"), Buffer.from(expected, "utf8"))
|
|
246
|
+
)
|
|
247
|
+
return undefined;
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const data = JSON.parse(
|
|
251
|
+
Buffer.from(payload, "base64url").toString("utf8"),
|
|
252
|
+
) as { sid?: string; o?: string; csrf?: string; exp?: number };
|
|
253
|
+
if (!data.sid || !data.o || !data.csrf || !data.exp) return undefined;
|
|
254
|
+
if (Date.now() > data.exp) return undefined;
|
|
255
|
+
|
|
256
|
+
const session: SessionRecord = {
|
|
257
|
+
sessionId: data.sid,
|
|
258
|
+
ownerId: data.o,
|
|
259
|
+
csrfToken: data.csrf,
|
|
260
|
+
createdAt: data.exp - this.ttlMs,
|
|
261
|
+
expiresAt: data.exp,
|
|
262
|
+
lastSeenAt: Date.now(),
|
|
263
|
+
};
|
|
264
|
+
this.sessions.set(session.sessionId, session);
|
|
265
|
+
return session;
|
|
266
|
+
} catch {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
203
270
|
}
|
|
204
271
|
|
|
205
272
|
type LoginAttemptState = {
|
package/src/web-ui-styles.ts
CHANGED
|
@@ -270,6 +270,22 @@ export const WEB_UI_STYLES = `
|
|
|
270
270
|
flex-direction: column;
|
|
271
271
|
padding: 12px 8px;
|
|
272
272
|
}
|
|
273
|
+
.sidebar-header {
|
|
274
|
+
display: flex;
|
|
275
|
+
align-items: center;
|
|
276
|
+
gap: 8px;
|
|
277
|
+
}
|
|
278
|
+
.sidebar-agent-name {
|
|
279
|
+
font-size: 14px;
|
|
280
|
+
font-weight: 500;
|
|
281
|
+
color: var(--fg-strong);
|
|
282
|
+
flex: 1;
|
|
283
|
+
min-width: 0;
|
|
284
|
+
overflow: hidden;
|
|
285
|
+
text-overflow: ellipsis;
|
|
286
|
+
white-space: nowrap;
|
|
287
|
+
padding-left: 10px;
|
|
288
|
+
}
|
|
273
289
|
.new-chat-btn {
|
|
274
290
|
background: transparent;
|
|
275
291
|
border: 0;
|
|
@@ -282,6 +298,7 @@ export const WEB_UI_STYLES = `
|
|
|
282
298
|
gap: 8px;
|
|
283
299
|
font-size: 13px;
|
|
284
300
|
cursor: pointer;
|
|
301
|
+
flex-shrink: 0;
|
|
285
302
|
transition: background 0.15s, color 0.15s;
|
|
286
303
|
}
|
|
287
304
|
.new-chat-btn:hover { color: var(--fg); }
|
|
@@ -784,6 +801,30 @@ export const WEB_UI_STYLES = `
|
|
|
784
801
|
opacity: 0.55;
|
|
785
802
|
cursor: not-allowed;
|
|
786
803
|
}
|
|
804
|
+
.approval-batch-actions {
|
|
805
|
+
display: flex;
|
|
806
|
+
gap: 6px;
|
|
807
|
+
margin-bottom: 8px;
|
|
808
|
+
}
|
|
809
|
+
.approval-batch-btn {
|
|
810
|
+
border-radius: 6px;
|
|
811
|
+
border: 1px solid var(--border-5);
|
|
812
|
+
background: var(--surface-4);
|
|
813
|
+
color: var(--fg-approval-btn);
|
|
814
|
+
font-size: 11px;
|
|
815
|
+
font-weight: 600;
|
|
816
|
+
padding: 4px 10px;
|
|
817
|
+
cursor: pointer;
|
|
818
|
+
}
|
|
819
|
+
.approval-batch-btn:hover { background: var(--surface-7); }
|
|
820
|
+
.approval-batch-btn.approve {
|
|
821
|
+
border-color: var(--approve-border);
|
|
822
|
+
color: var(--approve);
|
|
823
|
+
}
|
|
824
|
+
.approval-batch-btn.deny {
|
|
825
|
+
border-color: var(--deny-border);
|
|
826
|
+
color: var(--deny);
|
|
827
|
+
}
|
|
787
828
|
.user-bubble {
|
|
788
829
|
background: var(--bg-elevated);
|
|
789
830
|
border: 1px solid var(--border-2);
|
|
@@ -1180,6 +1221,9 @@ export const WEB_UI_STYLES = `
|
|
|
1180
1221
|
.shell.sidebar-open .sidebar { transform: translateX(0); }
|
|
1181
1222
|
.sidebar-toggle { display: grid; place-items: center; }
|
|
1182
1223
|
.topbar-new-chat { display: grid; place-items: center; }
|
|
1224
|
+
.sidebar-header { padding-right: 130px; }
|
|
1225
|
+
.sidebar-agent-name { padding-left: 0; }
|
|
1226
|
+
.new-chat-btn { order: -1; }
|
|
1183
1227
|
.poncho-badge {
|
|
1184
1228
|
display: none;
|
|
1185
1229
|
position: fixed;
|
|
@@ -1319,15 +1363,17 @@ export const WEB_UI_STYLES = `
|
|
|
1319
1363
|
font-size: 13px;
|
|
1320
1364
|
}
|
|
1321
1365
|
@media (max-width: 768px) {
|
|
1366
|
+
.main-body { flex-direction: column; }
|
|
1322
1367
|
.browser-panel {
|
|
1323
|
-
position:
|
|
1324
|
-
|
|
1325
|
-
|
|
1368
|
+
position: relative;
|
|
1369
|
+
order: -1;
|
|
1370
|
+
max-height: 35vh;
|
|
1326
1371
|
flex: none !important;
|
|
1327
|
-
|
|
1372
|
+
width: auto !important;
|
|
1373
|
+
border-bottom: 1px solid var(--border-1);
|
|
1328
1374
|
}
|
|
1329
1375
|
.browser-panel-resize { display: none !important; }
|
|
1330
|
-
.main-chat.has-browser { flex: 1 1 auto !important; min-width: 0; }
|
|
1376
|
+
.main-chat.has-browser { flex: 1 1 auto !important; min-width: 0; min-height: 0; }
|
|
1331
1377
|
}
|
|
1332
1378
|
|
|
1333
1379
|
/* --- Subagent UI --- */
|
package/src/web-ui.ts
CHANGED
|
@@ -130,9 +130,12 @@ ${WEB_UI_STYLES}
|
|
|
130
130
|
|
|
131
131
|
<div id="app" class="shell hidden">
|
|
132
132
|
<aside class="sidebar">
|
|
133
|
-
<
|
|
134
|
-
<
|
|
135
|
-
|
|
133
|
+
<div class="sidebar-header">
|
|
134
|
+
<span class="sidebar-agent-name">${agentName}</span>
|
|
135
|
+
<button id="new-chat" class="new-chat-btn">
|
|
136
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
136
139
|
<div id="conversation-list" class="conversation-list"></div>
|
|
137
140
|
<div class="sidebar-footer">
|
|
138
141
|
<button id="logout" class="logout-btn">Log out</button>
|