@luckydraw/cumulus 0.31.65 → 1.0.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/CHANGELOG.md +6 -555
- package/LICENSE +150 -0
- package/README.md +161 -17
- package/dist/gateway/adapters/webchat.d.ts +2 -0
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +22 -2
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/config.d.ts +17 -2
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +10 -3
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/daemon.d.ts +3 -1
- package/dist/gateway/daemon.d.ts.map +1 -1
- package/dist/gateway/daemon.js +128 -39
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/namespaces.d.ts +34 -0
- package/dist/gateway/namespaces.d.ts.map +1 -1
- package/dist/gateway/namespaces.js +58 -0
- package/dist/gateway/namespaces.js.map +1 -1
- package/dist/gateway/server.d.ts +8 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +150 -41
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/setup.d.ts +32 -0
- package/dist/gateway/setup.d.ts.map +1 -1
- package/dist/gateway/setup.js +23 -3
- package/dist/gateway/setup.js.map +1 -1
- package/dist/gateway/static/widget.js +897 -611
- package/dist/lib/gateway.d.ts +30 -8
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +36 -11
- package/dist/lib/gateway.js.map +1 -1
- package/dist/lib/history.d.ts +22 -0
- package/dist/lib/history.d.ts.map +1 -1
- package/dist/lib/history.js +59 -21
- package/dist/lib/history.js.map +1 -1
- package/dist/lib/huggingface-provider.d.ts.map +1 -1
- package/dist/lib/huggingface-provider.js +11 -3
- package/dist/lib/huggingface-provider.js.map +1 -1
- package/dist/lib/license.d.ts +76 -0
- package/dist/lib/license.d.ts.map +1 -0
- package/dist/lib/license.js +141 -0
- package/dist/lib/license.js.map +1 -0
- package/docs/agentic-harness-primer.md +283 -0
- package/docs/conditional-continuation.md +167 -0
- package/docs/web-app-agent-guide.md +520 -0
- package/examples/web-app-agent/README.md +187 -0
- package/examples/web-app-agent/agent/mcp-shim.js +105 -0
- package/examples/web-app-agent/gateway.config.example.json +52 -0
- package/examples/web-app-agent/package.json +13 -0
- package/examples/web-app-agent/public/agent/bridge-mount.js +75 -0
- package/examples/web-app-agent/public/agent/chat-client.js +104 -0
- package/examples/web-app-agent/public/agent/commands.js +250 -0
- package/examples/web-app-agent/public/agent/device-thread.js +48 -0
- package/examples/web-app-agent/public/agent/panel.css +107 -0
- package/examples/web-app-agent/public/agent/panel.js +369 -0
- package/examples/web-app-agent/public/app.js +250 -0
- package/examples/web-app-agent/public/index.html +111 -0
- package/examples/web-app-agent/server.js +242 -0
- package/package.json +7 -3
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/* The command registry — your app's capability surface.
|
|
2
|
+
|
|
3
|
+
This is the file you actually write for your own app. Everything else in
|
|
4
|
+
this directory is plumbing you copy once and forget.
|
|
5
|
+
|
|
6
|
+
The registry is the ONLY thing the model can do to your app. Whatever isn't
|
|
7
|
+
registered here doesn't exist, no matter how the visitor phrases it. And
|
|
8
|
+
commands act through window.HostApp — the app's own adapter over its own
|
|
9
|
+
actions — never by poking the DOM, so persistence, validation, and re-render
|
|
10
|
+
keep working no matter who is driving.
|
|
11
|
+
|
|
12
|
+
Frozen contract:
|
|
13
|
+
command { name, description, params, risk, execute(params) }
|
|
14
|
+
risk "read" — answers a question, changes nothing
|
|
15
|
+
"display" — changes what's on screen only, trivially undoable
|
|
16
|
+
"mutate" — changes stored data
|
|
17
|
+
"export" — irreversible or leaves the app. ALWAYS routed
|
|
18
|
+
through the confirm chip by the gateway; the model
|
|
19
|
+
cannot bypass it, and neither can this file.
|
|
20
|
+
execute -> { summary, data?, affected? } (may be async)
|
|
21
|
+
call() never throws — failures become { ok: false, summary }
|
|
22
|
+
|
|
23
|
+
Write descriptions for a reader who cannot see your UI. The description is
|
|
24
|
+
the entire basis on which the model decides to call the thing. */
|
|
25
|
+
(function () {
|
|
26
|
+
'use strict';
|
|
27
|
+
var defs = new Map();
|
|
28
|
+
|
|
29
|
+
function register(def) {
|
|
30
|
+
defs.set(def.name, def);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function call(name, params) {
|
|
34
|
+
var def = defs.get(name);
|
|
35
|
+
if (!def) return { ok: false, summary: 'Unknown command: ' + name };
|
|
36
|
+
if (!window.HostApp) return { ok: false, summary: 'App not ready' };
|
|
37
|
+
try {
|
|
38
|
+
var r = await def.execute(params || {});
|
|
39
|
+
return Object.assign({ ok: true }, r);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return { ok: false, summary: name + ' failed: ' + ((e && e.message) || e) };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Synchronous variant, for hooks that need a value immediately — the
|
|
46
|
+
bridge's describeView is called during send() and cannot await. Only valid
|
|
47
|
+
for commands whose execute() is synchronous. */
|
|
48
|
+
function callNow(name, params) {
|
|
49
|
+
var def = defs.get(name);
|
|
50
|
+
if (!def || !window.HostApp) return { ok: false, summary: 'Unavailable: ' + name };
|
|
51
|
+
try {
|
|
52
|
+
var r = def.execute(params || {});
|
|
53
|
+
if (r && typeof r.then === 'function')
|
|
54
|
+
return { ok: false, summary: name + ' is async — use call()' };
|
|
55
|
+
return Object.assign({ ok: true }, r);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return { ok: false, summary: name + ' failed: ' + ((e && e.message) || e) };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
window.AgentRegistry = {
|
|
62
|
+
register: register,
|
|
63
|
+
call: call,
|
|
64
|
+
callNow: callNow,
|
|
65
|
+
list: function () {
|
|
66
|
+
return Array.from(defs.keys());
|
|
67
|
+
},
|
|
68
|
+
manifest: function () {
|
|
69
|
+
return Array.from(defs.values()).map(function (d) {
|
|
70
|
+
return { name: d.name, description: d.description, risk: d.risk, input_schema: d.params };
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
var NO_PARAMS = { type: 'object', properties: {} };
|
|
76
|
+
function app() {
|
|
77
|
+
return window.HostApp;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ---- the agent's eyes -------------------------------------------------- */
|
|
81
|
+
|
|
82
|
+
register({
|
|
83
|
+
name: 'app.describe',
|
|
84
|
+
risk: 'read',
|
|
85
|
+
description:
|
|
86
|
+
'Static knowledge about this app: what it is, what a note is, and what you can do here. ' +
|
|
87
|
+
'Call once per conversation for grounding. For what is on screen right now, use app.describeView.',
|
|
88
|
+
params: NO_PARAMS,
|
|
89
|
+
execute: function () {
|
|
90
|
+
return {
|
|
91
|
+
summary: 'Demo Notes — a single-list note taker.',
|
|
92
|
+
data: {
|
|
93
|
+
app: 'Demo Notes',
|
|
94
|
+
concepts: {
|
|
95
|
+
note: 'A short piece of text with a "done" flag and a created timestamp. Notes have integer ids.',
|
|
96
|
+
filter:
|
|
97
|
+
'A text box above the list. When non-empty, only notes containing that text are shown. It is a view setting, not a deletion.',
|
|
98
|
+
},
|
|
99
|
+
recipes: [
|
|
100
|
+
'To find something: call notes.list and read it. Do NOT set the filter just to answer a question — the filter changes what the human sees.',
|
|
101
|
+
'To highlight a subset for the human: notes.setFilter.',
|
|
102
|
+
'To tick something off: notes.setDone with done=true, not notes.delete.',
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
register({
|
|
110
|
+
name: 'app.describeView',
|
|
111
|
+
risk: 'read',
|
|
112
|
+
description:
|
|
113
|
+
'What is on the screen right now: the active filter, how many notes are visible, and the visible notes themselves. ' +
|
|
114
|
+
'Recomputed on every call — never cached. Use this before acting so you act on what the human is actually looking at.',
|
|
115
|
+
params: NO_PARAMS,
|
|
116
|
+
execute: function () {
|
|
117
|
+
var state = app().getState();
|
|
118
|
+
var visible = app().visibleNotes();
|
|
119
|
+
return {
|
|
120
|
+
summary:
|
|
121
|
+
visible.length +
|
|
122
|
+
' of ' +
|
|
123
|
+
state.notes.length +
|
|
124
|
+
' notes visible' +
|
|
125
|
+
(state.filter ? ' (filter: "' + state.filter + '")' : ''),
|
|
126
|
+
data: {
|
|
127
|
+
filter: state.filter,
|
|
128
|
+
totalNotes: state.notes.length,
|
|
129
|
+
visibleNotes: visible,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
register({
|
|
136
|
+
name: 'notes.list',
|
|
137
|
+
risk: 'read',
|
|
138
|
+
description:
|
|
139
|
+
'Every note, ignoring the on-screen filter. { done?: boolean } narrows to done or not-done notes. ' +
|
|
140
|
+
'This is how you answer questions — it changes nothing the human can see.',
|
|
141
|
+
params: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: { done: { type: 'boolean', description: 'Only notes with this done state.' } },
|
|
144
|
+
},
|
|
145
|
+
execute: function (p) {
|
|
146
|
+
var notes = app().getState().notes;
|
|
147
|
+
if (typeof p.done === 'boolean')
|
|
148
|
+
notes = notes.filter(function (n) {
|
|
149
|
+
return !!n.done === p.done;
|
|
150
|
+
});
|
|
151
|
+
return { summary: notes.length + ' note(s)', data: { notes: notes } };
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
/* ---- display: changes the view, not the data --------------------------- */
|
|
156
|
+
|
|
157
|
+
register({
|
|
158
|
+
name: 'notes.setFilter',
|
|
159
|
+
risk: 'display',
|
|
160
|
+
description:
|
|
161
|
+
'Set the on-screen filter text so the human sees a subset. { text: string } — pass "" to clear it. ' +
|
|
162
|
+
'This hides nothing permanently and deletes nothing. Use it to SHOW someone something, not to look something up yourself.',
|
|
163
|
+
params: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
text: { type: 'string', description: 'Filter text; empty string clears the filter.' },
|
|
167
|
+
},
|
|
168
|
+
required: ['text'],
|
|
169
|
+
},
|
|
170
|
+
execute: function (p) {
|
|
171
|
+
app().setFilter(String(p.text || ''));
|
|
172
|
+
var visible = app().visibleNotes();
|
|
173
|
+
return {
|
|
174
|
+
summary: p.text
|
|
175
|
+
? 'Filtered to "' + p.text + '" — ' + visible.length + ' shown'
|
|
176
|
+
: 'Filter cleared',
|
|
177
|
+
data: { visibleCount: visible.length },
|
|
178
|
+
affected: ['filter'],
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/* ---- mutate: changes stored data, but reversibly ----------------------- */
|
|
184
|
+
|
|
185
|
+
register({
|
|
186
|
+
name: 'notes.create',
|
|
187
|
+
risk: 'mutate',
|
|
188
|
+
description: 'Add a note. { text: string }. Returns the new note including its id.',
|
|
189
|
+
params: {
|
|
190
|
+
type: 'object',
|
|
191
|
+
properties: { text: { type: 'string', description: 'The note body.' } },
|
|
192
|
+
required: ['text'],
|
|
193
|
+
},
|
|
194
|
+
execute: function (p) {
|
|
195
|
+
var text = String(p.text || '').trim();
|
|
196
|
+
if (!text) throw new Error('text is required');
|
|
197
|
+
var note = app().createNote(text);
|
|
198
|
+
return {
|
|
199
|
+
summary: 'Created note #' + note.id,
|
|
200
|
+
data: { note: note },
|
|
201
|
+
affected: ['note:' + note.id],
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
register({
|
|
207
|
+
name: 'notes.setDone',
|
|
208
|
+
risk: 'mutate',
|
|
209
|
+
description:
|
|
210
|
+
'Tick a note off or un-tick it. { id: number, done?: boolean (default true) }. ' +
|
|
211
|
+
'Get ids from notes.list — never guess one.',
|
|
212
|
+
params: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: {
|
|
215
|
+
id: { type: 'number', description: 'Note id from notes.list.' },
|
|
216
|
+
done: { type: 'boolean', description: 'Default true.' },
|
|
217
|
+
},
|
|
218
|
+
required: ['id'],
|
|
219
|
+
},
|
|
220
|
+
execute: function (p) {
|
|
221
|
+
var note = app().setDone(Number(p.id), p.done !== false);
|
|
222
|
+
if (!note) throw new Error('no note with id ' + p.id);
|
|
223
|
+
return {
|
|
224
|
+
summary: 'Note #' + note.id + (note.done ? ' marked done' : ' reopened'),
|
|
225
|
+
data: { note: note },
|
|
226
|
+
affected: ['note:' + note.id],
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/* ---- export: irreversible. The gateway stops for a human here. ---------- */
|
|
232
|
+
|
|
233
|
+
register({
|
|
234
|
+
name: 'notes.deleteAllDone',
|
|
235
|
+
risk: 'export',
|
|
236
|
+
description:
|
|
237
|
+
'Permanently delete every note already marked done. This cannot be undone. ' +
|
|
238
|
+
'The human confirms with a chip before it runs — say what you are about to delete first, ' +
|
|
239
|
+
'and prefer notes.list so they can see the count.',
|
|
240
|
+
params: NO_PARAMS,
|
|
241
|
+
execute: function () {
|
|
242
|
+
var removed = app().deleteAllDone();
|
|
243
|
+
return {
|
|
244
|
+
summary: 'Deleted ' + removed + ' completed note(s)',
|
|
245
|
+
data: { removed: removed },
|
|
246
|
+
affected: ['notes'],
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
})();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* Per-visitor thread identity.
|
|
2
|
+
|
|
3
|
+
The thread name IS the capability. A scoped key cannot enumerate threads, so
|
|
4
|
+
knowing the name is what grants access to a conversation — which means the
|
|
5
|
+
name must be unguessable and must never be minted server-side and broadcast.
|
|
6
|
+
|
|
7
|
+
This mints 16 hex characters (64 bits) once per browser, keeps it in
|
|
8
|
+
localStorage, and pins THREAD_ID = <base>-<deviceId>. The base the server
|
|
9
|
+
sends is "demoapp-v" — a sub-namespace under "demoapp", so visitor threads
|
|
10
|
+
get their own gateway config (cheap model, tight prompt) while the base
|
|
11
|
+
"demoapp" thread stays yours for working on the app.
|
|
12
|
+
|
|
13
|
+
Do not shorten the id. 8 hex characters is 32 bits, which is brute-forceable
|
|
14
|
+
against a live gateway. */
|
|
15
|
+
(function () {
|
|
16
|
+
'use strict';
|
|
17
|
+
var KEY = 'demoapp.deviceId';
|
|
18
|
+
|
|
19
|
+
window.agentDeviceThread = function (cfg) {
|
|
20
|
+
if (!cfg || !cfg.THREAD_ID) return null;
|
|
21
|
+
if (/-[0-9a-f]{16,}$/.test(cfg.THREAD_ID)) return cfg.THREAD_ID; // already suffixed
|
|
22
|
+
|
|
23
|
+
var id = null;
|
|
24
|
+
try {
|
|
25
|
+
id = localStorage.getItem(KEY);
|
|
26
|
+
} catch {
|
|
27
|
+
/* private mode — fall through and mint an ephemeral id */
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!id || !/^[0-9a-f]{16,}$/.test(id)) {
|
|
31
|
+
var bytes = new Uint8Array(8); // 8 bytes -> 16 hex chars
|
|
32
|
+
crypto.getRandomValues(bytes);
|
|
33
|
+
id = Array.prototype.map
|
|
34
|
+
.call(bytes, function (b) {
|
|
35
|
+
return ('0' + b.toString(16)).slice(-2);
|
|
36
|
+
})
|
|
37
|
+
.join('');
|
|
38
|
+
try {
|
|
39
|
+
localStorage.setItem(KEY, id);
|
|
40
|
+
} catch {
|
|
41
|
+
/* ignore — the visitor gets a fresh thread each load */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cfg.THREAD_ID = cfg.THREAD_ID + '-' + id;
|
|
46
|
+
return cfg.THREAD_ID;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* Agent panel styling. Everything is expressed through the host app's CSS
|
|
2
|
+
variables (--panel, --panel2, --panel3, --line, --text, --muted, --accent),
|
|
3
|
+
so the panel follows your theme — including light/dark — with no work.
|
|
4
|
+
Define those six variables in your app and this file needs no edits. */
|
|
5
|
+
|
|
6
|
+
#agent-root {
|
|
7
|
+
position: fixed;
|
|
8
|
+
z-index: 900;
|
|
9
|
+
left: 50%;
|
|
10
|
+
bottom: 0;
|
|
11
|
+
transform: translateX(-50%);
|
|
12
|
+
width: min(560px, 100vw - 32px);
|
|
13
|
+
font-size: 13.5px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@media (max-width: 720px) {
|
|
17
|
+
#agent-root { width: calc(100vw - 16px); bottom: env(safe-area-inset-bottom, 0px); }
|
|
18
|
+
#agent-root .agbar { margin-bottom: 8px; padding: 8px 12px; }
|
|
19
|
+
#agent-root.open .agwin { height: min(520px, 68dvh); margin-bottom: 8px; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* --- minimized home bar --- */
|
|
23
|
+
#agent-root .agbar {
|
|
24
|
+
display: flex; align-items: center; gap: 8px;
|
|
25
|
+
margin-bottom: 10px; padding: 9px 14px;
|
|
26
|
+
background: var(--panel2); border: 1px solid var(--line); border-radius: 22px;
|
|
27
|
+
box-shadow: 0 4px 18px rgba(0, 0, 0, .25);
|
|
28
|
+
cursor: text;
|
|
29
|
+
}
|
|
30
|
+
#agent-root.open .agbar { display: none; }
|
|
31
|
+
#agent-root .agstar { color: var(--accent); flex: none; }
|
|
32
|
+
#agent-root .agbarin {
|
|
33
|
+
flex: 1; background: none; border: none; outline: none;
|
|
34
|
+
color: var(--text); font: inherit;
|
|
35
|
+
}
|
|
36
|
+
#agent-root .agbarin::placeholder { color: var(--muted); }
|
|
37
|
+
|
|
38
|
+
/* --- expanded window (grows upward from the bar's slot) --- */
|
|
39
|
+
#agent-root .agwin { display: none; }
|
|
40
|
+
#agent-root.open .agwin {
|
|
41
|
+
display: flex; flex-direction: column;
|
|
42
|
+
margin-bottom: 16px;
|
|
43
|
+
height: min(520px, 72vh);
|
|
44
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
|
|
45
|
+
box-shadow: 0 10px 34px rgba(0, 0, 0, .35);
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
}
|
|
48
|
+
#agent-root .aghead {
|
|
49
|
+
display: flex; align-items: center; gap: 8px; flex: none;
|
|
50
|
+
padding: 10px 14px; border-bottom: 1px solid var(--line);
|
|
51
|
+
}
|
|
52
|
+
#agent-root .agtitle { font-weight: 600; }
|
|
53
|
+
#agent-root .agdot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
|
|
54
|
+
#agent-root .agdot.open { background: #47c98a; }
|
|
55
|
+
#agent-root .agdot.connecting { background: #f2b13e; }
|
|
56
|
+
#agent-root .agdot.closed { background: #f2665e; }
|
|
57
|
+
#agent-root .agmin {
|
|
58
|
+
margin-left: auto; background: none; border: none; color: var(--muted);
|
|
59
|
+
cursor: pointer; font: inherit; padding: 2px 6px; border-radius: 6px;
|
|
60
|
+
}
|
|
61
|
+
#agent-root .agmin:hover { background: var(--panel3); color: var(--text); }
|
|
62
|
+
|
|
63
|
+
/* --- log --- */
|
|
64
|
+
#agent-root .aglog { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
|
|
65
|
+
#agent-root .agmsg { max-width: 88%; padding: 8px 12px; border-radius: 10px; line-height: 1.45; overflow-wrap: break-word; }
|
|
66
|
+
#agent-root .agmsg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
|
|
67
|
+
#agent-root .agmsg.assistant { align-self: flex-start; background: var(--panel2); color: var(--text); border-bottom-left-radius: 4px; white-space: pre-wrap; }
|
|
68
|
+
#agent-root .agmsg.assistant p,
|
|
69
|
+
#agent-root .agmsg.assistant ul,
|
|
70
|
+
#agent-root .agmsg.assistant h4,
|
|
71
|
+
#agent-root .agmsg.assistant table,
|
|
72
|
+
#agent-root .agmsg.assistant pre { white-space: normal; margin: 0 0 8px; }
|
|
73
|
+
#agent-root .agmsg.assistant > :last-child { margin-bottom: 0; }
|
|
74
|
+
#agent-root .agmsg.assistant ul { padding-left: 18px; }
|
|
75
|
+
#agent-root .agmsg.system { align-self: center; color: var(--muted); font-size: 12px; background: none; }
|
|
76
|
+
#agent-root .agmsg.error { align-self: flex-start; background: rgba(242, 102, 94, .12); color: #f2665e; }
|
|
77
|
+
#agent-root .agmsg pre { background: var(--panel3); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; overflow-x: auto; font-size: 12px; }
|
|
78
|
+
#agent-root .agmsg code { background: var(--panel3); border-radius: 4px; padding: 1px 5px; font-size: 12.5px; }
|
|
79
|
+
#agent-root .agmsg table { border-collapse: collapse; font-size: 12.5px; }
|
|
80
|
+
#agent-root .agmsg th, #agent-root .agmsg td { border: 1px solid var(--line); padding: 4px 8px; text-align: left; }
|
|
81
|
+
|
|
82
|
+
#agent-root .agactivity { align-self: flex-start; color: var(--muted); font-size: 12px; padding: 0 4px; animation: agpulse 1.4s ease-in-out infinite; }
|
|
83
|
+
@keyframes agpulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
|
|
84
|
+
/* Browser tests set data-test-mode to kill animation timing races. */
|
|
85
|
+
[data-test-mode="true"] #agent-root .agactivity { animation: none; }
|
|
86
|
+
|
|
87
|
+
/* --- export-tier confirm chip --- */
|
|
88
|
+
#agent-root .agconfirm { align-self: stretch; background: var(--panel2); border: 1px solid #f2b13e88; border-radius: 10px; padding: 10px 12px; }
|
|
89
|
+
#agent-root .agconfirmrow { display: flex; gap: 8px; margin-top: 8px; }
|
|
90
|
+
#agent-root .agconfirmdone { color: var(--muted); margin-top: 6px; font-size: 12px; }
|
|
91
|
+
#agent-root .agbtn {
|
|
92
|
+
font: inherit; padding: 5px 14px; border-radius: 8px; cursor: pointer;
|
|
93
|
+
background: var(--panel3); color: var(--text); border: 1px solid var(--line);
|
|
94
|
+
}
|
|
95
|
+
#agent-root .agbtn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
96
|
+
|
|
97
|
+
/* --- composer row --- */
|
|
98
|
+
#agent-root .aginrow { display: flex; gap: 8px; flex: none; padding: 10px 12px; border-top: 1px solid var(--line); }
|
|
99
|
+
#agent-root .again {
|
|
100
|
+
flex: 1; resize: none; background: var(--panel2); border: 1px solid var(--line);
|
|
101
|
+
border-radius: 10px; padding: 8px 10px; color: var(--text); font: inherit; outline: none;
|
|
102
|
+
}
|
|
103
|
+
#agent-root .again:focus { border-color: var(--accent); }
|
|
104
|
+
#agent-root .agsend {
|
|
105
|
+
align-self: flex-end; width: 34px; height: 34px; border-radius: 50%;
|
|
106
|
+
background: var(--accent); color: #fff; border: none; cursor: pointer; font-size: 15px;
|
|
107
|
+
}
|