@ai-ecoverse/slicc-spectrum 1.1.0 → 1.3.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/README.md +30 -7
- package/dist/{chunk-4RGMMAR4.js → chunk-4GQ6THC2.js} +3 -3
- package/dist/{chunk-H5WU2XYY.js → chunk-5XWUZJTT.js} +2 -2
- package/dist/chunk-64K5VONJ.js +11 -0
- package/dist/chunk-64K5VONJ.js.map +7 -0
- package/dist/{chunk-J45KH7MF.js → chunk-6JNYNXUQ.js} +2 -2
- package/dist/chunk-6JYCQXDO.js +8 -0
- package/dist/chunk-6JYCQXDO.js.map +7 -0
- package/dist/{chunk-ZTPYBVZL.js → chunk-6N3GZNXH.js} +3 -3
- package/dist/chunk-6ODADG7T.js +8 -0
- package/dist/chunk-6ODADG7T.js.map +7 -0
- package/dist/chunk-6T3O5K6R.js +8 -0
- package/dist/{chunk-MR7SU4GJ.js → chunk-7PY7B3ZW.js} +2 -2
- package/dist/{chunk-Q4AC2O6Z.js → chunk-AXQFWXYZ.js} +8 -2
- package/dist/chunk-BDOI3WEE.js +8 -0
- package/dist/chunk-BDOI3WEE.js.map +7 -0
- package/dist/chunk-BHAGWMDD.js +8 -0
- package/dist/chunk-BHAGWMDD.js.map +7 -0
- package/dist/chunk-C7X653JJ.js +8 -0
- package/dist/chunk-CGS66GRP.js +11 -0
- package/dist/chunk-CGS66GRP.js.map +7 -0
- package/dist/chunk-CONYKGKV.js +11 -0
- package/dist/chunk-CONYKGKV.js.map +7 -0
- package/dist/chunk-CU7BC2RI.js +11 -0
- package/dist/chunk-CU7BC2RI.js.map +7 -0
- package/dist/chunk-EX57SDII.js +8 -0
- package/dist/chunk-EX57SDII.js.map +7 -0
- package/dist/chunk-GKXQPI4G.js +8 -0
- package/dist/chunk-GKXQPI4G.js.map +7 -0
- package/dist/{chunk-FN3Y2AYJ.js → chunk-I2NMYJ25.js} +3 -3
- package/dist/chunk-I2NMYJ25.js.map +7 -0
- package/dist/chunk-I4OYHPAU.js +11 -0
- package/dist/chunk-I4OYHPAU.js.map +7 -0
- package/dist/{chunk-MTF6JFSL.js → chunk-J3QOBXZJ.js} +2 -2
- package/dist/chunk-JBYBW627.js +8 -0
- package/dist/chunk-JBYBW627.js.map +7 -0
- package/dist/chunk-JWTAKXQS.js +8 -0
- package/dist/chunk-JWTAKXQS.js.map +7 -0
- package/dist/chunk-KMP5HO3E.js +11 -0
- package/dist/chunk-KMP5HO3E.js.map +7 -0
- package/dist/chunk-KWPU2PY3.js +14 -0
- package/dist/chunk-KWPU2PY3.js.map +7 -0
- package/dist/chunk-KYBD6ZRT.js +8 -0
- package/dist/chunk-KYBD6ZRT.js.map +7 -0
- package/dist/chunk-LBXW6XNL.js +18 -0
- package/dist/chunk-LBXW6XNL.js.map +7 -0
- package/dist/chunk-LDR4U5KX.js +8 -0
- package/dist/chunk-LDR4U5KX.js.map +7 -0
- package/dist/chunk-LII3WY6Y.js +11 -0
- package/dist/chunk-LII3WY6Y.js.map +7 -0
- package/dist/chunk-LY7WVBCB.js +10 -0
- package/dist/chunk-LY7WVBCB.js.map +7 -0
- package/dist/chunk-MJVAL555.js +8 -0
- package/dist/chunk-MJVAL555.js.map +7 -0
- package/dist/chunk-NLDFGCIL.js +11 -0
- package/dist/chunk-NLDFGCIL.js.map +7 -0
- package/dist/{chunk-SZ33GAVH.js → chunk-OFQ56DAN.js} +5 -5
- package/dist/chunk-S5EGO4NA.js +11 -0
- package/dist/chunk-S5EGO4NA.js.map +7 -0
- package/dist/{chunk-24WRGUXG.js → chunk-T2LAT2D6.js} +2 -2
- package/dist/chunk-TAZBKRHT.js +10 -0
- package/dist/chunk-TAZBKRHT.js.map +7 -0
- package/dist/chunk-TK4ZJ7LA.js +5346 -0
- package/dist/chunk-TK4ZJ7LA.js.map +7 -0
- package/dist/chunk-UCIBH53C.js +11 -0
- package/dist/chunk-UCIBH53C.js.map +7 -0
- package/dist/chunk-XZM23TK4.js +11 -0
- package/dist/chunk-XZM23TK4.js.map +7 -0
- package/dist/chunk-YPQKCWWG.js +19 -0
- package/dist/chunk-YPQKCWWG.js.map +7 -0
- package/dist/chunk-YX6SI5VA.js +11 -0
- package/dist/chunk-YX6SI5VA.js.map +7 -0
- package/dist/slicc-dummy.js +1475 -57
- package/dist/slicc-dummy.js.map +4 -4
- package/dist/slicc-terminal.js +7 -5344
- package/dist/slicc-terminal.js.map +4 -4
- package/dist/slicc-ui.js +56890 -17879
- package/dist/slicc-ui.js.map +4 -4
- package/dist/types/app/app.d.ts +7 -11
- package/dist/types/app/base.d.ts +9 -1
- package/dist/types/app/browser.d.ts +25 -0
- package/dist/types/app/changes.d.ts +24 -0
- package/dist/types/app/chat.d.ts +2 -14
- package/dist/types/app/composer.d.ts +69 -0
- package/dist/types/app/files.d.ts +56 -0
- package/dist/types/app/freezer.d.ts +11 -0
- package/dist/types/app/grammars.d.ts +6 -0
- package/dist/types/app/markdown.d.ts +12 -3
- package/dist/types/app/memory.d.ts +39 -0
- package/dist/types/app/messages.d.ts +21 -0
- package/dist/types/app/monitor.d.ts +11 -0
- package/dist/types/app/panels.d.ts +11 -3
- package/dist/types/app/settings.d.ts +21 -0
- package/dist/types/app/spectrum.d.ts +18 -0
- package/dist/types/app/sprinkle.d.ts +18 -0
- package/dist/types/app/terminals.d.ts +25 -0
- package/dist/types/app/tray.d.ts +25 -0
- package/dist/types/components/code-view.d.ts +47 -0
- package/dist/types/components/file-tree.d.ts +18 -0
- package/dist/types/dummy/agent.d.ts +21 -3
- package/dist/types/dummy/extras.d.ts +8 -0
- package/dist/types/dummy/fixtures.d.ts +2 -1
- package/dist/types/dummy/memory.d.ts +9 -0
- package/dist/types/dummy/monitor.d.ts +9 -0
- package/dist/types/dummy/script.d.ts +8 -2
- package/dist/types/dummy/showcase.d.ts +6 -0
- package/dist/types/dummy/sprinkles.d.ts +9 -0
- package/dist/types/dummy/tray.d.ts +10 -0
- package/dist/types/model/types.d.ts +280 -8
- package/dist/types/ui.d.ts +13 -3
- package/package.json +18 -1
- package/dist/chunk-T5QFSUYQ.js +0 -8
- /package/dist/{chunk-4RGMMAR4.js.map → chunk-4GQ6THC2.js.map} +0 -0
- /package/dist/{chunk-FN3Y2AYJ.js.map → chunk-5XWUZJTT.js.map} +0 -0
- /package/dist/{chunk-J45KH7MF.js.map → chunk-6JNYNXUQ.js.map} +0 -0
- /package/dist/{chunk-ZTPYBVZL.js.map → chunk-6N3GZNXH.js.map} +0 -0
- /package/dist/{chunk-H5WU2XYY.js.map → chunk-6T3O5K6R.js.map} +0 -0
- /package/dist/{chunk-MR7SU4GJ.js.map → chunk-7PY7B3ZW.js.map} +0 -0
- /package/dist/{chunk-Q4AC2O6Z.js.map → chunk-AXQFWXYZ.js.map} +0 -0
- /package/dist/{chunk-T5QFSUYQ.js.map → chunk-C7X653JJ.js.map} +0 -0
- /package/dist/{chunk-MTF6JFSL.js.map → chunk-J3QOBXZJ.js.map} +0 -0
- /package/dist/{chunk-SZ33GAVH.js.map → chunk-OFQ56DAN.js.map} +0 -0
- /package/dist/{chunk-24WRGUXG.js.map → chunk-T2LAT2D6.js.map} +0 -0
package/dist/slicc-dummy.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Emitter
|
|
3
3
|
} from "./chunk-T5NHW4KC.js";
|
|
4
|
-
import "./chunk-
|
|
4
|
+
import "./chunk-AXQFWXYZ.js";
|
|
5
5
|
|
|
6
6
|
// src/dummy/script.ts
|
|
7
7
|
var units = "/workspace/harbor/src/lib/units.ts";
|
|
@@ -56,6 +56,18 @@ function edit() {
|
|
|
56
56
|
await files3.write(units, before + kelvin, agentId);
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
type: "part",
|
|
61
|
+
part: {
|
|
62
|
+
type: "diff",
|
|
63
|
+
path: units,
|
|
64
|
+
before: "export function toFahrenheit(celsius: number): number {\n return Math.round((celsius * 9) / 5 + 32);\n}\n",
|
|
65
|
+
after: `export function toFahrenheit(celsius: number): number {
|
|
66
|
+
return Math.round((celsius * 9) / 5 + 32);
|
|
67
|
+
}
|
|
68
|
+
${kelvin}`
|
|
69
|
+
}
|
|
70
|
+
},
|
|
59
71
|
{
|
|
60
72
|
type: "text",
|
|
61
73
|
text: "Added `toKelvin()` to `src/lib/units.ts`. It rounds to two decimals, like the API\u2019s other values. The change is in **Changes**, ready for review."
|
|
@@ -125,36 +137,110 @@ function chat(prompt) {
|
|
|
125
137
|
}
|
|
126
138
|
];
|
|
127
139
|
}
|
|
128
|
-
function
|
|
140
|
+
function ask() {
|
|
141
|
+
return [
|
|
142
|
+
{ type: "thinking", text: "Two ways to do this. Ask before picking one." },
|
|
143
|
+
{
|
|
144
|
+
type: "part",
|
|
145
|
+
part: {
|
|
146
|
+
type: "question",
|
|
147
|
+
question: {
|
|
148
|
+
id: `q-${Math.random().toString(36).slice(2, 8)}`,
|
|
149
|
+
kind: "choice",
|
|
150
|
+
question: "Where should the cache live once the TTL fix ships?",
|
|
151
|
+
options: ["Keep it in memory", "Move it to KV", "Decide after the load test"],
|
|
152
|
+
state: "open"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
function look(attachments) {
|
|
159
|
+
const names = attachments.map((attachment) => attachment.name).join(", ");
|
|
160
|
+
const images = attachments.filter((attachment) => attachment.kind === "image").length;
|
|
161
|
+
return [
|
|
162
|
+
{
|
|
163
|
+
type: "tool",
|
|
164
|
+
name: "read_file",
|
|
165
|
+
title: `Read ${attachments.length === 1 ? "the attachment" : `${attachments.length} attachments`}`,
|
|
166
|
+
input: names,
|
|
167
|
+
output: attachments.map(
|
|
168
|
+
(attachment) => `${attachment.name} (${attachment.mimeType}, ${attachment.size} bytes)`
|
|
169
|
+
).join("\n"),
|
|
170
|
+
paths: [],
|
|
171
|
+
ticks: 10
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: `Got **${names}**${images ? `, including ${images === 1 ? "an image" : `${images} images`}` : ""}. I\u2019ll keep ${attachments.length === 1 ? "it" : "them"} in mind for the next step.`
|
|
176
|
+
}
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
var suggestions = [
|
|
180
|
+
[/\btests?\b/, "Open a pull request with the fix"],
|
|
181
|
+
[/\b(fix|edit|add|change)\b/, "Run the tests"],
|
|
182
|
+
[/\b(open|browse|page|docs)\b/, "Summarize the page"],
|
|
183
|
+
[/\b(files?|read|show|project)\b/, "Add a Kelvin helper to units.ts"]
|
|
184
|
+
];
|
|
185
|
+
function script(prompt, attachments = []) {
|
|
129
186
|
const text = prompt.toLowerCase();
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
if (/\b(
|
|
133
|
-
if (/\b
|
|
134
|
-
return
|
|
187
|
+
const suggestion = suggestions.find(([pattern]) => pattern.test(text))?.[1] ?? "Run the tests";
|
|
188
|
+
if (attachments.length > 0) return { steps: look(attachments), suggestion };
|
|
189
|
+
if (/\b(ask|choose|decide)\b/.test(text)) return { steps: ask(), suggestion };
|
|
190
|
+
if (/\btests?\b/.test(text)) return { steps: test(), suggestion };
|
|
191
|
+
if (/\b(fix|edit|add|change)\b/.test(text)) return { steps: edit(), suggestion };
|
|
192
|
+
if (/\b(open|browse|page|docs)\b/.test(text)) return { steps: browse(), suggestion };
|
|
193
|
+
if (/\b(files?|read|show|project)\b/.test(text)) return { steps: files(), suggestion };
|
|
194
|
+
return { steps: chat(prompt), suggestion };
|
|
135
195
|
}
|
|
136
196
|
|
|
137
197
|
// src/dummy/agent.ts
|
|
138
198
|
function chunks(text) {
|
|
139
199
|
return text.match(/\S+\s*|\s+/g) ?? [];
|
|
140
200
|
}
|
|
201
|
+
function outgoing(input) {
|
|
202
|
+
return typeof input === "string" ? { text: input } : input;
|
|
203
|
+
}
|
|
141
204
|
var DummyAgent = class extends Emitter {
|
|
142
205
|
#agents;
|
|
143
206
|
#messages = /* @__PURE__ */ new Map();
|
|
207
|
+
#queues = /* @__PURE__ */ new Map();
|
|
208
|
+
#frozen = [];
|
|
209
|
+
#ice = /* @__PURE__ */ new Map();
|
|
210
|
+
#suggestions = /* @__PURE__ */ new Map();
|
|
144
211
|
#runs = /* @__PURE__ */ new Map();
|
|
145
212
|
#active;
|
|
146
213
|
#clock;
|
|
147
214
|
#effects;
|
|
148
215
|
#next = 1;
|
|
149
|
-
constructor(agents2, conversations2, effects, clock2) {
|
|
216
|
+
constructor(agents2, conversations2, effects, clock2, queues2 = {}, frozen2 = []) {
|
|
150
217
|
super();
|
|
151
218
|
this.#clock = clock2;
|
|
152
219
|
this.#effects = effects;
|
|
153
220
|
this.#agents = agents2.map((agent) => ({ ...agent }));
|
|
154
221
|
for (const agent of this.#agents) {
|
|
155
222
|
this.#messages.set(agent.id, structuredClone([...conversations2[agent.id] ?? []]));
|
|
223
|
+
this.#queues.set(agent.id, structuredClone([...queues2[agent.id] ?? []]));
|
|
156
224
|
}
|
|
157
225
|
this.#active = this.#agents[0]?.id ?? "";
|
|
226
|
+
for (const { cone, messages } of frozen2) {
|
|
227
|
+
this.#frozen.push({ ...cone });
|
|
228
|
+
this.#ice.set(cone.id, [
|
|
229
|
+
{
|
|
230
|
+
agent: {
|
|
231
|
+
id: cone.id,
|
|
232
|
+
name: cone.name,
|
|
233
|
+
kind: "cone",
|
|
234
|
+
parentId: null,
|
|
235
|
+
status: "idle",
|
|
236
|
+
model: cone.model,
|
|
237
|
+
contextFill: 0.2,
|
|
238
|
+
unread: 0
|
|
239
|
+
},
|
|
240
|
+
messages: structuredClone([...messages])
|
|
241
|
+
}
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
158
244
|
}
|
|
159
245
|
list() {
|
|
160
246
|
return this.#agents.map((agent) => ({ ...agent }));
|
|
@@ -168,6 +254,9 @@ var DummyAgent = class extends Emitter {
|
|
|
168
254
|
#changed() {
|
|
169
255
|
this.emit("agents", this.list());
|
|
170
256
|
}
|
|
257
|
+
#id(prefix) {
|
|
258
|
+
return `${prefix}-${this.#next++}`;
|
|
259
|
+
}
|
|
171
260
|
select(id) {
|
|
172
261
|
const agent = this.#agent(id);
|
|
173
262
|
if (!agent) return;
|
|
@@ -179,6 +268,15 @@ var DummyAgent = class extends Emitter {
|
|
|
179
268
|
messages(agentId) {
|
|
180
269
|
return [...this.#messages.get(agentId) ?? []];
|
|
181
270
|
}
|
|
271
|
+
queue(agentId) {
|
|
272
|
+
return [...this.#queues.get(agentId) ?? []];
|
|
273
|
+
}
|
|
274
|
+
busy(agentId) {
|
|
275
|
+
return this.#runs.has(agentId);
|
|
276
|
+
}
|
|
277
|
+
suggestion(agentId) {
|
|
278
|
+
return this.#suggestions.get(agentId) ?? null;
|
|
279
|
+
}
|
|
182
280
|
#post(agentId, message) {
|
|
183
281
|
this.#messages.get(agentId)?.push(message);
|
|
184
282
|
this.emit("message", { agentId, message });
|
|
@@ -197,17 +295,10 @@ var DummyAgent = class extends Emitter {
|
|
|
197
295
|
this.emit("message", { agentId, message: reply });
|
|
198
296
|
}
|
|
199
297
|
}
|
|
200
|
-
async #
|
|
201
|
-
if (step.type !== "tool") {
|
|
202
|
-
this.#status(agentId, "thinking");
|
|
203
|
-
const part = { type: step.type, text: "" };
|
|
204
|
-
reply.parts.push(part);
|
|
205
|
-
await this.#stream(agentId, reply, part, step.text, run);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
298
|
+
async #tool(agentId, reply, step, run) {
|
|
208
299
|
this.#status(agentId, "working");
|
|
209
|
-
const
|
|
210
|
-
id:
|
|
300
|
+
const tool2 = {
|
|
301
|
+
id: this.#id("t"),
|
|
211
302
|
name: step.name,
|
|
212
303
|
title: step.title,
|
|
213
304
|
input: step.input,
|
|
@@ -215,50 +306,263 @@ var DummyAgent = class extends Emitter {
|
|
|
215
306
|
status: "running",
|
|
216
307
|
paths: step.paths
|
|
217
308
|
};
|
|
218
|
-
reply.parts.push({ type: "tool", tool });
|
|
309
|
+
reply.parts.push({ type: "tool", tool: tool2 });
|
|
219
310
|
this.emit("message", { agentId, message: reply });
|
|
220
311
|
await this.#clock.sleep(step.ticks);
|
|
221
|
-
const done = run.stopped ? { status: "
|
|
312
|
+
const done = run.stopped ? { status: "cancelled", output: "Stopped" } : { status: "done", output: step.output };
|
|
222
313
|
if (!run.stopped) await step.effect?.({ ...this.#effects, agentId });
|
|
223
|
-
Object.assign(
|
|
314
|
+
Object.assign(tool2, done);
|
|
224
315
|
this.emit("message", { agentId, message: reply });
|
|
225
316
|
}
|
|
226
|
-
async
|
|
317
|
+
async #step(agentId, reply, step, run) {
|
|
318
|
+
if (step.type === "tool") return this.#tool(agentId, reply, step, run);
|
|
319
|
+
if (step.type === "part") {
|
|
320
|
+
await this.#clock.sleep(2);
|
|
321
|
+
if (run.stopped) return;
|
|
322
|
+
reply.parts.push(structuredClone(step.part));
|
|
323
|
+
this.emit("message", { agentId, message: reply });
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
this.#status(agentId, "thinking");
|
|
327
|
+
const part = { type: step.type, text: "" };
|
|
328
|
+
reply.parts.push(part);
|
|
329
|
+
await this.#stream(agentId, reply, part, step.text, run);
|
|
330
|
+
}
|
|
331
|
+
#enqueue(agentId, input) {
|
|
332
|
+
const message = {
|
|
333
|
+
id: this.#id("q"),
|
|
334
|
+
role: "user",
|
|
335
|
+
text: input.text.trim(),
|
|
336
|
+
createdAt: Date.now(),
|
|
337
|
+
queued: true,
|
|
338
|
+
mode: "queue",
|
|
339
|
+
...input.attachments?.length ? { attachments: input.attachments } : {}
|
|
340
|
+
};
|
|
341
|
+
this.#queues.set(agentId, [...this.queue(agentId), message]);
|
|
342
|
+
this.emit("messages", agentId);
|
|
343
|
+
}
|
|
344
|
+
unqueue(agentId, messageId) {
|
|
345
|
+
this.#queues.set(
|
|
346
|
+
agentId,
|
|
347
|
+
this.queue(agentId).filter((message) => message.id !== messageId)
|
|
348
|
+
);
|
|
349
|
+
this.emit("messages", agentId);
|
|
350
|
+
}
|
|
351
|
+
async send(agentId, input) {
|
|
227
352
|
const agent = this.#agent(agentId);
|
|
228
|
-
const
|
|
229
|
-
|
|
353
|
+
const message = outgoing(input);
|
|
354
|
+
const prompt = message.text.trim();
|
|
355
|
+
if (!agent || !prompt && !message.attachments?.length) return;
|
|
356
|
+
if (this.busy(agentId) && message.mode !== "steer") {
|
|
357
|
+
this.#enqueue(agentId, message);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
230
360
|
this.stop(agentId);
|
|
361
|
+
this.#suggestions.delete(agentId);
|
|
231
362
|
const run = { stopped: false };
|
|
232
363
|
this.#runs.set(agentId, run);
|
|
233
364
|
this.#post(agentId, {
|
|
234
|
-
id:
|
|
365
|
+
id: this.#id("m"),
|
|
235
366
|
role: "user",
|
|
236
367
|
text: prompt,
|
|
237
|
-
createdAt: Date.now()
|
|
368
|
+
createdAt: Date.now(),
|
|
369
|
+
...message.mode === "steer" ? { mode: "steer" } : {},
|
|
370
|
+
...message.attachments?.length ? { attachments: message.attachments } : {}
|
|
238
371
|
});
|
|
239
372
|
const reply = {
|
|
240
|
-
id:
|
|
373
|
+
id: this.#id("m"),
|
|
241
374
|
role: "assistant",
|
|
242
375
|
parts: [],
|
|
243
376
|
status: "streaming",
|
|
244
|
-
createdAt: Date.now()
|
|
377
|
+
createdAt: Date.now(),
|
|
378
|
+
model: agent.model
|
|
245
379
|
};
|
|
246
380
|
this.#post(agentId, reply);
|
|
247
|
-
|
|
381
|
+
const { steps, suggestion } = script(prompt, message.attachments ?? []);
|
|
382
|
+
for (const step of steps) {
|
|
248
383
|
if (run.stopped) break;
|
|
249
384
|
await this.#step(agentId, reply, step, run);
|
|
250
385
|
}
|
|
251
386
|
reply.status = run.stopped ? "stopped" : "done";
|
|
387
|
+
reply.usage = {
|
|
388
|
+
input: 1200 + prompt.length * 4,
|
|
389
|
+
output: 180 + reply.parts.length * 60,
|
|
390
|
+
cost: 4e-3
|
|
391
|
+
};
|
|
252
392
|
if (this.#runs.get(agentId) === run) this.#runs.delete(agentId);
|
|
253
393
|
agent.contextFill = Math.min(0.95, Math.round((agent.contextFill + 0.03) * 100) / 100);
|
|
254
394
|
if (agentId !== this.#active) agent.unread += 1;
|
|
395
|
+
if (!run.stopped) this.#suggestions.set(agentId, suggestion);
|
|
255
396
|
this.emit("message", { agentId, message: reply });
|
|
256
397
|
agent.status = "idle";
|
|
257
398
|
this.#changed();
|
|
399
|
+
await this.#drain(agentId);
|
|
400
|
+
}
|
|
401
|
+
async #drain(agentId) {
|
|
402
|
+
if (this.busy(agentId)) return;
|
|
403
|
+
const [next, ...rest] = this.queue(agentId);
|
|
404
|
+
if (!next) return;
|
|
405
|
+
this.#queues.set(agentId, rest);
|
|
406
|
+
this.emit("messages", agentId);
|
|
407
|
+
await this.send(agentId, { text: next.text, attachments: next.attachments });
|
|
258
408
|
}
|
|
259
409
|
stop(agentId) {
|
|
260
410
|
const run = this.#runs.get(agentId);
|
|
261
411
|
if (run) run.stopped = true;
|
|
412
|
+
this.#runs.delete(agentId);
|
|
413
|
+
}
|
|
414
|
+
#find(agentId, test2) {
|
|
415
|
+
return this.#messages.get(agentId)?.find(test2);
|
|
416
|
+
}
|
|
417
|
+
answer(agentId, questionId, answer) {
|
|
418
|
+
const message = this.#find(
|
|
419
|
+
agentId,
|
|
420
|
+
(candidate) => candidate.role === "assistant" && candidate.parts.some((part) => part.type === "question" && part.question.id === questionId)
|
|
421
|
+
);
|
|
422
|
+
if (!message) return;
|
|
423
|
+
for (const part of message.parts) {
|
|
424
|
+
if (part.type === "question" && part.question.id === questionId) {
|
|
425
|
+
part.question.state = "answered";
|
|
426
|
+
part.question.answer = answer;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
this.emit("message", { agentId, message });
|
|
430
|
+
}
|
|
431
|
+
resolveLick(agentId, messageId, state) {
|
|
432
|
+
const message = this.#find(agentId, (candidate) => candidate.id === messageId);
|
|
433
|
+
if (message?.role !== "lick") return;
|
|
434
|
+
message.state = state;
|
|
435
|
+
this.emit("message", { agentId, message });
|
|
436
|
+
}
|
|
437
|
+
compact(agentId) {
|
|
438
|
+
const agent = this.#agent(agentId);
|
|
439
|
+
if (!agent) return;
|
|
440
|
+
const message = {
|
|
441
|
+
id: this.#id("m"),
|
|
442
|
+
role: "system",
|
|
443
|
+
kind: "compaction",
|
|
444
|
+
title: "Context compacted",
|
|
445
|
+
text: "Summarizing the conversation so far\u2026",
|
|
446
|
+
trigger: "manual",
|
|
447
|
+
state: "summarizing",
|
|
448
|
+
createdAt: Date.now()
|
|
449
|
+
};
|
|
450
|
+
this.#post(agentId, message);
|
|
451
|
+
void this.#clock.sleep(20).then(() => {
|
|
452
|
+
Object.assign(message, {
|
|
453
|
+
state: "summarized",
|
|
454
|
+
text: "The earlier conversation was summarized to free up context."
|
|
455
|
+
});
|
|
456
|
+
agent.contextFill = 0.06;
|
|
457
|
+
this.emit("message", { agentId, message });
|
|
458
|
+
this.#changed();
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
clear(agentId) {
|
|
462
|
+
if (!this.#messages.has(agentId)) return;
|
|
463
|
+
this.stop(agentId);
|
|
464
|
+
this.#messages.set(agentId, []);
|
|
465
|
+
this.#suggestions.delete(agentId);
|
|
466
|
+
this.emit("messages", agentId);
|
|
467
|
+
}
|
|
468
|
+
setModel(agentId, model) {
|
|
469
|
+
const agent = this.#agent(agentId);
|
|
470
|
+
if (!agent) return;
|
|
471
|
+
agent.model = model;
|
|
472
|
+
this.#changed();
|
|
473
|
+
}
|
|
474
|
+
createScoop(parentId, name) {
|
|
475
|
+
const parent = this.#agent(parentId);
|
|
476
|
+
const agent = {
|
|
477
|
+
id: this.#id("scoop"),
|
|
478
|
+
name,
|
|
479
|
+
kind: "scoop",
|
|
480
|
+
parentId: parent?.kind === "cone" ? parent.id : parent?.parentId ?? null,
|
|
481
|
+
status: "idle",
|
|
482
|
+
model: parent?.model ?? "claude-haiku-4-5",
|
|
483
|
+
contextFill: 0,
|
|
484
|
+
unread: 0
|
|
485
|
+
};
|
|
486
|
+
this.#agents.push(agent);
|
|
487
|
+
this.#messages.set(agent.id, []);
|
|
488
|
+
if (parent) {
|
|
489
|
+
this.#post(parentId, {
|
|
490
|
+
id: this.#id("m"),
|
|
491
|
+
role: "lick",
|
|
492
|
+
channel: "scoop-idle",
|
|
493
|
+
title: name,
|
|
494
|
+
text: "New scoop, waiting for its first task",
|
|
495
|
+
createdAt: Date.now()
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
this.#changed();
|
|
499
|
+
return { ...agent };
|
|
500
|
+
}
|
|
501
|
+
lick(agentId, channel, title, text, body) {
|
|
502
|
+
if (!this.#agent(agentId)) return;
|
|
503
|
+
this.#post(agentId, {
|
|
504
|
+
id: this.#id("m"),
|
|
505
|
+
role: "lick",
|
|
506
|
+
channel,
|
|
507
|
+
title,
|
|
508
|
+
text,
|
|
509
|
+
createdAt: Date.now(),
|
|
510
|
+
...body ? { body } : {}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
frozen() {
|
|
514
|
+
return this.#frozen.map((cone) => ({ ...cone }));
|
|
515
|
+
}
|
|
516
|
+
freeze(agentId) {
|
|
517
|
+
const agent = this.#agent(agentId);
|
|
518
|
+
if (agent?.kind !== "cone") return;
|
|
519
|
+
this.stop(agentId);
|
|
520
|
+
const family = this.#agents.filter(
|
|
521
|
+
(candidate) => candidate.id === agentId || candidate.parentId === agentId
|
|
522
|
+
);
|
|
523
|
+
const messages = this.#messages.get(agentId) ?? [];
|
|
524
|
+
const first = messages.find((message) => message.role === "user");
|
|
525
|
+
this.#frozen.unshift({
|
|
526
|
+
id: agentId,
|
|
527
|
+
name: agent.name,
|
|
528
|
+
title: first?.role === "user" ? first.text.slice(0, 80) : agent.name,
|
|
529
|
+
model: agent.model,
|
|
530
|
+
messages: messages.length,
|
|
531
|
+
frozenAt: Date.now()
|
|
532
|
+
});
|
|
533
|
+
this.#ice.set(
|
|
534
|
+
agentId,
|
|
535
|
+
family.map((member) => ({
|
|
536
|
+
agent: { ...member, status: "idle" },
|
|
537
|
+
messages: this.#messages.get(member.id)
|
|
538
|
+
}))
|
|
539
|
+
);
|
|
540
|
+
for (const member of family) this.stop(member.id);
|
|
541
|
+
this.#agents = this.#agents.filter((candidate) => !family.includes(candidate));
|
|
542
|
+
for (const member of family) this.#messages.delete(member.id);
|
|
543
|
+
if (this.#active === agentId) this.#active = this.#agents[0]?.id ?? "";
|
|
544
|
+
this.emit("frozen", this.frozen());
|
|
545
|
+
this.emit("active", this.#active);
|
|
546
|
+
this.#changed();
|
|
547
|
+
}
|
|
548
|
+
thaw(id) {
|
|
549
|
+
const ice = this.#ice.get(id);
|
|
550
|
+
if (!ice) return null;
|
|
551
|
+
this.#ice.delete(id);
|
|
552
|
+
this.#frozen = this.#frozen.filter((cone) => cone.id !== id);
|
|
553
|
+
for (const { agent, messages } of ice) {
|
|
554
|
+
this.#agents.push(agent);
|
|
555
|
+
this.#messages.set(agent.id, messages);
|
|
556
|
+
this.#queues.set(agent.id, []);
|
|
557
|
+
}
|
|
558
|
+
this.emit("frozen", this.frozen());
|
|
559
|
+
this.select(id);
|
|
560
|
+
return { ...ice[0].agent };
|
|
561
|
+
}
|
|
562
|
+
discard(id) {
|
|
563
|
+
this.#ice.delete(id);
|
|
564
|
+
this.#frozen = this.#frozen.filter((cone) => cone.id !== id);
|
|
565
|
+
this.emit("frozen", this.frozen());
|
|
262
566
|
}
|
|
263
567
|
};
|
|
264
568
|
|
|
@@ -281,6 +585,7 @@ function page(tab) {
|
|
|
281
585
|
}
|
|
282
586
|
function titleFor(url) {
|
|
283
587
|
const { host, pathname } = new URL(url);
|
|
588
|
+
if (!host) return "New tab";
|
|
284
589
|
return pathname === "/" ? host : `${host}${pathname}`;
|
|
285
590
|
}
|
|
286
591
|
var DummyBrowser = class extends Emitter {
|
|
@@ -365,6 +670,210 @@ function clock(delay) {
|
|
|
365
670
|
};
|
|
366
671
|
}
|
|
367
672
|
|
|
673
|
+
// src/dummy/extras.ts
|
|
674
|
+
var day = 864e5;
|
|
675
|
+
var now = Date.UTC(2026, 9, 5, 9, 0);
|
|
676
|
+
function frozenConversation(topic, reply) {
|
|
677
|
+
return [
|
|
678
|
+
{ id: `f-${topic}-1`, role: "user", text: topic, createdAt: now - 3 * day },
|
|
679
|
+
{
|
|
680
|
+
id: `f-${topic}-2`,
|
|
681
|
+
role: "assistant",
|
|
682
|
+
status: "done",
|
|
683
|
+
createdAt: now - 3 * day + 6e4,
|
|
684
|
+
parts: [{ type: "text", text: reply }]
|
|
685
|
+
}
|
|
686
|
+
];
|
|
687
|
+
}
|
|
688
|
+
var frozen = [
|
|
689
|
+
{
|
|
690
|
+
cone: {
|
|
691
|
+
id: "cone-onboarding",
|
|
692
|
+
name: "onboarding",
|
|
693
|
+
title: "Write the onboarding guide for new contributors",
|
|
694
|
+
model: "claude-sonnet-5-5",
|
|
695
|
+
messages: 42,
|
|
696
|
+
frozenAt: now - 2 * day
|
|
697
|
+
},
|
|
698
|
+
messages: frozenConversation(
|
|
699
|
+
"Write the onboarding guide for new contributors",
|
|
700
|
+
"The guide is in `/workspace/harbor/docs/onboarding.md`, with setup, tests and the release checklist."
|
|
701
|
+
)
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
cone: {
|
|
705
|
+
id: "cone-kv-spike",
|
|
706
|
+
name: "kv-spike",
|
|
707
|
+
title: "Spike: can the cache move to KV without a cold start?",
|
|
708
|
+
model: "claude-opus-5-5",
|
|
709
|
+
messages: 118,
|
|
710
|
+
frozenAt: now - 6 * day
|
|
711
|
+
},
|
|
712
|
+
messages: frozenConversation(
|
|
713
|
+
"Spike: can the cache move to KV without a cold start?",
|
|
714
|
+
"Yes, with a 5-minute in-memory layer in front. Reads from KV add 12 ms at the 95th percentile."
|
|
715
|
+
)
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
cone: {
|
|
719
|
+
id: "cone-q3-report",
|
|
720
|
+
name: "q3-report",
|
|
721
|
+
title: "Summarize Q3 uptime and incidents",
|
|
722
|
+
model: "claude-haiku-4-5",
|
|
723
|
+
messages: 17,
|
|
724
|
+
frozenAt: now - 21 * day
|
|
725
|
+
},
|
|
726
|
+
messages: frozenConversation(
|
|
727
|
+
"Summarize Q3 uptime and incidents",
|
|
728
|
+
"Uptime 99.96%. Two incidents: an expired certificate and an upstream outage."
|
|
729
|
+
)
|
|
730
|
+
}
|
|
731
|
+
];
|
|
732
|
+
var memories = [
|
|
733
|
+
{
|
|
734
|
+
id: "mem-1",
|
|
735
|
+
scope: "global",
|
|
736
|
+
section: "Preferences",
|
|
737
|
+
title: "Lead with the result",
|
|
738
|
+
body: "Keep replies short. Lead with the result, then the evidence.",
|
|
739
|
+
tag: "feedback",
|
|
740
|
+
updatedAt: now - 5 * day
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
id: "mem-2",
|
|
744
|
+
scope: "global",
|
|
745
|
+
section: "Preferences",
|
|
746
|
+
title: "Tests before \u201Cfixed\u201D",
|
|
747
|
+
body: "Run the tests before saying something is fixed, and quote the summary line.",
|
|
748
|
+
tag: "feedback",
|
|
749
|
+
updatedAt: now - 9 * day
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
id: "mem-3",
|
|
753
|
+
scope: "global",
|
|
754
|
+
section: "About the user",
|
|
755
|
+
title: "Works in UTC",
|
|
756
|
+
body: "Schedules, timestamps and cron expressions are in UTC unless stated otherwise.",
|
|
757
|
+
tag: "user",
|
|
758
|
+
updatedAt: now - 30 * day
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
id: "mem-4",
|
|
762
|
+
scope: "global",
|
|
763
|
+
section: "Projects",
|
|
764
|
+
title: "harbor",
|
|
765
|
+
body: "Forecast API, deployed as a worker. Cache bugs show up around midnight UTC. Staging deploys are frozen on Fridays.",
|
|
766
|
+
tag: "project",
|
|
767
|
+
updatedAt: now - day
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
id: "mem-5",
|
|
771
|
+
scope: "global",
|
|
772
|
+
section: "Projects",
|
|
773
|
+
title: "release-notes",
|
|
774
|
+
body: "Drafted every Friday from merged pull requests, grouped as features, fixes and chores.",
|
|
775
|
+
tag: "project",
|
|
776
|
+
updatedAt: now - 4 * day
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
id: "mem-6",
|
|
780
|
+
scope: "cone-harbor",
|
|
781
|
+
section: "Conventions",
|
|
782
|
+
title: "Cache keys",
|
|
783
|
+
body: "Cache keys are `city:UTC-date`. Never key on local time.",
|
|
784
|
+
tag: "project",
|
|
785
|
+
updatedAt: now - 2 * day
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
id: "mem-7",
|
|
789
|
+
scope: "cone-harbor",
|
|
790
|
+
section: "Conventions",
|
|
791
|
+
title: "Retries",
|
|
792
|
+
body: "Retry upstream calls three times with exponential backoff starting at 250 ms.",
|
|
793
|
+
tag: null,
|
|
794
|
+
updatedAt: now - 2 * day
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
id: "mem-8",
|
|
798
|
+
scope: "cone-release",
|
|
799
|
+
section: "Style",
|
|
800
|
+
title: "Past tense, no ticket numbers",
|
|
801
|
+
body: "One sentence per change, in the past tense, without ticket numbers.",
|
|
802
|
+
tag: "feedback",
|
|
803
|
+
updatedAt: now - 7 * day
|
|
804
|
+
}
|
|
805
|
+
];
|
|
806
|
+
var sprinkleStyle = `
|
|
807
|
+
body { margin: 0; padding: 16px; font: 13px/1.45 var(--font, system-ui, sans-serif); color: var(--fg); background: var(--bg); }
|
|
808
|
+
h1 { font-size: 15px; margin: 0 0 12px; }
|
|
809
|
+
ul { list-style: none; padding: 0; margin: 0 0 12px; display: grid; gap: 6px; }
|
|
810
|
+
li { display: flex; gap: 8px; align-items: center; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; }
|
|
811
|
+
li .state { margin-left: auto; color: var(--muted); }
|
|
812
|
+
li.done .state { color: var(--positive); }
|
|
813
|
+
button { font: inherit; padding: 4px 12px; border-radius: 14px; border: 1px solid var(--line); background: var(--bg); color: var(--fg); cursor: pointer; }
|
|
814
|
+
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
815
|
+
.actions { display: flex; gap: 8px; }
|
|
816
|
+
p.note { color: var(--muted); }
|
|
817
|
+
`;
|
|
818
|
+
var sprinkleScript = `
|
|
819
|
+
document.addEventListener('click', (event) => {
|
|
820
|
+
const button = event.target.closest('[data-event]');
|
|
821
|
+
if (!button) return;
|
|
822
|
+
parent.postMessage({ type: 'sprinkle-event', event: button.dataset.event, detail: button.dataset.detail ?? null }, '*');
|
|
823
|
+
const note = document.querySelector('.note');
|
|
824
|
+
if (note) note.textContent = 'Sent \u201C' + button.textContent.trim() + '\u201D to the agent.';
|
|
825
|
+
});
|
|
826
|
+
`;
|
|
827
|
+
function page2(body) {
|
|
828
|
+
return `<!doctype html><html><head><meta charset="utf-8"><style>${sprinkleStyle}</style></head><body>${body}<script>${sprinkleScript}<\/script></body></html>`;
|
|
829
|
+
}
|
|
830
|
+
var sprinkles = [
|
|
831
|
+
{
|
|
832
|
+
id: "release-board",
|
|
833
|
+
name: "release-board",
|
|
834
|
+
title: "Release board",
|
|
835
|
+
icon: "sp-icon-checkmark-circle",
|
|
836
|
+
agentId: "cone-release",
|
|
837
|
+
html: page2(`<h1>harbor 1.9.0</h1>
|
|
838
|
+
<ul>
|
|
839
|
+
<li class="done">Retry pull request merged <span class="state">done</span></li>
|
|
840
|
+
<li class="done">Release notes drafted <span class="state">done</span></li>
|
|
841
|
+
<li>Docs updated to Celsius <span class="state">in review</span></li>
|
|
842
|
+
<li>Staging smoke test <span class="state">waiting</span></li>
|
|
843
|
+
</ul>
|
|
844
|
+
<div class="actions"><button class="primary" data-event="ship" data-detail="1.9.0">Ship 1.9.0</button><button data-event="hold">Hold</button></div>
|
|
845
|
+
<p class="note"></p>`)
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
id: "loose-ends",
|
|
849
|
+
name: "loose-ends",
|
|
850
|
+
title: "Loose ends",
|
|
851
|
+
icon: "sp-icon-filter",
|
|
852
|
+
agentId: "cone-sliccy",
|
|
853
|
+
html: page2(`<h1>Loose ends</h1>
|
|
854
|
+
<ul>
|
|
855
|
+
<li>Reply to the vendor about Thursday <button data-event="done" data-detail="vendor">Done</button></li>
|
|
856
|
+
<li>Renew the staging certificate <button data-event="done" data-detail="certificate">Done</button></li>
|
|
857
|
+
<li>Move the cache to KV <button data-event="done" data-detail="kv">Done</button></li>
|
|
858
|
+
</ul>
|
|
859
|
+
<p class="note"></p>`)
|
|
860
|
+
}
|
|
861
|
+
];
|
|
862
|
+
var tray = {
|
|
863
|
+
name: "sliccstart",
|
|
864
|
+
kind: "sliccstart",
|
|
865
|
+
connection: "live",
|
|
866
|
+
role: "leader",
|
|
867
|
+
followers: [
|
|
868
|
+
{ id: "f-phone", name: "Phone", device: "phone", since: now - 36e5 },
|
|
869
|
+
{ id: "f-laptop", name: "Second browser", device: "browser", since: now - 6e5 }
|
|
870
|
+
],
|
|
871
|
+
spent: 23.67,
|
|
872
|
+
rate: 0.59,
|
|
873
|
+
budget: { percent: 32, window: "weekly", resets: "resets in 4 days" },
|
|
874
|
+
joinUrl: "https://www.example.com/join/7Q4D-K9TX"
|
|
875
|
+
};
|
|
876
|
+
|
|
368
877
|
// src/dummy/files.ts
|
|
369
878
|
function parents(path) {
|
|
370
879
|
const parts = path.split("/").slice(1, -1);
|
|
@@ -390,10 +899,10 @@ var DummyFiles = class extends Emitter {
|
|
|
390
899
|
#mkdirs(path) {
|
|
391
900
|
for (const directory of parents(path)) this.#directories.add(directory);
|
|
392
901
|
}
|
|
393
|
-
#put(path, text,
|
|
902
|
+
#put(path, text, now2) {
|
|
394
903
|
this.#mkdirs(path);
|
|
395
904
|
this.#contents.set(path, text);
|
|
396
|
-
this.#modified.set(path,
|
|
905
|
+
this.#modified.set(path, now2);
|
|
397
906
|
}
|
|
398
907
|
#entries() {
|
|
399
908
|
const directories2 = [...this.#directories].map(
|
|
@@ -465,6 +974,672 @@ var DummyFiles = class extends Emitter {
|
|
|
465
974
|
}
|
|
466
975
|
};
|
|
467
976
|
|
|
977
|
+
// src/dummy/showcase.ts
|
|
978
|
+
var start = Date.UTC(2026, 9, 4, 16, 0);
|
|
979
|
+
var at = (minutes) => start + minutes * 6e4;
|
|
980
|
+
function picture(label, from, to, width = 640, height = 400) {
|
|
981
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${from}"/><stop offset="1" stop-color="${to}"/></linearGradient></defs><rect width="${width}" height="${height}" fill="url(#g)"/><text x="50%" y="50%" font-family="sans-serif" font-size="${Math.round(height / 9)}" fill="#ffffff" text-anchor="middle" dominant-baseline="middle">${label}</text></svg>`;
|
|
982
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
983
|
+
}
|
|
984
|
+
function tone(seconds = 0.6, frequency = 440, rate = 8e3) {
|
|
985
|
+
const samples = Math.round(seconds * rate);
|
|
986
|
+
const bytes = new Uint8Array(44 + samples);
|
|
987
|
+
const view = new DataView(bytes.buffer);
|
|
988
|
+
const text = (offset, value) => {
|
|
989
|
+
for (let i = 0; i < value.length; i++) bytes[offset + i] = value.charCodeAt(i);
|
|
990
|
+
};
|
|
991
|
+
text(0, "RIFF");
|
|
992
|
+
view.setUint32(4, 36 + samples, true);
|
|
993
|
+
text(8, "WAVEfmt ");
|
|
994
|
+
view.setUint32(16, 16, true);
|
|
995
|
+
view.setUint16(20, 1, true);
|
|
996
|
+
view.setUint16(22, 1, true);
|
|
997
|
+
view.setUint32(24, rate, true);
|
|
998
|
+
view.setUint32(28, rate, true);
|
|
999
|
+
view.setUint16(32, 1, true);
|
|
1000
|
+
view.setUint16(34, 8, true);
|
|
1001
|
+
text(36, "data");
|
|
1002
|
+
view.setUint32(40, samples, true);
|
|
1003
|
+
for (let i = 0; i < samples; i++) {
|
|
1004
|
+
const fade = Math.min(1, (samples - i) / (rate / 10));
|
|
1005
|
+
bytes[44 + i] = 128 + Math.round(60 * fade * Math.sin(2 * Math.PI * frequency * i / rate));
|
|
1006
|
+
}
|
|
1007
|
+
let binary = "";
|
|
1008
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
1009
|
+
return `data:audio/wav;base64,${btoa(binary)}`;
|
|
1010
|
+
}
|
|
1011
|
+
function tool(id, name, title, input, output, extra = {}) {
|
|
1012
|
+
return { id, name, title, input, output, status: "done", paths: [], ...extra };
|
|
1013
|
+
}
|
|
1014
|
+
var markdown = [
|
|
1015
|
+
"## The forecast API in one page",
|
|
1016
|
+
"",
|
|
1017
|
+
"harbor answers `GET /forecast?city=\u2026` with the day\u2019s **high**, **low** and a *short summary*, and caches per city for an hour. ~~XML output~~ is gone.",
|
|
1018
|
+
"",
|
|
1019
|
+
"### How a request flows",
|
|
1020
|
+
"",
|
|
1021
|
+
"1. The worker parses the city.",
|
|
1022
|
+
"2. The cache answers if it has a fresh entry:",
|
|
1023
|
+
" - same UTC day",
|
|
1024
|
+
" - younger than one hour",
|
|
1025
|
+
"3. Otherwise the upstream call runs, with three retries.",
|
|
1026
|
+
"",
|
|
1027
|
+
"> Upstream is slow at the top of the hour. Retries back off at 250, 500 and 1000 ms.",
|
|
1028
|
+
"",
|
|
1029
|
+
"```ts",
|
|
1030
|
+
"export async function forecast(city: string): Promise<Forecast> {",
|
|
1031
|
+
" const cached = cache.get(city);",
|
|
1032
|
+
" if (cached) return cached;",
|
|
1033
|
+
" return cache.set(city, await retry(() => upstream(city)));",
|
|
1034
|
+
"}",
|
|
1035
|
+
"```",
|
|
1036
|
+
"",
|
|
1037
|
+
"| Route | Cache | Typical latency |",
|
|
1038
|
+
"| --- | --- | ---: |",
|
|
1039
|
+
"| `/forecast` | 1 hour, per city | 38 ms |",
|
|
1040
|
+
"| `/health` | none | 2 ms |",
|
|
1041
|
+
"| `/forecast` (miss) | \u2014 | 410 ms |",
|
|
1042
|
+
"",
|
|
1043
|
+
"- [x] TTL fix merged",
|
|
1044
|
+
"- [ ] Move the cache to KV",
|
|
1045
|
+
"",
|
|
1046
|
+
"Details are in the [API reference](https://api.example.com/docs/v2/forecast). A long path that must wrap: `/workspace/harbor/node_modules/.cache/wrangler/state/v3/cache/miniflare-CacheObject/7c1f0e3b9a5d4e2f8b6a1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f.sqlite`."
|
|
1047
|
+
].join("\n");
|
|
1048
|
+
var toolShapes = [
|
|
1049
|
+
tool("k-t1", "bash", "Count the routes", "rg -c 'pathname ===' src", "src/index.ts:2"),
|
|
1050
|
+
tool(
|
|
1051
|
+
"k-t2",
|
|
1052
|
+
"read_file",
|
|
1053
|
+
"Read the router",
|
|
1054
|
+
"/workspace/harbor/src/index.ts",
|
|
1055
|
+
"import { forecast } from './routes/forecast.ts';\n\u2026",
|
|
1056
|
+
{
|
|
1057
|
+
paths: ["/workspace/harbor/src/index.ts"]
|
|
1058
|
+
}
|
|
1059
|
+
),
|
|
1060
|
+
tool(
|
|
1061
|
+
"k-t3",
|
|
1062
|
+
"write_file",
|
|
1063
|
+
"Create the KV binding notes",
|
|
1064
|
+
"/workspace/harbor/docs/kv.md",
|
|
1065
|
+
"Wrote 18 lines",
|
|
1066
|
+
{
|
|
1067
|
+
paths: ["/workspace/harbor/docs/kv.md"]
|
|
1068
|
+
}
|
|
1069
|
+
),
|
|
1070
|
+
tool(
|
|
1071
|
+
"k-t4",
|
|
1072
|
+
"edit_file",
|
|
1073
|
+
"Rename the cache key",
|
|
1074
|
+
"/workspace/harbor/src/lib/cache.ts",
|
|
1075
|
+
'No match for "cityKey(" in src/lib/cache.ts',
|
|
1076
|
+
{
|
|
1077
|
+
status: "error",
|
|
1078
|
+
paths: ["/workspace/harbor/src/lib/cache.ts"]
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
tool(
|
|
1082
|
+
"k-t5",
|
|
1083
|
+
"grep",
|
|
1084
|
+
"Find TODOs",
|
|
1085
|
+
'TODO --glob "src/**"',
|
|
1086
|
+
"src/routes/forecast.ts:14: TODO: cache negative results\nsrc/lib/units.ts:3: TODO: round half to even"
|
|
1087
|
+
),
|
|
1088
|
+
tool(
|
|
1089
|
+
"k-t6",
|
|
1090
|
+
"web_fetch",
|
|
1091
|
+
"Fetch the API changelog",
|
|
1092
|
+
"https://api.example.com/changelog.json",
|
|
1093
|
+
'{ "latest": "2.4.1", "breaking": false }'
|
|
1094
|
+
),
|
|
1095
|
+
tool(
|
|
1096
|
+
"k-t7",
|
|
1097
|
+
"browser",
|
|
1098
|
+
"Screenshot the preview",
|
|
1099
|
+
"screenshot http://localhost:8787/",
|
|
1100
|
+
"Captured 1280\xD7800",
|
|
1101
|
+
{
|
|
1102
|
+
image: picture("localhost:8787", "#3b63fb", "#0a7a43", 640, 400)
|
|
1103
|
+
}
|
|
1104
|
+
),
|
|
1105
|
+
tool(
|
|
1106
|
+
"k-t8",
|
|
1107
|
+
"scoop_scoop",
|
|
1108
|
+
"Start a scoop for the docs",
|
|
1109
|
+
"name: docs-pass\ntask: Update the forecast page to Celsius",
|
|
1110
|
+
"Scoop docs-pass is running"
|
|
1111
|
+
),
|
|
1112
|
+
tool(
|
|
1113
|
+
"k-t9",
|
|
1114
|
+
"feed_scoop",
|
|
1115
|
+
"Hand the screenshots to docs-pass",
|
|
1116
|
+
"scoop_name: docs-pass\nmessage: Use the new screenshots in /tmp/shots",
|
|
1117
|
+
"Delivered"
|
|
1118
|
+
),
|
|
1119
|
+
tool(
|
|
1120
|
+
"k-t10",
|
|
1121
|
+
"send_message",
|
|
1122
|
+
"Tell quiet-otter the tests pass",
|
|
1123
|
+
"to: quiet-otter\nmessage: CI is green, merge when ready",
|
|
1124
|
+
"Delivered"
|
|
1125
|
+
),
|
|
1126
|
+
tool(
|
|
1127
|
+
"k-t11",
|
|
1128
|
+
"drop_scoop",
|
|
1129
|
+
"Wrap up amber-heron",
|
|
1130
|
+
"scoop_name: amber-heron",
|
|
1131
|
+
"Dropped amber-heron; its notes are in /shared/notes"
|
|
1132
|
+
),
|
|
1133
|
+
tool(
|
|
1134
|
+
"k-t12",
|
|
1135
|
+
"update_global_memory",
|
|
1136
|
+
"Remember the cache rule",
|
|
1137
|
+
"Cache keys are per city and per UTC day.",
|
|
1138
|
+
"Memory updated (3 sections, 412 characters)"
|
|
1139
|
+
),
|
|
1140
|
+
tool("k-t13", "bash", "Deploy to staging", "wrangler deploy --env staging", "Stopped", {
|
|
1141
|
+
status: "cancelled"
|
|
1142
|
+
}),
|
|
1143
|
+
tool("k-t14", "bash", "Watch the logs", "wrangler tail --env staging", "", { status: "running" })
|
|
1144
|
+
];
|
|
1145
|
+
var licks = [
|
|
1146
|
+
[
|
|
1147
|
+
"webhook",
|
|
1148
|
+
"GitHub \xB7 pull_request.opened",
|
|
1149
|
+
"example/harbor#58 Add retries for upstream timeouts",
|
|
1150
|
+
'{\n "action": "opened",\n "number": 58,\n "repository": { "full_name": "example/harbor" }\n}',
|
|
1151
|
+
{}
|
|
1152
|
+
],
|
|
1153
|
+
[
|
|
1154
|
+
"cron",
|
|
1155
|
+
"Every weekday 09:00",
|
|
1156
|
+
"Morning summary for the harbor cone",
|
|
1157
|
+
void 0,
|
|
1158
|
+
{ count: 3 }
|
|
1159
|
+
],
|
|
1160
|
+
[
|
|
1161
|
+
"sprinkle",
|
|
1162
|
+
"Release board",
|
|
1163
|
+
"Button \u201CShip 1.9.0\u201D clicked",
|
|
1164
|
+
'{ "button": "ship", "version": "1.9.0" }',
|
|
1165
|
+
{}
|
|
1166
|
+
],
|
|
1167
|
+
[
|
|
1168
|
+
"fswatch",
|
|
1169
|
+
"/workspace/harbor/src",
|
|
1170
|
+
"2 files changed",
|
|
1171
|
+
"modified /workspace/harbor/src/lib/cache.ts\ncreated /workspace/harbor/src/lib/retry.ts",
|
|
1172
|
+
{}
|
|
1173
|
+
],
|
|
1174
|
+
[
|
|
1175
|
+
"session-reload",
|
|
1176
|
+
"Session restored",
|
|
1177
|
+
"The page reloaded; 4 cones resumed from storage",
|
|
1178
|
+
void 0,
|
|
1179
|
+
{}
|
|
1180
|
+
],
|
|
1181
|
+
[
|
|
1182
|
+
"navigate",
|
|
1183
|
+
"api.example.com",
|
|
1184
|
+
"A page offered a SLICC handoff: open the forecast docs",
|
|
1185
|
+
'{ "verb": "open", "target": "https://api.example.com/docs/v2/forecast" }',
|
|
1186
|
+
{}
|
|
1187
|
+
],
|
|
1188
|
+
[
|
|
1189
|
+
"discovery",
|
|
1190
|
+
"api.example.com",
|
|
1191
|
+
"Found an ai-catalog.json with 3 tools",
|
|
1192
|
+
"https://api.example.com/.well-known/ai-catalog.json",
|
|
1193
|
+
{}
|
|
1194
|
+
],
|
|
1195
|
+
["upgrade", "SLICC updated", "6.4.0 \u2192 6.5.0", void 0, {}],
|
|
1196
|
+
[
|
|
1197
|
+
"workflow",
|
|
1198
|
+
"release-notes",
|
|
1199
|
+
"Step 2 of 4 finished: collect merged pull requests",
|
|
1200
|
+
void 0,
|
|
1201
|
+
{}
|
|
1202
|
+
],
|
|
1203
|
+
["bash", "Job 3", "npm run build exited with status 0 after 41 s", void 0, {}],
|
|
1204
|
+
["jshd", "collect.jsh", "Script finished: 12 pull requests since v1.8.0", void 0, {}],
|
|
1205
|
+
["preview", "Preview bridge", "Connected from localhost:8787", void 0, {}],
|
|
1206
|
+
[
|
|
1207
|
+
"cherry",
|
|
1208
|
+
"Host page",
|
|
1209
|
+
"Event \u201Ccart-updated\u201D from shop.example.com",
|
|
1210
|
+
'{ "items": 3, "total": 42.5 }',
|
|
1211
|
+
{}
|
|
1212
|
+
],
|
|
1213
|
+
["scoop-notify", "docs-pass", "Finished: the forecast page now shows Celsius", void 0, {}],
|
|
1214
|
+
["scoop-idle", "amber-heron", "Idle for 30 minutes", void 0, {}],
|
|
1215
|
+
["scoop-wait", "quiet-otter", "The wait for CI resolved: all checks passed", void 0, {}],
|
|
1216
|
+
[
|
|
1217
|
+
"sudo-request",
|
|
1218
|
+
"docs-pass",
|
|
1219
|
+
"Wants to push to example/harbor (branch docs/celsius)",
|
|
1220
|
+
"git push origin docs/celsius",
|
|
1221
|
+
{ state: "pending" }
|
|
1222
|
+
],
|
|
1223
|
+
[
|
|
1224
|
+
"sudo-request",
|
|
1225
|
+
"quiet-otter",
|
|
1226
|
+
"Wants to read secrets: UPSTREAM_TOKEN",
|
|
1227
|
+
void 0,
|
|
1228
|
+
{ state: "confirmed" }
|
|
1229
|
+
],
|
|
1230
|
+
[
|
|
1231
|
+
"sudo-request",
|
|
1232
|
+
"amber-heron",
|
|
1233
|
+
"Wants to run: rm -rf /workspace/harbor/node_modules",
|
|
1234
|
+
void 0,
|
|
1235
|
+
{ state: "dismissed" }
|
|
1236
|
+
]
|
|
1237
|
+
];
|
|
1238
|
+
var attachmentsMessage = {
|
|
1239
|
+
id: "k-u2",
|
|
1240
|
+
role: "user",
|
|
1241
|
+
text: "Here is the mockup, my notes and the old spec.",
|
|
1242
|
+
createdAt: at(4),
|
|
1243
|
+
attachments: [
|
|
1244
|
+
{
|
|
1245
|
+
id: "k-a1",
|
|
1246
|
+
name: "forecast-card.png",
|
|
1247
|
+
kind: "image",
|
|
1248
|
+
mimeType: "image/png",
|
|
1249
|
+
size: 48211,
|
|
1250
|
+
url: picture("Mockup", "#f5a623", "#d0021b", 480, 320)
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
id: "k-a2",
|
|
1254
|
+
name: "notes.txt",
|
|
1255
|
+
kind: "text",
|
|
1256
|
+
mimeType: "text/plain",
|
|
1257
|
+
size: 214,
|
|
1258
|
+
text: "Show \xB0C first.\nRound to whole degrees.\nKeep the summary under 60 characters."
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
id: "k-a3",
|
|
1262
|
+
name: "spec-v1.pdf",
|
|
1263
|
+
kind: "file",
|
|
1264
|
+
mimeType: "application/pdf",
|
|
1265
|
+
size: 1204331,
|
|
1266
|
+
path: "/home/user/downloads/spec-v1.pdf"
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
id: "k-a4",
|
|
1270
|
+
name: "UPSTREAM_TOKEN",
|
|
1271
|
+
kind: "secret",
|
|
1272
|
+
mimeType: "application/octet-stream",
|
|
1273
|
+
size: 0
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
id: "k-a5",
|
|
1277
|
+
name: "recording.mov",
|
|
1278
|
+
kind: "file",
|
|
1279
|
+
mimeType: "video/quicktime",
|
|
1280
|
+
size: 98304e3,
|
|
1281
|
+
error: "Too large: attachments are limited to 25 MB"
|
|
1282
|
+
}
|
|
1283
|
+
]
|
|
1284
|
+
};
|
|
1285
|
+
var showcaseAgent = {
|
|
1286
|
+
id: "cone-kitchen",
|
|
1287
|
+
name: "kitchen-sink",
|
|
1288
|
+
kind: "cone",
|
|
1289
|
+
parentId: null,
|
|
1290
|
+
status: "idle",
|
|
1291
|
+
model: "claude-opus-5-5",
|
|
1292
|
+
contextFill: 0.47,
|
|
1293
|
+
unread: 0
|
|
1294
|
+
};
|
|
1295
|
+
function opening() {
|
|
1296
|
+
return [
|
|
1297
|
+
{
|
|
1298
|
+
id: "k-u1",
|
|
1299
|
+
role: "user",
|
|
1300
|
+
text: "Give me the full tour of harbor, everything you can show.",
|
|
1301
|
+
createdAt: at(0)
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
id: "k-m1",
|
|
1305
|
+
role: "assistant",
|
|
1306
|
+
status: "done",
|
|
1307
|
+
createdAt: at(1),
|
|
1308
|
+
model: "claude-opus-5-5",
|
|
1309
|
+
usage: { input: 18204, output: 912, cost: 0.31 },
|
|
1310
|
+
parts: [
|
|
1311
|
+
{
|
|
1312
|
+
type: "thinking",
|
|
1313
|
+
text: "Start with the shape of the API, then show the pieces one kind at a time."
|
|
1314
|
+
},
|
|
1315
|
+
{ type: "text", text: markdown }
|
|
1316
|
+
]
|
|
1317
|
+
},
|
|
1318
|
+
attachmentsMessage,
|
|
1319
|
+
{
|
|
1320
|
+
id: "k-m2",
|
|
1321
|
+
role: "assistant",
|
|
1322
|
+
status: "done",
|
|
1323
|
+
createdAt: at(5),
|
|
1324
|
+
parts: [
|
|
1325
|
+
{
|
|
1326
|
+
type: "text",
|
|
1327
|
+
text: "Mockup, notes and spec received. The recording was too large to attach. Here are the current screens and the alert sound:"
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
type: "media",
|
|
1331
|
+
items: [
|
|
1332
|
+
{
|
|
1333
|
+
kind: "image",
|
|
1334
|
+
src: picture("Forecast card", "#3b63fb", "#7a5af8"),
|
|
1335
|
+
alt: "The forecast card in light mode"
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
kind: "image",
|
|
1339
|
+
src: picture("Dark card", "#1d1d1d", "#3b63fb"),
|
|
1340
|
+
alt: "The forecast card in dark mode"
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
kind: "image",
|
|
1344
|
+
src: picture("Phone", "#0a7a43", "#12b886", 360, 640),
|
|
1345
|
+
alt: "The forecast card on a phone"
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
kind: "video",
|
|
1349
|
+
src: "",
|
|
1350
|
+
poster: picture("\u25B6 Walkthrough", "#292929", "#717171"),
|
|
1351
|
+
alt: "A walkthrough of the forecast page"
|
|
1352
|
+
},
|
|
1353
|
+
{ kind: "audio", src: tone(), alt: "The storm alert sound" }
|
|
1354
|
+
]
|
|
1355
|
+
}
|
|
1356
|
+
]
|
|
1357
|
+
},
|
|
1358
|
+
{ id: "k-u3", role: "user", text: "Show me every tool you used.", createdAt: at(8) },
|
|
1359
|
+
{
|
|
1360
|
+
id: "k-m3",
|
|
1361
|
+
role: "assistant",
|
|
1362
|
+
status: "done",
|
|
1363
|
+
createdAt: at(9),
|
|
1364
|
+
parts: [
|
|
1365
|
+
{ type: "text", text: "Every tool from today\u2019s run, in order:" },
|
|
1366
|
+
...toolShapes.map((call) => ({ type: "tool", tool: call }))
|
|
1367
|
+
]
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
id: "k-tool1",
|
|
1371
|
+
role: "tool",
|
|
1372
|
+
createdAt: at(12),
|
|
1373
|
+
tool: tool(
|
|
1374
|
+
"k-t15",
|
|
1375
|
+
"bash",
|
|
1376
|
+
"You ran a command",
|
|
1377
|
+
"git log --oneline -3",
|
|
1378
|
+
"a1b2c3d fix: expire entries from the previous day\ne4f5a6b feat: retry upstream timeouts\n0c9d8e7 chore: update wrangler"
|
|
1379
|
+
)
|
|
1380
|
+
}
|
|
1381
|
+
];
|
|
1382
|
+
}
|
|
1383
|
+
function work() {
|
|
1384
|
+
return [
|
|
1385
|
+
{
|
|
1386
|
+
id: "k-m4",
|
|
1387
|
+
role: "assistant",
|
|
1388
|
+
status: "done",
|
|
1389
|
+
createdAt: at(14),
|
|
1390
|
+
parts: [
|
|
1391
|
+
{ type: "text", text: "Here is where things stand." },
|
|
1392
|
+
{
|
|
1393
|
+
type: "card",
|
|
1394
|
+
card: {
|
|
1395
|
+
variant: "pr",
|
|
1396
|
+
number: 58,
|
|
1397
|
+
title: "Add retries for upstream timeouts",
|
|
1398
|
+
branch: "fix/retry-upstream",
|
|
1399
|
+
checks: "4 of 4 checks passed",
|
|
1400
|
+
additions: 42,
|
|
1401
|
+
deletions: 7,
|
|
1402
|
+
files: 3,
|
|
1403
|
+
tone: "positive"
|
|
1404
|
+
}
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
type: "card",
|
|
1408
|
+
card: {
|
|
1409
|
+
variant: "tool",
|
|
1410
|
+
title: "Visual regression suite",
|
|
1411
|
+
badge: "bash",
|
|
1412
|
+
status: "2 snapshots changed",
|
|
1413
|
+
files: ["forecast-card.png", "forecast-card-dark.png"],
|
|
1414
|
+
tone: "notice"
|
|
1415
|
+
}
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
type: "card",
|
|
1419
|
+
card: {
|
|
1420
|
+
variant: "light",
|
|
1421
|
+
title: "Staging is healthy",
|
|
1422
|
+
detail: "/health answered in 2 ms",
|
|
1423
|
+
tone: "informative"
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
type: "plan",
|
|
1428
|
+
items: [
|
|
1429
|
+
"Merge the retry pull request",
|
|
1430
|
+
"Move the cache to KV",
|
|
1431
|
+
"Update the forecast docs to Celsius",
|
|
1432
|
+
"Tag 1.9.0"
|
|
1433
|
+
]
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
type: "check",
|
|
1437
|
+
items: [
|
|
1438
|
+
{ text: "TTL fix merged" },
|
|
1439
|
+
{ text: "Retries reviewed", tone: "informative" },
|
|
1440
|
+
{ text: "Docs pass running", tone: "notice" },
|
|
1441
|
+
{ text: "KV migration not started", tone: "negative" }
|
|
1442
|
+
]
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
type: "diff",
|
|
1446
|
+
path: "/workspace/harbor/src/lib/units.ts",
|
|
1447
|
+
before: "export function toCelsius(f: number): number {\n return ((f - 32) * 5) / 9;\n}\n",
|
|
1448
|
+
after: "export function toCelsius(f: number): number {\n return Math.round(((f - 32) * 5) / 9);\n}\n"
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
type: "link",
|
|
1452
|
+
url: "https://api.example.com/docs/v2/forecast",
|
|
1453
|
+
title: "Forecast API reference",
|
|
1454
|
+
description: "Parameters, units and rate limits for /v2/forecast."
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
type: "delegation",
|
|
1458
|
+
kind: "scoop",
|
|
1459
|
+
scoop: "docs-pass",
|
|
1460
|
+
text: "Started a scoop to update the docs"
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
type: "delegation",
|
|
1464
|
+
kind: "feed",
|
|
1465
|
+
scoop: "docs-pass",
|
|
1466
|
+
text: "Handed over the new screenshots"
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
type: "delegation",
|
|
1470
|
+
kind: "sprinkle",
|
|
1471
|
+
scoop: "release-board",
|
|
1472
|
+
text: "Opened the release board sprinkle"
|
|
1473
|
+
},
|
|
1474
|
+
{ type: "delegation", kind: "drop", scoop: "amber-heron", text: "Wrapped up amber-heron" }
|
|
1475
|
+
]
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
id: "k-m5",
|
|
1479
|
+
role: "assistant",
|
|
1480
|
+
status: "done",
|
|
1481
|
+
createdAt: at(18),
|
|
1482
|
+
parts: [
|
|
1483
|
+
{ type: "text", text: "Three decisions from earlier:" },
|
|
1484
|
+
{
|
|
1485
|
+
type: "question",
|
|
1486
|
+
question: {
|
|
1487
|
+
id: "k-q1",
|
|
1488
|
+
kind: "yes-no",
|
|
1489
|
+
question: "Tag 1.9.0 after the merge?",
|
|
1490
|
+
options: [],
|
|
1491
|
+
state: "answered",
|
|
1492
|
+
answer: "Yes"
|
|
1493
|
+
}
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
type: "question",
|
|
1497
|
+
question: {
|
|
1498
|
+
id: "k-q2",
|
|
1499
|
+
kind: "text",
|
|
1500
|
+
question: "What should the release be called?",
|
|
1501
|
+
options: [],
|
|
1502
|
+
state: "inert"
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
{
|
|
1506
|
+
type: "question",
|
|
1507
|
+
question: {
|
|
1508
|
+
id: "k-q3",
|
|
1509
|
+
kind: "choice",
|
|
1510
|
+
question: "Where should the cache live?",
|
|
1511
|
+
options: ["Keep it in memory", "Move it to KV", "Decide after the load test"],
|
|
1512
|
+
state: "open"
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
]
|
|
1516
|
+
}
|
|
1517
|
+
];
|
|
1518
|
+
}
|
|
1519
|
+
function events() {
|
|
1520
|
+
return [
|
|
1521
|
+
{
|
|
1522
|
+
id: "k-s1",
|
|
1523
|
+
role: "system",
|
|
1524
|
+
kind: "compaction",
|
|
1525
|
+
title: "Context compacted",
|
|
1526
|
+
text: "58 earlier messages were summarized.",
|
|
1527
|
+
trigger: "threshold",
|
|
1528
|
+
state: "summarized",
|
|
1529
|
+
createdAt: at(1440)
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
id: "k-s2",
|
|
1533
|
+
role: "system",
|
|
1534
|
+
kind: "compaction",
|
|
1535
|
+
title: "Compaction fell back",
|
|
1536
|
+
text: "The summary failed, so the oldest 20 messages were dropped instead.",
|
|
1537
|
+
trigger: "overflow",
|
|
1538
|
+
state: "fallback",
|
|
1539
|
+
createdAt: at(1441)
|
|
1540
|
+
},
|
|
1541
|
+
...licks.map(
|
|
1542
|
+
([channel, title, text, body, extra], index2) => ({
|
|
1543
|
+
id: `k-l${index2 + 1}`,
|
|
1544
|
+
role: "lick",
|
|
1545
|
+
channel,
|
|
1546
|
+
title,
|
|
1547
|
+
text,
|
|
1548
|
+
createdAt: at(1445 + index2),
|
|
1549
|
+
...body ? { body } : {},
|
|
1550
|
+
...extra
|
|
1551
|
+
})
|
|
1552
|
+
),
|
|
1553
|
+
{
|
|
1554
|
+
id: "k-u4",
|
|
1555
|
+
role: "user",
|
|
1556
|
+
text: "Actually, stop the deploy, staging is frozen today.",
|
|
1557
|
+
mode: "steer",
|
|
1558
|
+
createdAt: at(1470)
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
id: "k-m6",
|
|
1562
|
+
role: "assistant",
|
|
1563
|
+
status: "stopped",
|
|
1564
|
+
createdAt: at(1470),
|
|
1565
|
+
parts: [{ type: "text", text: "Stopping the staging deploy and leaving" }]
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
id: "k-u5",
|
|
1569
|
+
role: "user",
|
|
1570
|
+
text: "Update the forecast page to Celsius and use the new screenshots.",
|
|
1571
|
+
from: "harbor",
|
|
1572
|
+
createdAt: at(1472)
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
id: "k-m7",
|
|
1576
|
+
role: "assistant",
|
|
1577
|
+
status: "error",
|
|
1578
|
+
createdAt: at(1475),
|
|
1579
|
+
parts: [
|
|
1580
|
+
{
|
|
1581
|
+
type: "thinking",
|
|
1582
|
+
text: "The model rejected the request because the context is too large."
|
|
1583
|
+
},
|
|
1584
|
+
{
|
|
1585
|
+
type: "error",
|
|
1586
|
+
message: "The model returned 413: the request is larger than its context window.",
|
|
1587
|
+
action: "change-model"
|
|
1588
|
+
}
|
|
1589
|
+
]
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
id: "k-s3",
|
|
1593
|
+
role: "system",
|
|
1594
|
+
kind: "error",
|
|
1595
|
+
title: "docs-pass failed",
|
|
1596
|
+
text: "The Adobe session expired. Log in again to continue.",
|
|
1597
|
+
action: "login",
|
|
1598
|
+
createdAt: at(1476)
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
id: "k-s4",
|
|
1602
|
+
role: "system",
|
|
1603
|
+
kind: "error",
|
|
1604
|
+
title: "Rate limited",
|
|
1605
|
+
text: "Anthropic answered 429. Retrying in 30 seconds.",
|
|
1606
|
+
action: "retry",
|
|
1607
|
+
createdAt: at(1477)
|
|
1608
|
+
},
|
|
1609
|
+
{
|
|
1610
|
+
id: "k-s5",
|
|
1611
|
+
role: "system",
|
|
1612
|
+
kind: "notice",
|
|
1613
|
+
title: "Model changed",
|
|
1614
|
+
text: "This cone now uses Claude Opus 5.5.",
|
|
1615
|
+
createdAt: at(1478)
|
|
1616
|
+
}
|
|
1617
|
+
];
|
|
1618
|
+
}
|
|
1619
|
+
function showcase() {
|
|
1620
|
+
return [...opening(), ...work(), ...events()];
|
|
1621
|
+
}
|
|
1622
|
+
function showcaseQueue() {
|
|
1623
|
+
return [
|
|
1624
|
+
{
|
|
1625
|
+
id: "k-q-1",
|
|
1626
|
+
role: "user",
|
|
1627
|
+
text: "Then open a pull request for the docs.",
|
|
1628
|
+
createdAt: at(1480),
|
|
1629
|
+
queued: true,
|
|
1630
|
+
mode: "queue"
|
|
1631
|
+
},
|
|
1632
|
+
{
|
|
1633
|
+
id: "k-q-2",
|
|
1634
|
+
role: "user",
|
|
1635
|
+
text: "And post the link in the release channel.",
|
|
1636
|
+
createdAt: at(1481),
|
|
1637
|
+
queued: true,
|
|
1638
|
+
mode: "queue"
|
|
1639
|
+
}
|
|
1640
|
+
];
|
|
1641
|
+
}
|
|
1642
|
+
|
|
468
1643
|
// src/dummy/fixtures.ts
|
|
469
1644
|
var cacheBefore = `export interface Entry<T> {
|
|
470
1645
|
value: T;
|
|
@@ -743,6 +1918,7 @@ var agents = [
|
|
|
743
1918
|
contextFill: 0.21,
|
|
744
1919
|
unread: 0
|
|
745
1920
|
},
|
|
1921
|
+
showcaseAgent,
|
|
746
1922
|
{
|
|
747
1923
|
id: "cone-harbor",
|
|
748
1924
|
name: "harbor",
|
|
@@ -804,21 +1980,23 @@ var agents = [
|
|
|
804
1980
|
unread: 0
|
|
805
1981
|
}
|
|
806
1982
|
];
|
|
807
|
-
var
|
|
808
|
-
var
|
|
1983
|
+
var start2 = Date.UTC(2026, 9, 5, 8, 30);
|
|
1984
|
+
var at2 = (minutes) => start2 + minutes * 6e4;
|
|
1985
|
+
var queues = { "cone-kitchen": showcaseQueue() };
|
|
809
1986
|
var conversations = {
|
|
1987
|
+
"cone-kitchen": showcase(),
|
|
810
1988
|
"cone-sliccy": [
|
|
811
1989
|
{
|
|
812
1990
|
id: "m-s1",
|
|
813
1991
|
role: "user",
|
|
814
1992
|
text: "What is on my plate today?",
|
|
815
|
-
createdAt:
|
|
1993
|
+
createdAt: at2(0)
|
|
816
1994
|
},
|
|
817
1995
|
{
|
|
818
1996
|
id: "m-s2",
|
|
819
1997
|
role: "assistant",
|
|
820
1998
|
status: "done",
|
|
821
|
-
createdAt:
|
|
1999
|
+
createdAt: at2(1),
|
|
822
2000
|
parts: [
|
|
823
2001
|
{
|
|
824
2002
|
type: "tool",
|
|
@@ -842,23 +2020,23 @@ var conversations = {
|
|
|
842
2020
|
"cone-harbor": [
|
|
843
2021
|
{
|
|
844
2022
|
id: "m-h1",
|
|
845
|
-
role: "
|
|
846
|
-
|
|
2023
|
+
role: "lick",
|
|
2024
|
+
channel: "webhook",
|
|
847
2025
|
title: "GitHub \xB7 issue opened",
|
|
848
2026
|
text: "example/harbor#57: Forecast stays on yesterday after midnight UTC",
|
|
849
|
-
createdAt:
|
|
2027
|
+
createdAt: at2(2)
|
|
850
2028
|
},
|
|
851
2029
|
{
|
|
852
2030
|
id: "m-h2",
|
|
853
2031
|
role: "user",
|
|
854
2032
|
text: "The forecast endpoint serves stale data after midnight UTC. Find out why and fix it.",
|
|
855
|
-
createdAt:
|
|
2033
|
+
createdAt: at2(3)
|
|
856
2034
|
},
|
|
857
2035
|
{
|
|
858
2036
|
id: "m-h3",
|
|
859
2037
|
role: "assistant",
|
|
860
2038
|
status: "done",
|
|
861
|
-
createdAt:
|
|
2039
|
+
createdAt: at2(4),
|
|
862
2040
|
parts: [
|
|
863
2041
|
{
|
|
864
2042
|
type: "thinking",
|
|
@@ -920,11 +2098,11 @@ var conversations = {
|
|
|
920
2098
|
},
|
|
921
2099
|
{
|
|
922
2100
|
id: "m-h4",
|
|
923
|
-
role: "
|
|
924
|
-
|
|
2101
|
+
role: "lick",
|
|
2102
|
+
channel: "scoop-notify",
|
|
925
2103
|
title: "quiet-otter",
|
|
926
2104
|
text: "Started: add retries with backoff around the upstream fetch",
|
|
927
|
-
createdAt:
|
|
2105
|
+
createdAt: at2(9)
|
|
928
2106
|
}
|
|
929
2107
|
],
|
|
930
2108
|
"scoop-otter": [
|
|
@@ -932,13 +2110,13 @@ var conversations = {
|
|
|
932
2110
|
id: "m-o1",
|
|
933
2111
|
role: "user",
|
|
934
2112
|
text: "Wrap the upstream fetch in src/routes/forecast.ts with three retries and exponential backoff.",
|
|
935
|
-
createdAt:
|
|
2113
|
+
createdAt: at2(9)
|
|
936
2114
|
},
|
|
937
2115
|
{
|
|
938
2116
|
id: "m-o2",
|
|
939
2117
|
role: "assistant",
|
|
940
2118
|
status: "done",
|
|
941
|
-
createdAt:
|
|
2119
|
+
createdAt: at2(10),
|
|
942
2120
|
parts: [
|
|
943
2121
|
{
|
|
944
2122
|
type: "tool",
|
|
@@ -973,17 +2151,17 @@ var conversations = {
|
|
|
973
2151
|
"cone-release": [
|
|
974
2152
|
{
|
|
975
2153
|
id: "m-r1",
|
|
976
|
-
role: "
|
|
977
|
-
|
|
2154
|
+
role: "lick",
|
|
2155
|
+
channel: "cron",
|
|
978
2156
|
title: "Every Friday 09:00",
|
|
979
2157
|
text: "Draft the release notes for example/harbor",
|
|
980
|
-
createdAt:
|
|
2158
|
+
createdAt: at2(-1440)
|
|
981
2159
|
},
|
|
982
2160
|
{
|
|
983
2161
|
id: "m-r2",
|
|
984
2162
|
role: "assistant",
|
|
985
2163
|
status: "done",
|
|
986
|
-
createdAt:
|
|
2164
|
+
createdAt: at2(-1438),
|
|
987
2165
|
parts: [
|
|
988
2166
|
{
|
|
989
2167
|
type: "tool",
|
|
@@ -1007,17 +2185,19 @@ var conversations = {
|
|
|
1007
2185
|
"cone-triage": [
|
|
1008
2186
|
{
|
|
1009
2187
|
id: "m-t1",
|
|
1010
|
-
role: "
|
|
2188
|
+
role: "system",
|
|
1011
2189
|
kind: "compaction",
|
|
2190
|
+
trigger: "threshold",
|
|
2191
|
+
state: "summarized",
|
|
1012
2192
|
title: "Context compacted",
|
|
1013
2193
|
text: "41 earlier messages were summarized to stay under the context limit.",
|
|
1014
|
-
createdAt:
|
|
2194
|
+
createdAt: at2(-30)
|
|
1015
2195
|
},
|
|
1016
2196
|
{
|
|
1017
2197
|
id: "m-t2",
|
|
1018
2198
|
role: "assistant",
|
|
1019
2199
|
status: "done",
|
|
1020
|
-
createdAt:
|
|
2200
|
+
createdAt: at2(-20),
|
|
1021
2201
|
parts: [
|
|
1022
2202
|
{
|
|
1023
2203
|
type: "text",
|
|
@@ -1027,11 +2207,12 @@ var conversations = {
|
|
|
1027
2207
|
},
|
|
1028
2208
|
{
|
|
1029
2209
|
id: "m-t3",
|
|
1030
|
-
role: "
|
|
2210
|
+
role: "system",
|
|
1031
2211
|
kind: "error",
|
|
2212
|
+
action: "login",
|
|
1032
2213
|
title: "Mail connector",
|
|
1033
2214
|
text: "The connector returned 401. Reconnect the account in Settings.",
|
|
1034
|
-
createdAt:
|
|
2215
|
+
createdAt: at2(-19)
|
|
1035
2216
|
}
|
|
1036
2217
|
]
|
|
1037
2218
|
};
|
|
@@ -1062,6 +2243,177 @@ var terminals = [
|
|
|
1062
2243
|
{ id: "term-1", title: "bash", cwd: "/workspace/harbor", agentId: null }
|
|
1063
2244
|
];
|
|
1064
2245
|
|
|
2246
|
+
// src/dummy/memory.ts
|
|
2247
|
+
var DummyMemory = class extends Emitter {
|
|
2248
|
+
#memories;
|
|
2249
|
+
#next = 1;
|
|
2250
|
+
constructor(memories2) {
|
|
2251
|
+
super();
|
|
2252
|
+
this.#memories = memories2.map((memory2) => ({ ...memory2 }));
|
|
2253
|
+
}
|
|
2254
|
+
list() {
|
|
2255
|
+
return this.#memories.map((memory2) => ({ ...memory2 }));
|
|
2256
|
+
}
|
|
2257
|
+
save(draft) {
|
|
2258
|
+
const memory2 = {
|
|
2259
|
+
...draft,
|
|
2260
|
+
id: draft.id ?? `mem-new-${this.#next++}`,
|
|
2261
|
+
updatedAt: Date.now()
|
|
2262
|
+
};
|
|
2263
|
+
const index2 = this.#memories.findIndex((candidate) => candidate.id === memory2.id);
|
|
2264
|
+
if (index2 < 0) this.#memories.push(memory2);
|
|
2265
|
+
else this.#memories[index2] = memory2;
|
|
2266
|
+
this.emit("memories", this.list());
|
|
2267
|
+
return { ...memory2 };
|
|
2268
|
+
}
|
|
2269
|
+
remove(id) {
|
|
2270
|
+
this.#memories = this.#memories.filter((memory2) => memory2.id !== id);
|
|
2271
|
+
this.emit("memories", this.list());
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
|
|
2275
|
+
// src/dummy/monitor.ts
|
|
2276
|
+
var statuses = {
|
|
2277
|
+
idle: "idle",
|
|
2278
|
+
thinking: "active",
|
|
2279
|
+
working: "active",
|
|
2280
|
+
waiting: "warn",
|
|
2281
|
+
error: "error"
|
|
2282
|
+
};
|
|
2283
|
+
function wave(seed, length = 24) {
|
|
2284
|
+
return Array.from(
|
|
2285
|
+
{ length },
|
|
2286
|
+
(_, i) => Math.round(40 + 25 * Math.sin((i + seed) / 3) + 10 * Math.cos(i * seed / 5))
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
var DummyMonitor = class extends Emitter {
|
|
2290
|
+
#model;
|
|
2291
|
+
#tick = 0;
|
|
2292
|
+
constructor(model) {
|
|
2293
|
+
super();
|
|
2294
|
+
this.#model = model;
|
|
2295
|
+
const update = () => this.resync();
|
|
2296
|
+
model.agent.on("agents", update);
|
|
2297
|
+
model.terminals.on("terminals", update);
|
|
2298
|
+
model.browser.on("tabs", update);
|
|
2299
|
+
model.files.on("changes", update);
|
|
2300
|
+
model.tray.on("status", update);
|
|
2301
|
+
}
|
|
2302
|
+
snapshot() {
|
|
2303
|
+
const { agent, terminals: terminals2, browser, files: files3, sprinkles: sprinkles2, tray: tray2 } = this.#model;
|
|
2304
|
+
const listed = agent.list();
|
|
2305
|
+
const agents2 = listed.filter((candidate) => candidate.kind === "cone").flatMap((cone) => [cone, ...listed.filter((candidate) => candidate.parentId === cone.id)]);
|
|
2306
|
+
const active = agents2.filter((candidate) => candidate.status !== "idle").length;
|
|
2307
|
+
const status = tray2.status();
|
|
2308
|
+
const fill = Math.max(...agents2.map((candidate) => candidate.contextFill), 0);
|
|
2309
|
+
const agentRows = agents2.map(
|
|
2310
|
+
(candidate) => ({
|
|
2311
|
+
name: candidate.kind === "scoop" ? `\u21B3 ${candidate.name}` : candidate.name,
|
|
2312
|
+
meta: `${candidate.status} \xB7 ${Math.round(candidate.contextFill * 100)}% context`,
|
|
2313
|
+
status: statuses[candidate.status],
|
|
2314
|
+
badges: candidate.unread ? [`${candidate.unread} unread`] : []
|
|
2315
|
+
})
|
|
2316
|
+
);
|
|
2317
|
+
return {
|
|
2318
|
+
updatedAt: Date.now(),
|
|
2319
|
+
vitals: [
|
|
2320
|
+
{
|
|
2321
|
+
id: "agents",
|
|
2322
|
+
label: "Active agents",
|
|
2323
|
+
value: String(active),
|
|
2324
|
+
unit: `of ${agents2.length}`,
|
|
2325
|
+
series: wave(this.#tick + 1)
|
|
2326
|
+
},
|
|
2327
|
+
{
|
|
2328
|
+
id: "spend",
|
|
2329
|
+
label: "Spend",
|
|
2330
|
+
value: `$${status.spent.toFixed(2)}`,
|
|
2331
|
+
delta: `$${status.rate.toFixed(2)}/h`,
|
|
2332
|
+
series: wave(this.#tick + 7)
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
id: "budget",
|
|
2336
|
+
label: `Budget (${status.budget.window})`,
|
|
2337
|
+
value: `${status.budget.percent}%`,
|
|
2338
|
+
unit: status.budget.resets,
|
|
2339
|
+
ratio: status.budget.percent / 100
|
|
2340
|
+
},
|
|
2341
|
+
{
|
|
2342
|
+
id: "context",
|
|
2343
|
+
label: "Fullest context",
|
|
2344
|
+
value: `${Math.round(fill * 100)}%`,
|
|
2345
|
+
ratio: fill
|
|
2346
|
+
}
|
|
2347
|
+
],
|
|
2348
|
+
alerts: [
|
|
2349
|
+
...agents2.filter((candidate) => candidate.contextFill >= 0.6).map((candidate) => ({
|
|
2350
|
+
id: `fill-${candidate.id}`,
|
|
2351
|
+
title: `${candidate.name} is ${Math.round(candidate.contextFill * 100)}% full`,
|
|
2352
|
+
detail: "Compact it soon to keep replies fast.",
|
|
2353
|
+
severity: "warn"
|
|
2354
|
+
})),
|
|
2355
|
+
...status.connection === "live" ? [] : [
|
|
2356
|
+
{
|
|
2357
|
+
id: "tray",
|
|
2358
|
+
title: `Tray ${status.connection}`,
|
|
2359
|
+
detail: "Followers can\u2019t see this session.",
|
|
2360
|
+
severity: "error"
|
|
2361
|
+
}
|
|
2362
|
+
]
|
|
2363
|
+
],
|
|
2364
|
+
sections: [
|
|
2365
|
+
{ id: "agents", label: "Cones and scoops", rows: agentRows },
|
|
2366
|
+
{
|
|
2367
|
+
id: "terminals",
|
|
2368
|
+
label: "Terminals",
|
|
2369
|
+
rows: terminals2.list().map((terminal) => ({
|
|
2370
|
+
name: terminal.title,
|
|
2371
|
+
meta: terminal.cwd,
|
|
2372
|
+
status: "active"
|
|
2373
|
+
}))
|
|
2374
|
+
},
|
|
2375
|
+
{
|
|
2376
|
+
id: "tabs",
|
|
2377
|
+
label: "Browser tabs",
|
|
2378
|
+
rows: browser.list().map((tab) => ({
|
|
2379
|
+
name: tab.title,
|
|
2380
|
+
meta: new URL(tab.url).host || tab.url,
|
|
2381
|
+
status: tab.status === "loading" ? "active" : "idle",
|
|
2382
|
+
badges: tab.agentId ? ["agent"] : []
|
|
2383
|
+
}))
|
|
2384
|
+
},
|
|
2385
|
+
{
|
|
2386
|
+
id: "changes",
|
|
2387
|
+
label: "Pending changes",
|
|
2388
|
+
rows: files3.changes().map((change) => ({ name: change.path, meta: change.status, status: "warn" }))
|
|
2389
|
+
},
|
|
2390
|
+
{
|
|
2391
|
+
id: "sprinkles",
|
|
2392
|
+
label: "Sprinkles",
|
|
2393
|
+
rows: sprinkles2.list().map((sprinkle) => ({
|
|
2394
|
+
name: sprinkle.title,
|
|
2395
|
+
meta: sprinkle.name,
|
|
2396
|
+
status: "idle"
|
|
2397
|
+
}))
|
|
2398
|
+
},
|
|
2399
|
+
{
|
|
2400
|
+
id: "followers",
|
|
2401
|
+
label: "Tray followers",
|
|
2402
|
+
rows: status.followers.map((follower) => ({
|
|
2403
|
+
name: follower.name,
|
|
2404
|
+
meta: follower.device,
|
|
2405
|
+
status: "active"
|
|
2406
|
+
}))
|
|
2407
|
+
}
|
|
2408
|
+
]
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
resync() {
|
|
2412
|
+
this.#tick += 1;
|
|
2413
|
+
this.emit("snapshot", this.snapshot());
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
|
|
1065
2417
|
// src/dummy/settings.ts
|
|
1066
2418
|
var key = "slicc-ui.settings";
|
|
1067
2419
|
var DummySettings = class extends Emitter {
|
|
@@ -1116,6 +2468,33 @@ var DummySettings = class extends Emitter {
|
|
|
1116
2468
|
}
|
|
1117
2469
|
};
|
|
1118
2470
|
|
|
2471
|
+
// src/dummy/sprinkles.ts
|
|
2472
|
+
var DummySprinkles = class extends Emitter {
|
|
2473
|
+
#sprinkles;
|
|
2474
|
+
#agent;
|
|
2475
|
+
constructor(sprinkles2, agent) {
|
|
2476
|
+
super();
|
|
2477
|
+
this.#sprinkles = sprinkles2.map((sprinkle) => ({ ...sprinkle }));
|
|
2478
|
+
this.#agent = agent;
|
|
2479
|
+
}
|
|
2480
|
+
list() {
|
|
2481
|
+
return this.#sprinkles.map((sprinkle) => ({ ...sprinkle }));
|
|
2482
|
+
}
|
|
2483
|
+
send(id, payload) {
|
|
2484
|
+
const sprinkle = this.#sprinkles.find((candidate) => candidate.id === id);
|
|
2485
|
+
if (!sprinkle) return;
|
|
2486
|
+
const data = payload ?? {};
|
|
2487
|
+
const what = data.event ? `\u201C${data.event}\u201D${data.detail ? ` (${data.detail})` : ""}` : "an event";
|
|
2488
|
+
this.#agent.lick(
|
|
2489
|
+
sprinkle.agentId,
|
|
2490
|
+
"sprinkle",
|
|
2491
|
+
sprinkle.title,
|
|
2492
|
+
`Sent ${what}`,
|
|
2493
|
+
JSON.stringify(payload, null, 2)
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
|
|
1119
2498
|
// src/dummy/shell.ts
|
|
1120
2499
|
var encoder = new TextEncoder();
|
|
1121
2500
|
var decoder = new TextDecoder();
|
|
@@ -1368,13 +2747,48 @@ var DummyTerminals = class extends Emitter {
|
|
|
1368
2747
|
}
|
|
1369
2748
|
};
|
|
1370
2749
|
|
|
2750
|
+
// src/dummy/tray.ts
|
|
2751
|
+
var DummyTray = class extends Emitter {
|
|
2752
|
+
#status;
|
|
2753
|
+
#followers;
|
|
2754
|
+
#clock;
|
|
2755
|
+
constructor(status, clock2) {
|
|
2756
|
+
super();
|
|
2757
|
+
this.#status = structuredClone(status);
|
|
2758
|
+
this.#followers = structuredClone(status.followers);
|
|
2759
|
+
this.#clock = clock2;
|
|
2760
|
+
}
|
|
2761
|
+
status() {
|
|
2762
|
+
return structuredClone(this.#status);
|
|
2763
|
+
}
|
|
2764
|
+
#set(patch) {
|
|
2765
|
+
this.#status = { ...this.#status, ...patch };
|
|
2766
|
+
this.emit("status", this.status());
|
|
2767
|
+
}
|
|
2768
|
+
reconnect() {
|
|
2769
|
+
this.#set({ connection: "reconnecting" });
|
|
2770
|
+
void this.#clock.sleep(15).then(() => this.#set({ connection: "live", followers: structuredClone(this.#followers) }));
|
|
2771
|
+
}
|
|
2772
|
+
disconnect() {
|
|
2773
|
+
this.#set({ connection: "offline", followers: [] });
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
|
|
1371
2777
|
// src/dummy/index.ts
|
|
1372
2778
|
function createDummyModel({ delay = 30, storage = null } = {}) {
|
|
1373
2779
|
const time = clock(delay);
|
|
1374
2780
|
const files3 = new DummyFiles(files2, directories, pending, time);
|
|
1375
2781
|
const browser = new DummyBrowser(tabs, time);
|
|
1376
|
-
|
|
1377
|
-
|
|
2782
|
+
const agent = new DummyAgent(
|
|
2783
|
+
agents,
|
|
2784
|
+
conversations,
|
|
2785
|
+
{ files: files3, browser },
|
|
2786
|
+
time,
|
|
2787
|
+
queues,
|
|
2788
|
+
frozen
|
|
2789
|
+
);
|
|
2790
|
+
const base = {
|
|
2791
|
+
agent,
|
|
1378
2792
|
files: files3,
|
|
1379
2793
|
browser,
|
|
1380
2794
|
terminals: new DummyTerminals(terminals, files3, time),
|
|
@@ -1384,8 +2798,12 @@ function createDummyModel({ delay = 30, storage = null } = {}) {
|
|
|
1384
2798
|
accounts,
|
|
1385
2799
|
storage,
|
|
1386
2800
|
time
|
|
1387
|
-
)
|
|
2801
|
+
),
|
|
2802
|
+
memory: new DummyMemory(memories),
|
|
2803
|
+
sprinkles: new DummySprinkles(sprinkles, agent),
|
|
2804
|
+
tray: new DummyTray(tray, time)
|
|
1388
2805
|
};
|
|
2806
|
+
return { ...base, monitor: new DummyMonitor(base) };
|
|
1389
2807
|
}
|
|
1390
2808
|
export {
|
|
1391
2809
|
createDummyModel
|