gestalt-mobile 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/index-BOHLkK8Y.css +1 -0
- package/dist/client/assets/index-DK64debX.js +24 -0
- package/dist/client/index.html +2 -2
- package/dist/server/server/composition.js +16 -5
- package/dist/server/server/features/sessions/get-history/endpoint.js +15 -4
- package/dist/server/server/features/sessions/get-history/history-mapper.js +9 -0
- package/dist/server/server/features/sessions/register-routes.js +5 -1
- package/dist/server/server/features/sessions/respond-interaction/endpoint.js +19 -3
- package/dist/server/server/features/sessions/session-events/endpoint.js +47 -12
- package/dist/server/server/features/sessions/start-turn/endpoint.js +38 -0
- package/dist/server/server/platform/codex/session-runtime.js +1 -0
- package/dist/server/server/platform/persistence/migrate.js +10 -1
- package/dist/server/server/platform/persistence/sqlite-pending-interaction-store.js +43 -6
- package/dist/server/shared/contracts/chat-snapshot.js +6 -0
- package/package.json +1 -1
- package/dist/client/assets/index-2L2KZzwK.css +0 -1
- package/dist/client/assets/index-Dnm_kS5p.js +0 -24
package/dist/client/index.html
CHANGED
|
@@ -16,8 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
16
16
|
<link rel="icon" href="/icons/gestalt-mobile-192.png" />
|
|
17
17
|
<link rel="apple-touch-icon" href="/icons/gestalt-mobile-180.png" />
|
|
18
18
|
<title>Gestalt Mobile</title>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-DK64debX.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BOHLkK8Y.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="app"></div>
|
|
@@ -124,10 +124,15 @@ export async function composeRelayApp(options) {
|
|
|
124
124
|
}
|
|
125
125
|
events.publish(journal.append(sessionId, normalized.type, normalized.payload, normalized.occurredAt));
|
|
126
126
|
}, (sessionId, request) => {
|
|
127
|
-
const
|
|
127
|
+
const rawInteraction = toPendingInteraction(request);
|
|
128
128
|
const session = withPendingInteractions(sessions.find(sessionId));
|
|
129
|
-
if (!
|
|
129
|
+
if (!rawInteraction || !session)
|
|
130
130
|
return false;
|
|
131
|
+
const interaction = {
|
|
132
|
+
...rawInteraction,
|
|
133
|
+
turnId: session.activeTurnId,
|
|
134
|
+
requestedAt: new Date().toISOString(),
|
|
135
|
+
};
|
|
131
136
|
interactions.add(sessionId, interaction);
|
|
132
137
|
const updated = RelaySession.rehydrate(session).requestInteraction(interaction, new Date().toISOString()).snapshot;
|
|
133
138
|
sessions.save(updated);
|
|
@@ -305,8 +310,11 @@ export async function composeRelayApp(options) {
|
|
|
305
310
|
replyInteraction: runtime
|
|
306
311
|
? (sessionId, requestId, value) => runtime.resolveServerRequest(sessionId, requestId, value)
|
|
307
312
|
: undefined,
|
|
308
|
-
interactionResolved: (sessionId, requestId, occurredAt) => {
|
|
309
|
-
|
|
313
|
+
interactionResolved: (sessionId, requestId, occurredAt, outcome) => {
|
|
314
|
+
const interaction = interactions
|
|
315
|
+
.snapshot(sessionId)
|
|
316
|
+
.find((item) => item.requestId === requestId);
|
|
317
|
+
events.publish(journal.append(sessionId, 'interaction.resolved', { requestId, turnId: interaction?.turnId ?? null, resolvedAt: occurredAt, outcome }, occurredAt));
|
|
310
318
|
},
|
|
311
319
|
},
|
|
312
320
|
sessionEvents: {
|
|
@@ -341,7 +349,10 @@ export async function composeRelayApp(options) {
|
|
|
341
349
|
}
|
|
342
350
|
: {}),
|
|
343
351
|
interactions: {
|
|
344
|
-
resolve: (sessionId, requestId, resolvedAt) => interactions.resolve(sessionId, requestId, resolvedAt),
|
|
352
|
+
resolve: (sessionId, requestId, resolvedAt, outcome) => interactions.resolve(sessionId, requestId, resolvedAt, outcome),
|
|
353
|
+
alreadyResolved: (sessionId, requestId) => interactions.resolved(sessionId, requestId),
|
|
354
|
+
snapshot: (sessionId) => interactions.snapshot(sessionId),
|
|
355
|
+
pending: (sessionId, requestId) => interactions.find(sessionId, requestId) !== null,
|
|
345
356
|
validate: (sessionId, requestId, value) => {
|
|
346
357
|
const interaction = interactions.find(sessionId, requestId);
|
|
347
358
|
if (!interaction)
|
|
@@ -3,12 +3,18 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
-
import { toChatItems } from './history-mapper.js';
|
|
6
|
+
import { toChatItems, toChatTurns } from './history-mapper.js';
|
|
7
|
+
/**
|
|
8
|
+
* ChatSnapshot is a lower-bound cut: baseSequence is sampled before the
|
|
9
|
+
* upstream read, therefore every event with a higher sequence remains eligible
|
|
10
|
+
* for ordered replay even when its effect is already visible in the snapshot.
|
|
11
|
+
*/
|
|
7
12
|
export function registerGetHistory(app, deps) {
|
|
8
13
|
app.get('/api/sessions/:id/history', async (request, reply) => {
|
|
9
14
|
const session = deps.find(request.params.id);
|
|
10
15
|
if (!session)
|
|
11
16
|
return reply.code(404).send({ code: 'SESSION_NOT_FOUND' });
|
|
17
|
+
const baseSequence = deps.currentSequence(session.id);
|
|
12
18
|
let history;
|
|
13
19
|
try {
|
|
14
20
|
history = await deps.read(session);
|
|
@@ -33,10 +39,15 @@ export function registerGetHistory(app, deps) {
|
|
|
33
39
|
});
|
|
34
40
|
}
|
|
35
41
|
const items = toChatItems(history.turns);
|
|
36
|
-
|
|
42
|
+
const snapshot = {
|
|
37
43
|
items,
|
|
44
|
+
turns: toChatTurns(history.turns),
|
|
38
45
|
activeTurnId: history.activeTurnId,
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
interactions: deps.interactions?.(session.id) ?? [],
|
|
47
|
+
baseSequence,
|
|
48
|
+
// Temporary compatibility for clients which have not adopted ChatSnapshot.
|
|
49
|
+
currentSequence: baseSequence,
|
|
50
|
+
};
|
|
51
|
+
return reply.send(snapshot);
|
|
41
52
|
});
|
|
42
53
|
}
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
+
/** Canonical grouping preserves the upstream Codex turn identity across reloads. */
|
|
7
|
+
export function toChatTurns(turns) {
|
|
8
|
+
return turns.map((turn, index) => ({
|
|
9
|
+
id: turn.id ?? `history-turn-${index}`,
|
|
10
|
+
items: turn.items.flatMap((item) => toChatItem(item, occurredAt(item, turn))),
|
|
11
|
+
startedAt: turn.startedAt,
|
|
12
|
+
completedAt: turn.completedAt,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
6
15
|
export function toChatItems(turns) {
|
|
7
16
|
return turns.flatMap((turn) => turn.items.flatMap((item) => toChatItem(item, occurredAt(item, turn))));
|
|
8
17
|
}
|
|
@@ -54,6 +54,7 @@ export function registerSessionRoutes(app, deps) {
|
|
|
54
54
|
find: sessions.find,
|
|
55
55
|
read: sessions.readHistory,
|
|
56
56
|
currentSequence: sessions.currentSequence ?? (() => 0),
|
|
57
|
+
interactions: deps.interactions?.snapshot,
|
|
57
58
|
});
|
|
58
59
|
if (sessions.startTurn)
|
|
59
60
|
registerStartTurn(app, {
|
|
@@ -61,6 +62,7 @@ export function registerSessionRoutes(app, deps) {
|
|
|
61
62
|
start: sessions.startTurn,
|
|
62
63
|
save: sessions.save,
|
|
63
64
|
onStarted: sessions.onTurnStarted,
|
|
65
|
+
idempotency: sessions.idempotency,
|
|
64
66
|
});
|
|
65
67
|
if (sessions.models)
|
|
66
68
|
registerSelectModel(app, {
|
|
@@ -108,8 +110,10 @@ export function registerSessionRoutes(app, deps) {
|
|
|
108
110
|
if (sessions.replyInteraction && deps.interactions)
|
|
109
111
|
registerRespondInteraction(app, {
|
|
110
112
|
exists: (id) => sessions.find(id) !== null,
|
|
111
|
-
resolve: (sessionId, requestId, resolvedAt) => deps.interactions.resolve(sessionId, requestId, resolvedAt),
|
|
113
|
+
resolve: (sessionId, requestId, resolvedAt, outcome) => deps.interactions.resolve(sessionId, requestId, resolvedAt, outcome),
|
|
112
114
|
validate: deps.interactions.validate,
|
|
115
|
+
pending: deps.interactions.pending,
|
|
116
|
+
alreadyResolved: deps.interactions.alreadyResolved,
|
|
113
117
|
reply: sessions.replyInteraction,
|
|
114
118
|
resolved: sessions.interactionResolved,
|
|
115
119
|
now: sessions.now,
|
|
@@ -11,14 +11,30 @@ export function registerRespondInteraction(app, deps) {
|
|
|
11
11
|
const value = request.body;
|
|
12
12
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
13
13
|
return reply.code(400).send({ code: 'INTERACTION_RESPONSE_INVALID' });
|
|
14
|
+
const recorded = deps.alreadyResolved?.(id, requestId);
|
|
15
|
+
if (recorded)
|
|
16
|
+
return reply.code(202).send({ accepted: true, ...recorded });
|
|
17
|
+
if (deps.pending && !deps.pending(id, requestId))
|
|
18
|
+
return reply.code(409).send({ code: 'INTERACTION_NOT_PENDING' });
|
|
14
19
|
if (deps.validate && !deps.validate(id, requestId, value))
|
|
15
20
|
return reply.code(400).send({ code: 'INTERACTION_RESPONSE_INVALID' });
|
|
16
21
|
if (!deps.reply(id, requestId, value))
|
|
17
22
|
return reply.code(409).send({ code: 'INTERACTION_NOT_PENDING' });
|
|
18
23
|
const resolvedAt = deps.now();
|
|
19
|
-
|
|
24
|
+
const outcome = safeOutcome(value);
|
|
25
|
+
if (!deps.resolve(id, requestId, resolvedAt, outcome))
|
|
20
26
|
return reply.code(409).send({ code: 'INTERACTION_NOT_PENDING' });
|
|
21
|
-
deps.resolved?.(id, requestId, resolvedAt);
|
|
22
|
-
return reply.code(202).send({ accepted: true });
|
|
27
|
+
deps.resolved?.(id, requestId, resolvedAt, outcome);
|
|
28
|
+
return reply.code(202).send({ accepted: true, resolvedAt, outcome });
|
|
23
29
|
});
|
|
24
30
|
}
|
|
31
|
+
function safeOutcome(value) {
|
|
32
|
+
if (typeof value !== 'object' || value === null)
|
|
33
|
+
return 'answered';
|
|
34
|
+
const decision = value.decision;
|
|
35
|
+
if (decision === 'approved' || decision === 'accept')
|
|
36
|
+
return 'approved';
|
|
37
|
+
if (decision === 'denied' || decision === 'decline')
|
|
38
|
+
return 'denied';
|
|
39
|
+
return 'answered';
|
|
40
|
+
}
|
|
@@ -24,25 +24,60 @@ export function registerSessionEvents(app, deps) {
|
|
|
24
24
|
}
|
|
25
25
|
server.handleUpgrade(request, socket, head, (connection) => {
|
|
26
26
|
const sessionId = match[1];
|
|
27
|
+
let lastSentSequence = after;
|
|
27
28
|
const send = (event) => {
|
|
28
|
-
|
|
29
|
+
// A subscription intentionally overlaps the durable replay. The sequence
|
|
30
|
+
// is the authoritative identity of an event, so a bounded high-water mark
|
|
31
|
+
// de-duplicates both replay overlap and stale live callbacks.
|
|
32
|
+
if (event.sessionId !== sessionId ||
|
|
33
|
+
!Number.isSafeInteger(event.sequence) ||
|
|
34
|
+
event.sequence < 1)
|
|
35
|
+
return;
|
|
36
|
+
if (event.sequence <= lastSentSequence)
|
|
37
|
+
return;
|
|
38
|
+
lastSentSequence = event.sequence;
|
|
39
|
+
if (isSlowClient(deps.bufferedAmount?.(connection) ?? connection.bufferedAmount))
|
|
29
40
|
return connection.close(1013, 'slow client');
|
|
30
41
|
connection.send(JSON.stringify({ type: 'relay.event', event }));
|
|
31
42
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
let unsubscribe;
|
|
44
|
+
let removeHeartbeat;
|
|
45
|
+
let replaying = true;
|
|
46
|
+
const liveDuringReplay = [];
|
|
47
|
+
const cleanup = () => {
|
|
48
|
+
removeHeartbeat?.();
|
|
49
|
+
removeHeartbeat = undefined;
|
|
50
|
+
unsubscribe?.();
|
|
51
|
+
unsubscribe = undefined;
|
|
52
|
+
};
|
|
53
|
+
try {
|
|
54
|
+
// Subscribe before reading the journal: every event after this point is
|
|
55
|
+
// either in replay or delivered live, and the sequence de-duplicates it.
|
|
56
|
+
unsubscribe = deps.subscribe(sessionId, (event) => {
|
|
57
|
+
if (replaying)
|
|
58
|
+
liveDuringReplay.push(event);
|
|
59
|
+
else
|
|
60
|
+
send(event);
|
|
61
|
+
});
|
|
62
|
+
const replay = replayOrResync(deps.since(sessionId, after), after);
|
|
63
|
+
if (replay.kind === 'resync') {
|
|
64
|
+
const currentSequence = deps.since(sessionId, 0).at(-1)?.sequence ?? after;
|
|
65
|
+
connection.send(JSON.stringify({ type: 'relay.resyncRequired', currentSequence }));
|
|
66
|
+
cleanup();
|
|
67
|
+
connection.close();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
replay.events.forEach(send);
|
|
71
|
+
replaying = false;
|
|
72
|
+
liveDuringReplay.sort((left, right) => left.sequence - right.sequence).forEach(send);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
cleanup();
|
|
36
76
|
connection.close();
|
|
37
77
|
return;
|
|
38
78
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const removeHeartbeat = installWebSocketHeartbeat(connection, deps.heartbeatIntervalMs ?? 25_000, () => deps.authorized?.(request.headers.cookie) ?? true);
|
|
42
|
-
connection.on('close', () => {
|
|
43
|
-
removeHeartbeat();
|
|
44
|
-
unsubscribe();
|
|
45
|
-
});
|
|
79
|
+
removeHeartbeat = installWebSocketHeartbeat(connection, deps.heartbeatIntervalMs ?? 25_000, () => deps.authorized?.(request.headers.cookie) ?? true);
|
|
80
|
+
connection.on('close', cleanup);
|
|
46
81
|
});
|
|
47
82
|
});
|
|
48
83
|
app.addHook('onClose', async () => server.close());
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
6
7
|
export function registerStartTurn(app, deps) {
|
|
8
|
+
const inflight = new Map();
|
|
9
|
+
// Durable idempotency results are session-scoped and replay for as long as the
|
|
10
|
+
// store retains them; a reused key with a different prompt is rejected.
|
|
7
11
|
// Text is capped at 100,000 characters; leave room for JSON encoding only.
|
|
8
12
|
app.post('/api/sessions/:id/turns', { bodyLimit: 128 * 1024 }, async (request, reply) => {
|
|
9
13
|
const session = deps.find(request.params.id);
|
|
@@ -14,9 +18,43 @@ export function registerStartTurn(app, deps) {
|
|
|
14
18
|
return reply.code(400).send({ code: 'TURN_INPUT_TOO_LONG' });
|
|
15
19
|
if (!text || session.state !== 'ready')
|
|
16
20
|
return reply.code(409).send({ code: 'SESSION_NOT_READY' });
|
|
21
|
+
const key = request.headers['idempotency-key'];
|
|
22
|
+
if (typeof key === 'string' && key && deps.idempotency) {
|
|
23
|
+
const scope = `start-turn:${session.id}`;
|
|
24
|
+
const fingerprint = createHash('sha256').update(text).digest('hex');
|
|
25
|
+
const cached = deps.idempotency.get(scope, key);
|
|
26
|
+
if (cached)
|
|
27
|
+
return replay(cached, fingerprint, reply);
|
|
28
|
+
const inflightKey = `${scope}:${key}`;
|
|
29
|
+
const operation = inflight.get(inflightKey) ??
|
|
30
|
+
(async () => {
|
|
31
|
+
const started = await deps.start(session, text);
|
|
32
|
+
deps.save(started);
|
|
33
|
+
deps.onStarted?.(started);
|
|
34
|
+
const result = {
|
|
35
|
+
statusCode: 202,
|
|
36
|
+
body: JSON.stringify({ fingerprint, response: started }),
|
|
37
|
+
};
|
|
38
|
+
deps.idempotency.put(scope, key, result.statusCode, result.body);
|
|
39
|
+
return result;
|
|
40
|
+
})();
|
|
41
|
+
inflight.set(inflightKey, operation);
|
|
42
|
+
try {
|
|
43
|
+
return replay(await operation, fingerprint, reply);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
inflight.delete(inflightKey);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
17
49
|
const started = await deps.start(session, text);
|
|
18
50
|
deps.save(started);
|
|
19
51
|
deps.onStarted?.(started);
|
|
20
52
|
return reply.code(202).send(started);
|
|
21
53
|
});
|
|
22
54
|
}
|
|
55
|
+
function replay(result, fingerprint, reply) {
|
|
56
|
+
const cached = JSON.parse(result.body);
|
|
57
|
+
if (cached.fingerprint !== fingerprint)
|
|
58
|
+
return reply.code(409).send({ code: 'IDEMPOTENCY_KEY_REUSED' });
|
|
59
|
+
return reply.code(result.statusCode).send(cached.response);
|
|
60
|
+
}
|
|
@@ -180,6 +180,7 @@ export class CodexSessionRuntime {
|
|
|
180
180
|
const activeTurn = rawTurns.find((turn) => turn.status === 'inProgress' && typeof turn.id === 'string');
|
|
181
181
|
return {
|
|
182
182
|
turns: rawTurns.map((turn) => ({
|
|
183
|
+
...(typeof turn.id === 'string' ? { id: turn.id } : {}),
|
|
183
184
|
items: turn.items ?? [],
|
|
184
185
|
startedAt: typeof turn.startedAt === 'number' ? turn.startedAt : null,
|
|
185
186
|
completedAt: typeof turn.completedAt === 'number' ? turn.completedAt : null,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
-
const schema = `CREATE TABLE IF NOT EXISTS relay_sessions (id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL, workspace_path TEXT NOT NULL, profile TEXT NOT NULL, model TEXT, branch TEXT, thread_id TEXT, state TEXT NOT NULL, desired_state TEXT NOT NULL, active_turn_id TEXT, protocol_version TEXT, failure_count INTEGER NOT NULL DEFAULT 0, effective_skill_selection_json TEXT, last_org_plan_json TEXT, next_sequence INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS pending_interactions (session_id TEXT NOT NULL REFERENCES relay_sessions(id) ON DELETE CASCADE, request_id TEXT NOT NULL, kind TEXT NOT NULL, payload_json TEXT NOT NULL, resolved_at TEXT, PRIMARY KEY (session_id, request_id)); CREATE TABLE IF NOT EXISTS session_events (session_id TEXT NOT NULL REFERENCES relay_sessions(id) ON DELETE CASCADE, sequence INTEGER NOT NULL, occurred_at TEXT NOT NULL, type TEXT NOT NULL, payload_json TEXT NOT NULL, PRIMARY KEY (session_id, sequence)); CREATE TABLE IF NOT EXISTS idempotency_results (scope TEXT NOT NULL, key TEXT NOT NULL, status_code INTEGER NOT NULL, body_json TEXT NOT NULL, PRIMARY KEY (scope, key));`;
|
|
6
|
+
const schema = `CREATE TABLE IF NOT EXISTS relay_sessions (id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL, workspace_path TEXT NOT NULL, profile TEXT NOT NULL, model TEXT, branch TEXT, thread_id TEXT, state TEXT NOT NULL, desired_state TEXT NOT NULL, active_turn_id TEXT, protocol_version TEXT, failure_count INTEGER NOT NULL DEFAULT 0, effective_skill_selection_json TEXT, last_org_plan_json TEXT, next_sequence INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS pending_interactions (session_id TEXT NOT NULL REFERENCES relay_sessions(id) ON DELETE CASCADE, request_id TEXT NOT NULL, kind TEXT NOT NULL, payload_json TEXT NOT NULL, turn_id TEXT, requested_at TEXT, resolved_at TEXT, outcome TEXT, PRIMARY KEY (session_id, request_id)); CREATE TABLE IF NOT EXISTS session_events (session_id TEXT NOT NULL REFERENCES relay_sessions(id) ON DELETE CASCADE, sequence INTEGER NOT NULL, occurred_at TEXT NOT NULL, type TEXT NOT NULL, payload_json TEXT NOT NULL, PRIMARY KEY (session_id, sequence)); CREATE TABLE IF NOT EXISTS idempotency_results (scope TEXT NOT NULL, key TEXT NOT NULL, status_code INTEGER NOT NULL, body_json TEXT NOT NULL, PRIMARY KEY (scope, key));`;
|
|
7
7
|
export function migrate(database) {
|
|
8
8
|
database.exec(schema);
|
|
9
9
|
const columns = database.prepare('PRAGMA table_info(relay_sessions)').all();
|
|
@@ -15,4 +15,13 @@ export function migrate(database) {
|
|
|
15
15
|
database.exec('ALTER TABLE relay_sessions ADD COLUMN model TEXT');
|
|
16
16
|
if (!columns.some((column) => column.name === 'branch'))
|
|
17
17
|
database.exec('ALTER TABLE relay_sessions ADD COLUMN branch TEXT');
|
|
18
|
+
const interactionColumns = database
|
|
19
|
+
.prepare('PRAGMA table_info(pending_interactions)')
|
|
20
|
+
.all();
|
|
21
|
+
if (!interactionColumns.some((column) => column.name === 'turn_id'))
|
|
22
|
+
database.exec('ALTER TABLE pending_interactions ADD COLUMN turn_id TEXT');
|
|
23
|
+
if (!interactionColumns.some((column) => column.name === 'requested_at'))
|
|
24
|
+
database.exec('ALTER TABLE pending_interactions ADD COLUMN requested_at TEXT');
|
|
25
|
+
if (!interactionColumns.some((column) => column.name === 'outcome'))
|
|
26
|
+
database.exec('ALTER TABLE pending_interactions ADD COLUMN outcome TEXT');
|
|
18
27
|
}
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
+
function safeInteractionOutcome(value) {
|
|
7
|
+
return value === 'approved' || value === 'denied' || value === 'answered' ? value : 'answered';
|
|
8
|
+
}
|
|
6
9
|
export class SqlitePendingInteractionStore {
|
|
7
10
|
db;
|
|
8
11
|
constructor(db) {
|
|
@@ -10,24 +13,58 @@ export class SqlitePendingInteractionStore {
|
|
|
10
13
|
}
|
|
11
14
|
add(sessionId, interaction) {
|
|
12
15
|
this.db
|
|
13
|
-
.prepare('INSERT INTO pending_interactions (session_id,request_id,kind,payload_json) VALUES (
|
|
14
|
-
.run(sessionId, interaction.requestId, interaction.kind, JSON.stringify(interaction.payload));
|
|
16
|
+
.prepare('INSERT INTO pending_interactions (session_id,request_id,kind,payload_json,turn_id,requested_at) VALUES (?,?,?,?,?,?)')
|
|
17
|
+
.run(sessionId, interaction.requestId, interaction.kind, JSON.stringify(interaction.payload), interaction.turnId ?? null, interaction.requestedAt ?? null);
|
|
15
18
|
}
|
|
16
|
-
resolve(sessionId, requestId, resolvedAt) {
|
|
19
|
+
resolve(sessionId, requestId, resolvedAt, outcome = 'answered') {
|
|
17
20
|
return (this.db
|
|
18
|
-
.prepare('UPDATE pending_interactions SET resolved_at = ? WHERE session_id = ? AND request_id = ? AND resolved_at IS NULL')
|
|
19
|
-
.run(resolvedAt, sessionId, requestId).changes === 1);
|
|
21
|
+
.prepare('UPDATE pending_interactions SET resolved_at = ?, outcome = ? WHERE session_id = ? AND request_id = ? AND resolved_at IS NULL')
|
|
22
|
+
.run(resolvedAt, outcome, sessionId, requestId).changes === 1);
|
|
20
23
|
}
|
|
21
24
|
list(sessionId) {
|
|
22
25
|
return this.db
|
|
23
|
-
.prepare('SELECT request_id,kind,payload_json FROM pending_interactions WHERE session_id = ? AND resolved_at IS NULL ORDER BY rowid')
|
|
26
|
+
.prepare('SELECT request_id,kind,payload_json,turn_id,requested_at FROM pending_interactions WHERE session_id = ? AND resolved_at IS NULL ORDER BY rowid')
|
|
24
27
|
.all(sessionId).map((row) => ({
|
|
25
28
|
requestId: row.request_id,
|
|
26
29
|
kind: row.kind,
|
|
27
30
|
payload: JSON.parse(row.payload_json),
|
|
31
|
+
...(row.turn_id ? { turnId: row.turn_id } : {}),
|
|
32
|
+
...(row.requested_at ? { requestedAt: row.requested_at } : {}),
|
|
28
33
|
}));
|
|
29
34
|
}
|
|
30
35
|
find(sessionId, requestId) {
|
|
31
36
|
return this.list(sessionId).find((interaction) => interaction.requestId === requestId) ?? null;
|
|
32
37
|
}
|
|
38
|
+
resolved(sessionId, requestId) {
|
|
39
|
+
const row = this.db
|
|
40
|
+
.prepare('SELECT resolved_at,outcome FROM pending_interactions WHERE session_id = ? AND request_id = ? AND resolved_at IS NOT NULL')
|
|
41
|
+
.get(sessionId, requestId);
|
|
42
|
+
return row
|
|
43
|
+
? {
|
|
44
|
+
resolvedAt: row.resolved_at,
|
|
45
|
+
outcome: safeInteractionOutcome(row.outcome),
|
|
46
|
+
}
|
|
47
|
+
: null;
|
|
48
|
+
}
|
|
49
|
+
snapshot(sessionId) {
|
|
50
|
+
return this.db
|
|
51
|
+
.prepare('SELECT request_id,kind,payload_json,turn_id,requested_at,resolved_at,outcome FROM pending_interactions WHERE session_id = ? ORDER BY CASE WHEN resolved_at IS NULL THEN 0 ELSE 1 END, requested_at, rowid')
|
|
52
|
+
.all(sessionId).map((row) => row.resolved_at
|
|
53
|
+
? {
|
|
54
|
+
requestId: row.request_id,
|
|
55
|
+
kind: row.kind,
|
|
56
|
+
turnId: row.turn_id,
|
|
57
|
+
requestedAt: row.requested_at,
|
|
58
|
+
resolvedAt: row.resolved_at,
|
|
59
|
+
outcome: safeInteractionOutcome(row.outcome),
|
|
60
|
+
}
|
|
61
|
+
: {
|
|
62
|
+
requestId: row.request_id,
|
|
63
|
+
kind: row.kind,
|
|
64
|
+
turnId: row.turn_id,
|
|
65
|
+
requestedAt: row.requested_at,
|
|
66
|
+
resolvedAt: null,
|
|
67
|
+
payload: JSON.parse(row.payload_json),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
33
70
|
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.session-path.svelte-t9laac{text-align:center;text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;margin:0;overflow:hidden}.session-model.svelte-t9laac{color:var(--theme-text-muted);font-family:var(--theme-font-code);margin-inline-start:clamp(.75rem,3vw,2rem)}@media (width<=34rem){.session-model.svelte-t9laac{display:none}}#chat-activity.svelte-thyolt{--activity-gap:.4rem;--activity-type-color:var(--theme-text-muted)}#chat-activity.svelte-thyolt>summary:where(.svelte-thyolt){color:var(--activity-type-color);cursor:pointer;font-size:.875em}.activity-list.svelte-thyolt{gap:var(--activity-gap);margin-block:.25rem 0;margin-inline:0;padding:0;list-style:none;display:grid}.activity-row.svelte-thyolt{flex-wrap:wrap;align-items:baseline;gap:.25rem .5rem;display:flex}.activity-row[data-activity-status=failed].svelte-thyolt{border-inline-start:.25rem solid var(--theme-error);padding-inline-start:.375rem}.activity-row[data-activity-status=completed].svelte-thyolt{border-inline-start:.25rem solid var(--theme-success);padding-inline-start:.375rem}.activity-row[data-activity-status].svelte-thyolt:not([data-activity-status=completed]):not([data-activity-status=failed]){border-inline-start:.25rem solid var(--theme-info);padding-inline-start:.375rem}.activity-type.svelte-thyolt{color:var(--activity-type-color);font-size:.75em;line-height:1.2}.activity-content.svelte-thyolt{overflow-wrap:anywhere;white-space:pre-wrap;min-inline-size:0}.quiz-form.svelte-77f3gb{gap:1rem;display:grid}fieldset.svelte-77f3gb{border:0;min-inline-size:0;margin:0;padding:0}legend.svelte-77f3gb{gap:.25rem;max-inline-size:100%;display:grid}.choices.svelte-77f3gb{gap:.5rem;margin-block-start:.5rem;display:grid}.choice.svelte-77f3gb{position:relative}.choice.svelte-77f3gb>input:where(.svelte-77f3gb){opacity:0;cursor:pointer;block-size:100%;inline-size:100%;margin:0;position:absolute;inset:0}.choice.svelte-77f3gb>label:where(.svelte-77f3gb){background:var(--theme-surface);border:2px solid var(--theme-border);border-radius:.5rem;gap:.125rem;min-block-size:44px;padding:.75rem;display:grid}.choice.svelte-77f3gb>input:where(.svelte-77f3gb):checked+label:where(.svelte-77f3gb){border-width:3px;border-color:var(--theme-accent);background:var(--theme-surface-subtle)}.choice.svelte-77f3gb>input:where(.svelte-77f3gb):focus-visible+label:where(.svelte-77f3gb){outline:3px solid var(--theme-focus);outline-offset:2px}label.svelte-77f3gb span:where(.svelte-77f3gb),legend.svelte-77f3gb span:where(.svelte-77f3gb){color:var(--theme-text-muted)}.custom.svelte-77f3gb{margin-block-start:.25rem}.custom.svelte-77f3gb input:where(.svelte-77f3gb){box-sizing:border-box;min-block-size:44px;inline-size:100%;font:inherit;margin-block-start:.375rem}.command-approval-command.svelte-ajsqcq{white-space:pre-wrap;max-inline-size:100%;font-family:var(--theme-font-code);background:var(--theme-code);border:1px solid var(--theme-border);border-radius:.375rem;margin-block:.5rem .75rem;padding:.625rem;overflow-x:auto}.command-approval-missing.svelte-ajsqcq{margin-block:.5rem .75rem}.file-change-approval-targets.svelte-ajsqcq{overflow-wrap:anywhere;margin-block:.5rem .75rem;padding-inline-start:1.5rem}.file-change-approval-missing.svelte-ajsqcq{margin-block:.5rem .75rem}.approval-actions.svelte-ajsqcq{flex-wrap:wrap;gap:clamp(.5rem,2vw,1rem);display:flex}form.svelte-1264sfg>[role=status]:where(.svelte-1264sfg){text-align:start;margin-block:1rem .35rem}.command-menu.svelte-1264sfg{background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:.375rem;gap:.125rem;max-inline-size:min(100%,32rem);margin:0 0 .35rem;padding:.25rem;list-style:none;display:grid}.command-menu.svelte-1264sfg button:where(.svelte-1264sfg){inline-size:100%;color:var(--theme-text);text-align:start;background:0 0;border:0;border-radius:.25rem;align-items:baseline;gap:.5rem;padding:.25rem .375rem;display:flex}.command-menu.svelte-1264sfg .command-selected:where(.svelte-1264sfg) button:where(.svelte-1264sfg),.command-menu.svelte-1264sfg button:where(.svelte-1264sfg):hover{background:var(--theme-control-hover)}.command-menu.svelte-1264sfg small:where(.svelte-1264sfg){color:var(--theme-text-muted);font-size:.8em}.command-menu.svelte-1264sfg button:where(.svelte-1264sfg):focus-visible{outline:2px solid var(--theme-focus);outline-offset:1px}.prompt-row.svelte-1264sfg{align-items:end;gap:.5rem;display:flex}textarea.svelte-1264sfg{resize:vertical;flex:auto;min-block-size:2.75rem}.prompt-row.svelte-1264sfg button:where(.svelte-1264sfg){flex:none;place-items:center;min-block-size:3rem;inline-size:3rem;padding:0;display:grid}.prompt-row.svelte-1264sfg svg:where(.svelte-1264sfg){fill:none;stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;block-size:1.35rem;inline-size:1.35rem}.block-cursor.svelte-1264sfg{vertical-align:-.1em;background:var(--theme-accent);block-size:1em;inline-size:.55ch;animation:1s steps(2,start) infinite svelte-1264sfg-blink;display:inline-block}@media (prefers-reduced-motion:reduce){.block-cursor.svelte-1264sfg{animation:none}}@keyframes svelte-1264sfg-blink{50%{opacity:0}}ol.svelte-1im954t{inline-size:100%;margin:0;padding:0;list-style:none}li.svelte-1im954t{box-sizing:border-box;white-space:pre-wrap;overflow-wrap:anywhere;inline-size:100%;margin-block-end:0}li.svelte-1im954t+li:where(.svelte-1im954t){margin-block-start:1rem}.prompt-turn.svelte-1im954t{background:var(--theme-surface-subtle);border-inline-start:.25rem solid var(--theme-accent);border-radius:.375rem;padding:.5rem .625rem}.answer-turn.svelte-1im954t{border-inline-start:.25rem solid var(--theme-border);padding-inline-start:.625rem}.commentary-turn.svelte-1im954t,.commentary-content.svelte-1im954t{background:var(--theme-surface-subtle);border-inline-start:.25rem solid var(--theme-info);border-radius:.375rem;padding:.5rem .625rem}.entry-heading.svelte-1im954t{align-items:baseline;gap:.75rem;display:flex}time.svelte-1im954t{color:var(--theme-text-muted);white-space:nowrap;margin-inline-start:auto;font-size:.875em}.commentary-toggle.svelte-1im954t{min-block-size:1.5rem;color:var(--theme-text-muted);background:0 0;border:0;padding:0 .125rem;font-size:.875em}.commentary-content.svelte-1im954t{margin-block:.25rem .5rem}.entry-content.svelte-1im954t{margin-block:.125rem 0;margin-inline:0}pre.svelte-1im954t,code.svelte-1im954t{font-family:var(--theme-font-code)}pre.svelte-1im954t{white-space:pre;background:var(--theme-code);border:1px solid var(--theme-border);border-radius:var(--theme-radius);padding:.625rem;overflow-x:auto}.svelte-1im954t:not(pre)>code:where(.svelte-1im954t){background:var(--theme-code);border-radius:.2rem;padding-inline:.2em}a.svelte-1im954t{color:var(--theme-info)}.table-scroll.svelte-1im954t{margin-block:.75rem;overflow-x:auto}table.svelte-1im954t{border-collapse:collapse;white-space:normal;width:max-content;min-width:100%}th.svelte-1im954t,td.svelte-1im954t{text-align:left;vertical-align:top;border:1px solid var(--theme-border);padding:.25rem .5rem}th.svelte-1im954t{background:var(--theme-surface-subtle);font-weight:600}.toast-viewport.svelte-1a0rutm{z-index:10;pointer-events:none;gap:.5rem;max-block-size:calc(100dvh - 7rem);inline-size:min(24rem,100vw - 1.5rem);display:grid;position:fixed;inset-block-start:calc(1rem + env(safe-area-inset-top));inset-inline-end:max(.75rem, env(safe-area-inset-right));overflow-y:auto}.toast.svelte-1a0rutm{box-sizing:border-box;min-inline-size:0;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-info);box-shadow:0 .4rem 1.2rem var(--theme-shadow);pointer-events:auto;border-inline-start-width:.35rem;border-radius:.55rem;grid-template-columns:auto minmax(0,1fr) 44px;align-items:start;gap:.6rem;padding:.65rem;display:grid}.toast.error.svelte-1a0rutm{color:var(--theme-error);border-color:var(--theme-error);border-inline-start-style:double}.toast.success.svelte-1a0rutm{color:var(--theme-success);border-color:var(--theme-success);border-inline-start-style:solid}.toast-symbol.svelte-1a0rutm{box-sizing:border-box;border:2px solid;border-radius:50%;place-items:center;block-size:1.6rem;inline-size:1.6rem;font-weight:800;line-height:1;display:grid}.toast-copy.svelte-1a0rutm{overflow-wrap:anywhere;gap:.15rem;min-inline-size:0;padding-block:.1rem;display:grid}.toast-copy.svelte-1a0rutm strong:where(.svelte-1a0rutm){text-transform:capitalize}.toast-copy.svelte-1a0rutm small:where(.svelte-1a0rutm){opacity:.72}button.svelte-1a0rutm{min-block-size:44px;inline-size:44px;color:var(--theme-text);font:inherit;background:0 0;border:0;border-radius:.35rem;place-items:center;padding:0;font-size:1.5rem;display:grid}button.svelte-1a0rutm:hover{background:var(--theme-control-hover)}@media (width<=24rem){.toast.svelte-1a0rutm{grid-template-columns:minmax(0,1fr) 44px;gap:.25rem;max-block-size:5rem;padding:.35rem;overflow-y:auto}.toast-symbol.svelte-1a0rutm{display:none}.toast-copy.svelte-1a0rutm strong:where(.svelte-1a0rutm){clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;block-size:1px;inline-size:1px;padding:0;position:absolute;overflow:hidden}}@media (forced-colors:active){.toast.svelte-1a0rutm,.toast-symbol.svelte-1a0rutm{border-color:canvastext}}.evidence-page.svelte-1j55xyh{box-sizing:border-box;min-block-size:100dvh;inline-size:100%;padding:1rem 1rem 7rem}h1.svelte-1j55xyh{margin-block:0 .5rem}.evidence-composer.svelte-1j55xyh{position:fixed;inset-block-end:2.75rem;inset-inline:1rem}.evidence-prompt.svelte-1j55xyh{box-sizing:border-box;inline-size:100%;margin-block-start:.35rem;display:block}.evidence-nav.svelte-1j55xyh{padding:.5rem 1rem calc(.5rem + env(safe-area-inset-bottom));background:var(--theme-surface);border-block-start:1px solid var(--theme-border);grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem;display:grid;position:fixed;inset-block-end:0;inset-inline:0}.evidence-nav.svelte-1j55xyh button:where(.svelte-1j55xyh){text-overflow:ellipsis;min-block-size:44px;min-inline-size:0;overflow:hidden}@media (width<=24rem){.evidence-page.svelte-1j55xyh{padding:.5rem .5rem 7rem}.evidence-composer.svelte-1j55xyh{inset-inline:.5rem}}.filesystem-tree.svelte-6z3q4v{inline-size:100%;max-inline-size:100%;display:grid;overflow-x:clip}.tree-row.svelte-6z3q4v{min-inline-size:0;align-items:stretch;padding-inline-start:min(calc(var(--tree-level) * .75rem), 4.5rem);display:flex}.disclosure.svelte-6z3q4v,.disclosure-spacer.svelte-6z3q4v{flex:0 0 44px;min-block-size:44px;inline-size:44px}.disclosure.svelte-6z3q4v{color:inherit;font:inherit;background:0 0;border:0;border-radius:.35rem;place-items:center;padding:0;font-size:1.25rem;font-weight:700;display:grid}.disclosure.svelte-6z3q4v:hover{background:var(--theme-control-hover)}.tree-item.svelte-6z3q4v{min-block-size:44px;min-inline-size:0;color:inherit;font:inherit;text-align:start;background:0 0;border:1px solid #0000;border-radius:.35rem;flex:auto;align-items:center;gap:.5rem;padding:.3rem .5rem;display:flex;overflow:hidden}.tree-item.svelte-6z3q4v:hover{background:var(--theme-control-hover)}.tree-item.selected.svelte-6z3q4v{color:var(--theme-control-pressed-contrast);background:var(--theme-control-pressed);border-color:var(--theme-accent);font-weight:650}.tree-item[aria-disabled=true].svelte-6z3q4v{opacity:.58}.tree-item.repository.svelte-6z3q4v{border-inline-start-width:3px}.selection-mark.svelte-6z3q4v{text-align:center;flex:0 0 1rem;inline-size:1rem}.node-copy.svelte-6z3q4v{flex:auto;min-inline-size:0;display:grid}.node-name.svelte-6z3q4v,.node-path.svelte-6z3q4v{text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;overflow:hidden}.node-path.svelte-6z3q4v{opacity:.72;font-size:.75em;font-weight:400}.repository-badge.svelte-6z3q4v{border:1px solid;border-radius:999px;flex:none;padding:.1rem .35rem;font-size:.72em;font-weight:700;line-height:1.2}@media (width<=24rem){.tree-row.svelte-6z3q4v{padding-inline-start:min(calc(var(--tree-level) * .25rem), 1.5rem)}.tree-item.svelte-6z3q4v{gap:.35rem;padding-inline:.35rem}.repository-badge.svelte-6z3q4v{display:none}}@media (forced-colors:active){.tree-item.selected.svelte-6z3q4v,.tree-item.repository.svelte-6z3q4v,.repository-badge.svelte-6z3q4v{border-color:canvastext}}.evidence-shell.svelte-1d6w4ti{box-sizing:border-box;inline-size:min(100%,48rem);min-inline-size:0;margin-inline:auto;padding:1rem}.eyebrow.svelte-1d6w4ti{letter-spacing:.08em;text-transform:uppercase;margin:0;font-size:.8rem;font-weight:750}h1.svelte-1d6w4ti{margin-block:.25rem .5rem;font-size:clamp(1.35rem,6vw,2rem);line-height:1.15}.instructions.svelte-1d6w4ti{max-inline-size:42rem;margin-block:0 1rem}.tree-panel.svelte-1d6w4ti{border:1px solid var(--theme-border);border-radius:.6rem;min-inline-size:0;padding:.5rem}@media (width<=24rem){.evidence-shell.svelte-1d6w4ti{padding:.5rem}.tree-panel.svelte-1d6w4ti{padding:.25rem}}.git-view.svelte-pudr3t{gap:1.5rem;inline-size:100%;min-inline-size:0;display:grid}.git-tree.svelte-pudr3t,.repository-details.svelte-pudr3t{min-inline-size:0}.section-heading.svelte-pudr3t h3:where(.svelte-pudr3t),.repository-details.svelte-pudr3t h3:where(.svelte-pudr3t){margin-block:0 .35rem}.section-heading.svelte-pudr3t p:where(.svelte-pudr3t){color:var(--theme-text-muted);margin-block:0 .75rem}.clone-form.svelte-pudr3t{gap:.35rem;min-inline-size:0;display:grid}.clone-controls.svelte-pudr3t{grid-template-columns:minmax(10rem,.45fr) minmax(0,1fr) auto;align-items:end;gap:.5rem;display:grid}.clone-field.svelte-pudr3t{gap:.35rem;min-inline-size:0;display:grid}.field-label.svelte-pudr3t{font-weight:600}.clone-destination.svelte-pudr3t output:where(.svelte-pudr3t){overflow-wrap:anywhere;border:1px solid var(--theme-border);border-radius:.35rem;min-block-size:2.75rem;padding:.65rem .75rem}.clone-controls.svelte-pudr3t button:where(.svelte-pudr3t){min-inline-size:5.75rem}.clone-form.svelte-pudr3t p:where(.svelte-pudr3t){color:var(--theme-text-muted);margin:0;font-size:.875rem}.clone-status.svelte-pudr3t{font-weight:600;color:var(--theme-info)!important}.git-overview.svelte-pudr3t{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:1rem;display:grid}.git-status.svelte-pudr3t p:where(.svelte-pudr3t){margin-block:.5rem}.git-actions.svelte-pudr3t{gap:.5rem;display:grid}.git-actions.svelte-pudr3t button:where(.svelte-pudr3t){min-inline-size:6rem}.commit-history.svelte-pudr3t{margin-block-start:2rem}.commit-history.svelte-pudr3t h3:where(.svelte-pudr3t){margin-block-end:.75rem}.commit-list.svelte-pudr3t{gap:.5rem;margin:0;padding:0;list-style:none;display:grid}.commit-entry.svelte-pudr3t{border-block-end:1px solid var(--theme-border);gap:.35rem;min-inline-size:0;padding:.8rem 0;display:grid}.commit-subject.svelte-pudr3t,.commit-meta.svelte-pudr3t{gap:.65rem;min-inline-size:0;display:flex}.commit-subject.svelte-pudr3t{overflow-wrap:anywhere;align-items:baseline;font-weight:600}.commit-subject.svelte-pudr3t code:where(.svelte-pudr3t){color:var(--theme-text-muted);flex:none;font-size:.875em}.commit-meta.svelte-pudr3t{color:var(--theme-text-muted);flex-wrap:wrap;justify-content:space-between;font-size:.875rem}.commit-meta.svelte-pudr3t time:where(.svelte-pudr3t){overflow-wrap:anywhere;min-inline-size:0;max-inline-size:100%}@media (width<=28rem){.clone-controls.svelte-pudr3t{grid-template-columns:1fr}.clone-controls.svelte-pudr3t button:where(.svelte-pudr3t){inline-size:100%}}.plan.svelte-3rmyvg{overflow-wrap:anywhere;min-inline-size:0}header.svelte-3rmyvg{justify-content:space-between;align-items:start;gap:1rem;display:flex}h2.svelte-3rmyvg,p.svelte-3rmyvg{margin-block:.4rem}progress.svelte-3rmyvg{inline-size:100%}ol.svelte-3rmyvg{padding-inline-start:1.25rem}details.svelte-3rmyvg{margin-block:.5rem}summary.svelte-3rmyvg{cursor:pointer;gap:.2rem;display:grid}.measurement.svelte-3rmyvg{color:var(--muted,currentColor);font-variant-numeric:tabular-nums}.metadata.svelte-3rmyvg{gap:.25rem;display:grid}.metadata.svelte-3rmyvg div:where(.svelte-3rmyvg){gap:.5rem;display:flex}.metadata.svelte-3rmyvg dd:where(.svelte-3rmyvg){margin:0}.close.svelte-3rmyvg{flex:none;block-size:2.75rem;inline-size:2.75rem;font-size:1.5rem}button.svelte-3rmyvg:focus-visible,summary.svelte-3rmyvg:focus-visible{outline-offset:2px;outline:3px solid}@media (prefers-reduced-motion:reduce){.svelte-3rmyvg{scroll-behavior:auto}}.plans.svelte-x2ko9l{overflow-wrap:anywhere;min-inline-size:0}h2.svelte-x2ko9l{margin-block:.4rem}ul.svelte-x2ko9l{gap:.5rem;margin:0;padding:0;list-style:none;display:grid}button.svelte-x2ko9l{text-align:start;gap:.2rem;min-block-size:2.75rem;inline-size:100%;padding:.75rem;display:grid}button.svelte-x2ko9l:focus-visible{outline-offset:2px;outline:3px solid}.session-list.svelte-hxb22h{gap:1rem;inline-size:100%;margin-block:0 1.5rem;padding-inline:0;list-style:none;display:grid}.current-session.svelte-hxb22h{outline:2px solid var(--theme-accent);outline-offset:2px}.session-select.svelte-hxb22h{inline-size:100%;color:inherit;font:inherit;text-align:inherit;cursor:pointer;background:0 0;border:0;padding:0;display:block}.managed-session.svelte-hxb22h{box-sizing:border-box;border:1px solid var(--theme-border);border-radius:.5rem;grid-template-columns:max-content minmax(0,1fr);align-items:start;gap:.75rem;inline-size:100%;padding:.75rem;display:grid}.open-session.svelte-hxb22h{border-inline-start:.3rem solid var(--theme-accent);background:var(--theme-control-hover)}.session-details.svelte-hxb22h{min-inline-size:0}.workspace-path.svelte-hxb22h{overflow-wrap:anywhere}.profile-badge.svelte-hxb22h{background:var(--theme-control-pressed);color:var(--theme-control-pressed-contrast);overflow-wrap:anywhere;border-radius:999px;margin-block-start:.35rem;padding:.15rem .4rem;font-size:.875rem;display:inline-block}.org-plan-metadata.svelte-hxb22h{gap:.125rem;margin-block-start:.35rem;display:grid}.org-plan-title.svelte-hxb22h{font-weight:600}.org-plan-filename.svelte-hxb22h{overflow-wrap:anywhere;color:var(--theme-text-muted)}.session-actions.svelte-hxb22h{flex-wrap:wrap;gap:.5rem;display:flex}@media (width<=30rem){.managed-session.svelte-hxb22h{grid-template-columns:minmax(0,1fr)}}.recent-session.svelte-hxb22h{grid-template-columns:minmax(0,1fr) max-content;align-items:start;gap:.75rem;display:grid}.session-base.svelte-hxb22h{gap:.65rem;min-inline-size:0;margin-block:1rem;display:grid}.session-base-heading.svelte-hxb22h h3:where(.svelte-hxb22h),.session-base-heading.svelte-hxb22h p:where(.svelte-hxb22h){margin:0}.session-base-heading.svelte-hxb22h p:where(.svelte-hxb22h){margin-block-start:.25rem}.tree-panel.svelte-hxb22h{box-sizing:border-box;overscroll-behavior:contain;scrollbar-gutter:stable;border:1px solid var(--theme-border);border-radius:.6rem;max-block-size:min(22rem,48vh);min-inline-size:0;padding:.35rem;overflow-y:auto}.session-settings.svelte-hxb22h{gap:.5rem;min-inline-size:0;margin-block-end:.75rem;display:grid}.session-setting-labels.svelte-hxb22h,.session-setting-controls.svelte-hxb22h{grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem;min-inline-size:0;display:grid}.session-setting-labels.svelte-hxb22h label:where(.svelte-hxb22h){overflow-wrap:anywhere;min-inline-size:0}.session-setting-controls.svelte-hxb22h select:where(.svelte-hxb22h){text-overflow:ellipsis;inline-size:100%;min-inline-size:0;max-inline-size:100%;overflow:hidden}.session-secondary-actions.svelte-hxb22h,.model-control.svelte-hxb22h{align-items:center;gap:.5rem;min-inline-size:0;display:flex}.session-secondary-actions.svelte-hxb22h{flex-wrap:wrap}.model-control.svelte-hxb22h{margin-inline-start:auto}.skills-profile-error.svelte-hxb22h{color:var(--theme-error);margin:0;font-size:.875rem}.new-session-button.svelte-hxb22h{inline-size:auto;color:var(--theme-accent-contrast);background:var(--theme-accent);border-color:var(--theme-accent);box-shadow:inset 0 .15rem 0 var(--theme-accent-contrast);font-weight:700}@media (width<=28rem){.tree-panel.svelte-hxb22h{padding:.2rem}.model-control.svelte-hxb22h{margin-inline-start:0}}.skills-view.svelte-dg6sjj{min-inline-size:0;padding:1rem max(1rem, env(safe-area-inset-right)) calc(5rem + env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left))}.intro.svelte-dg6sjj,.save-intent.svelte-dg6sjj,.summary.svelte-dg6sjj{max-inline-size:70ch}form.svelte-dg6sjj,.field-grid.svelte-dg6sjj{gap:.65rem;display:grid}.field-grid.svelte-dg6sjj{grid-template-columns:minmax(0,1fr)}label.svelte-dg6sjj{font-weight:650}select.svelte-dg6sjj,input.svelte-dg6sjj,button.svelte-dg6sjj{box-sizing:border-box;min-block-size:3rem;inline-size:100%;font:inherit}dialog.svelte-dg6sjj{max-inline-size:min(100% - 2rem,32rem)}.dialog-actions.svelte-dg6sjj{gap:.65rem;display:grid}input.svelte-dg6sjj,select.svelte-dg6sjj{padding-inline:.7rem;font-size:1rem}button.svelte-dg6sjj{padding-inline:1rem}.profile-actions.svelte-dg6sjj{grid-template-columns:repeat(2,minmax(0,1fr));gap:.65rem;display:grid}.skills-toolbar.svelte-dg6sjj{flex-wrap:wrap;align-items:center;gap:.65rem;margin-block:1rem;display:flex}.refresh-skills.svelte-dg6sjj{flex:none;min-block-size:2.25rem;inline-size:auto;padding-inline:.65rem}.refresh-skills.svelte-dg6sjj span:where(.svelte-dg6sjj):first-child{font-size:1.2em;line-height:1}.summary.svelte-dg6sjj{overflow-wrap:anywhere;min-inline-size:0;margin:0}.notice.svelte-dg6sjj{border-inline-start:.3rem solid var(--theme-warning);color:var(--theme-warning);padding-inline-start:.7rem}.error.svelte-dg6sjj{border-inline-start:.3rem solid var(--theme-error);color:var(--theme-error);padding-inline-start:.7rem}.skill-list.svelte-dg6sjj{gap:.75rem;padding:0;list-style:none;display:grid}.skill-card.svelte-dg6sjj{border:1px solid var(--theme-border);border-radius:.5rem;min-inline-size:0;padding:.75rem;scroll-margin-block:1rem}.skill-toggle.svelte-dg6sjj{flex-wrap:wrap;align-items:center;gap:.65rem;min-block-size:3rem;display:flex}.skill-toggle.svelte-dg6sjj>span:where(.svelte-dg6sjj){overflow-wrap:anywhere;min-inline-size:0}.skill-toggle.svelte-dg6sjj input:where(.svelte-dg6sjj){flex:none;min-block-size:1.25rem;inline-size:1.25rem}.state.svelte-dg6sjj{margin-inline-start:auto;font-weight:500}.skill-details.svelte-dg6sjj{overflow-wrap:anywhere;padding-inline-start:1.9rem}.skill-details.svelte-dg6sjj>:where(.svelte-dg6sjj):first-child{margin-block-start:.25rem}.skill-details.svelte-dg6sjj>:where(.svelte-dg6sjj):last-child{margin-block-end:0}dl.svelte-dg6sjj{grid-template-columns:minmax(7rem,auto) minmax(0,1fr);gap:.45rem .75rem;display:grid}dt.svelte-dg6sjj{font-weight:650}dd.svelte-dg6sjj{min-inline-size:0;margin:0}dd.svelte-dg6sjj ul:where(.svelte-dg6sjj){margin:0;padding-inline-start:1.25rem}.path.svelte-dg6sjj{overflow-wrap:anywhere}.svelte-dg6sjj:where(button:where(.svelte-dg6sjj),input:where(.svelte-dg6sjj),select:where(.svelte-dg6sjj)):focus-visible{outline:3px solid var(--theme-focus);outline-offset:2px}@media (width>=42rem){.field-grid.svelte-dg6sjj{grid-template-columns:minmax(10rem,1fr) minmax(0,2fr);align-items:center}}@media (width<=30rem){dl.svelte-dg6sjj{grid-template-columns:1fr;gap:.2rem}}.devices.svelte-1nygetn{max-inline-size:48rem;color:var(--theme-text);margin:auto;padding:1rem}.devices.svelte-1nygetn header:where(.svelte-1nygetn){align-items:center;gap:1rem;display:flex}.devices.svelte-1nygetn h1:where(.svelte-1nygetn),.devices.svelte-1nygetn h2:where(.svelte-1nygetn){font-family:var(--theme-font-display)}.devices.svelte-1nygetn ul:where(.svelte-1nygetn){padding:0;list-style:none}.devices.svelte-1nygetn li:where(.svelte-1nygetn),section.svelte-1nygetn{background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .25rem .75rem var(--theme-shadow);margin-block:1rem;padding:1rem}.devices.svelte-1nygetn form:where(.svelte-1nygetn){gap:.65rem;display:grid}.devices.svelte-1nygetn p:where(.svelte-1nygetn){overflow-wrap:anywhere;margin:0}.devices.svelte-1nygetn input:where(.svelte-1nygetn),.devices.svelte-1nygetn textarea:where(.svelte-1nygetn),.devices.svelte-1nygetn button:where(.svelte-1nygetn){min-block-size:44px;font:inherit}.devices.svelte-1nygetn input:where(.svelte-1nygetn),.devices.svelte-1nygetn textarea:where(.svelte-1nygetn){min-inline-size:0;padding:.45rem}.devices.svelte-1nygetn textarea:where(.svelte-1nygetn){box-sizing:border-box;overflow-wrap:anywhere;resize:vertical;inline-size:100%;font-family:var(--theme-font-code)}.devices.svelte-1nygetn .actions:where(.svelte-1nygetn){flex-wrap:wrap;gap:.5rem;display:flex}.devices.svelte-1nygetn .actions:where(.svelte-1nygetn) button:where(.svelte-1nygetn){flex:11rem}.devices.svelte-1nygetn img:where(.svelte-1nygetn){background:var(--theme-canvas);inline-size:12rem;max-inline-size:100%;margin-block:.75rem;padding:.5rem;display:block}.feedback.svelte-1nygetn{color:var(--theme-error);font-weight:700}dialog.svelte-1nygetn{max-inline-size:min(32rem,100% - 2rem);color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .5rem 1.5rem var(--theme-shadow)}dialog.svelte-1nygetn h2:where(.svelte-1nygetn){font-family:var(--theme-font-display)}dialog.svelte-1nygetn form:where(.svelte-1nygetn){flex-wrap:wrap;gap:.5rem;display:flex}@media (forced-colors:active){.devices.svelte-1nygetn li:where(.svelte-1nygetn),section.svelte-1nygetn,dialog.svelte-1nygetn{border-color:canvastext}}@media (prefers-reduced-motion:reduce){.svelte-1nygetn{transition-duration:.01ms!important;animation-duration:.01ms!important}}dialog.svelte-eu5bnq{box-sizing:border-box;max-block-size:calc(100dvh - 1rem);inline-size:min(52rem,100vw - 1rem);max-inline-size:none;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .75rem 2rem var(--theme-shadow);padding:0}dialog.svelte-eu5bnq::backdrop{background:color-mix(in srgb, var(--theme-canvas) 70%, transparent)}.scratchpad-shell.svelte-eu5bnq{box-sizing:border-box;min-block-size:min(42rem,100dvh - 1rem);max-block-size:calc(100dvh - 1rem);padding:max(1rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(1rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));grid-template-rows:auto auto minmax(12rem,1fr) auto;gap:.75rem;display:grid}header.svelte-eu5bnq{justify-content:space-between;align-items:flex-start;gap:1rem;display:flex}h2.svelte-eu5bnq,p.svelte-eu5bnq{margin:0}h2.svelte-eu5bnq{font-family:var(--theme-font-display)}header.svelte-eu5bnq p:where(.svelte-eu5bnq){color:var(--theme-text-muted)}label.svelte-eu5bnq{font-weight:700}textarea.svelte-eu5bnq{box-sizing:border-box;resize:none;min-block-size:12rem;inline-size:100%;min-inline-size:0;color:var(--theme-text);background:var(--theme-canvas);border:1px solid var(--theme-border);border-radius:calc(var(--theme-radius) / 2);font:1rem/1.45 var(--theme-font-code);tab-size:2;padding:.75rem}button.svelte-eu5bnq{min-block-size:44px;font:inherit}.close.svelte-eu5bnq{inline-size:44px;color:inherit;background:0 0;border:0;flex:none;padding:0;font-size:2rem;line-height:1}.actions.svelte-eu5bnq{flex-wrap:wrap;gap:.5rem;display:flex}.actions.svelte-eu5bnq button:where(.svelte-eu5bnq){flex:7rem}.danger.svelte-eu5bnq{color:var(--theme-error)}.clear-confirmation.svelte-eu5bnq{border:1px solid var(--theme-error);border-radius:calc(var(--theme-radius) / 2);gap:.5rem;padding:.75rem;display:grid}.feedback.svelte-eu5bnq{color:var(--theme-error);font-weight:700}@media (width<=32rem){dialog.svelte-eu5bnq{border-inline:0;border-radius:0;max-block-size:100dvh;inline-size:100vw}.scratchpad-shell.svelte-eu5bnq{min-block-size:100dvh;max-block-size:100dvh}}@media (forced-colors:active){dialog.svelte-eu5bnq,textarea.svelte-eu5bnq,.clear-confirmation.svelte-eu5bnq{border-color:canvastext}}.chat-view.svelte-1d2e8zz{margin-inline:calc(.25rem - var(--page-inline-padding))}.swipe-surface.svelte-1d2e8zz{touch-action:pan-y}.evidence-mode.svelte-1d2e8zz{box-sizing:border-box;inline-size:100%;min-inline-size:0;padding:0}.session-profile-manager-header.svelte-1d2e8zz{box-sizing:border-box;inline-size:100%;min-inline-size:0;padding-inline:max(1rem, env(safe-area-inset-left)) max(1rem, env(safe-area-inset-right));justify-content:space-between;align-items:safe center;gap:1rem;display:flex}.session-profile-manager-header.svelte-1d2e8zz h2:where(.svelte-1d2e8zz){overflow-wrap:anywhere;min-inline-size:0;margin:0}.close-profile-manager.svelte-1d2e8zz{min-block-size:2.5rem;inline-size:2.5rem;color:inherit;background:0 0;border:0;flex:none;padding:0;font-size:2rem;line-height:1}.enrollment.svelte-1c5qcm6{box-sizing:border-box;inline-size:100%;max-inline-size:none;background:var(--theme-page);min-height:100dvh;color:var(--theme-text);place-items:center;margin:0;padding:1rem;display:grid}.card.svelte-1c5qcm6{box-sizing:border-box;background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:calc(var(--theme-radius) * 2);width:min(100%,42rem);box-shadow:0 .5rem 1.5rem var(--theme-shadow);padding:clamp(1.25rem,5vw,3rem)}.brand.svelte-1c5qcm6{color:var(--theme-info);font-family:var(--theme-font-display);letter-spacing:.08em;text-transform:uppercase;font-weight:700}h1.svelte-1c5qcm6{font-family:var(--theme-font-display);margin-block:.25rem 1rem;font-size:clamp(1.8rem,7vw,3rem)}.warning.svelte-1c5qcm6{border-left:.3rem solid var(--theme-warning);background:var(--theme-surface-subtle);padding:1rem}label.svelte-1c5qcm6,input.svelte-1c5qcm6{box-sizing:border-box;width:100%;display:block}label.svelte-1c5qcm6{margin-top:1.5rem;font-weight:700}input.svelte-1c5qcm6,button.svelte-1c5qcm6{min-height:44px;font:inherit}input.svelte-1c5qcm6{margin-top:.4rem;padding:.6rem}button.svelte-1c5qcm6{cursor:pointer;border-radius:.4rem;margin-top:1rem;padding:.55rem 1rem}.authorize.svelte-1c5qcm6{background:var(--theme-accent);width:100%;color:var(--theme-accent-contrast);border-color:var(--theme-accent);font-weight:700}.feedback.svelte-1c5qcm6{color:var(--theme-info);font-weight:700}.error.svelte-1c5qcm6{color:var(--theme-error);font-weight:700}.handoff.svelte-1c5qcm6{border-top:1px solid var(--theme-border);grid-template-columns:minmax(0,1fr);gap:1rem;margin-top:2rem;padding-top:1.5rem;display:grid}.handoff.svelte-1c5qcm6 img:where(.svelte-1c5qcm6){background:var(--theme-canvas);width:12rem;max-width:100%;padding:.5rem}a.svelte-1c5qcm6{color:var(--theme-info);overflow-wrap:anywhere}.copy.svelte-1c5qcm6{display:block}@media (width>=48rem){.handoff.svelte-1c5qcm6{grid-template-columns:12rem minmax(0,1fr);align-items:start}}@media (prefers-reduced-motion:reduce){.svelte-1c5qcm6,.svelte-1c5qcm6:before,.svelte-1c5qcm6:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}.login.svelte-1byekj9{box-sizing:border-box;inline-size:100%;max-inline-size:none;background:var(--theme-page);min-height:100dvh;color:var(--theme-text);place-items:center;margin:0;padding:1rem;display:grid}.card.svelte-1byekj9{box-sizing:border-box;background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:calc(var(--theme-radius) * 2);width:min(100%,32rem);box-shadow:0 .5rem 1.5rem var(--theme-shadow);padding:clamp(1.25rem,5vw,3rem)}.brand.svelte-1byekj9{color:var(--theme-info);font-family:var(--theme-font-display);letter-spacing:.08em;text-transform:uppercase;font-weight:700}h1.svelte-1byekj9{font-family:var(--theme-font-display);margin-block:.25rem 1rem;font-size:clamp(1.8rem,7vw,3rem)}.sign-in.svelte-1byekj9{border-radius:var(--theme-radius);background:var(--theme-accent);width:100%;min-height:44px;color:var(--theme-accent-contrast);border-color:var(--theme-accent);font:inherit;margin-top:1rem;padding:.55rem 1rem;font-weight:700}.error.svelte-1byekj9{color:var(--theme-error);font-weight:700}@media (prefers-reduced-motion:reduce){.svelte-1byekj9,.svelte-1byekj9:before,.svelte-1byekj9:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}.auth-gate.svelte-y74n0s{max-width:32rem;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .5rem 1.5rem var(--theme-shadow);margin:12vh auto;padding:2rem}.auth-gate.svelte-y74n0s h1:where(.svelte-y74n0s){font-family:var(--theme-font-display)}button.svelte-y74n0s{margin-right:.75rem}@font-face{font-family:Syne;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/syne-greek-600-normal-BpFII5Ja.woff2)format("woff2"),url(/assets/syne-greek-600-normal-BJs4kvyH.woff)format("woff");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Syne;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/syne-latin-ext-600-normal-ZnizrDKU.woff2)format("woff2"),url(/assets/syne-latin-ext-600-normal-DEzBMW8B.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,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:Syne;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/syne-latin-600-normal-BiwQbQXw.woff2)format("woff2"),url(/assets/syne-latin-600-normal-dRu9QuIh.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-cyrillic-ext-400-normal-C_uLvvQ5.woff2)format("woff2"),url(/assets/jetbrains-mono-cyrillic-ext-400-normal-Bh0R7Dhr.woff)format("woff");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2)format("woff2"),url(/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff)format("woff");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2)format("woff2"),url(/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff)format("woff");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-vietnamese-400-normal-ByoDsISC.woff2)format("woff2"),url(/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff)format("woff");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,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:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--theme-font-body:system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--theme-font-display:"Syne", var(--theme-font-body);--theme-font-code:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--theme-motion-fast:.12s;--theme-radius:.5rem}:root[data-theme=minimal-light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--theme-page:#fff;--theme-canvas:#fff;--theme-surface:#fff;--theme-surface-subtle:#f3f4f6;--theme-text:#171717;--theme-text-muted:#525252;--theme-border:#525252;--theme-focus:#005fcc;--theme-accent:#171717;--theme-accent-contrast:#fff;--theme-control-hover:#e5e7eb;--theme-control-pressed:#262626;--theme-control-pressed-contrast:#fff;--theme-control-disabled:#737373;--theme-success:#166534;--theme-warning:#854d0e;--theme-error:#b91c1c;--theme-info:#1d4ed8;--theme-code:#f3f4f6;--theme-shadow:#17171740}:root[data-theme=minimal-dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--theme-page:#171717;--theme-canvas:#171717;--theme-surface:#262626;--theme-surface-subtle:#404040;--theme-text:#fafafa;--theme-text-muted:#d4d4d4;--theme-border:#a3a3a3;--theme-focus:#93c5fd;--theme-accent:#fafafa;--theme-accent-contrast:#171717;--theme-control-hover:#404040;--theme-control-pressed:#fafafa;--theme-control-pressed-contrast:#171717;--theme-control-disabled:#a3a3a3;--theme-success:#86efac;--theme-warning:#fcd34d;--theme-error:#fca5a5;--theme-info:#93c5fd;--theme-code:#262626;--theme-shadow:#00000080}:root[data-theme=dyne-org]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--theme-page:#f4f0e8;--theme-canvas:#fffdf8;--theme-surface:#fffaf1;--theme-surface-subtle:#eee6d8;--theme-text:#171411;--theme-text-muted:#5f564c;--theme-border:#75695b;--theme-focus:#5e2ca5;--theme-accent:#c7460c;--theme-accent-contrast:#fffdf8;--theme-control-hover:#f4ddcf;--theme-control-pressed:#bd3d0d;--theme-control-pressed-contrast:#fffdf8;--theme-control-disabled:#93887b;--theme-success:#287a50;--theme-warning:#9a5a00;--theme-error:#b42318;--theme-info:#5e2ca5;--theme-code:#f1e9db;--theme-shadow:#17141138}:root{--bottom-navigation-clearance:calc(4rem + env(safe-area-inset-bottom));--sticky-header-clearance:calc(5rem + env(safe-area-inset-top));--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;font-family:var(--theme-font-body)}body{min-block-size:100dvh;color:var(--theme-text);background:var(--theme-page);accent-color:var(--theme-accent);margin:0}.visually-hidden{clip-path:inset(50%)!important;white-space:nowrap!important;border:0!important;block-size:1px!important;inline-size:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}main{--page-inline-padding:1rem;box-sizing:border-box;min-block-size:100dvh;max-inline-size:42rem;padding:calc(1rem + env(safe-area-inset-top)) var(--page-inline-padding) calc(var(--bottom-navigation-clearance) + 1rem);margin-inline:auto}@media (width<=24rem){main{--page-inline-padding:.5rem}}.app-header{z-index:1;margin:calc(-1rem - env(safe-area-inset-top)) calc(.25rem - var(--page-inline-padding)) 1.5rem;padding:calc(1rem + env(safe-area-inset-top)) calc(var(--page-inline-padding) - .25rem) .75rem;background:color-mix(in srgb, var(--theme-page) 78%, transparent);-webkit-backdrop-filter:blur(.75rem);backdrop-filter:blur(.75rem);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:1rem;display:flex;position:sticky;inset-block-start:0}.app-header [role=status]{overflow-wrap:anywhere;text-align:end;min-inline-size:0;margin:0}.brand{align-items:center;gap:.4rem;display:flex}.header-actions{flex:none;align-items:center;gap:.25rem;margin-inline-start:auto;display:flex}.weekly-quota{font-variant-numeric:tabular-nums;white-space:nowrap;font-size:.875rem}.menu-trigger{min-block-size:44px;inline-size:44px;color:inherit;background:0 0;border:0;flex:none;place-items:center;padding:0;display:grid}.menu-lines{gap:.25rem;inline-size:1.25rem;display:grid}.menu-lines span{box-sizing:border-box;background:currentColor;block-size:1px;display:block}.menu-lines span:first-child,.menu-lines span:nth-child(2),.menu-lines span:last-child{block-size:2px}@media (width<=28rem){.app-header [role=status]{flex-basis:100%;margin-inline-start:0}}.brand-icon{block-size:2rem;inline-size:2rem}.brand-logotype{block-size:auto;inline-size:6.25rem}.dark-asset,:root[data-logo-tone=light] .light-asset{display:none}:root[data-logo-tone=light] .dark-asset{display:initial}.configuration-panel{box-sizing:border-box;overscroll-behavior:contain;max-block-size:calc(100dvh - 5.5rem);min-inline-size:min(13rem,100vw - 2rem);max-inline-size:calc(100vw - 2rem);color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .5rem 1.5rem var(--theme-shadow);padding:.75rem;position:fixed;inset:4.5rem 1rem auto auto;overflow-y:auto}.configuration-logo{block-size:auto;inline-size:4.5rem;margin-block-end:.5rem;margin-inline-start:auto;display:block}.appearance-control{align-items:center;gap:.5rem;min-inline-size:0;display:flex}.appearance-control span{font-family:var(--theme-font-display);letter-spacing:-.08em;font-weight:700}.appearance-control select{field-sizing:content;flex:auto;min-block-size:2.25rem;inline-size:auto;min-inline-size:0;max-inline-size:100%;padding-inline:.5rem}.bottom-navigation{padding:.5rem calc(.25rem + env(safe-area-inset-right)) calc(.5rem + env(safe-area-inset-bottom)) calc(.25rem + env(safe-area-inset-left));background:var(--theme-surface);border-block-start:1px solid var(--theme-border);grid-template-columns:repeat(4,minmax(0,1fr));gap:.25rem;display:grid;position:fixed;inset-block-end:0;inset-inline:0}.bottom-navigation button{overflow-wrap:anywhere;word-break:break-word;min-inline-size:0;max-inline-size:100%;transition:color var(--theme-motion-fast) ease, background-color var(--theme-motion-fast) ease, box-shadow var(--theme-motion-fast) ease;padding-inline:0}.bottom-navigation button:hover:not(:disabled){background:var(--theme-control-hover)}.bottom-navigation button:active:not(:disabled){background:var(--theme-control-pressed);color:var(--theme-control-pressed-contrast)}.bottom-navigation button[aria-pressed=true]{color:var(--theme-accent-contrast);background:var(--theme-accent);box-shadow:inset 0 .2rem 0 color-mix(in srgb, var(--theme-accent-contrast) 25%, transparent);font-weight:700}.bottom-navigation button:disabled{color:var(--theme-control-disabled);cursor:not-allowed;opacity:.65}@media (width<=30rem){:root{--bottom-navigation-clearance:calc(10rem + env(safe-area-inset-bottom));--sticky-header-clearance:calc(14rem + env(safe-area-inset-top))}.bottom-navigation{grid-template-columns:repeat(2,minmax(0,1fr))}.bottom-navigation button{text-align:center;padding-inline:.25rem}}button{min-block-size:44px;font:inherit;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);cursor:pointer}button:hover:not(:disabled){background:var(--theme-control-hover)}button:active:not(:disabled){background:var(--theme-control-pressed);color:var(--theme-control-pressed-contrast)}button:disabled{color:var(--theme-control-disabled);cursor:not-allowed}.git-view button{scroll-margin-block:var(--sticky-header-clearance) var(--bottom-navigation-clearance)}input,select,textarea{box-sizing:border-box;min-block-size:44px;inline-size:100%;font:inherit;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);font-size:max(1rem,16px)}input::placeholder,textarea::placeholder{color:var(--theme-text-muted)}:focus-visible{outline:3px solid var(--theme-focus);outline-offset:3px}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}@media (forced-colors:active){button,input,select,textarea{border:1px solid canvastext}}
|