letagents 0.12.30 → 0.12.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/mcp/local-state/storage.js +0 -5
- package/dist/mcp/server/register-tools.js +2 -0
- package/dist/mcp/server/runtime/api.js +53 -1
- package/dist/mcp/server/runtime/identity/config.js +30 -1
- package/dist/mcp/server/runtime/identity/liveness.js +19 -1
- package/dist/mcp/server/runtime/process-connection.js +239 -0
- package/dist/mcp/server/runtime/worker-bearer.js +0 -4
- package/dist/mcp/server/runtime/worker-handles.js +109 -15
- package/dist/mcp/server/runtime.js +1 -1
- package/dist/mcp/server/supervised-tool-facade.js +2 -17
- package/dist/mcp/server/tools/agent-sessions.js +38 -6
- package/dist/mcp/server/tools/knowledge.js +29 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +1 -1
- package/dist/mcp/server/tools/rooms/inspection-tools.js +1 -1
- package/dist/mcp/server/tools/wake-rules.js +96 -0
- package/dist/mcp/server.js +29 -7
- package/dist/mcp/worker-call-context.js +4 -0
- package/dist/shared/agent-identity.js +4 -0
- package/dist/shared/agent-presence.js +141 -0
- package/dist/shared/codenames.js +2 -104
- package/dist/shared/handoff.js +0 -6
- package/dist/shared/request-headers.js +11 -0
- package/dist/shared/room-agent-prompts.js +1 -1
- package/package.json +4 -5
- package/shared/activation-routing.d.mts +6 -1
- package/shared/activation-routing.mjs +10 -1
- package/shared/agent-codenames.d.mts +17 -0
- package/shared/agent-codenames.mjs +96 -0
- package/shared/daemon-maintenance.d.mts +13 -0
- package/shared/daemon-maintenance.mjs +99 -0
- package/shared/permission-review.d.mts +30 -0
- package/shared/permission-review.mjs +482 -0
- package/shared/provider-acquisition-evidence.d.mts +15 -0
- package/shared/provider-acquisition-evidence.mjs +33 -0
- package/shared/room-agent-work.d.mts +1 -0
- package/shared/room-agent-work.mjs +11 -0
- package/shared/room-resource-invalidation.d.mts +3 -0
- package/shared/room-resource-invalidation.mjs +2 -0
- package/shared/room-settings.d.mts +48 -0
- package/shared/room-settings.mjs +108 -0
- package/shared/scratch-workspace-repository.d.mts +13 -0
- package/shared/scratch-workspace-repository.mjs +213 -0
- package/shared/supervised-read-tools.d.mts +2 -0
- package/shared/supervised-read-tools.mjs +25 -0
- package/shared/ui/PrivateMessages.vue +12 -17
- package/shared/ui/WakeGlyph.vue +55 -0
- package/shared/ui/WakeRuleLine.vue +51 -0
- package/shared/ui/WakeRulesPanel.vue +201 -0
- package/shared/ui/private-messages.css +35 -25
- package/shared/ui/wake-rules.css +273 -0
- package/shared/wake-rules.d.mts +145 -0
- package/shared/wake-rules.mjs +425 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section v-if="rows.length || recent.length" ref="root" class="wake-rules" aria-label="Wake rules">
|
|
3
|
+
<TransitionGroup tag="ul" name="wake-rule" class="wake-rules-active">
|
|
4
|
+
<li
|
|
5
|
+
v-for="row in rows"
|
|
6
|
+
:key="row.rule.id"
|
|
7
|
+
class="wake-rule"
|
|
8
|
+
:data-rule-id="row.rule.id"
|
|
9
|
+
:data-state="row.cancelled ? 'cancelled' : 'active'"
|
|
10
|
+
>
|
|
11
|
+
<span class="wake-rule-mark"><WakeGlyph :state="row.cancelled ? 'ended' : 'waiting'" /></span>
|
|
12
|
+
<div class="wake-rule-copy">
|
|
13
|
+
<p class="wake-rule-kicker">{{ row.cancelled ? "Cancelled" : `Waiting ${describe(row.rule).preposition}` }}</p>
|
|
14
|
+
<strong>{{ describe(row.rule).object }}</strong>
|
|
15
|
+
<span v-if="row.rule.note" class="wake-rule-note">{{ row.rule.note }}</span>
|
|
16
|
+
<span class="wake-rule-meta">
|
|
17
|
+
<template v-if="row.rule.event === 'timer'">
|
|
18
|
+
Wakes in <time :datetime="row.rule.arguments.at" :title="fullTime(row.rule.arguments.at)">{{ until(row.rule.arguments.at!) }}</time>
|
|
19
|
+
</template>
|
|
20
|
+
<template v-else>
|
|
21
|
+
<time :datetime="row.rule.created_at" :title="`Since ${fullTime(row.rule.created_at)}`">{{ span(row.rule.created_at) }}</time> so far
|
|
22
|
+
· {{ row.rule.repeat ? "Repeats until" : "Expires" }}
|
|
23
|
+
<time :datetime="row.rule.expires_at" :title="fullTime(row.rule.expires_at)">{{ clock(row.rule.expires_at) }}</time>
|
|
24
|
+
</template>
|
|
25
|
+
</span>
|
|
26
|
+
<span v-if="row.error" class="wake-rule-error" role="status">{{ row.error }}</span>
|
|
27
|
+
</div>
|
|
28
|
+
<button
|
|
29
|
+
v-if="row.cancelled"
|
|
30
|
+
type="button"
|
|
31
|
+
class="wake-rule-action"
|
|
32
|
+
data-tone="undo"
|
|
33
|
+
:disabled="busy.has(row.rule.id)"
|
|
34
|
+
@click="undo(row.rule)"
|
|
35
|
+
>Undo</button>
|
|
36
|
+
<button
|
|
37
|
+
v-else-if="api"
|
|
38
|
+
type="button"
|
|
39
|
+
class="wake-rule-action"
|
|
40
|
+
:disabled="busy.has(row.rule.id)"
|
|
41
|
+
:aria-label="`Cancel wake rule: waiting ${describe(row.rule).preposition} ${describe(row.rule).object}`"
|
|
42
|
+
@click="cancel(row.rule)"
|
|
43
|
+
>Cancel</button>
|
|
44
|
+
</li>
|
|
45
|
+
</TransitionGroup>
|
|
46
|
+
|
|
47
|
+
<details v-if="recent.length" class="wake-rules-recent">
|
|
48
|
+
<summary>Recent wakes <span>{{ recent.length }}</span></summary>
|
|
49
|
+
<ul>
|
|
50
|
+
<li v-for="rule in recent" :key="rule.id" :data-status="rule.status">
|
|
51
|
+
<WakeGlyph :state="rule.status === 'fired' ? 'woke' : 'ended'" still />
|
|
52
|
+
<span class="wake-rules-recent-copy">
|
|
53
|
+
<strong>{{ outcomeLabel(rule) }}</strong>
|
|
54
|
+
<span>{{ describe(rule).preposition === "until" ? `until ${describe(rule).object}` : describe(rule).object }}</span>
|
|
55
|
+
</span>
|
|
56
|
+
<button
|
|
57
|
+
v-if="rule.wake_message_id"
|
|
58
|
+
type="button"
|
|
59
|
+
class="wake-rules-recent-time"
|
|
60
|
+
:title="`Show the wake message · ${fullTime(endedAt(rule))}`"
|
|
61
|
+
@click="emit('open-message', rule.wake_message_id)"
|
|
62
|
+
><time :datetime="endedAt(rule)">{{ clock(endedAt(rule)) }}</time></button>
|
|
63
|
+
<time v-else class="wake-rules-recent-time" :datetime="endedAt(rule)" :title="fullTime(endedAt(rule))">{{ clock(endedAt(rule)) }}</time>
|
|
64
|
+
</li>
|
|
65
|
+
</ul>
|
|
66
|
+
</details>
|
|
67
|
+
</section>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
|
|
72
|
+
import {
|
|
73
|
+
describeWakeRule,
|
|
74
|
+
formatWakeClock,
|
|
75
|
+
formatWakeSpan,
|
|
76
|
+
type WakeRule,
|
|
77
|
+
type WakeRuleApi,
|
|
78
|
+
} from "../wake-rules.mjs";
|
|
79
|
+
import WakeGlyph from "./WakeGlyph.vue";
|
|
80
|
+
|
|
81
|
+
const props = withDefaults(defineProps<{
|
|
82
|
+
roomId: string;
|
|
83
|
+
/** The agent's active rules, soonest first. */
|
|
84
|
+
rules: readonly WakeRule[];
|
|
85
|
+
recent?: readonly WakeRule[];
|
|
86
|
+
/** Without an API the panel is read-only. */
|
|
87
|
+
api?: Pick<WakeRuleApi, "cancel" | "restore"> | null;
|
|
88
|
+
}>(), { recent: () => [], api: null });
|
|
89
|
+
const emit = defineEmits<{ "open-message": [messageId: string] }>();
|
|
90
|
+
|
|
91
|
+
/** How long a cancelled rule stays in place with Undo before it leaves. */
|
|
92
|
+
const UNDO_WINDOW_MS = 8000;
|
|
93
|
+
const now = ref(new Date());
|
|
94
|
+
const busy = reactive(new Set<string>());
|
|
95
|
+
const errors = reactive(new Map<string, string>());
|
|
96
|
+
const cancelled = reactive(new Map<string, { rule: WakeRule; timer: ReturnType<typeof setTimeout> }>());
|
|
97
|
+
/**
|
|
98
|
+
* Restored rules stay on screen until the room's lists catch up: back among
|
|
99
|
+
* the active rules, or among recent ones if anything that happened while it
|
|
100
|
+
* was cancelled woke the agent straight away.
|
|
101
|
+
*/
|
|
102
|
+
const restored = reactive(new Map<string, WakeRule>());
|
|
103
|
+
watch(() => [props.rules, props.recent] as const, ([rules, recent]) => {
|
|
104
|
+
for (const rule of [...rules, ...recent]) {
|
|
105
|
+
if (rule.status !== "cancelled") restored.delete(rule.id);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Undo happens where the action was: a cancelled rule keeps its row until
|
|
110
|
+
// the window closes, even after the room's list no longer includes it.
|
|
111
|
+
const rows = computed(() => {
|
|
112
|
+
const active = props.rules.filter((rule) => !cancelled.has(rule.id));
|
|
113
|
+
const held = [...cancelled.values()].map(({ rule }) => rule);
|
|
114
|
+
const revived = [...restored.values()].filter((rule) => !cancelled.has(rule.id) && !props.rules.some((candidate) => candidate.id === rule.id));
|
|
115
|
+
return [...active, ...held, ...revived]
|
|
116
|
+
.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id))
|
|
117
|
+
.map((rule) => ({ rule, cancelled: cancelled.has(rule.id), error: errors.get(rule.id) ?? null }));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const describe = (rule: Pick<WakeRule, "event" | "arguments">) =>
|
|
121
|
+
describeWakeRule(rule, { formatTime: (iso) => formatWakeClock(iso, now.value) });
|
|
122
|
+
const clock = (iso: string) => formatWakeClock(iso, now.value);
|
|
123
|
+
const span = (iso: string) => formatWakeSpan(now.value.getTime() - Date.parse(iso));
|
|
124
|
+
const until = (iso: string) => formatWakeSpan(Date.parse(iso) - now.value.getTime());
|
|
125
|
+
const fullTime = (iso: string | undefined) => iso
|
|
126
|
+
? new Date(iso).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" })
|
|
127
|
+
: "";
|
|
128
|
+
const endedAt = (rule: WakeRule) => rule.last_fired_at ?? rule.updated_at;
|
|
129
|
+
const root = ref<HTMLElement | null>(null);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Cancel and Undo replace each other in the same place. When the pressed
|
|
133
|
+
* button was focused, focus moves to its replacement instead of the page.
|
|
134
|
+
*/
|
|
135
|
+
async function keepFocusOnRow(ruleId: string, hadFocus: boolean): Promise<void> {
|
|
136
|
+
if (!hadFocus) return;
|
|
137
|
+
await nextTick();
|
|
138
|
+
root.value?.querySelector<HTMLButtonElement>(`[data-rule-id="${CSS.escape(ruleId)}"] .wake-rule-action`)?.focus();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function rowHasFocus(ruleId: string): boolean {
|
|
142
|
+
const active = document.activeElement;
|
|
143
|
+
return Boolean(active && root.value?.querySelector(`[data-rule-id="${CSS.escape(ruleId)}"]`)?.contains(active));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function outcomeLabel(rule: WakeRule): string {
|
|
147
|
+
if (rule.status === "fired" || (rule.status === "active" && rule.fire_count > 0)) return "Woke";
|
|
148
|
+
if (rule.status === "expired") return "Stopped waiting";
|
|
149
|
+
return rule.cancelled_by ? `Cancelled by ${rule.cancelled_by.label}` : "Cancelled";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function release(ruleId: string): void {
|
|
153
|
+
const held = cancelled.get(ruleId);
|
|
154
|
+
if (held) clearTimeout(held.timer);
|
|
155
|
+
cancelled.delete(ruleId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function cancel(rule: WakeRule): Promise<void> {
|
|
159
|
+
if (!props.api || busy.has(rule.id)) return;
|
|
160
|
+
const hadFocus = rowHasFocus(rule.id);
|
|
161
|
+
busy.add(rule.id);
|
|
162
|
+
errors.delete(rule.id);
|
|
163
|
+
cancelled.set(rule.id, { rule, timer: setTimeout(() => release(rule.id), UNDO_WINDOW_MS) });
|
|
164
|
+
try {
|
|
165
|
+
await props.api.cancel(props.roomId, rule.id);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
release(rule.id);
|
|
168
|
+
errors.set(rule.id, error instanceof Error ? error.message : "Couldn't cancel this wake rule. Try again.");
|
|
169
|
+
} finally {
|
|
170
|
+
busy.delete(rule.id);
|
|
171
|
+
}
|
|
172
|
+
// Undo (or Cancel again, if cancelling failed) is enabled only now.
|
|
173
|
+
await keepFocusOnRow(rule.id, hadFocus);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function undo(rule: WakeRule): Promise<void> {
|
|
177
|
+
if (!props.api || busy.has(rule.id)) return;
|
|
178
|
+
const hadFocus = rowHasFocus(rule.id);
|
|
179
|
+
busy.add(rule.id);
|
|
180
|
+
try {
|
|
181
|
+
restored.set(rule.id, await props.api.restore(props.roomId, rule.id));
|
|
182
|
+
release(rule.id);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
errors.set(rule.id, error instanceof Error ? error.message : "Couldn't restore this wake rule.");
|
|
185
|
+
} finally {
|
|
186
|
+
busy.delete(rule.id);
|
|
187
|
+
}
|
|
188
|
+
await keepFocusOnRow(rule.id, hadFocus);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let tick: ReturnType<typeof setInterval> | undefined;
|
|
192
|
+
onMounted(() => {
|
|
193
|
+
tick = setInterval(() => { now.value = new Date(); }, 30_000);
|
|
194
|
+
});
|
|
195
|
+
onBeforeUnmount(() => {
|
|
196
|
+
clearInterval(tick);
|
|
197
|
+
for (const ruleId of [...cancelled.keys()]) release(ruleId);
|
|
198
|
+
});
|
|
199
|
+
</script>
|
|
200
|
+
|
|
201
|
+
<style src="./wake-rules.css"></style>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
.private-messages {
|
|
2
2
|
display: grid;
|
|
3
|
-
grid-template-columns:
|
|
3
|
+
grid-template-columns: 300px minmax(0, 1fr);
|
|
4
|
+
grid-template-rows: minmax(0, 1fr);
|
|
4
5
|
flex: 1;
|
|
5
6
|
min-height: 0;
|
|
7
|
+
min-width: 0;
|
|
6
8
|
height: 100%;
|
|
7
9
|
background: var(--bg);
|
|
8
10
|
color: var(--text);
|
|
@@ -66,13 +68,24 @@
|
|
|
66
68
|
min-height: 0;
|
|
67
69
|
border-right: 1px solid var(--border);
|
|
68
70
|
background: var(--bg-subtle);
|
|
69
|
-
padding:
|
|
71
|
+
padding: 16px 12px 12px;
|
|
70
72
|
}
|
|
71
73
|
.conversation-list-heading {
|
|
72
74
|
display: flex;
|
|
73
75
|
align-items: center;
|
|
74
76
|
justify-content: space-between;
|
|
75
|
-
|
|
77
|
+
gap: 12px;
|
|
78
|
+
min-height: 36px;
|
|
79
|
+
padding: 0 4px 16px;
|
|
80
|
+
}
|
|
81
|
+
.private-messages .conversation-new-chat {
|
|
82
|
+
min-height: 32px;
|
|
83
|
+
padding: 6px 10px;
|
|
84
|
+
font-size: 12px;
|
|
85
|
+
}
|
|
86
|
+
.conversation-new-chat svg {
|
|
87
|
+
width: 15px;
|
|
88
|
+
height: 15px;
|
|
76
89
|
}
|
|
77
90
|
.chat-icon-button {
|
|
78
91
|
display: inline-flex;
|
|
@@ -148,6 +161,7 @@
|
|
|
148
161
|
}
|
|
149
162
|
.conversation-rows {
|
|
150
163
|
overflow: auto;
|
|
164
|
+
overscroll-behavior: contain;
|
|
151
165
|
min-height: 0;
|
|
152
166
|
flex: 1;
|
|
153
167
|
}
|
|
@@ -238,13 +252,8 @@
|
|
|
238
252
|
padding: 32px 18px;
|
|
239
253
|
font-size: 12px;
|
|
240
254
|
}
|
|
241
|
-
.conversation-list-empty
|
|
242
|
-
|
|
243
|
-
height: 26px;
|
|
244
|
-
margin-bottom: 10px;
|
|
245
|
-
}
|
|
246
|
-
.conversation-list-empty p {
|
|
247
|
-
margin-bottom: 8px;
|
|
255
|
+
.conversation-list-empty-initial {
|
|
256
|
+
display: none;
|
|
248
257
|
}
|
|
249
258
|
.chat-text-button {
|
|
250
259
|
border: 0;
|
|
@@ -547,9 +556,9 @@
|
|
|
547
556
|
max-width: 430px;
|
|
548
557
|
}
|
|
549
558
|
.conversation-welcome h2 {
|
|
550
|
-
font-size:
|
|
551
|
-
font-weight:
|
|
552
|
-
letter-spacing: -0.
|
|
559
|
+
font-size: 18px;
|
|
560
|
+
font-weight: 600;
|
|
561
|
+
letter-spacing: -0.02em;
|
|
553
562
|
}
|
|
554
563
|
.conversation-welcome p,
|
|
555
564
|
.conversation-timeline-empty p {
|
|
@@ -557,20 +566,13 @@
|
|
|
557
566
|
font-size: 13px;
|
|
558
567
|
line-height: 1.7;
|
|
559
568
|
}
|
|
560
|
-
.conversation-welcome .chat-primary {
|
|
561
|
-
margin-top: 8px;
|
|
562
|
-
}
|
|
563
569
|
.conversation-empty-mark {
|
|
564
|
-
width:
|
|
565
|
-
height:
|
|
570
|
+
width: 40px;
|
|
571
|
+
height: 40px;
|
|
566
572
|
display: flex;
|
|
567
573
|
align-items: center;
|
|
568
574
|
justify-content: center;
|
|
569
|
-
background: var(--bg-card);
|
|
570
|
-
border: 1px solid var(--border-strong);
|
|
571
|
-
border-radius: 16px;
|
|
572
575
|
margin-bottom: 6px;
|
|
573
|
-
box-shadow: inset 0 1px 0 var(--border);
|
|
574
576
|
}
|
|
575
577
|
.conversation-empty-mark svg {
|
|
576
578
|
width: 24px;
|
|
@@ -583,6 +585,8 @@
|
|
|
583
585
|
letter-spacing: -0.02em;
|
|
584
586
|
}
|
|
585
587
|
.conversation-compose-people {
|
|
588
|
+
min-height: 0;
|
|
589
|
+
overflow-y: auto;
|
|
586
590
|
padding: 28px;
|
|
587
591
|
max-width: 740px;
|
|
588
592
|
width: 100%;
|
|
@@ -742,9 +746,9 @@
|
|
|
742
746
|
color: var(--text);
|
|
743
747
|
}
|
|
744
748
|
}
|
|
745
|
-
@
|
|
749
|
+
@container private-messages (max-width: 900px) {
|
|
746
750
|
.private-messages {
|
|
747
|
-
grid-template-columns:
|
|
751
|
+
grid-template-columns: 260px minmax(0, 1fr);
|
|
748
752
|
}
|
|
749
753
|
.conversation-header {
|
|
750
754
|
padding: 14px 18px;
|
|
@@ -756,13 +760,19 @@
|
|
|
756
760
|
padding: 12px 20px 16px;
|
|
757
761
|
}
|
|
758
762
|
}
|
|
759
|
-
@
|
|
763
|
+
@container private-messages (max-width: 640px) {
|
|
760
764
|
.private-messages {
|
|
761
765
|
grid-template-columns: minmax(0, 1fr);
|
|
762
766
|
}
|
|
763
767
|
.conversation-list {
|
|
764
768
|
border: 0;
|
|
765
769
|
}
|
|
770
|
+
.conversation-list-empty-initial {
|
|
771
|
+
display: block;
|
|
772
|
+
}
|
|
773
|
+
.private-messages .conversation-new-chat {
|
|
774
|
+
min-height: 40px;
|
|
775
|
+
}
|
|
766
776
|
.conversation-content {
|
|
767
777
|
display: none;
|
|
768
778
|
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Wake rules read as a quiet state, not an alert: neutral colour, one glyph,
|
|
3
|
+
* and motion only when a person changes something.
|
|
4
|
+
*/
|
|
5
|
+
.wake-rules {
|
|
6
|
+
display: grid;
|
|
7
|
+
gap: 8px;
|
|
8
|
+
}
|
|
9
|
+
.wake-rules ul {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
list-style: none;
|
|
13
|
+
}
|
|
14
|
+
.wake-rules time,
|
|
15
|
+
.wake-rule-copy strong {
|
|
16
|
+
font-variant-numeric: tabular-nums slashed-zero;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.wake-rules-active {
|
|
20
|
+
position: relative;
|
|
21
|
+
display: grid;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
}
|
|
24
|
+
.wake-rule {
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-columns: 30px minmax(0, 1fr) auto;
|
|
27
|
+
gap: 11px;
|
|
28
|
+
align-items: start;
|
|
29
|
+
padding: 14px;
|
|
30
|
+
border: 1px solid color-mix(in srgb, var(--text) 7.5%, transparent);
|
|
31
|
+
border-radius: 16px;
|
|
32
|
+
background: color-mix(in srgb, var(--text) 2.6%, transparent);
|
|
33
|
+
}
|
|
34
|
+
.wake-rule-mark {
|
|
35
|
+
display: grid;
|
|
36
|
+
place-items: center;
|
|
37
|
+
width: 30px;
|
|
38
|
+
height: 30px;
|
|
39
|
+
border-radius: 10px;
|
|
40
|
+
background: color-mix(in srgb, var(--text-secondary) 12%, transparent);
|
|
41
|
+
color: var(--text-secondary);
|
|
42
|
+
}
|
|
43
|
+
.wake-rule-copy {
|
|
44
|
+
min-width: 0;
|
|
45
|
+
}
|
|
46
|
+
.wake-rule-kicker {
|
|
47
|
+
margin: 0;
|
|
48
|
+
color: var(--text-secondary);
|
|
49
|
+
font-size: 0.64rem;
|
|
50
|
+
font-weight: 760;
|
|
51
|
+
letter-spacing: 0.08em;
|
|
52
|
+
text-transform: uppercase;
|
|
53
|
+
}
|
|
54
|
+
.wake-rule-copy strong {
|
|
55
|
+
display: block;
|
|
56
|
+
margin-top: 4px;
|
|
57
|
+
color: var(--text);
|
|
58
|
+
font-size: 0.84rem;
|
|
59
|
+
font-weight: 640;
|
|
60
|
+
line-height: 1.45;
|
|
61
|
+
overflow-wrap: anywhere;
|
|
62
|
+
}
|
|
63
|
+
.wake-rule-note {
|
|
64
|
+
display: block;
|
|
65
|
+
margin-top: 3px;
|
|
66
|
+
color: var(--text-secondary);
|
|
67
|
+
font-size: 0.75rem;
|
|
68
|
+
line-height: 1.45;
|
|
69
|
+
overflow-wrap: anywhere;
|
|
70
|
+
}
|
|
71
|
+
.wake-rule-meta {
|
|
72
|
+
display: block;
|
|
73
|
+
margin-top: 6px;
|
|
74
|
+
color: var(--text-tertiary);
|
|
75
|
+
font-size: 0.69rem;
|
|
76
|
+
line-height: 1.4;
|
|
77
|
+
}
|
|
78
|
+
.wake-rule-error {
|
|
79
|
+
display: block;
|
|
80
|
+
margin-top: 6px;
|
|
81
|
+
color: var(--red-text);
|
|
82
|
+
font-size: 0.71rem;
|
|
83
|
+
line-height: 1.4;
|
|
84
|
+
}
|
|
85
|
+
.wake-rule[data-state="cancelled"] {
|
|
86
|
+
border-style: dashed;
|
|
87
|
+
background: transparent;
|
|
88
|
+
}
|
|
89
|
+
.wake-rule[data-state="cancelled"] .wake-rule-copy strong {
|
|
90
|
+
color: var(--text-secondary);
|
|
91
|
+
text-decoration: line-through;
|
|
92
|
+
text-decoration-color: color-mix(in srgb, var(--text-secondary) 45%, transparent);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.wake-rule-action {
|
|
96
|
+
align-self: center;
|
|
97
|
+
min-height: 32px;
|
|
98
|
+
padding: 0 11px;
|
|
99
|
+
border: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
|
|
100
|
+
border-radius: 9px;
|
|
101
|
+
background: transparent;
|
|
102
|
+
color: var(--text-secondary);
|
|
103
|
+
font: inherit;
|
|
104
|
+
font-size: 0.72rem;
|
|
105
|
+
font-weight: 650;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
transition:
|
|
108
|
+
transform 120ms var(--ease-out),
|
|
109
|
+
background-color 140ms ease,
|
|
110
|
+
border-color 140ms ease,
|
|
111
|
+
color 140ms ease;
|
|
112
|
+
}
|
|
113
|
+
.wake-rule-action[data-tone="undo"] {
|
|
114
|
+
border-color: color-mix(in srgb, var(--blue) 38%, transparent);
|
|
115
|
+
color: var(--blue);
|
|
116
|
+
}
|
|
117
|
+
@media (hover: hover) and (pointer: fine) {
|
|
118
|
+
.wake-rule-action:hover:not(:disabled) {
|
|
119
|
+
background: color-mix(in srgb, var(--text) 6%, transparent);
|
|
120
|
+
color: var(--text);
|
|
121
|
+
}
|
|
122
|
+
.wake-rule-action[data-tone="undo"]:hover:not(:disabled) {
|
|
123
|
+
background: color-mix(in srgb, var(--blue) 10%, transparent);
|
|
124
|
+
color: var(--blue);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
.wake-rule-action:active:not(:disabled) {
|
|
128
|
+
transform: scale(0.97);
|
|
129
|
+
}
|
|
130
|
+
.wake-rule-action:focus-visible,
|
|
131
|
+
.wake-rules-recent summary:focus-visible,
|
|
132
|
+
.wake-rules-recent-time:focus-visible {
|
|
133
|
+
outline: 2px solid var(--blue);
|
|
134
|
+
outline-offset: 2px;
|
|
135
|
+
}
|
|
136
|
+
.wake-rule-action:disabled {
|
|
137
|
+
cursor: default;
|
|
138
|
+
opacity: 0.5;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* A person's cancel or undo is the only change that moves a row. */
|
|
142
|
+
.wake-rule-enter-active {
|
|
143
|
+
transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out);
|
|
144
|
+
}
|
|
145
|
+
.wake-rule-enter-from {
|
|
146
|
+
opacity: 0;
|
|
147
|
+
transform: translateY(4px) scale(0.98);
|
|
148
|
+
}
|
|
149
|
+
.wake-rule-leave-active {
|
|
150
|
+
position: absolute;
|
|
151
|
+
inset-inline: 0;
|
|
152
|
+
pointer-events: none;
|
|
153
|
+
transition: opacity 150ms ease-in, transform 150ms ease-in;
|
|
154
|
+
}
|
|
155
|
+
.wake-rule-leave-to {
|
|
156
|
+
opacity: 0;
|
|
157
|
+
transform: scale(0.99);
|
|
158
|
+
}
|
|
159
|
+
.wake-rule-move {
|
|
160
|
+
transition: transform 220ms var(--ease-out);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.wake-rules-recent summary {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
gap: 6px;
|
|
167
|
+
width: fit-content;
|
|
168
|
+
padding: 4px 2px;
|
|
169
|
+
border-radius: 6px;
|
|
170
|
+
color: var(--text-secondary);
|
|
171
|
+
font-size: 0.64rem;
|
|
172
|
+
font-weight: 760;
|
|
173
|
+
letter-spacing: 0.08em;
|
|
174
|
+
text-transform: uppercase;
|
|
175
|
+
list-style: none;
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
}
|
|
178
|
+
.wake-rules-recent summary::-webkit-details-marker {
|
|
179
|
+
display: none;
|
|
180
|
+
}
|
|
181
|
+
.wake-rules-recent summary::before {
|
|
182
|
+
content: "";
|
|
183
|
+
width: 5px;
|
|
184
|
+
height: 5px;
|
|
185
|
+
border-right: 1.5px solid currentColor;
|
|
186
|
+
border-bottom: 1.5px solid currentColor;
|
|
187
|
+
rotate: -45deg;
|
|
188
|
+
transition: rotate 160ms var(--ease-out);
|
|
189
|
+
}
|
|
190
|
+
.wake-rules-recent[open] summary::before {
|
|
191
|
+
rotate: 45deg;
|
|
192
|
+
}
|
|
193
|
+
.wake-rules-recent summary span {
|
|
194
|
+
color: var(--text-tertiary);
|
|
195
|
+
letter-spacing: 0;
|
|
196
|
+
}
|
|
197
|
+
.wake-rules-recent li {
|
|
198
|
+
display: grid;
|
|
199
|
+
grid-template-columns: 16px minmax(0, 1fr) auto;
|
|
200
|
+
gap: 9px;
|
|
201
|
+
align-items: center;
|
|
202
|
+
padding: 7px 2px;
|
|
203
|
+
color: var(--text-tertiary);
|
|
204
|
+
font-size: 0.73rem;
|
|
205
|
+
line-height: 1.4;
|
|
206
|
+
}
|
|
207
|
+
.wake-rules-recent li[data-status="fired"] > .wake-glyph {
|
|
208
|
+
color: var(--text-secondary);
|
|
209
|
+
}
|
|
210
|
+
.wake-rules-recent-copy {
|
|
211
|
+
display: flex;
|
|
212
|
+
gap: 6px;
|
|
213
|
+
min-width: 0;
|
|
214
|
+
}
|
|
215
|
+
.wake-rules-recent-copy strong {
|
|
216
|
+
flex-shrink: 0;
|
|
217
|
+
color: var(--text-secondary);
|
|
218
|
+
font-weight: 650;
|
|
219
|
+
}
|
|
220
|
+
.wake-rules-recent-copy span {
|
|
221
|
+
overflow: hidden;
|
|
222
|
+
text-overflow: ellipsis;
|
|
223
|
+
white-space: nowrap;
|
|
224
|
+
}
|
|
225
|
+
.wake-rules-recent-time {
|
|
226
|
+
min-height: 0;
|
|
227
|
+
padding: 2px 4px;
|
|
228
|
+
border: 0;
|
|
229
|
+
border-radius: 5px;
|
|
230
|
+
background: transparent;
|
|
231
|
+
color: var(--text-tertiary);
|
|
232
|
+
font: inherit;
|
|
233
|
+
font-size: 0.69rem;
|
|
234
|
+
line-height: 1.4;
|
|
235
|
+
}
|
|
236
|
+
/* An inline link: compact row, generous target. Hosts may size buttons for touch. */
|
|
237
|
+
.wake-rules button.wake-rules-recent-time {
|
|
238
|
+
position: relative;
|
|
239
|
+
min-height: 0;
|
|
240
|
+
cursor: pointer;
|
|
241
|
+
}
|
|
242
|
+
.wake-rules button.wake-rules-recent-time::after {
|
|
243
|
+
content: "";
|
|
244
|
+
position: absolute;
|
|
245
|
+
inset: -8px -6px;
|
|
246
|
+
}
|
|
247
|
+
@media (hover: hover) and (pointer: fine) {
|
|
248
|
+
button.wake-rules-recent-time:hover {
|
|
249
|
+
color: var(--text);
|
|
250
|
+
text-decoration: underline;
|
|
251
|
+
text-underline-offset: 2px;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@media (prefers-reduced-motion: reduce) {
|
|
256
|
+
.wake-rule-enter-active,
|
|
257
|
+
.wake-rule-leave-active,
|
|
258
|
+
.wake-rule-move {
|
|
259
|
+
transition: opacity 150ms ease;
|
|
260
|
+
}
|
|
261
|
+
.wake-rule-enter-from,
|
|
262
|
+
.wake-rule-leave-to {
|
|
263
|
+
transform: none;
|
|
264
|
+
}
|
|
265
|
+
.wake-rules-recent summary::before {
|
|
266
|
+
transition: none;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
@media (prefers-contrast: more) {
|
|
270
|
+
.wake-rule {
|
|
271
|
+
border-color: color-mix(in srgb, var(--text) 28%, transparent);
|
|
272
|
+
}
|
|
273
|
+
}
|