@csdwd/ai-teams-server 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import websocket from "@fastify/websocket";
|
|
|
13
13
|
import fastifyStatic from "@fastify/static";
|
|
14
14
|
|
|
15
15
|
// ../../packages/shared/dist/index.js
|
|
16
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "timeout"]);
|
|
16
17
|
var ProtocolError = class extends Error {
|
|
17
18
|
constructor(message) {
|
|
18
19
|
super(message);
|
|
@@ -578,6 +579,16 @@ async function hydrateState(db, state, defaultTimeoutSec, maxLogChunksPerTask) {
|
|
|
578
579
|
queue.push(task.id);
|
|
579
580
|
state.taskQueues.set(task.employeeId, queue);
|
|
580
581
|
}
|
|
582
|
+
} else if (task.employeeId && !TERMINAL_STATUSES.has(task.status)) {
|
|
583
|
+
task.status = "queued";
|
|
584
|
+
persistTask(db, task);
|
|
585
|
+
if (task.targetMode === "queue") {
|
|
586
|
+
state.sharedTaskQueue.push(task.id);
|
|
587
|
+
} else {
|
|
588
|
+
const queue = state.mainTaskQueues.get(task.employeeId) ?? [];
|
|
589
|
+
queue.push(task.id);
|
|
590
|
+
state.mainTaskQueues.set(task.employeeId, queue);
|
|
591
|
+
}
|
|
581
592
|
}
|
|
582
593
|
}
|
|
583
594
|
const logRows = await db.all(
|
|
@@ -1222,7 +1233,6 @@ var scheduleListResponseSchema = {
|
|
|
1222
1233
|
// src/dispatch.ts
|
|
1223
1234
|
import { createHmac, randomUUID } from "node:crypto";
|
|
1224
1235
|
import WebSocket from "ws";
|
|
1225
|
-
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "timeout"]);
|
|
1226
1236
|
function nowIso() {
|
|
1227
1237
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1228
1238
|
}
|
|
@@ -1959,11 +1969,52 @@ function createDispatch(ctx) {
|
|
|
1959
1969
|
break;
|
|
1960
1970
|
}
|
|
1961
1971
|
}
|
|
1972
|
+
function startDisconnectRecovery(employeeId) {
|
|
1973
|
+
const employee = state.employees.get(employeeId);
|
|
1974
|
+
if (!employee) return;
|
|
1975
|
+
const taskIds = [];
|
|
1976
|
+
if (employee.mainTaskId) taskIds.push(employee.mainTaskId);
|
|
1977
|
+
if (employee.queueTaskId) taskIds.push(employee.queueTaskId);
|
|
1978
|
+
if (taskIds.length === 0) return;
|
|
1979
|
+
const timer = setTimeout(() => {
|
|
1980
|
+
state.disconnectTimers.delete(employeeId);
|
|
1981
|
+
if (state.agentSockets.has(employeeId)) return;
|
|
1982
|
+
for (const taskId of taskIds) {
|
|
1983
|
+
const task = state.tasks.get(taskId);
|
|
1984
|
+
if (!task || TERMINAL_STATUSES.has(task.status)) continue;
|
|
1985
|
+
clearTaskTimeout(taskId);
|
|
1986
|
+
task.status = "queued";
|
|
1987
|
+
task.employeeId = null;
|
|
1988
|
+
upsertTask(task);
|
|
1989
|
+
log.info({ taskId, employeeId }, "Task re-queued after disconnect grace period");
|
|
1990
|
+
if (task.targetMode === "queue") {
|
|
1991
|
+
enqueueSharedTask(taskId);
|
|
1992
|
+
} else {
|
|
1993
|
+
const queue = state.mainTaskQueues.get(employeeId) ?? [];
|
|
1994
|
+
if (!queue.includes(taskId)) queue.push(taskId);
|
|
1995
|
+
state.mainTaskQueues.set(employeeId, queue);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
const emp = state.employees.get(employeeId);
|
|
1999
|
+
if (emp) {
|
|
2000
|
+
if (taskIds.includes(emp.mainTaskId ?? "")) setMainTask(employeeId, null, null);
|
|
2001
|
+
if (taskIds.includes(emp.queueTaskId ?? "")) setQueueTask(employeeId, null, null);
|
|
2002
|
+
broadcastToLeaders({ type: "employee.upsert", employee: emp });
|
|
2003
|
+
}
|
|
2004
|
+
for (const taskId of taskIds) {
|
|
2005
|
+
const task = state.tasks.get(taskId);
|
|
2006
|
+
if (task) broadcastToLeaders({ type: "task.upsert", task });
|
|
2007
|
+
}
|
|
2008
|
+
dispatchSharedQueuedTasks();
|
|
2009
|
+
}, ctx.disconnectGraceMs);
|
|
2010
|
+
state.disconnectTimers.set(employeeId, timer);
|
|
2011
|
+
}
|
|
1962
2012
|
return {
|
|
1963
2013
|
dispatchLeaderCommand,
|
|
1964
2014
|
handleAgentMessage,
|
|
1965
2015
|
handleLeaderMessage,
|
|
1966
|
-
cancelTaskById
|
|
2016
|
+
cancelTaskById,
|
|
2017
|
+
startDisconnectRecovery
|
|
1967
2018
|
};
|
|
1968
2019
|
}
|
|
1969
2020
|
|
|
@@ -2157,7 +2208,7 @@ async function createAiTeamsServer(options) {
|
|
|
2157
2208
|
disconnectGraceMs,
|
|
2158
2209
|
encryptor: createEncryptor(process.env.AI_TEAMS_ENCRYPTION_KEY)
|
|
2159
2210
|
};
|
|
2160
|
-
const { dispatchLeaderCommand, handleAgentMessage, handleLeaderMessage, cancelTaskById } = createDispatch(dispatchCtx);
|
|
2211
|
+
const { dispatchLeaderCommand, handleAgentMessage, handleLeaderMessage, cancelTaskById, startDisconnectRecovery } = createDispatch(dispatchCtx);
|
|
2161
2212
|
const scheduleDispatchFn = (message, webhookUrl, cliConfig, priority, requiredLabels) => {
|
|
2162
2213
|
return dispatchLeaderCommand(message, webhookUrl, cliConfig, priority, requiredLabels);
|
|
2163
2214
|
};
|
|
@@ -2786,6 +2837,7 @@ async function createAiTeamsServer(options) {
|
|
|
2786
2837
|
for (const leaderSocket of state.leaderSockets) {
|
|
2787
2838
|
sendJson(leaderSocket, { type: "employee.upsert", employee }, dispatchCtx.encryptor);
|
|
2788
2839
|
}
|
|
2840
|
+
startDisconnectRecovery(employeeId);
|
|
2789
2841
|
}
|
|
2790
2842
|
app.log.info({ employeeId }, "Agent disconnected");
|
|
2791
2843
|
});
|
|
@@ -2891,7 +2943,7 @@ if (isCli) {
|
|
|
2891
2943
|
getArgValue2 = getArgValue, resolveDataDir2 = resolveDataDir, resolvePidFile2 = resolvePidFile, resolveLogDir2 = resolveLogDir, applyCliArgsToEnv2 = applyCliArgsToEnv;
|
|
2892
2944
|
const args = process.argv.slice(2);
|
|
2893
2945
|
if (args.includes("--version") || args.includes("-v")) {
|
|
2894
|
-
console.log("0.3.
|
|
2946
|
+
console.log("0.3.2");
|
|
2895
2947
|
process.exit(0);
|
|
2896
2948
|
}
|
|
2897
2949
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{color:#e8eefc;background:radial-gradient(circle at top left,rgba(105,170,255,.24),transparent 34%),radial-gradient(circle at bottom right,rgba(68,111,196,.28),transparent 32%),#091221;font-family:SF Pro Display,PingFang SC,Microsoft YaHei,sans-serif}*{box-sizing:border-box}html,body,#root{height:100%;overflow:hidden}body{margin:0;min-height:100vh;background:transparent;font-size:13px;line-height:1.42}button,input,select,textarea{font:inherit}.app-shell{display:grid;height:100dvh;min-height:0;overflow:hidden;grid-template-columns:minmax(0,1fr) 500px;grid-template-rows:auto minmax(0,1fr);grid-template-areas:"topbar topbar" "workspace command";align-items:stretch}.auth-screen{display:grid;min-height:100dvh;place-items:center;padding:18px;overflow:auto}.auth-card{width:min(390px,100%);border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:18px;background:#0d1727e0;box-shadow:0 14px 32px #0108143d}.auth-brand{margin-bottom:14px}.topbar,.command-panel{padding:12px 14px;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);background:#091221c2}.topbar{grid-area:topbar;display:flex;align-items:center;justify-content:space-between;gap:14px;border-bottom:1px solid rgba(255,255,255,.08);position:relative;z-index:10}.brand{display:flex;gap:10px;align-items:center;flex:0 0 auto}.brand-badge{display:grid;place-items:center;width:32px;height:32px;border-radius:10px;background:linear-gradient(135deg,#63b3ff,#67ffd4);color:#07203f;font-weight:800;font-size:13px}.brand strong{font-size:14px}.brand p{margin:2px 0 0;color:#90a1be;font-size:12px}.nav-list{display:flex;flex:1;flex-direction:row;gap:6px;justify-content:flex-end;overflow-x:auto}.nav-item{flex:0 0 auto;border:0;border-radius:9px;padding:7px 10px;text-align:left;color:#c9d6f2;background:#ffffff0a;cursor:pointer;font-size:12px;white-space:nowrap}.nav-item.active{background:linear-gradient(135deg,#63b3ff52,#67ffd42e);color:#fff}.workspace-panel{grid-area:workspace;display:flex;min-width:0;min-height:0;flex-direction:column;gap:10px;padding:12px 14px;overflow:auto;overscroll-behavior:contain}.board,.task-log-page{min-width:0}.board-header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:10px}.board-header h1,.panel-card h2,.task-log-panel h1{margin:0}.board-header h1,.task-log-panel h1{font-size:20px;line-height:1.2}.panel-card h2,.section-title-row h2{font-size:15px;line-height:1.25}.board-header p{margin:5px 0 0;color:#92a6c8;font-size:12px}.employee-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,560px),1fr));gap:10px}.employee-card,.panel-card,.task-log-panel,.empty-state{border:1px solid rgba(255,255,255,.08);border-radius:14px;background:#0d1727cc;box-shadow:0 12px 30px #0108143d}.employee-card{padding:10px}.task-log-panel{flex:0 0 auto;margin:0;padding:10px}.section-title-row{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;margin-bottom:10px}.section-title-row h2{margin:0}.section-title-row p{margin:4px 0 0;color:#90a1be;font-size:12px}.filter-row{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}.filter-chip,.target-chip{border:1px solid rgba(255,255,255,.08);border-radius:999px;padding:5px 8px;color:#c9d6f2;background:#ffffff0a;cursor:pointer;font-size:12px}.filter-chip.active,.target-chip.active{color:#061626;background:#79ffd1}.employee-card__header{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;margin-bottom:8px}.employee-card__title{display:flex;flex-wrap:wrap;gap:6px;align-items:baseline}.employee-card__header h2{margin:0;font-size:15px}.task-state-badge{display:inline-flex;align-items:center;border-radius:999px;padding:1px 6px;color:#90a1be;background:#ffffff0a;font-size:10px;line-height:1.4;text-transform:uppercase}.task-state-badge.task-running,.task-state-badge.task-accepted,.task-state-badge.task-dispatched{color:#9bc8ff;background:#5da6ff1a}.task-state-badge.task-completed{color:#9df6b5;background:#79ffd114}.task-state-badge.task-failed,.task-state-badge.task-cancelled,.task-state-badge.task-timeout{color:#ff9e9e;background:#ff59591a}.employee-meta,.task-strip,.history-meta,.chat-message__meta{display:flex;justify-content:space-between;gap:10px}.employee-meta{flex-wrap:wrap;justify-content:flex-start;row-gap:4px;margin-bottom:8px;color:#7f93b5;font-size:11px}.task-strip{align-items:center;margin-bottom:8px;padding:6px 8px;border-radius:9px;background:#ffffff0a}.task-strip.task-failed,.task-row.task-failed{border-color:#ff77776b;background:#ff59591a}.task-strip.task-completed,.task-row.task-completed{border-color:#79ffd13d}.task-strip span{flex:1;color:#c9d6f2;font-size:12px}.log-window{margin:0;width:100%;aspect-ratio:16 / 9;min-height:220px;max-height:460px;overflow:auto;border-radius:10px;border:1px solid rgba(121,255,209,.12);padding:10px;background:linear-gradient(180deg,rgba(121,255,209,.03),transparent 34%),#06101b;color:#bfffd4;font-family:SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.55;white-space:pre-wrap;box-shadow:inset 0 0 0 1px #ffffff05}.status-pill{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 8px;font-size:11px;background:#ffffff0d}.status-pill.online{color:#9df6b5}.status-pill.offline{color:#f5a8a8}.status-pill.agent-presence{flex:0 0 auto}.status-pill.busy{color:#9bc8ff;background:#5da6ff1f}.status-pill.failed{color:#ff9e9e;background:#ff59591f}.status-pill.completed{color:#9df6b5}.status-pill.running,.status-pill.accepted,.status-pill.dispatched{color:#9bc8ff}.command-panel{grid-area:command;display:flex;min-width:0;min-height:0;height:100%;flex-direction:column;gap:10px;border-left:1px solid rgba(255,255,255,.08);overflow:hidden}.panel-card{padding:10px}.chat-panel{display:flex;flex:1;min-height:0;flex-direction:column;overflow:hidden}.chat-header{display:flex;flex:0 0 auto;justify-content:space-between;gap:10px;align-items:flex-start}.chat-header p{margin:3px 0 0;color:#90a1be;font-size:12px}.chat-header code{color:#79ffd1}.chat-list{display:flex;flex:1 1 auto;min-height:0;flex-direction:column;gap:8px;margin-top:10px;overflow:auto;padding-right:4px;overscroll-behavior:contain}.chat-composer{flex:0 0 auto;margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,.08)}.target-picker{display:flex;flex-wrap:wrap;gap:6px;max-height:78px;overflow:auto;overscroll-behavior:contain}.chat-message{width:fit-content;max-width:88%;border-radius:11px 11px 4px;padding:8px}.leader-message{border-bottom-right-radius:6px;background:linear-gradient(135deg,#68b6ff47,#79ffd11f)}.leader-message-wrapper{display:flex;flex-direction:column;align-items:flex-end;width:fit-content;max-width:88%;align-self:flex-end}.chat-message__time-outside{color:#90a1be;font-size:11px;margin-top:2px;padding-right:4px}.chat-message__executing{display:flex;flex-direction:column;gap:2px;margin-top:4px;padding-top:4px;border-top:1px solid rgba(255,255,255,.1)}.chat-message__executing span{color:#9bc8ff;font-size:11px}.employee-message{align-self:flex-start;border-bottom-left-radius:6px;background:#ffffff0f}.employee-message.task-failed,.employee-message.task-timeout{background:#ff59591f}.chat-message p{margin:6px 0;color:#eef4ff;white-space:pre-wrap}.chat-message small{color:#79ffd1}.chat-message__meta span,.chat-empty{color:#90a1be;font-size:11px}.field{display:flex;flex-direction:column;gap:5px;margin-top:9px}.field span{color:#98abcb;font-size:12px}.field input,.field select,.field textarea{width:100%;border:1px solid rgba(255,255,255,.08);border-radius:9px;padding:8px 10px;background:#ffffff0a;color:#eef4ff;font-size:12px}.field textarea{min-height:82px;max-height:112px;resize:none;overflow:auto}.primary-button,.secondary-button{border:0;border-radius:10px;cursor:pointer}.primary-button{width:100%;margin-top:10px;padding:8px 12px;color:#07203f;background:linear-gradient(135deg,#68b6ff,#79ffd1);font-weight:700;font-size:12px}.secondary-button{padding:6px 8px;background:#ffffff14;color:#eef4ff;font-size:12px}.history-list{display:flex;flex-direction:column;gap:10px;margin-top:14px}.task-table{display:flex;flex-direction:column;gap:6px}.task-row{display:flex;justify-content:space-between;gap:10px;border:1px solid rgba(255,255,255,.08);border-radius:9px;padding:8px 10px;background:#ffffff0a}.task-row__main{min-width:0}.task-row__main p{margin:4px 0 0;color:#c9d6f2}.task-row__side{display:flex;flex-direction:column;align-items:flex-end;gap:6px;white-space:nowrap}.task-row__side small,.error-text{color:#90a1be}.error-text{display:inline-block;margin-top:4px;color:#ffb3b3}.history-item{border-radius:10px;padding:10px;background:#ffffff0a}.history-item p,.history-empty{margin:6px 0 0;color:#c9d6f2}.history-meta span{color:#90a1be;font-size:11px}.empty-state{padding:24px;color:#90a1be;text-align:center}.reconnect-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#091221bf;color:#79ffd1;font-size:16px;font-weight:600;letter-spacing:.05em;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none}.mobile-status-bar,.mobile-chat-feed,.mobile-input-bar,.mobile-logout,.mobile-terminal-overlay{display:none}@media(max-width:1100px)and (min-width:641px){.app-shell{grid-template-columns:1fr;grid-template-rows:auto minmax(0,1fr) minmax(min(500px,62dvh),62dvh);grid-template-areas:"topbar" "workspace" "command"}.topbar,.command-panel{border:0}.topbar{align-items:flex-start;flex-direction:column;gap:10px;padding:12px 14px}.nav-list{width:100%;justify-content:flex-start}.command-panel{border-top:1px solid rgba(255,255,255,.08);padding:10px}.command-panel .panel-card{padding:10px}.section-title-row,.task-row{flex-direction:column}.task-row__side{align-items:flex-start}.field textarea{min-height:68px;max-height:78px}}@media(max-width:640px){html,body,#root{position:fixed;top:0;left:0;right:0;bottom:0;overflow:hidden}.app-shell{height:100%;height:100dvh;box-sizing:border-box;padding-bottom:env(safe-area-inset-bottom,0px);grid-template-columns:1fr;grid-template-rows:auto auto 1fr auto;grid-template-areas:"topbar" "mobile-status" "mobile-chat" "mobile-input";align-items:stretch}.topbar{grid-area:topbar;padding:6px 10px;gap:8px;overflow:hidden}.topbar .nav-list,.brand p{display:none}.brand strong{font-size:13px}.brand-badge{width:26px;height:26px;font-size:11px;border-radius:8px}.workspace-panel,.command-panel{display:none}.mobile-status-bar{grid-area:mobile-status;display:flex;flex:0 0 auto;gap:6px;padding:4px 10px;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;background:#09122199;border-bottom:1px solid rgba(255,255,255,.06);-webkit-overflow-scrolling:touch;scrollbar-width:none}.mobile-status-bar::-webkit-scrollbar{display:none}.mobile-agent-pill{display:flex;align-items:center;gap:4px;border-radius:999px;padding:3px 8px;background:#ffffff0d;font-size:11px;white-space:nowrap;flex:0 0 auto}.mobile-agent-pill .dot{width:6px;height:6px;border-radius:50%;background:#6b7a96}.mobile-agent-pill.online .dot{background:#67e8a0;box-shadow:0 0 4px #67e8a080}.mobile-agent-pill.busy .dot{background:#63b3ff;animation:pulse-dot 1.5s infinite}.mobile-agent-pill.offline .dot{background:#6b7a96}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.4}}.mobile-agent-pill .agent-name{color:#c9d6f2;font-weight:500}.mobile-agent-pill .agent-task{color:#7f93b5;max-width:80px;overflow:hidden;text-overflow:ellipsis}.mobile-chat-feed{grid-area:mobile-chat;display:flex;flex-direction:column;gap:6px;padding:8px 10px;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}.mobile-chat-feed .chat-message{max-width:88%;border-radius:10px 10px 4px;padding:6px 8px}.mobile-chat-feed .chat-message p{margin:4px 0;font-size:12px;white-space:pre-wrap;word-break:break-word}.mobile-chat-feed .chat-message__meta{gap:6px}.mobile-chat-feed .chat-message__meta strong{font-size:11px}.mobile-chat-feed .chat-message__meta span,.mobile-chat-feed .chat-message small{font-size:10px}.mobile-chat-feed .leader-message-wrapper{max-width:88%}.mobile-chat-feed .chat-message__time-outside,.mobile-chat-feed .chat-message__executing span{font-size:10px}.mobile-chat-empty{flex:1;display:flex;align-items:center;justify-content:center;color:#6b7a96;font-size:12px}.mobile-input-bar{grid-area:mobile-input;display:flex;flex-direction:column;gap:6px;padding:8px 10px;flex:0 0 auto;background:#091221eb;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-top:1px solid rgba(255,255,255,.06)}.mobile-target-row{display:flex;gap:4px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none}.mobile-target-row::-webkit-scrollbar{display:none}.mobile-target-row .target-chip{padding:3px 7px;font-size:11px;white-space:nowrap;flex:0 0 auto}.mobile-input-row{display:flex;gap:6px;align-items:flex-end}.mobile-input-row textarea{flex:1;border:1px solid rgba(255,255,255,.08);border-radius:9px;padding:8px 10px;background:#ffffff0a;color:#eef4ff;font-size:14px;font-family:inherit;height:36px;max-height:120px;resize:none;line-height:1.4;overflow-y:auto}.mobile-input-row .send-btn{flex:0 0 auto;width:36px;height:36px;border-radius:50%;border:0;background:linear-gradient(135deg,#68b6ff,#79ffd1);color:#07203f;font-size:16px;cursor:pointer;display:grid;place-items:center}.mobile-input-row .send-btn:disabled{opacity:.4;cursor:default}.auth-card{width:min(340px,100%);padding:14px}.auth-brand{margin-bottom:10px}.mobile-logout{display:block}.mobile-status-bar,.mobile-chat-feed,.mobile-input-bar{display:flex}.mobile-agent-pill.selected{background:#67e8a02e;border:1px solid rgba(103,232,160,.3)}.mobile-terminal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:100;display:flex;flex-direction:column;justify-content:flex-end;background:#0000008c;-webkit-tap-highlight-color:transparent}.mobile-terminal-card{display:flex;flex-direction:column;max-height:70dvh;margin:0 6px 6px;border-radius:14px;border:1px solid rgba(255,255,255,.08);background:#06101bf7;box-shadow:0 -8px 30px #0006;overflow:hidden}.mobile-terminal-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255,255,255,.06);font-size:12px;color:#c9d6f2;font-weight:600}.mobile-terminal-header button{border:0;background:#ffffff0f;color:#90a1be;border-radius:50%;width:26px;height:26px;font-size:13px;cursor:pointer;display:grid;place-items:center}.mobile-terminal-content{flex:1;min-height:0;margin:0;padding:10px;overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;color:#bfffd4;font-family:SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.55;white-space:pre-wrap;word-break:break-all}}
|