palmier 0.7.9 → 0.8.1
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/commands/run.js +55 -0
- package/dist/commands/serve.js +22 -2
- package/dist/device-capabilities.d.ts +1 -1
- package/dist/event-queues.d.ts +36 -0
- package/dist/event-queues.js +53 -0
- package/dist/mcp-tools.js +2 -2
- package/dist/platform/windows.js +5 -2
- package/dist/pwa/assets/index-CQxcuDhM.css +1 -0
- package/dist/pwa/assets/index-DQfOEB03.js +120 -0
- package/dist/pwa/assets/{web-CF-N8Di6.js → web-D7Kq3Nvk.js} +1 -1
- package/dist/pwa/assets/{web-BpM3fNCn.js → web-DOyOiwsW.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/pwa/service-worker.js +1 -1
- package/dist/rpc-handler.js +6 -5
- package/dist/transports/http-transport.js +15 -0
- package/dist/types.d.ts +6 -5
- package/package.json +1 -1
- package/palmier-server/README.md +1 -1
- package/palmier-server/pwa/src/App.css +5 -0
- package/palmier-server/pwa/src/App.tsx +15 -1
- package/palmier-server/pwa/src/components/HostMenu.tsx +155 -456
- package/palmier-server/pwa/src/components/SessionsView.tsx +9 -3
- package/palmier-server/pwa/src/components/TaskCard.tsx +12 -4
- package/palmier-server/pwa/src/components/TaskForm.tsx +79 -32
- package/palmier-server/pwa/src/components/TasksView.tsx +5 -0
- package/palmier-server/pwa/src/constants.ts +1 -1
- package/palmier-server/pwa/src/native/Device.ts +48 -0
- package/palmier-server/pwa/src/pages/PairHost.tsx +18 -2
- package/palmier-server/pwa/src/types.ts +1 -1
- package/palmier-server/spec.md +12 -2
- package/src/commands/run.ts +61 -0
- package/src/commands/serve.ts +22 -2
- package/src/device-capabilities.ts +1 -1
- package/src/event-queues.ts +56 -0
- package/src/mcp-tools.ts +2 -2
- package/src/platform/windows.ts +5 -2
- package/src/rpc-handler.ts +8 -7
- package/src/transports/http-transport.ts +14 -0
- package/src/types.ts +6 -5
- package/dist/pwa/assets/index-FP1Mipr6.js +0 -120
- package/dist/pwa/assets/index-bLTn8zBj.css +0 -1
package/dist/commands/run.js
CHANGED
|
@@ -209,6 +209,15 @@ export async function runCommand(taskId) {
|
|
|
209
209
|
await publishTaskEvent(nc, config, taskDir, taskId, outcome, taskName, runId);
|
|
210
210
|
console.log(`Task ${taskId} completed (command-triggered).`);
|
|
211
211
|
}
|
|
212
|
+
else if (task.frontmatter.schedule_type === "on_new_notification"
|
|
213
|
+
|| task.frontmatter.schedule_type === "on_new_sms") {
|
|
214
|
+
// Event-triggered mode (driven by NATS pub/sub of device notifications/SMS)
|
|
215
|
+
const result = await runEventTriggeredMode(ctx);
|
|
216
|
+
const outcome = resolveOutcome(taskDir, result.outcome);
|
|
217
|
+
appendRunMessage(taskDir, runId, { role: "status", time: Date.now(), content: "", type: outcome });
|
|
218
|
+
await publishTaskEvent(nc, config, taskDir, taskId, outcome, taskName, runId);
|
|
219
|
+
console.log(`Task ${taskId} completed (event-triggered).`);
|
|
220
|
+
}
|
|
212
221
|
else {
|
|
213
222
|
// Standard execution — add user prompt as first message
|
|
214
223
|
await appendAndNotify(ctx, {
|
|
@@ -380,6 +389,52 @@ async function runCommandTriggeredMode(ctx) {
|
|
|
380
389
|
}
|
|
381
390
|
return { outcome: "finished", endTime };
|
|
382
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Event-triggered execution mode.
|
|
394
|
+
*
|
|
395
|
+
* Drains the daemon-owned per-task event queue via the local /task-event/pop
|
|
396
|
+
* HTTP endpoint, invoking the agent once per event with the payload spliced
|
|
397
|
+
* into the user prompt. The run process itself holds no NATS subscription;
|
|
398
|
+
* the daemon handles that and atomically clears the active flag when we see
|
|
399
|
+
* an empty pop, so it can fire up a fresh run on the next incoming event.
|
|
400
|
+
*/
|
|
401
|
+
async function runEventTriggeredMode(ctx) {
|
|
402
|
+
const scheduleType = ctx.task.frontmatter.schedule_type;
|
|
403
|
+
const label = scheduleType === "on_new_notification" ? "notification" : "SMS";
|
|
404
|
+
const port = ctx.config.httpPort ?? 7256;
|
|
405
|
+
const popUrl = `http://localhost:${port}/task-event/pop?taskId=${encodeURIComponent(ctx.taskId)}`;
|
|
406
|
+
console.log(`[event-triggered] Draining ${label} queue`);
|
|
407
|
+
appendRunMessage(ctx.taskDir, ctx.runId, { role: "status", time: Date.now(), content: "", type: "monitoring" });
|
|
408
|
+
await publishHostEvent(ctx.nc, ctx.config.hostId, ctx.taskId, { event_type: "result-updated", run_id: ctx.runId });
|
|
409
|
+
let eventsProcessed = 0;
|
|
410
|
+
try {
|
|
411
|
+
// eslint-disable-next-line no-constant-condition
|
|
412
|
+
while (true) {
|
|
413
|
+
const res = await fetch(popUrl, { method: "POST" });
|
|
414
|
+
if (!res.ok)
|
|
415
|
+
throw new Error(`pop-event failed: ${res.status} ${res.statusText}`);
|
|
416
|
+
const body = await res.json();
|
|
417
|
+
if (body.empty || !body.event)
|
|
418
|
+
break;
|
|
419
|
+
eventsProcessed++;
|
|
420
|
+
console.log(`[event-triggered] Processing ${label} #${eventsProcessed}`);
|
|
421
|
+
const perEventPrompt = `${ctx.task.frontmatter.user_prompt}\n\nProcess this new ${label}:\n${body.event}`;
|
|
422
|
+
const perEventTask = {
|
|
423
|
+
frontmatter: { ...ctx.task.frontmatter, user_prompt: perEventPrompt },
|
|
424
|
+
};
|
|
425
|
+
await invokeAgentWithRetries(ctx, perEventTask);
|
|
426
|
+
appendRunMessage(ctx.taskDir, ctx.runId, { role: "status", time: Date.now(), content: "", type: "monitoring" });
|
|
427
|
+
await publishHostEvent(ctx.nc, ctx.config.hostId, ctx.taskId, { event_type: "result-updated", run_id: ctx.runId });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch (err) {
|
|
431
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
432
|
+
appendRunMessage(ctx.taskDir, ctx.runId, { role: "status", time: Date.now(), content: errorMsg, type: "error" });
|
|
433
|
+
await publishHostEvent(ctx.nc, ctx.config.hostId, ctx.taskId, { event_type: "result-updated", run_id: ctx.runId });
|
|
434
|
+
return { outcome: "failed", endTime: Date.now() };
|
|
435
|
+
}
|
|
436
|
+
return { outcome: "finished", endTime: Date.now() };
|
|
437
|
+
}
|
|
383
438
|
async function publishTaskEvent(nc, config, taskDir, taskId, eventType, taskName, runId) {
|
|
384
439
|
writeTaskStatus(taskDir, {
|
|
385
440
|
running_state: eventType,
|
package/dist/commands/serve.js
CHANGED
|
@@ -14,6 +14,7 @@ import { CONFIG_DIR } from "../config.js";
|
|
|
14
14
|
import { StringCodec } from "nats";
|
|
15
15
|
import { addNotification } from "../notification-store.js";
|
|
16
16
|
import { addSmsMessage } from "../sms-store.js";
|
|
17
|
+
import { enqueueEvent } from "../event-queues.js";
|
|
17
18
|
const POLL_INTERVAL_MS = 30_000;
|
|
18
19
|
const DAEMON_PID_FILE = path.join(CONFIG_DIR, "daemon.pid");
|
|
19
20
|
/**
|
|
@@ -119,28 +120,47 @@ export async function serveCommand() {
|
|
|
119
120
|
startNatsTransport(config, handleRpc, nc);
|
|
120
121
|
// Subscribe to device notifications and SMS from Android
|
|
121
122
|
const sc = StringCodec();
|
|
123
|
+
// Dispatch a raw event payload to every task whose schedule matches.
|
|
124
|
+
function dispatchDeviceEvent(scheduleType, payload) {
|
|
125
|
+
for (const task of listTasks(config.projectRoot)) {
|
|
126
|
+
if (task.frontmatter.schedule_type !== scheduleType)
|
|
127
|
+
continue;
|
|
128
|
+
if (!task.frontmatter.schedule_enabled)
|
|
129
|
+
continue;
|
|
130
|
+
const { shouldStart } = enqueueEvent(task.frontmatter.id, payload);
|
|
131
|
+
if (shouldStart) {
|
|
132
|
+
platform.startTask(task.frontmatter.id).catch((err) => {
|
|
133
|
+
console.error(`[event-trigger] Failed to start ${task.frontmatter.id}:`, err);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
122
138
|
const notifSub = nc.subscribe(`host.${config.hostId}.device.notifications`);
|
|
123
139
|
(async () => {
|
|
124
140
|
for await (const msg of notifSub) {
|
|
141
|
+
const raw = sc.decode(msg.data);
|
|
125
142
|
try {
|
|
126
|
-
const data = JSON.parse(
|
|
143
|
+
const data = JSON.parse(raw);
|
|
127
144
|
addNotification({ ...data, receivedAt: Date.now() });
|
|
128
145
|
}
|
|
129
146
|
catch (err) {
|
|
130
147
|
console.error("[nats] Failed to parse device notification:", err);
|
|
131
148
|
}
|
|
149
|
+
dispatchDeviceEvent("on_new_notification", raw);
|
|
132
150
|
}
|
|
133
151
|
})();
|
|
134
152
|
const smsSub = nc.subscribe(`host.${config.hostId}.device.sms`);
|
|
135
153
|
(async () => {
|
|
136
154
|
for await (const msg of smsSub) {
|
|
155
|
+
const raw = sc.decode(msg.data);
|
|
137
156
|
try {
|
|
138
|
-
const data = JSON.parse(
|
|
157
|
+
const data = JSON.parse(raw);
|
|
139
158
|
addSmsMessage({ ...data, receivedAt: Date.now() });
|
|
140
159
|
}
|
|
141
160
|
catch (err) {
|
|
142
161
|
console.error("[nats] Failed to parse device SMS:", err);
|
|
143
162
|
}
|
|
163
|
+
dispatchDeviceEvent("on_new_sms", raw);
|
|
144
164
|
}
|
|
145
165
|
})();
|
|
146
166
|
}
|
|
@@ -2,7 +2,7 @@ export interface RegisteredDevice {
|
|
|
2
2
|
clientToken: string;
|
|
3
3
|
fcmToken: string;
|
|
4
4
|
}
|
|
5
|
-
export type DeviceCapability = "location" | "notifications" | "sms" | "contacts" | "calendar" | "alert" | "battery" | "email" | "dnd";
|
|
5
|
+
export type DeviceCapability = "location" | "notifications" | "sms" | "contacts" | "calendar" | "alert" | "battery" | "send-email" | "dnd";
|
|
6
6
|
export declare function getCapabilityDevice(capability: DeviceCapability): RegisteredDevice | null;
|
|
7
7
|
export declare function setCapabilityDevice(capability: DeviceCapability, clientToken: string, fcmToken: string): void;
|
|
8
8
|
export declare function clearCapabilityDevice(capability: DeviceCapability): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-task in-memory event queues for event-triggered schedules
|
|
3
|
+
* (schedule_type: "on_new_notification" | "on_new_sms").
|
|
4
|
+
*
|
|
5
|
+
* The daemon owns the NATS subscription and populates these queues; the
|
|
6
|
+
* `palmier run` process drains them via the localhost /task-event/pop HTTP
|
|
7
|
+
* endpoint. `activeRuns` tracks whether a run process is currently draining,
|
|
8
|
+
* so we don't race a fresh startTask with a teardown-phase run.
|
|
9
|
+
*
|
|
10
|
+
* Lifecycle invariants:
|
|
11
|
+
* - activeRuns is cleared atomically inside popEvent when the queue is
|
|
12
|
+
* drained. At that point the calling run has already finished its last
|
|
13
|
+
* agent invocation and is only tearing down.
|
|
14
|
+
* - enqueueEvent returns shouldStart=true only if the task transitioned
|
|
15
|
+
* from idle (no active run) to active — callers must then startTask.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Queue a raw (JSON-string) event payload for a task. Returns whether the
|
|
19
|
+
* caller should now start the run process.
|
|
20
|
+
*/
|
|
21
|
+
export declare function enqueueEvent(taskId: string, payload: string): {
|
|
22
|
+
shouldStart: boolean;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Pop the oldest queued event for a task. Returns `{ event }` when one is
|
|
26
|
+
* available (keeps the task marked active), or `{ empty: true }` after
|
|
27
|
+
* clearing the active flag atomically.
|
|
28
|
+
*/
|
|
29
|
+
export declare function popEvent(taskId: string): {
|
|
30
|
+
event: string;
|
|
31
|
+
} | {
|
|
32
|
+
empty: true;
|
|
33
|
+
};
|
|
34
|
+
/** Remove any state for a task (called from task.delete). */
|
|
35
|
+
export declare function clearTaskQueue(taskId: string): void;
|
|
36
|
+
//# sourceMappingURL=event-queues.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-task in-memory event queues for event-triggered schedules
|
|
3
|
+
* (schedule_type: "on_new_notification" | "on_new_sms").
|
|
4
|
+
*
|
|
5
|
+
* The daemon owns the NATS subscription and populates these queues; the
|
|
6
|
+
* `palmier run` process drains them via the localhost /task-event/pop HTTP
|
|
7
|
+
* endpoint. `activeRuns` tracks whether a run process is currently draining,
|
|
8
|
+
* so we don't race a fresh startTask with a teardown-phase run.
|
|
9
|
+
*
|
|
10
|
+
* Lifecycle invariants:
|
|
11
|
+
* - activeRuns is cleared atomically inside popEvent when the queue is
|
|
12
|
+
* drained. At that point the calling run has already finished its last
|
|
13
|
+
* agent invocation and is only tearing down.
|
|
14
|
+
* - enqueueEvent returns shouldStart=true only if the task transitioned
|
|
15
|
+
* from idle (no active run) to active — callers must then startTask.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_QUEUE_SIZE = 100;
|
|
18
|
+
const queues = new Map();
|
|
19
|
+
const activeRuns = new Set();
|
|
20
|
+
/**
|
|
21
|
+
* Queue a raw (JSON-string) event payload for a task. Returns whether the
|
|
22
|
+
* caller should now start the run process.
|
|
23
|
+
*/
|
|
24
|
+
export function enqueueEvent(taskId, payload) {
|
|
25
|
+
const queue = queues.get(taskId) ?? [];
|
|
26
|
+
if (queue.length >= MAX_QUEUE_SIZE)
|
|
27
|
+
queue.shift();
|
|
28
|
+
queue.push(payload);
|
|
29
|
+
queues.set(taskId, queue);
|
|
30
|
+
if (activeRuns.has(taskId))
|
|
31
|
+
return { shouldStart: false };
|
|
32
|
+
activeRuns.add(taskId);
|
|
33
|
+
return { shouldStart: true };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Pop the oldest queued event for a task. Returns `{ event }` when one is
|
|
37
|
+
* available (keeps the task marked active), or `{ empty: true }` after
|
|
38
|
+
* clearing the active flag atomically.
|
|
39
|
+
*/
|
|
40
|
+
export function popEvent(taskId) {
|
|
41
|
+
const queue = queues.get(taskId);
|
|
42
|
+
if (queue && queue.length > 0) {
|
|
43
|
+
return { event: queue.shift() };
|
|
44
|
+
}
|
|
45
|
+
activeRuns.delete(taskId);
|
|
46
|
+
return { empty: true };
|
|
47
|
+
}
|
|
48
|
+
/** Remove any state for a task (called from task.delete). */
|
|
49
|
+
export function clearTaskQueue(taskId) {
|
|
50
|
+
queues.delete(taskId);
|
|
51
|
+
activeRuns.delete(taskId);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=event-queues.js.map
|
package/dist/mcp-tools.js
CHANGED
|
@@ -597,9 +597,9 @@ const sendEmailTool = {
|
|
|
597
597
|
async handler(args, ctx) {
|
|
598
598
|
if (!ctx.nc)
|
|
599
599
|
throw new ToolError("Not connected to server (NATS unavailable)", 503);
|
|
600
|
-
const device = getCapabilityDevice("email");
|
|
600
|
+
const device = getCapabilityDevice("send-email");
|
|
601
601
|
if (!device)
|
|
602
|
-
throw new ToolError("No device has email access enabled", 400);
|
|
602
|
+
throw new ToolError("No device has send-email access enabled", 400);
|
|
603
603
|
const { to, subject, body, cc, bcc } = args;
|
|
604
604
|
if (!to)
|
|
605
605
|
throw new ToolError("to is required", 400);
|
package/dist/platform/windows.js
CHANGED
|
@@ -177,11 +177,14 @@ export class WindowsPlatform {
|
|
|
177
177
|
const tn = schtasksTaskName(taskId);
|
|
178
178
|
const script = process.argv[1] || "palmier";
|
|
179
179
|
const tr = `"${process.execPath}" "${script}" run ${taskId}`;
|
|
180
|
-
// Build trigger XML elements
|
|
180
|
+
// Build trigger XML elements. Event-based schedule types (on_new_notification,
|
|
181
|
+
// on_new_sms) carry no values and are driven by the run process, not the OS
|
|
182
|
+
// scheduler — they intentionally produce only the dummy trigger below.
|
|
181
183
|
const triggerElements = [];
|
|
182
184
|
const scheduleType = task.frontmatter.schedule_type;
|
|
183
185
|
const scheduleValues = task.frontmatter.schedule_values;
|
|
184
|
-
|
|
186
|
+
const isTimerSchedule = scheduleType === "crons" || scheduleType === "specific_times";
|
|
187
|
+
if (task.frontmatter.schedule_enabled && isTimerSchedule && scheduleValues?.length) {
|
|
185
188
|
for (const value of scheduleValues) {
|
|
186
189
|
try {
|
|
187
190
|
triggerElements.push(scheduleValueToXml(scheduleType, value));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(data:font/woff2;base64,d09GMgABAAAAAAa0ABQAAAAADOwAAAZHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbbhwoP0hWQVJtP01WQVJGBmA/U1RBVIEcAGQvXBEICoMkgmcLFgAwhEwBNgIkAyYEIAWGXAdiDAcbOgtRlHLSRcD8TEzkdoghPRuspKefbVnJIM5yRfA8vZv9uTNJSCZY21Scrigr6qyJ/C3sE1OFKuVP3e+lxUTbYio1zb/kFdk2bufY2BlhWERRBrcDUBhB5GEpd2Cy+MDxqf9zLPX+XVsk5r+s50d6IpWLatqbdT6f2MAikiOKQocbjya2QT0DqPDX4R0jQBgAUAiCRhDA0EkvYeLk9X3I2bEWI8jBAihIANm+kUWQBgggWChcBBdFhXlAUgOCAgBA0AgKjUJ8HKvjddFCKSgAVklEBYAeEJgCQJH0qB3B+neDg4sAA4hAD0AVIEAA0IACJGeaUjMIIIhBUE/zNABjTQMBwEVRcwcYBUCObLERABAgjAA1M/ZSa9hSi4OlpsnZ2KBOVUsAZlRGl1W0NZ6gSwhQPIAFs7YFL0QS91vRQgdgx2VDAnIegKpQvwAJIoLgooBAGtCJcHizg0TDiz8vhPgIcVXaDJu37ZyDAIjFy4AAiC1JAA0olJwN6nFAACQk/zfQVUuA3t5ELKMT9hpBSRj+HBSggYCIAIEkUoC4PCIQIWHGA8+IStxf0PW15ntP22gc+Wo+BLyQAHECQiAVd+c3Ba8gZ4NOACBaLOhsbIMkAXEmuHsSAvkf4oJaAogH6nEQAzoBIEihUAwAA0AACAObAGcANYAAABaqgMmpWAhiiVyrvNyqFV8tMQ5Iyq6r9Lf7W82i8ILl2cv8zbfdJ5lnPzSnvt/XXPfRR/5pH3xA+u5uS0798EPJ3Fft33w36Wc+8E/7aIB/9fvvb1pE9erNXLB9mkw//cSg8Cnx03VGXDvZnRzKqV9Xkhn7eRHb3wVjHpg19nkZpX9bBU+vnHegrsuQcbW9Bi7oO33h+8SU0Tly/MbeYUuG1cftVz6oNxrW0qH1iezq446fkDFo/rgvTkqt7zmteFzncZ0uxXti09FFF2z+C7CODpjUudOE5NSeb3bv/mbPKcmOEzpWQ3VBgBAuj/AOY3qMm7UvgA8qo3+qvAUBBAX0wADQG4AG+jCKaSxkBe/zvcTG8jRpRZgqhCog4sd++cOo7J+/RdvaaMoc78Ri/PNPSclx6fDvG1Kt3qQFi1rq+5EaavTtt9TVvfvucYsA5wc4oESU+E1ikF9TkrOkIsIH5Fawx7SBNLnMcWlM3skBluCmQNs7GeyFvYVDamsqwhxx18n/+WdxzDgOLU1AqbUlb3m+KOT9+ONvY/7XlrdLS//5v7nN97XHj0VzJpJvz4spq0V7ioXDuv2YrSxtF/KZmvbWfKtorXSh8dfvgGu+ev/nFSaXM6jyuCE/f/EpmuP7Rqu8R98vP/+tXUdrOgKlZP9Q/s+fRF9+3CHZd4EugfPv158me9X7v/6aSMCxMJmIr//65bNXngl7+uiPP5oKx+y27D8trp+psUzMacpm80XlnYeMAct0LynxhNYgaP3dtn8/LBAi+ksr+7NvjjtJV+UXrz0uLXV1x08AZRN1S2A8EaVE5afk/dfGjz//+fdHn36Xdttz2d9/z+bawZ8/slNN1aYtq+ZPGzNuPGdwOsfYflwmHCkPgs5DcKizvdpS/3gEUMW9/lvQvWp1bGTW1fpngC/uSVYAfH3TeSusL8TtlHUD4KAAEHjahk4YpuffGwEBN/dUmpHqdHyJ3Ap8O3UlQ4gCNrMRwLBBKiskL6a+RoBi/9XGlgc8L4/CUejQxaiIyqmomdhktOhfgDbDfaNDb4+yKIPd6IgmzDa0CByijWFmL2dlSRKagTIWXeIU9HDphoZiJeBTjAefHMxDhVSRFUgVcOkW3EGMahYVMFjWHhMluB2wAcbHYqF1LpsDF9C6s+CI2fDgh4wSuFEyGadjXAmIk3CugIRibLIti9ZtC8S4VSqfikGqPaoI122XyRYLBmsOmdiiTpqK1OklUQzpMcZmQRQV4M4oJCMkfRQXK+qvjifUcQd1bRdetW/LWjacYxvcttnVjWg5h0q4xw6rZyejSpZVZ78LzC4uyDNRQ4bymHSTMyM+SZ7D75mg/7YTlmNz7W8T00h0VEiGKB+F7iWYZFvSTiA4LVxttm2ATt5EoUWLJbY4EnLGrfsvEROlHtzlKn3H9VUT5tU/2dt3/EBv7foYzV/W4upyj04woO/gh6Vwwt3WGQAA) format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2) format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2) format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2) format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--color-bg: #F7F8FC;--color-surface: #ffffff;--color-primary: #2E5CE5;--color-primary-hover: #1D4ED8;--color-primary-subtle: #EEF2FF;--color-secondary: #64748B;--color-secondary-hover: #475569;--color-text: #1A1F36;--color-text-secondary: #697386;--color-muted: #94A3B8;--color-success: #22C55E;--color-error: #EF4444;--color-error-bg: #FEF2F2;--color-warning: #F59E0B;--color-border: #E2E8F0;--color-border-subtle: #F1F5F9;--color-hover: rgba(15, 23, 42, .06);--color-overlay: rgba(15, 23, 42, .5);--color-input-focus: rgba(46, 92, 229, .15);--shadow-xs: 0 1px 2px rgba(15, 23, 42, .05);--shadow-sm: 0 1px 3px rgba(15, 23, 42, .06), 0 1px 2px rgba(15, 23, 42, .04);--shadow-md: 0 4px 6px -1px rgba(15, 23, 42, .07), 0 2px 4px -2px rgba(15, 23, 42, .05);--shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, .08), 0 4px 6px -4px rgba(15, 23, 42, .04);--shadow-xl: 0 20px 25px -5px rgba(15, 23, 42, .1), 0 8px 10px -6px rgba(15, 23, 42, .06);--radius-sm: 8px;--radius-md: 12px;--radius-lg: 16px;--radius-full: 50%;--font-sans: "Plus Jakarta Sans Variable", "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--space-xs: 4px;--space-sm: 8px;--space-md: 16px;--space-lg: 24px;--space-xl: 32px;--space-2xl: 48px;--transition-fast: .12s ease;--transition-base: .2s ease}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;-webkit-text-size-adjust:100%}body{font-family:var(--font-sans);background:var(--color-bg);color:var(--color-text);line-height:1.6;min-height:100dvh;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;overscroll-behavior-y:contain}.pull-to-refresh{position:fixed;top:-48px;left:0;right:0;height:48px;display:flex;align-items:center;justify-content:center;pointer-events:none;z-index:40;transition:opacity .15s}.pull-to-refresh-badge{width:40px;height:40px;border-radius:50%;background:var(--color-surface);border:1px solid var(--color-border);box-shadow:var(--shadow-md);display:flex;align-items:center;justify-content:center;color:var(--color-text-secondary)}#root{min-height:100dvh}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid transparent;border-radius:var(--radius-sm);font-family:var(--font-sans);font-size:.875rem;font-weight:600;padding:10px 18px;cursor:pointer;transition:all var(--transition-base);-webkit-tap-highlight-color:transparent;letter-spacing:-.01em;line-height:1.25}.btn:disabled{opacity:.45;cursor:not-allowed}.btn:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.btn-primary{background:var(--color-primary);color:#fff;box-shadow:0 1px 2px #2e5ce54d,inset 0 1px #ffffff1a}.btn-primary:hover:not(:disabled){background:var(--color-primary-hover);box-shadow:0 2px 4px #2e5ce559,inset 0 1px #ffffff1a;transform:translateY(-.5px)}.btn-primary:active:not(:disabled){transform:translateY(0);box-shadow:0 1px 2px #2e5ce54d}.btn-secondary{background:var(--color-surface);color:var(--color-text);border-color:var(--color-border);box-shadow:var(--shadow-xs)}.btn-secondary:hover:not(:disabled){background:var(--color-border-subtle);border-color:#cbd5e1}.btn-danger{background:var(--color-error);color:#fff;box-shadow:0 1px 2px #ef44444d}.btn-danger:hover:not(:disabled){background:#dc2626;box-shadow:0 2px 4px #ef444459}.btn-link{background:none;border:none;color:var(--color-primary);padding:0;font-weight:500;box-shadow:none}.btn-link:hover:not(:disabled){text-decoration:underline;background:none;box-shadow:none}.btn-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle;margin-right:6px}.btn-sm{font-size:.8125rem;padding:6px 12px;border-radius:6px}.btn-full{width:100%}.form-label{display:flex;flex-direction:column;gap:6px;font-size:.8125rem;font-weight:600;color:var(--color-text);margin-bottom:var(--space-md);letter-spacing:-.01em}.form-input,.form-textarea,.form-select{font-family:var(--font-sans);font-size:.9375rem;padding:10px 12px;border:1.5px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);transition:border-color var(--transition-fast),box-shadow var(--transition-fast);width:100%}.form-input:focus,.form-textarea:focus,.form-select:focus{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 3px var(--color-input-focus)}.form-input-mono{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.8125rem}.agent-picker-label{font-size:.8125rem;color:var(--color-text-secondary);white-space:nowrap}.form-input::placeholder,.form-textarea::placeholder{color:var(--color-muted)}.form-textarea{resize:vertical;min-height:80px;line-height:1.5}.form-error{background:var(--color-error-bg);color:var(--color-error);border:1px solid #FECACA;border-radius:var(--radius-sm);padding:var(--space-sm) var(--space-md);font-size:.8125rem;font-weight:500;margin-bottom:var(--space-md)}.form-warning{background:#fef3c7;color:#92400e;border:1px solid #FDE68A;border-radius:var(--radius-sm);padding:var(--space-sm) var(--space-md);font-size:.8125rem;font-weight:500;margin-bottom:var(--space-md)}.dashboard{min-height:100dvh;display:flex;flex-direction:column;padding-bottom:0}.dashboard-main{flex:1;padding:var(--space-md);width:100%}.status-dot{display:inline-block;width:8px;height:8px;border-radius:var(--radius-full);flex-shrink:0;box-shadow:0 0 0 2px #fffc}.status-spinner{display:inline-flex;align-items:center;gap:2px;flex-shrink:0;height:8px}.status-spinner:before,.status-spinner>span,.status-spinner:after{content:"";display:block;width:3px;height:3px;border-radius:var(--radius-full);background-color:var(--color-success);animation:marching-dot 1.2s ease-in-out infinite}.status-spinner:before{animation-delay:0s}.status-spinner>span{animation-delay:.2s}.status-spinner:after{animation-delay:.4s}@keyframes marching-dot{0%,60%,to{opacity:.25;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.pair-page{display:flex;align-items:flex-start;justify-content:center;min-height:100dvh;padding:var(--space-xl) var(--space-md);background:var(--color-bg)}.pair-card{width:100%;max-width:420px;display:flex;flex-direction:column;gap:var(--space-lg)}.pair-header{text-align:center}.pair-title{font-size:1.5rem;font-weight:700;color:var(--color-text);letter-spacing:-.02em}.pair-subtitle{margin-top:var(--space-xs);font-size:.875rem;color:var(--color-text-secondary)}.pair-instructions{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-md)}.pair-instruction-block{display:flex;flex-direction:column;gap:var(--space-sm)}.pair-instruction-heading{font-size:.8125rem;font-weight:600;color:var(--color-text);letter-spacing:-.01em}.pair-steps{list-style:none;counter-reset:pair-step;display:flex;flex-direction:column;gap:6px;padding:0}.pair-steps li{counter-increment:pair-step;font-size:.8125rem;color:var(--color-text-secondary);line-height:1.5;display:flex;flex-wrap:wrap;gap:0 var(--space-sm)}.pair-steps li:before{content:counter(pair-step) ".";font-weight:600;color:var(--color-muted);min-width:16px;flex-shrink:0}.pair-steps li .pair-command{flex-basis:100%}.pair-steps code{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:4px;padding:1px 5px;color:var(--color-text);white-space:nowrap}.pair-command{display:block;margin-top:4px;padding:6px 10px!important;border-radius:var(--radius-sm)!important;overflow-x:auto;-webkit-overflow-scrolling:touch}.pair-instruction-divider{height:1px;background:var(--color-border)}.pair-form{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-lg);display:flex;flex-direction:column;gap:var(--space-md);box-shadow:var(--shadow-sm)}.pair-code-input{font-size:1.5rem!important;letter-spacing:.25em;text-align:center;padding:14px 12px!important;text-transform:uppercase}.pair-code-input::placeholder{letter-spacing:.25em;opacity:.35}.pair-label-hint{font-weight:400;color:var(--color-muted);font-size:.75rem}.pair-error{font-size:.8125rem;color:var(--color-error);background:var(--color-error-bg);padding:var(--space-sm) var(--space-md);border-radius:var(--radius-sm);line-height:1.4}.loading-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--space-2xl) 0;gap:var(--space-sm)}.empty-state-text{font-size:1rem;font-weight:600;color:var(--color-text-secondary)}.empty-state-hint{font-size:.8125rem;color:var(--color-muted)}.revoked-state{display:flex;flex-direction:column;align-items:center;text-align:center;padding:var(--space-2xl) var(--space-lg);margin:var(--space-md) 0;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);animation:revokedFadeIn .35s ease}@keyframes revokedFadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.revoked-icon{width:56px;height:56px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-full);background:var(--color-error-bg);color:var(--color-error);margin-bottom:var(--space-md)}.revoked-title{font-size:1.125rem;font-weight:700;color:var(--color-text);margin-bottom:var(--space-xs)}.revoked-description{font-size:.875rem;line-height:1.6;color:var(--color-text-secondary);max-width:320px;margin-bottom:var(--space-lg)}.revoked-command{padding:8px 16px;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);margin-bottom:var(--space-lg)}.revoked-command code{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.8125rem;color:var(--color-text);letter-spacing:-.01em}.revoked-actions{display:flex;gap:var(--space-sm);width:100%;max-width:280px}.revoked-actions .btn{flex:1}.spinner{width:24px;height:24px;border:2.5px solid var(--color-border);border-top-color:var(--color-primary);border-radius:var(--radius-full);animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.session-composer{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-sm);margin-bottom:var(--space-md);display:flex;flex-direction:column;gap:var(--space-sm)}.session-composer:focus-within{border-color:var(--color-primary);box-shadow:0 0 0 2px var(--color-input-focus)}.session-composer-textarea{width:100%;border:none;resize:vertical;outline:none;background:transparent;color:var(--color-text);font-family:var(--font-sans);font-size:.9375rem;line-height:1.5;padding:var(--space-xs) var(--space-sm);min-height:3.5em}.session-composer-textarea::placeholder{color:var(--color-muted)}.session-composer-controls{display:flex;align-items:center;gap:var(--space-sm);padding-left:var(--space-xs)}.session-composer-yolo{display:inline-flex;align-items:center;gap:4px;font-size:.8rem;color:var(--color-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none}.session-composer-yolo input{margin:0;cursor:pointer}.session-composer-controls .chat-send-btn{margin-left:auto}.fab{position:fixed;right:var(--space-lg);bottom:calc(var(--space-lg) + env(safe-area-inset-bottom,0px));width:56px;height:56px;border-radius:50%;border:none;background:var(--color-primary);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:var(--shadow-lg);transition:background var(--transition-base),transform var(--transition-fast),box-shadow var(--transition-base);z-index:50;-webkit-tap-highlight-color:transparent}.fab:hover{background:var(--color-primary-hover);box-shadow:var(--shadow-xl);transform:translateY(-1px)}.fab:active{transform:translateY(0)}.fab:focus-visible{outline:2px solid var(--color-primary);outline-offset:3px}.section-label{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-muted);margin:var(--space-md) 0 var(--space-xs)}.agent-picker-section-inline{display:flex;align-items:center;gap:var(--space-xs)}.agent-picker-section-inline .agent-picker-label{font-size:.8rem;color:var(--color-text-secondary);white-space:nowrap}.agent-picker-section-inline .form-select{width:auto;min-width:0;font-size:.8rem;padding:4px 8px}.task-list{display:flex;flex-direction:column;gap:10px;padding-bottom:calc(56px + var(--space-lg) * 2 + env(safe-area-inset-bottom,0px))}.task-card{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-sm);cursor:pointer;transition:box-shadow var(--transition-base),border-color var(--transition-base);-webkit-tap-highlight-color:transparent}.task-card:hover{box-shadow:var(--shadow-md);border-color:#cbd5e1}.task-card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm)}.task-card-title-row{display:flex;align-items:center;gap:10px;min-width:0;flex:1}.task-card-name{font-size:.9375rem;font-weight:600;letter-spacing:-.01em;color:var(--color-text);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.task-card-meta{display:flex;flex-wrap:wrap;gap:var(--space-sm);font-size:.75rem;color:var(--color-text-secondary)}.task-card-agent{color:var(--color-text-tertiary)}.task-card-last-event-link{color:var(--color-primary);cursor:pointer;text-decoration:underline}.task-card-actions{display:flex;gap:var(--space-xs);flex-shrink:0}.task-card-menu{position:relative}.task-card-menu-btn{background:none;border:none;color:var(--color-text-secondary);font-size:1.5rem;min-width:44px;min-height:44px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);cursor:pointer;line-height:1}.task-card-menu-btn:hover{background:var(--color-hover);color:var(--color-text)}.task-card-menu-dropdown{position:absolute;right:0;top:100%;margin-top:4px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-md);min-width:150px;z-index:10;overflow:hidden}.task-card-menu-dropdown button{display:flex;align-items:center;gap:var(--space-sm);width:100%;text-align:left;padding:var(--space-sm) var(--space-md);background:none;border:none;font-size:.9375rem;color:var(--color-text);cursor:pointer;white-space:nowrap}.task-card-menu-dropdown button:hover{background:var(--color-hover)}.task-card-menu-dropdown .menu-item-danger{color:var(--color-error)}.menu-icon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;font-size:.875rem;flex-shrink:0;line-height:1}.bottom-sheet-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);z-index:1000;display:flex;align-items:flex-end;justify-content:center;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn var(--transition-fast)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.bottom-sheet{background:var(--color-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;width:100%;max-width:480px;padding:var(--space-sm) var(--space-md) var(--space-xl);box-shadow:var(--shadow-xl);animation:sheetSlideUp .25s cubic-bezier(.16,1,.3,1)}@keyframes sheetSlideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.bottom-sheet-handle{width:36px;height:4px;background:var(--color-border);border-radius:2px;margin:0 auto var(--space-md)}.bottom-sheet-title{font-size:.9375rem;font-weight:600;color:var(--color-text);padding:0 var(--space-xs) var(--space-md);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bottom-sheet-actions{display:flex;flex-direction:column}.bottom-sheet-actions button{display:flex;align-items:center;gap:var(--space-md);width:100%;text-align:left;padding:var(--space-md);background:none;border:none;border-radius:var(--radius-sm);font-size:.9375rem;font-family:var(--font-sans);color:var(--color-text);cursor:pointer;transition:background var(--transition-fast)}.bottom-sheet-actions button:active{background:var(--color-hover)}.bottom-sheet-actions .menu-item-danger{color:var(--color-error)}.task-form-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:transparent;display:flex;align-items:stretch;justify-content:center;z-index:100;padding:0;overflow:hidden;overscroll-behavior:contain}.task-form{background:var(--color-surface);border-radius:0;padding:var(--space-lg) var(--space-md);width:100%;max-width:none;max-height:none;overflow-y:auto;animation:slideUp .25s cubic-bezier(.16,1,.3,1);display:flex;flex-direction:column;gap:var(--space-md)}@media(min-width:600px){.task-form-overlay{background:var(--color-overlay);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);align-items:center;padding:var(--space-md)}.task-form{border-radius:var(--radius-lg);box-shadow:var(--shadow-xl);max-width:540px;max-height:90dvh;padding:var(--space-lg)}}@keyframes slideUp{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}.task-form-header{display:flex;align-items:center;justify-content:space-between}.task-form h2{font-size:1.0625rem;font-weight:700;letter-spacing:-.02em;margin-bottom:0}.plan-actions{display:flex;gap:var(--space-sm);flex-wrap:wrap}.plan-dialog{display:flex;flex-direction:column;gap:var(--space-md);overflow:hidden;flex:1;min-height:0}.plan-dialog h2{margin:0;font-size:1.125rem}.plan-dialog-scroll{flex:1;min-height:0;overflow-y:auto}.plan-dialog .plan-preview{max-height:none}.plan-empty{color:var(--color-text-secondary);font-size:.875rem;font-style:italic;margin:var(--space-md) 0}.permissions-section{border-top:1px solid var(--color-border);padding-top:var(--space-md);margin-top:var(--space-md)}.permissions-section h3{margin:0 0 var(--space-xs) 0;font-size:.95rem}.permissions-list{margin:0;padding-left:1.25em}.permission-item{margin-bottom:var(--space-sm);display:flex;flex-direction:column}.permission-tool{font-weight:600;font-family:var(--font-mono, monospace)}.permission-desc{color:var(--color-text-secondary);font-size:.9em}.plan-dialog-actions{display:flex;gap:var(--space-sm);align-items:center;justify-content:flex-end}@media(min-width:600px){.plan-dialog{max-width:none}}.result-times{display:flex;flex-direction:column;gap:var(--space-xs);font-size:.8125rem;color:var(--color-text-secondary);margin-bottom:var(--space-sm)}.skeleton-line{height:.875rem;border-radius:var(--radius-sm);background:var(--color-border);animation:skeleton-pulse 1.2s ease-in-out infinite}@keyframes skeleton-pulse{0%,to{opacity:.4}50%{opacity:1}}.plan-preview{font-size:.8125rem;line-height:1.6;color:var(--color-text-secondary)}.plan-preview h1,.plan-preview h2,.plan-preview h3,.plan-preview h4{color:var(--color-text);margin:var(--space-sm) 0 var(--space-xs) 0}.plan-preview h1{font-size:1.1rem}.plan-preview h2{font-size:1rem}.plan-preview h3{font-size:.9375rem}.plan-preview p{margin:var(--space-xs) 0}.plan-preview ul,.plan-preview ol{padding-left:var(--space-md);margin:var(--space-xs) 0}.plan-preview code{background:var(--color-border);border-radius:3px;padding:1px 4px;font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem}.plan-preview pre{background:var(--color-border);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto}.plan-preview pre code{background:none;padding:0}.plan-preview table{width:100%;border-collapse:collapse;margin:var(--space-xs) 0}.plan-preview th,.plan-preview td{border:1px solid var(--color-border);padding:var(--space-xs) var(--space-sm);text-align:left}.plan-preview th{background:var(--color-border);color:var(--color-text);font-weight:600}.granted-permissions-row{flex-basis:100%}.granted-permissions-row .btn-link{padding:0}.schedule-section{display:flex;flex-direction:column;gap:var(--space-sm)}.schedule-section-title{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-muted);margin:0}.schedule-reactive{display:flex;flex-direction:column;gap:var(--space-xs)}.yolo-inline{display:inline-flex;align-items:center;gap:4px;font-size:.8rem;color:var(--color-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap}.yolo-inline input{margin:0;cursor:pointer}.yolo-warning{flex-basis:100%;margin:0;font-size:.75rem;color:var(--color-text-secondary);line-height:1.4}.trigger-row-card{display:flex;align-items:center;gap:8px;padding:4px 0}.trigger-row-content{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}.trigger-row-top{display:flex;align-items:center;gap:8px}.trigger-row-top .form-select{flex:0 0 auto;width:auto}.trigger-row-top .form-input{flex:1}.trigger-details{display:flex;gap:6px}.trigger-details .form-input[type=date]{flex:1;min-width:0}.trigger-details .form-input[type=time]{flex:0 0 auto;width:auto}.schedule-section>.form-select,.trigger-row-card .form-select,.trigger-row-card .form-input{margin-bottom:0;font-size:.8125rem;padding:6px 8px;height:32px;box-sizing:border-box;min-width:0}.schedule-section>.form-select{width:100%}.trigger-row-card .form-select,.trigger-row-card .form-input{flex:1}.trigger-remove-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:none;border-radius:var(--radius-full);background:transparent;color:var(--color-muted);font-size:1.1rem;line-height:1;cursor:pointer;transition:all var(--transition-fast);flex-shrink:0}.trigger-remove-btn:hover{background:var(--color-error-bg);color:var(--color-error)}.trigger-add-btn{display:inline-flex;align-items:center;gap:4px;border:1px dashed var(--color-border);border-radius:var(--radius-sm);background:transparent;color:var(--color-text-secondary);font-family:var(--font-sans);font-size:.8125rem;font-weight:500;padding:8px 14px;cursor:pointer;transition:all var(--transition-fast);width:100%;justify-content:center}.trigger-add-btn:hover{border-color:var(--color-primary);color:var(--color-primary);background:var(--color-primary-subtle)}.form-select{width:auto;min-width:80px}.toggles-section{display:flex;flex-direction:column;gap:10px;margin-bottom:var(--space-md)}.toggle-label{display:flex;align-items:center;gap:10px;font-size:.875rem;font-weight:500;cursor:pointer;-webkit-tap-highlight-color:transparent;color:var(--color-text)}.toggle-label input[type=checkbox]{width:16px;height:16px;accent-color:var(--color-primary);border-radius:4px}.toggles-group{display:flex;flex-direction:column;gap:var(--space-xs)}.command-section,.command-section-active .toggle-label{margin-bottom:0}.command-help-text{font-size:.75rem;color:var(--color-text-secondary);line-height:1.4;margin:6px 0 10px}.command-section-active .form-input{font-size:.8125rem;padding:6px 8px;height:32px;box-sizing:border-box}.form-actions{display:flex;gap:var(--space-sm);position:sticky;bottom:0;background:var(--color-surface);padding:var(--space-sm) 0}.confirm-modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);display:flex;align-items:center;justify-content:center;z-index:1000;padding:var(--space-md);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.confirm-modal{background:var(--color-surface);border-radius:var(--radius-lg);border:1px solid var(--color-border);padding:var(--space-xl);width:100%;max-width:380px;text-align:center;box-shadow:var(--shadow-xl);animation:slideUp .25s cubic-bezier(.16,1,.3,1)}.confirm-modal-title{font-size:1.125rem;font-weight:700;letter-spacing:-.02em;margin-bottom:var(--space-xs)}.confirm-modal-subtitle{font-size:.8rem;color:var(--color-muted);margin-bottom:var(--space-sm)}.confirm-modal-message{font-size:.9375rem;color:var(--color-text-secondary);margin-bottom:var(--space-lg);line-height:1.5}.confirm-modal-actions{display:flex;gap:var(--space-sm);justify-content:center}.confirm-modal-actions .btn{flex:1;padding:10px var(--space-lg);font-size:.875rem}.permission-modal{text-align:left;max-width:400px}.permission-modal .confirm-modal-title{text-align:center}.permission-modal .confirm-modal-message{text-align:center;word-break:break-word}.permission-list{display:flex;flex-direction:column;gap:var(--space-xs);margin-bottom:var(--space-lg)}.permission-item{display:flex;flex-direction:column;gap:2px;padding:var(--space-sm);background:var(--color-hover);border-radius:var(--radius-sm)}.permission-name{font-size:.8125rem;font-weight:600;color:var(--color-text)}.permission-desc{font-size:.75rem;color:var(--color-text-secondary);line-height:1.4}.permission-actions{display:flex;gap:var(--space-sm)}.permission-actions .btn{flex:1;padding:10px var(--space-md);font-size:.875rem}.permission-abort-link{display:block;width:100%;margin-top:var(--space-sm);background:none;border:none;color:var(--color-error);font-size:.8125rem;cursor:pointer;text-align:center;padding:var(--space-xs) 0;opacity:.8}.permission-abort-link:hover{opacity:1;text-decoration:underline}.input-modal{text-align:left;max-width:400px}.input-modal .confirm-modal-title{text-align:center}.input-modal .confirm-modal-message{text-align:center;word-break:break-word}.input-list{display:flex;flex-direction:column;gap:var(--space-md);margin-bottom:var(--space-lg)}.input-item{display:flex;flex-direction:column;gap:var(--space-xs)}.input-label{font-size:.8125rem;font-weight:600;color:var(--color-text)}.input-field{width:100%;padding:var(--space-sm) var(--space-md);font-size:.875rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);box-sizing:border-box}.input-field:focus{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb, 99, 102, 241),.2)}.input-actions{display:flex;gap:var(--space-sm)}.input-actions .btn{flex:1;padding:10px var(--space-md);font-size:.875rem}.host-picker-inline{border:1px solid var(--color-border);border-radius:var(--radius-md);overflow:hidden}.host-picker-list{max-height:240px;overflow-y:auto;padding:var(--space-xs) 0}.host-picker-item-wrapper{position:relative}.host-picker-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:none;background:none;cursor:pointer;font-family:var(--font-sans);font-size:.8125rem;color:var(--color-text);text-align:left;transition:background var(--transition-fast);-webkit-tap-highlight-color:transparent}.host-picker-item:hover{background:var(--color-border-subtle)}.host-picker-item-active,.host-picker-item-active:hover{background:var(--color-primary-subtle)}.host-picker-item-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px}.host-picker-pending{font-size:.6875rem;font-weight:500;color:var(--color-muted);background:var(--color-border-subtle);padding:1px 6px;border-radius:4px;flex-shrink:0}.host-picker-item-actions{flex-shrink:0;width:24px;display:flex;align-items:center;justify-content:center}.host-picker-edit-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:none;cursor:pointer;color:var(--color-muted);border-radius:4px;flex-shrink:0;transition:all var(--transition-fast)}.host-picker-edit-btn:hover{color:var(--color-primary);background:var(--color-primary-subtle)}.host-picker-rename-input{font-size:.8125rem!important;padding:3px 6px!important;flex:1;min-width:0}.host-picker-delete-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:none;cursor:pointer;color:var(--color-muted);border-radius:4px;flex-shrink:0;transition:all var(--transition-fast)}.host-picker-delete-btn:hover{color:var(--color-error);background:var(--color-error-bg)}.hamburger-btn{display:flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:none;background:none;color:var(--color-text);cursor:pointer;border-radius:var(--radius-sm);transition:background var(--transition-fast)}.hamburger-btn:hover{background:var(--color-primary-subtle)}.drawer-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);z-index:100;animation:drawerFadeIn .2s ease}.drawer-panel{position:fixed;top:0;left:0;bottom:0;width:280px;max-width:80vw;background:var(--color-surface);box-shadow:var(--shadow-xl);z-index:101;display:flex;flex-direction:column;animation:drawerSlideIn .25s ease}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-md);border-bottom:1px solid var(--color-border)}.drawer-title{font-size:1.125rem;font-weight:800;color:var(--color-primary);letter-spacing:-.04em}.drawer-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:none;background:none;color:var(--color-text-secondary);cursor:pointer;border-radius:var(--radius-sm);transition:background var(--transition-fast)}.drawer-close-btn:hover{background:var(--color-border-subtle);color:var(--color-text)}.drawer-section{padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-sm)}.drawer-section-label{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-muted);margin-bottom:var(--space-sm)}.drawer-toggle{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm)}.drawer-toggle-label{font-size:.85rem;color:var(--color-text)}.toggle-switch{position:relative;width:40px;height:22px;border-radius:11px;border:none;background:var(--color-border);cursor:pointer;padding:0;transition:background .2s;flex-shrink:0}.toggle-switch-on{background:var(--color-primary)}.toggle-switch-thumb{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:#fff;transition:transform .2s}.toggle-switch-on .toggle-switch-thumb{transform:translate(18px)}.toggle-switch:disabled{opacity:.5;cursor:not-allowed}.drawer-footer{margin-top:auto;padding:var(--space-md)}.drawer-version{font-size:.75rem;color:var(--color-muted)}.drawer-legal{font-size:.75rem;color:var(--color-muted);margin-top:var(--space-xs)}.drawer-legal a{color:var(--color-muted);text-decoration:none}.drawer-legal a:hover{text-decoration:underline}.drawer-legal-sep{margin:0 var(--space-xs)}.drawer-divider{height:1px;background:var(--color-border);margin:0 var(--space-md)}@keyframes drawerFadeIn{0%{opacity:0}to{opacity:1}}@keyframes drawerFadeOut{0%{opacity:1}to{opacity:0}}@keyframes drawerSlideIn{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes drawerSlideOut{0%{transform:translate(0)}to{transform:translate(-100%)}}.drawer-overlay-closing{animation:drawerFadeOut .2s ease forwards}.drawer-panel-closing{animation:drawerSlideOut .2s ease forwards}.tab-bar{display:flex;align-items:center;background:color-mix(in srgb,var(--color-surface) 92%,transparent);border-bottom:1px solid var(--color-border);position:sticky;top:0;z-index:10;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:12px 0;border:none;background:none;font-family:var(--font-sans);font-size:.875rem;font-weight:600;color:var(--color-text-secondary);cursor:pointer;border-bottom:2px solid transparent;transition:color var(--transition-fast),border-color var(--transition-fast)}.tab-icon{flex-shrink:0}.tab-btn:hover{color:var(--color-text)}.tab-btn-active{color:var(--color-primary);border-bottom-color:var(--color-primary)}.sessions-view{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}.sessions-card{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md);display:flex;align-items:center;gap:var(--space-sm);cursor:pointer;transition:box-shadow var(--transition-base),border-color var(--transition-base);-webkit-tap-highlight-color:transparent}.sessions-card:hover{box-shadow:var(--shadow-md);border-color:#cbd5e1}.sessions-card-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-xs)}.sessions-card-name{font-size:.9375rem;font-weight:600;letter-spacing:-.01em;color:var(--color-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sessions-card-meta{display:flex;flex-wrap:wrap;gap:var(--space-sm);font-size:.75rem;color:var(--color-text-secondary)}.sessions-card-chevron{flex-shrink:0;align-self:center;color:var(--color-text-secondary);font-size:1.25rem;line-height:1;opacity:.4;transition:opacity var(--transition-base)}.sessions-card:hover .sessions-card-chevron{opacity:.8}.sessions-filter-chip{display:inline-flex;align-items:center;gap:var(--space-xs);padding:4px 10px;font-size:.8125rem;color:var(--color-text-secondary);background:var(--color-bg);border:1px solid var(--color-border);border-radius:999px}.sessions-filter-chip button{display:inline-flex;align-items:center;justify-content:center;border:none;background:none;cursor:pointer;padding:0;color:var(--color-text-secondary);font-size:1rem;line-height:1}.sessions-filter-chip button:hover{color:var(--color-text)}.run-detail{display:flex;flex-direction:column;gap:var(--space-md);background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md)}.run-detail-back{display:inline-flex;align-items:center;gap:var(--space-xs);border:none;background:none;cursor:pointer;padding:0;color:var(--color-text-secondary);font-size:.8125rem}.run-detail-back:hover{color:var(--color-text)}.chat-thread{display:flex;flex-direction:column;gap:var(--space-sm);overflow-y:auto;flex:1;min-height:0}.chat-message{display:flex;flex-direction:column;gap:var(--space-xs);max-width:85%;min-width:0;padding:var(--space-sm) var(--space-md);border-radius:var(--radius-md);font-size:.8125rem;line-height:1.6}.chat-message--assistant{align-self:flex-start;background:var(--color-surface);border:1px solid var(--color-border)}.chat-message--user{align-self:flex-end;background:var(--color-primary-subtle);border:1px solid var(--color-border-subtle)}.chat-message-agent{font-size:.6875rem;font-weight:500;color:var(--color-text-tertiary);margin-bottom:var(--space-xs)}.chat-message-content{color:var(--color-text);overflow-wrap:break-word}.chat-message-content h1,.chat-message-content h2,.chat-message-content h3,.chat-message-content h4{margin:.75em 0 .25em;font-weight:600}.chat-message-content h1{font-size:1.1rem}.chat-message-content h2{font-size:1rem}.chat-message-content h3{font-size:.9375rem}.chat-message-content p{margin:.5em 0}.chat-message-content ul,.chat-message-content ol{margin:.5em 0;padding-left:1.5em}.chat-message-content code{font-size:.8em;background:var(--color-hover);padding:.15em .35em;border-radius:4px}.chat-message-content pre{background:var(--color-bg);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto;margin:.5em 0}.chat-message-content pre code{background:none;padding:0}.chat-message-content table{border-collapse:collapse;width:100%;margin:.5em 0}.chat-message-content th,.chat-message-content td{border:1px solid var(--color-border);padding:.35em .6em;text-align:left}.chat-message-content th{background:var(--color-bg);font-weight:600}.chat-message-meta{display:flex;align-items:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted)}.chat-message-type{font-weight:600;text-transform:uppercase;letter-spacing:.04em;font-size:.625rem}.chat-message-attachments{display:flex;flex-wrap:wrap;gap:var(--space-xs)}.chat-attachment-chip{display:inline-flex;align-items:center;padding:.2em .6em;font-size:.75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);cursor:pointer;color:var(--color-primary);font-weight:500}.chat-attachment-chip:hover{background:var(--color-primary-subtle)}.chat-message-report{margin-top:var(--space-xs);padding:var(--space-sm);background:var(--color-bg);border-radius:var(--radius-sm);border:1px solid var(--color-border);font-size:.8125rem;line-height:1.6;color:var(--color-text-secondary)}.chat-status{display:flex;flex-direction:column;align-items:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted);padding:var(--space-xs) 0}.chat-status>div{display:flex;align-items:center;gap:var(--space-xs)}.chat-status--error{color:var(--color-error)}.chat-status-detail{margin-top:var(--space-xs);padding:var(--space-sm);background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);font-size:.75rem;font-family:SF Mono,Fira Code,monospace;white-space:pre-wrap;word-break:break-word;max-width:100%;max-height:200px;overflow-y:auto;color:var(--color-text-secondary)}.chat-status-time{color:var(--color-muted)}.chat-monitoring-indicator{display:flex;align-items:center;justify-content:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted);padding:var(--space-md) 0}.chat-monitoring-dot{width:6px;height:6px;border-radius:50%;background:var(--color-muted);animation:monitoring-pulse 1.5s ease-in-out infinite}@keyframes monitoring-pulse{0%,to{opacity:.3}50%{opacity:1}}.chat-abort-bar{display:flex;justify-content:center;padding:var(--space-sm) 0;flex-shrink:0}.chat-abort-btn{color:var(--color-error);border-color:var(--color-error)}.chat-abort-btn:hover:not(:disabled){background:var(--color-error-bg)}.chat-stop-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border-radius:50%;flex-shrink:0;margin-left:auto;background:var(--color-error);color:#fff;border-color:var(--color-error)}.chat-stop-btn:hover:not(:disabled){background:#dc2626;border-color:#dc2626}.chat-input-bar{display:flex;gap:var(--space-xs);padding:var(--space-sm) 0;flex-shrink:0}.chat-input{flex:1;min-width:0;padding:var(--space-sm) var(--space-md);border:1px solid var(--color-border);border-radius:var(--radius-md);font-size:.8125rem;outline:none;background:var(--color-surface);color:var(--color-text)}.chat-input:focus{border-color:var(--color-primary);box-shadow:0 0 0 2px var(--color-primary-subtle)}.chat-input:disabled{opacity:.6}.chat-send-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border-radius:50%;flex-shrink:0}.report-dialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;background:var(--color-overlay);display:flex;align-items:stretch;justify-content:center}.report-dialog{display:flex;flex-direction:column;width:100%;max-width:800px;background:var(--color-surface);overflow:hidden}.report-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-sm) var(--space-md);border-bottom:1px solid var(--color-border);flex-shrink:0}.report-dialog-title{font-size:.875rem;font-weight:600;color:var(--color-text)}.report-dialog-close{display:flex;align-items:center;justify-content:center;border:none;background:none;cursor:pointer;color:var(--color-text-secondary);padding:4px;border-radius:var(--radius-sm)}.report-dialog-close:hover{background:var(--color-hover);color:var(--color-text)}.report-dialog-body{flex:1;overflow-y:auto;padding:var(--space-md);font-size:.8125rem;line-height:1.6;color:var(--color-text)}.report-dialog-body h1,.report-dialog-body h2,.report-dialog-body h3,.report-dialog-body h4{margin:.75em 0 .25em;font-weight:600}.report-dialog-body h1{font-size:1.1rem}.report-dialog-body h2{font-size:1rem}.report-dialog-body h3{font-size:.9375rem}.report-dialog-body p{margin:.5em 0}.report-dialog-body img{max-width:100%;height:auto}.report-dialog-body ul,.report-dialog-body ol{margin:.5em 0;padding-left:1.5em}.report-dialog-body code{font-size:.8em;background:var(--color-hover);padding:.15em .35em;border-radius:4px}.report-dialog-body pre{background:var(--color-bg);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto;margin:.5em 0}.report-dialog-body pre code{background:none;padding:0}.report-dialog-body table{border-collapse:collapse;width:100%;margin:.5em 0}.report-dialog-body th,.report-dialog-body td{border:1px solid var(--color-border);padding:.35em .6em;text-align:left}.report-dialog-body th{background:var(--color-bg);font-weight:600}.chat-typing-indicator{display:flex;align-items:center;gap:4px;padding:4px 0}.chat-typing-indicator span{width:6px;height:6px;border-radius:50%;background:var(--color-muted);animation:chat-typing 1.4s infinite}.chat-typing-indicator span:nth-child(2){animation-delay:.2s}.chat-typing-indicator span:nth-child(3){animation-delay:.4s}@keyframes chat-typing{0%,60%,to{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.pair-consent{font-size:.75rem;color:var(--color-muted);text-align:center;line-height:1.5}.pair-consent a{color:var(--color-muted);text-decoration:underline}.pair-consent a:hover{color:var(--color-text-secondary)}.dashboard-content{display:flex;flex-direction:column;flex:1;min-height:100dvh;max-width:800px;margin:0 auto;width:100%}.drawer-panel-desktop{position:sticky;top:0;height:100dvh;width:280px;min-width:280px;background:var(--color-surface);border-right:1px solid var(--color-border);display:flex;flex-direction:column;overflow-y:auto;animation:none}@media(min-width:768px){.dashboard{flex-direction:row}.dashboard-main{padding:var(--space-lg)}.fab{right:max(var(--space-lg),calc((100vw - 1080px)/2))}}
|