@ai-ecoverse/slicc-spectrum 1.91.0 → 1.93.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.
@@ -0,0 +1,1885 @@
1
+ import {
2
+ Emitter
3
+ } from "./chunk-T5NHW4KC.js";
4
+
5
+ // src/dummy/showcase.ts
6
+ var start = Date.UTC(2026, 9, 4, 16, 0);
7
+ var at = (minutes) => start + minutes * 6e4;
8
+ function picture(label, from, to, width = 640, height = 400) {
9
+ 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>`;
10
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
11
+ }
12
+ function tone(seconds = 0.6, frequency = 440, rate = 8e3) {
13
+ const samples = Math.round(seconds * rate);
14
+ const bytes = new Uint8Array(44 + samples);
15
+ const view = new DataView(bytes.buffer);
16
+ const text = (offset, value) => {
17
+ for (let i = 0; i < value.length; i++) bytes[offset + i] = value.charCodeAt(i);
18
+ };
19
+ text(0, "RIFF");
20
+ view.setUint32(4, 36 + samples, true);
21
+ text(8, "WAVEfmt ");
22
+ view.setUint32(16, 16, true);
23
+ view.setUint16(20, 1, true);
24
+ view.setUint16(22, 1, true);
25
+ view.setUint32(24, rate, true);
26
+ view.setUint32(28, rate, true);
27
+ view.setUint16(32, 1, true);
28
+ view.setUint16(34, 8, true);
29
+ text(36, "data");
30
+ view.setUint32(40, samples, true);
31
+ for (let i = 0; i < samples; i++) {
32
+ const fade = Math.min(1, (samples - i) / (rate / 10));
33
+ bytes[44 + i] = 128 + Math.round(60 * fade * Math.sin(2 * Math.PI * frequency * i / rate));
34
+ }
35
+ let binary = "";
36
+ for (const byte of bytes) binary += String.fromCharCode(byte);
37
+ return `data:audio/wav;base64,${btoa(binary)}`;
38
+ }
39
+ function tool(id, name, title, input, output, extra = {}) {
40
+ return { id, name, title, input, output, status: "done", paths: [], ...extra };
41
+ }
42
+ var markdown = [
43
+ "## The forecast API in one page",
44
+ "",
45
+ "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.",
46
+ "",
47
+ "### How a request flows",
48
+ "",
49
+ "1. The worker parses the city.",
50
+ "2. The cache answers if it has a fresh entry:",
51
+ " - same UTC day",
52
+ " - younger than one hour",
53
+ "3. Otherwise the upstream call runs, with three retries.",
54
+ "",
55
+ "> Upstream is slow at the top of the hour. Retries back off at 250, 500 and 1000 ms.",
56
+ "",
57
+ "```ts",
58
+ "export async function forecast(city: string): Promise<Forecast> {",
59
+ " const cached = cache.get(city);",
60
+ " if (cached) return cached;",
61
+ " return cache.set(city, await retry(() => upstream(city)));",
62
+ "}",
63
+ "```",
64
+ "",
65
+ "| Route | Cache | Typical latency |",
66
+ "| --- | --- | ---: |",
67
+ "| `/forecast` | 1 hour, per city | 38 ms |",
68
+ "| `/health` | none | 2 ms |",
69
+ "| `/forecast` (miss) | \u2014 | 410 ms |",
70
+ "",
71
+ "- [x] TTL fix merged",
72
+ "- [ ] Move the cache to KV",
73
+ "",
74
+ "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`."
75
+ ].join("\n");
76
+ var toolShapes = [
77
+ tool("k-t1", "bash", "Count the routes", "rg -c 'pathname ===' src", "src/index.ts:2"),
78
+ tool(
79
+ "k-t2",
80
+ "read_file",
81
+ "Read the router",
82
+ "/workspace/harbor/src/index.ts",
83
+ "import { forecast } from './routes/forecast.ts';\n\u2026",
84
+ {
85
+ paths: ["/workspace/harbor/src/index.ts"]
86
+ }
87
+ ),
88
+ tool(
89
+ "k-t3",
90
+ "write_file",
91
+ "Create the KV binding notes",
92
+ "/workspace/harbor/docs/kv.md",
93
+ "Wrote 18 lines",
94
+ {
95
+ paths: ["/workspace/harbor/docs/kv.md"]
96
+ }
97
+ ),
98
+ tool(
99
+ "k-t4",
100
+ "edit_file",
101
+ "Rename the cache key",
102
+ "/workspace/harbor/src/lib/cache.ts",
103
+ 'No match for "cityKey(" in src/lib/cache.ts',
104
+ {
105
+ status: "error",
106
+ paths: ["/workspace/harbor/src/lib/cache.ts"]
107
+ }
108
+ ),
109
+ tool(
110
+ "k-t5",
111
+ "grep",
112
+ "Find TODOs",
113
+ 'TODO --glob "src/**"',
114
+ "src/routes/forecast.ts:14: TODO: cache negative results\nsrc/lib/units.ts:3: TODO: round half to even"
115
+ ),
116
+ tool(
117
+ "k-t6",
118
+ "web_fetch",
119
+ "Fetch the API changelog",
120
+ "https://api.example.com/changelog.json",
121
+ '{ "latest": "2.4.1", "breaking": false }'
122
+ ),
123
+ tool(
124
+ "k-t7",
125
+ "browser",
126
+ "Screenshot the preview",
127
+ "screenshot http://localhost:8787/",
128
+ "Captured 1280\xD7800",
129
+ {
130
+ image: picture("localhost:8787", "#3b63fb", "#0a7a43", 640, 400)
131
+ }
132
+ ),
133
+ tool(
134
+ "k-t8",
135
+ "scoop_scoop",
136
+ "Start a scoop for the docs",
137
+ "name: docs-pass\ntask: Update the forecast page to Celsius",
138
+ "Scoop docs-pass is running"
139
+ ),
140
+ tool(
141
+ "k-t9",
142
+ "feed_scoop",
143
+ "Hand the screenshots to docs-pass",
144
+ "scoop_name: docs-pass\nmessage: Use the new screenshots in /tmp/shots",
145
+ "Delivered"
146
+ ),
147
+ tool(
148
+ "k-t10",
149
+ "send_message",
150
+ "Tell quiet-otter the tests pass",
151
+ "to: quiet-otter\nmessage: CI is green, merge when ready",
152
+ "Delivered"
153
+ ),
154
+ tool(
155
+ "k-t11",
156
+ "drop_scoop",
157
+ "Wrap up amber-heron",
158
+ "scoop_name: amber-heron",
159
+ "Dropped amber-heron; its notes are in /shared/notes"
160
+ ),
161
+ tool(
162
+ "k-t12",
163
+ "update_global_memory",
164
+ "Remember the cache rule",
165
+ "Cache keys are per city and per UTC day.",
166
+ "Memory updated (3 sections, 412 characters)"
167
+ ),
168
+ tool(
169
+ "k-mcp",
170
+ "mcp__docs__search",
171
+ "docs \xB7 search",
172
+ '{\n "query": "forecast units"\n}',
173
+ "2 pages: /forecast, /units"
174
+ ),
175
+ tool("k-t13", "bash", "Deploy to staging", "wrangler deploy --env staging", "Stopped", {
176
+ status: "cancelled"
177
+ }),
178
+ tool("k-t14", "bash", "Watch the logs", "wrangler tail --env staging", "", { status: "running" }),
179
+ tool(
180
+ "k-t16",
181
+ "bash",
182
+ "Check the staging routes",
183
+ "wrangler routes list --env staging",
184
+ "ROUTE ZONE WORKER ENV\nforecast.harbor.example/v2/forecast/* harbor.example harbor-forecast-v2 staging\nerror: the route forecast.harbor.example/v2/units/* belongs to another account; ask its owner to release it first",
185
+ {
186
+ status: "error",
187
+ notes: [
188
+ { severity: "error", text: "Exited with code 5" },
189
+ {
190
+ severity: "warn",
191
+ text: "Output truncated: 12 lines and 1,840 bytes dropped from the start"
192
+ },
193
+ { severity: "info", text: "Full output in /tmp/pi-bash-7f3a.log" }
194
+ ]
195
+ }
196
+ )
197
+ ];
198
+ var licks = [
199
+ [
200
+ "webhook",
201
+ "GitHub \xB7 pull_request.opened",
202
+ "example/harbor#58 Add retries for upstream timeouts",
203
+ '{\n "action": "opened",\n "number": 58,\n "repository": { "full_name": "example/harbor" }\n}',
204
+ {}
205
+ ],
206
+ [
207
+ "cron",
208
+ "Every weekday 09:00",
209
+ "Morning summary for the harbor cone",
210
+ void 0,
211
+ { count: 3 }
212
+ ],
213
+ [
214
+ "sprinkle",
215
+ "Release board",
216
+ "Button \u201CShip 1.9.0\u201D clicked",
217
+ '{ "button": "ship", "version": "1.9.0" }',
218
+ {}
219
+ ],
220
+ [
221
+ "fswatch",
222
+ "/workspace/harbor/src",
223
+ "2 files changed",
224
+ "modified /workspace/harbor/src/lib/cache.ts\ncreated /workspace/harbor/src/lib/retry.ts",
225
+ {}
226
+ ],
227
+ [
228
+ "session-reload",
229
+ "Session restored",
230
+ "The page reloaded; 4 cones resumed from storage",
231
+ void 0,
232
+ {}
233
+ ],
234
+ [
235
+ "navigate",
236
+ "api.example.com",
237
+ "A page offered a SLICC handoff: open the forecast docs",
238
+ '{ "verb": "open", "target": "https://api.example.com/docs/v2/forecast" }',
239
+ {}
240
+ ],
241
+ [
242
+ "discovery",
243
+ "api.example.com",
244
+ "Found an ai-catalog.json with 3 tools",
245
+ "https://api.example.com/.well-known/ai-catalog.json",
246
+ {}
247
+ ],
248
+ ["upgrade", "SLICC updated", "6.4.0 \u2192 6.5.0", void 0, {}],
249
+ [
250
+ "workflow",
251
+ "release-notes",
252
+ "Step 2 of 4 finished: collect merged pull requests",
253
+ void 0,
254
+ {}
255
+ ],
256
+ ["bash", "Job 3", "npm run build exited with status 0 after 41 s", void 0, {}],
257
+ ["jshd", "collect.jsh", "Script finished: 12 pull requests since v1.8.0", void 0, {}],
258
+ ["preview", "Preview bridge", "Connected from localhost:8787", void 0, {}],
259
+ [
260
+ "cherry",
261
+ "Host page",
262
+ "Event \u201Ccart-updated\u201D from shop.example.com",
263
+ '{ "items": 3, "total": 42.5 }',
264
+ {}
265
+ ],
266
+ ["scoop-notify", "docs-pass", "Finished: the forecast page now shows Celsius", void 0, {}],
267
+ ["scoop-idle", "amber-heron", "Idle for 30 minutes", void 0, {}],
268
+ ["scoop-wait", "quiet-otter", "The wait for CI resolved: all checks passed", void 0, {}],
269
+ [
270
+ "sudo-request",
271
+ "docs-pass",
272
+ "Wants to push to example/harbor (branch docs/celsius)",
273
+ "git push origin docs/celsius",
274
+ { state: "pending", expiresAt: Date.now() + 10 * 6e4 }
275
+ ],
276
+ [
277
+ "sudo-request",
278
+ "quiet-otter",
279
+ "Wants to read secrets: UPSTREAM_TOKEN",
280
+ void 0,
281
+ { state: "confirmed" }
282
+ ],
283
+ [
284
+ "sudo-request",
285
+ "amber-heron",
286
+ "Wants to run: rm -rf /workspace/harbor/node_modules",
287
+ void 0,
288
+ { state: "dismissed" }
289
+ ]
290
+ ];
291
+ var attachmentsMessage = {
292
+ id: "k-u2",
293
+ role: "user",
294
+ text: "Here is the mockup, my notes and the old spec.",
295
+ createdAt: at(4),
296
+ attachments: [
297
+ {
298
+ id: "k-a1",
299
+ name: "forecast-card.png",
300
+ kind: "image",
301
+ mimeType: "image/png",
302
+ size: 48211,
303
+ url: picture("Mockup", "#f5a623", "#d0021b", 480, 320)
304
+ },
305
+ {
306
+ id: "k-a2",
307
+ name: "notes.txt",
308
+ kind: "text",
309
+ mimeType: "text/plain",
310
+ size: 214,
311
+ text: "Show \xB0C first.\nRound to whole degrees.\nKeep the summary under 60 characters."
312
+ },
313
+ {
314
+ id: "k-a3",
315
+ name: "spec-v1.pdf",
316
+ kind: "file",
317
+ mimeType: "application/pdf",
318
+ size: 1204331,
319
+ path: "/home/user/downloads/spec-v1.pdf"
320
+ },
321
+ {
322
+ id: "k-a4",
323
+ name: "UPSTREAM_TOKEN",
324
+ kind: "secret",
325
+ mimeType: "application/octet-stream",
326
+ size: 0
327
+ },
328
+ {
329
+ id: "k-a5",
330
+ name: "recording.mov",
331
+ kind: "file",
332
+ mimeType: "video/quicktime",
333
+ size: 98304e3,
334
+ error: "Too large: attachments are limited to 25 MB"
335
+ }
336
+ ]
337
+ };
338
+ var showcaseAgent = {
339
+ id: "cone-kitchen",
340
+ name: "kitchen-sink",
341
+ kind: "cone",
342
+ parentId: null,
343
+ status: "idle",
344
+ model: "claude-opus-5-5",
345
+ contextFill: 0.47,
346
+ unread: 0
347
+ };
348
+ function opening() {
349
+ return [
350
+ {
351
+ id: "k-u1",
352
+ role: "user",
353
+ text: "Give me the full tour of harbor, everything you can show.",
354
+ createdAt: at(0)
355
+ },
356
+ {
357
+ id: "k-m1",
358
+ role: "assistant",
359
+ status: "done",
360
+ createdAt: at(1),
361
+ model: "claude-sonnet-5-5",
362
+ usage: { input: 18204, output: 912, cost: 0.31 },
363
+ parts: [
364
+ {
365
+ type: "thinking",
366
+ text: "Start with the shape of the API, then show the pieces one kind at a time."
367
+ },
368
+ { type: "text", text: markdown }
369
+ ]
370
+ },
371
+ attachmentsMessage,
372
+ {
373
+ id: "k-m2",
374
+ role: "assistant",
375
+ status: "done",
376
+ createdAt: at(5),
377
+ model: "claude-sonnet-5-5",
378
+ parts: [
379
+ {
380
+ type: "text",
381
+ text: "Mockup, notes and spec received. The recording was too large to attach. Here are the current screens and the alert sound:"
382
+ },
383
+ {
384
+ type: "media",
385
+ items: [
386
+ {
387
+ kind: "image",
388
+ src: picture("Forecast card", "#3b63fb", "#7a5af8"),
389
+ alt: "The forecast card in light mode"
390
+ },
391
+ {
392
+ kind: "image",
393
+ src: picture("Dark card", "#1d1d1d", "#3b63fb"),
394
+ alt: "The forecast card in dark mode"
395
+ },
396
+ {
397
+ kind: "image",
398
+ src: picture("Phone", "#0a7a43", "#12b886", 360, 640),
399
+ alt: "The forecast card on a phone"
400
+ },
401
+ {
402
+ kind: "video",
403
+ src: "",
404
+ poster: picture("\u25B6 Walkthrough", "#292929", "#717171"),
405
+ alt: "A walkthrough of the forecast page"
406
+ },
407
+ { kind: "audio", src: tone(), alt: "The storm alert sound" }
408
+ ]
409
+ }
410
+ ]
411
+ },
412
+ { id: "k-u3", role: "user", text: "Show me every tool you used.", createdAt: at(8) },
413
+ {
414
+ id: "k-m3",
415
+ role: "assistant",
416
+ status: "done",
417
+ createdAt: at(9),
418
+ model: "claude-sonnet-5-5",
419
+ parts: [
420
+ { type: "text", text: "Every tool from today\u2019s run, in order:" },
421
+ ...toolShapes.map((call) => ({ type: "tool", tool: call }))
422
+ ]
423
+ },
424
+ {
425
+ id: "k-tool1",
426
+ role: "tool",
427
+ createdAt: at(12),
428
+ tool: tool(
429
+ "k-t15",
430
+ "bash",
431
+ "You ran a command",
432
+ "git log --oneline -3",
433
+ "a1b2c3d fix: expire entries from the previous day\ne4f5a6b feat: retry upstream timeouts\n0c9d8e7 chore: update wrangler"
434
+ )
435
+ }
436
+ ];
437
+ }
438
+ function runs() {
439
+ return [
440
+ {
441
+ id: "k-u6",
442
+ role: "user",
443
+ text: "Get the forecast docs ready for the Celsius release.",
444
+ createdAt: at(13)
445
+ },
446
+ {
447
+ id: "k-m8",
448
+ role: "assistant",
449
+ status: "done",
450
+ createdAt: at(13.1),
451
+ model: "claude-sonnet-5-5",
452
+ parts: [
453
+ { type: "text", text: "Checking what the docs and the API say today." },
454
+ ...[
455
+ tool(
456
+ "k-r1",
457
+ "bash",
458
+ "Ask the API for Lisbon",
459
+ "curl -s https://api.example.com/v2/forecast?city=Lisbon",
460
+ '{ "city": "Lisbon", "high": 71, "low": 58, "unit": "F" }'
461
+ ),
462
+ tool(
463
+ "k-r2",
464
+ "bash",
465
+ "Read the forecast page",
466
+ "cat /workspace/harbor/docs/forecast.md",
467
+ "# Forecast\n\nTemperatures are in Fahrenheit."
468
+ ),
469
+ tool("k-r3", "bash", "See what changed", "git status --short", " M src/lib/units.ts"),
470
+ tool(
471
+ "k-r4",
472
+ "read_file",
473
+ "Read the units helper",
474
+ "/workspace/harbor/src/lib/units.ts",
475
+ "export function toCelsius(f: number): number {\u2026}",
476
+ { paths: ["/workspace/harbor/src/lib/units.ts"] }
477
+ )
478
+ ].map((call) => ({ type: "tool", tool: call })),
479
+ {
480
+ type: "text",
481
+ text: "The page still says Fahrenheit. Updating it, then building and testing."
482
+ },
483
+ ...[
484
+ tool(
485
+ "k-r5",
486
+ "edit_file",
487
+ "Switch the page to Celsius",
488
+ "/workspace/harbor/docs/forecast.md",
489
+ "Edited 1 line",
490
+ {
491
+ paths: ["/workspace/harbor/docs/forecast.md"],
492
+ diff: {
493
+ before: "# Forecast\n\nTemperatures are in Fahrenheit.\n",
494
+ after: "# Forecast\n\nTemperatures are in Celsius.\n"
495
+ }
496
+ }
497
+ ),
498
+ tool("k-r6", "bash", "Build the site", "npm run build", "Built 14 pages in 2.1s", {
499
+ meta: "timeout 120s"
500
+ }),
501
+ tool(
502
+ "k-r7",
503
+ "bash",
504
+ "Run the unit tests",
505
+ "npx vitest run src/lib/units.test.ts",
506
+ "FAIL src/lib/units.test.ts > rounds half to even\nExpected 22, received 21",
507
+ {
508
+ status: "error",
509
+ notes: [{ severity: "error", text: "Exited with code 1" }]
510
+ }
511
+ ),
512
+ tool(
513
+ "k-r8",
514
+ "bash",
515
+ "Open the release pull request",
516
+ 'gh pr create --repo https://git.example.com/example/harbor --title "Celsius docs"',
517
+ "Stopped before the tests pass",
518
+ { status: "cancelled" }
519
+ ),
520
+ tool("k-r9", "bash", "Watch the preview logs", "wrangler tail --env preview", "", {
521
+ status: "running"
522
+ })
523
+ ].map((call) => ({ type: "tool", tool: call })),
524
+ {
525
+ type: "text",
526
+ text: "One test fails on rounding, so the pull request waits for the fix."
527
+ }
528
+ ]
529
+ },
530
+ {
531
+ id: "k-tool2",
532
+ role: "tool",
533
+ createdAt: at(13.5),
534
+ tool: tool(
535
+ "k-r10",
536
+ "bash",
537
+ "Check the preview port",
538
+ "lsof -i :8787",
539
+ "workerd 4120 harbor 12u IPv4 TCP localhost:8787 (LISTEN)"
540
+ )
541
+ }
542
+ ];
543
+ }
544
+ function work() {
545
+ return [
546
+ {
547
+ id: "k-m4",
548
+ role: "assistant",
549
+ status: "done",
550
+ createdAt: at(14),
551
+ model: "claude-sonnet-5-5",
552
+ parts: [
553
+ { type: "text", text: "Here is where things stand." },
554
+ { type: "sprinkle", sprinkle: "status" },
555
+ {
556
+ type: "delegation",
557
+ kind: "scoop",
558
+ scoop: "docs-pass",
559
+ text: "Started a scoop to update the docs"
560
+ },
561
+ {
562
+ type: "delegation",
563
+ kind: "feed",
564
+ scoop: "docs-pass",
565
+ text: "Handed over the new screenshots"
566
+ },
567
+ {
568
+ type: "delegation",
569
+ kind: "sprinkle",
570
+ scoop: "suggestions",
571
+ text: "Opened the suggestions sprinkle"
572
+ },
573
+ { type: "delegation", kind: "drop", scoop: "amber-heron", text: "Wrapped up amber-heron" }
574
+ ]
575
+ },
576
+ {
577
+ id: "k-m5",
578
+ role: "assistant",
579
+ status: "done",
580
+ createdAt: at(18),
581
+ model: "claude-sonnet-5-5",
582
+ parts: [
583
+ { type: "text", text: "Three decisions from earlier:" },
584
+ {
585
+ type: "question",
586
+ question: {
587
+ id: "k-q1",
588
+ kind: "yes-no",
589
+ question: "Tag 1.9.0 after the merge?",
590
+ options: [],
591
+ state: "answered",
592
+ answer: "Yes"
593
+ }
594
+ },
595
+ {
596
+ type: "question",
597
+ question: {
598
+ id: "k-q2",
599
+ kind: "text",
600
+ question: "What should the release be called?",
601
+ options: [],
602
+ state: "inert"
603
+ }
604
+ },
605
+ {
606
+ type: "question",
607
+ question: {
608
+ id: "k-q3",
609
+ kind: "choice",
610
+ question: "Where should the cache live?",
611
+ options: ["Keep it in memory", "Move it to KV", "Decide after the load test"],
612
+ state: "open"
613
+ }
614
+ }
615
+ ]
616
+ }
617
+ ];
618
+ }
619
+ function events() {
620
+ return [
621
+ {
622
+ id: "k-s1",
623
+ role: "system",
624
+ kind: "compaction",
625
+ title: "Context compacted",
626
+ text: "58 earlier messages were summarized.",
627
+ trigger: "threshold",
628
+ state: "summarized",
629
+ createdAt: at(1440)
630
+ },
631
+ {
632
+ id: "k-s2",
633
+ role: "system",
634
+ kind: "compaction",
635
+ title: "Compaction failed, transcript truncated",
636
+ text: "The summary failed, so the oldest 20 messages were dropped instead.",
637
+ trigger: "overflow",
638
+ state: "fallback",
639
+ createdAt: at(1441)
640
+ },
641
+ ...licks.map(
642
+ ([channel, title, text, body, extra], index2) => ({
643
+ id: `k-l${index2 + 1}`,
644
+ role: "lick",
645
+ channel,
646
+ title,
647
+ text,
648
+ createdAt: at(1445 + index2),
649
+ ...body ? { body } : {},
650
+ ...extra
651
+ })
652
+ ),
653
+ {
654
+ id: "k-u4",
655
+ role: "user",
656
+ text: "Actually, stop the deploy, staging is frozen today.",
657
+ mode: "steer",
658
+ createdAt: at(1470)
659
+ },
660
+ {
661
+ id: "k-m6",
662
+ role: "assistant",
663
+ status: "stopped",
664
+ createdAt: at(1470),
665
+ model: "claude-haiku-4-5",
666
+ parts: [{ type: "text", text: "Stopping the staging deploy and leaving" }]
667
+ },
668
+ {
669
+ id: "k-u5",
670
+ role: "user",
671
+ text: "Update the forecast page to Celsius and use the new screenshots.",
672
+ from: "harbor",
673
+ createdAt: at(1472)
674
+ },
675
+ {
676
+ id: "k-m7",
677
+ role: "assistant",
678
+ status: "error",
679
+ createdAt: at(1475),
680
+ model: "claude-haiku-4-5",
681
+ parts: [
682
+ {
683
+ type: "thinking",
684
+ text: "The model rejected the request because the context is too large."
685
+ },
686
+ {
687
+ type: "error",
688
+ message: "The request is too large for this model.",
689
+ detail: "The model returned 413: the request is larger than its context window.",
690
+ action: "change-model"
691
+ }
692
+ ]
693
+ },
694
+ {
695
+ id: "k-s3",
696
+ role: "system",
697
+ kind: "error",
698
+ title: "docs-pass failed",
699
+ text: "The Adobe session expired. Log in again to continue.",
700
+ action: "login",
701
+ account: "ai-adobe",
702
+ createdAt: at(1476)
703
+ },
704
+ {
705
+ id: "k-s4",
706
+ role: "system",
707
+ kind: "error",
708
+ title: "Rate limited",
709
+ text: "Anthropic answered 429. Retrying in 30 seconds.",
710
+ action: "retry",
711
+ createdAt: at(1477)
712
+ },
713
+ {
714
+ id: "k-s5",
715
+ role: "system",
716
+ kind: "notice",
717
+ title: "Model changed",
718
+ text: "to Claude Opus 5.5 (Anthropic)",
719
+ createdAt: at(1478)
720
+ }
721
+ ];
722
+ }
723
+ function showcase() {
724
+ return [...opening(), ...runs(), ...work(), ...events()];
725
+ }
726
+ function showcaseQueue() {
727
+ return [
728
+ {
729
+ id: "k-q-1",
730
+ role: "user",
731
+ text: "Then open a pull request for the docs.",
732
+ createdAt: at(1480),
733
+ queued: true,
734
+ mode: "queue"
735
+ },
736
+ {
737
+ id: "k-q-2",
738
+ role: "user",
739
+ text: "And post the link in the release channel.",
740
+ createdAt: at(1481),
741
+ queued: true,
742
+ mode: "queue"
743
+ }
744
+ ];
745
+ }
746
+
747
+ // src/dummy/fixtures.ts
748
+ var cacheBefore = `export interface Entry<T> {
749
+ value: T;
750
+ storedAt: number;
751
+ }
752
+
753
+ const TTL = 60 * 60 * 1000;
754
+
755
+ export class ForecastCache<T> {
756
+ #entries = new Map<string, Entry<T>>();
757
+
758
+ get(key: string, now = Date.now()): T | undefined {
759
+ const entry = this.#entries.get(key);
760
+ if (!entry) return undefined;
761
+ const day = new Date(entry.storedAt).getDate();
762
+ if (day !== new Date(now).getDate()) return entry.value;
763
+ if (now - entry.storedAt > TTL) return undefined;
764
+ return entry.value;
765
+ }
766
+
767
+ set(key: string, value: T, now = Date.now()): void {
768
+ this.#entries.set(key, { value, storedAt: now });
769
+ }
770
+ }
771
+ `;
772
+ var cacheAfter = `export interface Entry<T> {
773
+ value: T;
774
+ storedAt: number;
775
+ }
776
+
777
+ const TTL = 60 * 60 * 1000;
778
+
779
+ export class ForecastCache<T> {
780
+ #entries = new Map<string, Entry<T>>();
781
+
782
+ get(key: string, now = Date.now()): T | undefined {
783
+ const entry = this.#entries.get(key);
784
+ if (!entry) return undefined;
785
+ if (dayOf(entry.storedAt) !== dayOf(now)) {
786
+ this.#entries.delete(key);
787
+ return undefined;
788
+ }
789
+ if (now - entry.storedAt > TTL) return undefined;
790
+ return entry.value;
791
+ }
792
+
793
+ set(key: string, value: T, now = Date.now()): void {
794
+ this.#entries.set(key, { value, storedAt: now });
795
+ }
796
+ }
797
+
798
+ function dayOf(time: number): string {
799
+ return new Date(time).toISOString().slice(0, 10);
800
+ }
801
+ `;
802
+ var retry = `export async function retry<T>(task: () => Promise<T>, attempts = 3, wait = 250): Promise<T> {
803
+ let last: unknown;
804
+ for (let attempt = 0; attempt < attempts; attempt++) {
805
+ try {
806
+ return await task();
807
+ } catch (error) {
808
+ last = error;
809
+ await new Promise((resolve) => setTimeout(resolve, wait * 2 ** attempt));
810
+ }
811
+ }
812
+ throw last;
813
+ }
814
+ `;
815
+ var legacyXml = `export function toXml(forecast: { city: string; high: number; low: number }): string {
816
+ return \`<forecast city="\${forecast.city}"><high>\${forecast.high}</high><low>\${forecast.low}</low></forecast>\`;
817
+ }
818
+ `;
819
+ var forecastBefore = `import { ForecastCache } from '../lib/cache.ts';
820
+ import { toCelsius } from '../lib/units.ts';
821
+
822
+ const cache = new ForecastCache<Forecast>();
823
+
824
+ export interface Forecast {
825
+ city: string;
826
+ high: number;
827
+ low: number;
828
+ summary: string;
829
+ }
830
+
831
+ export async function forecast(city: string, upstream: typeof fetch = fetch): Promise<Forecast> {
832
+ const cached = cache.get(city);
833
+ if (cached) return cached;
834
+ const response = await upstream(\`https://api.example.com/v2/forecast?city=\${encodeURIComponent(city)}\`);
835
+ const body = await response.json();
836
+ const result = {
837
+ city,
838
+ high: toCelsius(body.daily.max),
839
+ low: toCelsius(body.daily.min),
840
+ summary: body.daily.summary,
841
+ };
842
+ cache.set(city, result);
843
+ return result;
844
+ }
845
+ `;
846
+ var forecastAfter = forecastBefore.replace(
847
+ "import { toCelsius } from '../lib/units.ts';",
848
+ "import { retry } from '../lib/retry.ts';\nimport { toCelsius } from '../lib/units.ts';"
849
+ ).replace("const response = await upstream(", "const response = await retry(() =>\n upstream(").replace("(city)}`);", "(city)}`)\n );");
850
+ var units = `export function toCelsius(fahrenheit: number): number {
851
+ return Math.round(((fahrenheit - 32) * 5) / 9);
852
+ }
853
+
854
+ export function toFahrenheit(celsius: number): number {
855
+ return Math.round((celsius * 9) / 5 + 32);
856
+ }
857
+ `;
858
+ var unitsTest = `import assert from 'node:assert/strict';
859
+ import { test } from 'node:test';
860
+ import { toCelsius, toFahrenheit } from '../src/lib/units.ts';
861
+
862
+ test('converts both ways', () => {
863
+ assert.equal(toCelsius(212), 100);
864
+ assert.equal(toFahrenheit(0), 32);
865
+ });
866
+ `;
867
+ var index = `import { forecast } from './routes/forecast.ts';
868
+
869
+ export default {
870
+ async fetch(request: Request): Promise<Response> {
871
+ const url = new URL(request.url);
872
+ if (url.pathname === '/health') return new Response('ok');
873
+ const city = url.searchParams.get('city');
874
+ if (!city) return Response.json({ error: 'city is required' }, { status: 400 });
875
+ return Response.json(await forecast(city));
876
+ },
877
+ };
878
+ `;
879
+ var readme = `# harbor
880
+
881
+ A small forecast API that sits in front of the upstream weather service and caches per city.
882
+
883
+ ## Development
884
+
885
+ \`\`\`sh
886
+ npm install
887
+ npm test
888
+ npm run dev
889
+ \`\`\`
890
+
891
+ The worker listens on \`http://localhost:8787\`. Try \`/forecast?city=Lisbon\`.
892
+ `;
893
+ var pkg = `{
894
+ "name": "harbor",
895
+ "private": true,
896
+ "type": "module",
897
+ "scripts": {
898
+ "dev": "wrangler dev",
899
+ "test": "node --test test/"
900
+ },
901
+ "devDependencies": {
902
+ "wrangler": "4.40.0"
903
+ }
904
+ }
905
+ `;
906
+ var skill = `---
907
+ name: release-notes
908
+ description: Draft release notes from merged pull requests since the last tag.
909
+ ---
910
+
911
+ 1. Run \`collect.jsh\` with the repository and the previous tag.
912
+ 2. Group the pull requests by label: features, fixes, chores.
913
+ 3. Write one sentence per change, in the past tense, without ticket numbers.
914
+ `;
915
+ var collect = `const [repo, since] = process.argv.slice(2);
916
+ const pulls = await gh(\`repos/\${repo}/pulls?state=closed&per_page=100\`);
917
+ for (const pull of pulls.filter((p) => p.merged_at > since)) {
918
+ console.log(\`\${pull.number}\\t\${pull.labels.map((l) => l.name).join(',')}\\t\${pull.title}\`);
919
+ }
920
+ `;
921
+ var memory = `# Shared memory
922
+
923
+ ## Preferences
924
+ - Keep replies short. Lead with the result, then the evidence.
925
+ - Run the tests before saying something is fixed.
926
+
927
+ ## Projects
928
+ - harbor: forecast API, deployed as a worker. Cache bugs show up around midnight UTC.
929
+ - release-notes: drafted every Friday from merged pull requests.
930
+ `;
931
+ var standup = `# Standup, 4 October
932
+
933
+ - harbor: retries for upstream timeouts are in review.
934
+ - Docs: the forecast endpoint page still shows Fahrenheit examples.
935
+ - Next: move the cache to KV once the TTL fix lands.
936
+ `;
937
+ var sample = `{
938
+ "city": "Lisbon",
939
+ "daily": { "max": 77, "min": 61, "summary": "Clear, light wind from the north" }
940
+ }
941
+ `;
942
+ var bashrc = `export PS1='\\u@slicc:\\w\\$ '
943
+ alias ll='ls -la'
944
+ `;
945
+ var suggestions = `${JSON.stringify(
946
+ [
947
+ {
948
+ id: "sug-units",
949
+ kind: "skill",
950
+ title: "Unit conversions",
951
+ body: "You converted Fahrenheit to Celsius by hand in three chats this week. A skill can do it for you.",
952
+ skill: "unit-converter",
953
+ install: "unit-converter from raw.githubusercontent.com/example/skills/main/unit-converter/SKILL.md"
954
+ },
955
+ {
956
+ id: "sug-notes",
957
+ kind: "use-case",
958
+ title: "Release notes from the week",
959
+ body: "You write release notes for harbor every Friday. Let the cone draft them from the commits.",
960
+ prompt: "Draft release notes for harbor from the commits since last Friday."
961
+ },
962
+ {
963
+ id: "sug-cache",
964
+ kind: "tip",
965
+ title: "Name your cache keys",
966
+ body: "Forecast cache keys mix the city and the units. Splitting them makes stale entries easier to find."
967
+ }
968
+ ],
969
+ null,
970
+ 2
971
+ )}
972
+ `;
973
+ var files = {
974
+ "/workspace/harbor/README.md": readme,
975
+ "/workspace/harbor/package.json": pkg,
976
+ "/workspace/harbor/wrangler.toml": 'name = "harbor"\nmain = "src/index.ts"\ncompatibility_date = "2026-09-01"\n',
977
+ "/workspace/harbor/.gitignore": "node_modules/\n.wrangler/\n",
978
+ "/workspace/harbor/src/index.ts": index,
979
+ "/workspace/harbor/src/routes/forecast.ts": forecastBefore,
980
+ "/workspace/harbor/src/lib/cache.ts": cacheBefore,
981
+ "/workspace/harbor/src/lib/units.ts": units,
982
+ "/workspace/harbor/src/legacy/xml.ts": legacyXml,
983
+ "/workspace/harbor/static/radar.png": "\x89PNG\r\n\n",
984
+ "/workspace/harbor/test/units.test.ts": unitsTest,
985
+ "/workspace/skills/release-notes/SKILL.md": skill,
986
+ "/workspace/skills/release-notes/scripts/collect.jsh": collect,
987
+ "/shared/MEMORY.md": memory,
988
+ "/shared/notes/2026-10-04-standup.md": standup,
989
+ "/home/user/.bashrc": bashrc,
990
+ "/home/.gelatiere/suggestions.json": suggestions,
991
+ "/tmp/forecast-sample.json": sample
992
+ };
993
+ var directories = ["/sessions", "/home/user/downloads"];
994
+ var mountPoint = "/mnt/photos";
995
+ var mounted = {
996
+ "README.md": "# Photos\n\nHarbor shots for the forecast pages, one album per season.\n",
997
+ "albums/autumn.json": '{ "title": "Autumn", "shots": ["breakwater-fog", "pier-at-dusk"] }\n',
998
+ "albums/winter.json": '{ "title": "Winter", "shots": ["ice-on-the-buoys"] }\n',
999
+ "captions.txt": "breakwater-fog: low cloud over the outer wall\npier-at-dusk: lamps on, tide out\n"
1000
+ };
1001
+ var pending = [
1002
+ {
1003
+ path: "/workspace/harbor/src/lib/cache.ts",
1004
+ status: "modified",
1005
+ before: cacheBefore,
1006
+ after: cacheAfter,
1007
+ agentId: "cone-harbor"
1008
+ },
1009
+ {
1010
+ path: "/workspace/harbor/src/lib/retry.ts",
1011
+ status: "added",
1012
+ before: null,
1013
+ after: retry,
1014
+ agentId: "scoop-otter"
1015
+ },
1016
+ {
1017
+ path: "/workspace/harbor/src/routes/forecast.ts",
1018
+ status: "modified",
1019
+ before: forecastBefore,
1020
+ after: forecastAfter,
1021
+ agentId: "scoop-otter"
1022
+ },
1023
+ {
1024
+ path: "/workspace/harbor/src/legacy/xml.ts",
1025
+ status: "deleted",
1026
+ before: legacyXml,
1027
+ after: null,
1028
+ agentId: "cone-harbor"
1029
+ },
1030
+ {
1031
+ path: "/workspace/harbor/static/radar.png",
1032
+ status: "modified",
1033
+ before: null,
1034
+ after: null,
1035
+ agentId: "scoop-otter"
1036
+ }
1037
+ ];
1038
+ var skillEdit = {
1039
+ path: "/workspace/skills/release-notes/SKILL.md",
1040
+ status: "modified",
1041
+ before: skill,
1042
+ after: `${skill}4. Link each change to its pull request.
1043
+ `,
1044
+ agentId: null
1045
+ };
1046
+ var models = [
1047
+ { id: "claude-opus-5-5", label: "Claude Opus 5.5", provider: "Anthropic", images: true },
1048
+ { id: "claude-sonnet-5-5", label: "Claude Sonnet 5.5", provider: "Anthropic", images: true },
1049
+ { id: "claude-haiku-4-5", label: "Claude Haiku 4.5", provider: "Anthropic", images: true },
1050
+ { id: "local-small", label: "Local (ort-llama)", provider: "This browser", images: false },
1051
+ { id: "local-guard", label: "Local guard", provider: "This browser", kind: "classifier" },
1052
+ { id: "local-tiny", label: "Local tiny (no tools)", provider: "This browser", tools: false }
1053
+ ];
1054
+ var laterModels = [
1055
+ { id: "gpt-6", label: "GPT-6", provider: "OpenAI", images: true },
1056
+ { id: "gpt-6-mini", label: "GPT-6 mini", provider: "OpenAI", images: true, default: true }
1057
+ ];
1058
+ var defaults = {
1059
+ color: "system",
1060
+ model: "claude-sonnet-5-5",
1061
+ thinking: "medium",
1062
+ sendOnEnter: true,
1063
+ pushToTalk: false,
1064
+ showThinking: true,
1065
+ diffStyle: "unified"
1066
+ };
1067
+ var accounts = [
1068
+ { id: "anthropic", provider: "Anthropic", identity: "API key ending 3f2a", status: "connected" },
1069
+ { id: "github", provider: "GitHub", identity: "@example-dev", status: "connected" },
1070
+ { id: "adobe", provider: "Adobe", identity: "dev@example.com", status: "expired" },
1071
+ {
1072
+ id: "ai-adobe",
1073
+ provider: "Adobe AI",
1074
+ identity: "harbor-ops@example.com",
1075
+ status: "expired",
1076
+ auth: "oauth"
1077
+ },
1078
+ { id: "openai", provider: "OpenAI", identity: "", status: "disconnected", auth: "api-key" },
1079
+ {
1080
+ id: "amazon-bedrock",
1081
+ provider: "Amazon Bedrock",
1082
+ identity: "",
1083
+ status: "disconnected",
1084
+ auth: "api-key",
1085
+ fields: [
1086
+ {
1087
+ name: "region",
1088
+ label: "Region",
1089
+ required: true,
1090
+ options: [
1091
+ { value: "us-east-1", label: "US East (N. Virginia)" },
1092
+ { value: "us-west-2", label: "US West (Oregon)" },
1093
+ { value: "eu-central-1", label: "Europe (Frankfurt)" },
1094
+ { value: "ap-northeast-1", label: "Asia Pacific (Tokyo)" }
1095
+ ],
1096
+ value: "us-west-2"
1097
+ }
1098
+ ]
1099
+ },
1100
+ {
1101
+ id: "mcp:docs",
1102
+ provider: "docs",
1103
+ identity: "docs.example",
1104
+ status: "connected",
1105
+ auth: "api-key",
1106
+ group: "mcp",
1107
+ secret: "token"
1108
+ },
1109
+ {
1110
+ id: "mcp:tickets",
1111
+ provider: "tickets",
1112
+ identity: "tickets.example",
1113
+ status: "expired",
1114
+ auth: "api-key",
1115
+ group: "mcp",
1116
+ secret: "token"
1117
+ },
1118
+ {
1119
+ id: "mcp:design",
1120
+ provider: "design",
1121
+ identity: "design.example",
1122
+ status: "disconnected",
1123
+ auth: "oauth",
1124
+ group: "mcp"
1125
+ }
1126
+ ];
1127
+ var agents = [
1128
+ {
1129
+ id: "cone-sliccy",
1130
+ name: "sliccy",
1131
+ kind: "cone",
1132
+ parentId: null,
1133
+ status: "idle",
1134
+ model: "claude-sonnet-5-5",
1135
+ contextFill: 0.21,
1136
+ unread: 0
1137
+ },
1138
+ showcaseAgent,
1139
+ {
1140
+ id: "cone-harbor",
1141
+ name: "harbor",
1142
+ kind: "cone",
1143
+ parentId: null,
1144
+ status: "idle",
1145
+ model: "claude-opus-5-5",
1146
+ contextFill: 0.38,
1147
+ unread: 0
1148
+ },
1149
+ {
1150
+ id: "scoop-otter",
1151
+ name: "quiet-otter",
1152
+ kind: "scoop",
1153
+ parentId: "cone-harbor",
1154
+ status: "working",
1155
+ model: "claude-haiku-4-5",
1156
+ contextFill: 0.12,
1157
+ unread: 0
1158
+ },
1159
+ {
1160
+ id: "scoop-heron",
1161
+ name: "amber-heron",
1162
+ kind: "scoop",
1163
+ parentId: "cone-harbor",
1164
+ status: "idle",
1165
+ model: "claude-haiku-4-5",
1166
+ contextFill: 0.04,
1167
+ unread: 0
1168
+ },
1169
+ {
1170
+ id: "cone-release",
1171
+ name: "release-notes",
1172
+ kind: "cone",
1173
+ parentId: null,
1174
+ status: "idle",
1175
+ model: "claude-sonnet-5-5",
1176
+ contextFill: 0.7,
1177
+ unread: 1
1178
+ },
1179
+ {
1180
+ id: "cone-triage",
1181
+ name: "inbox-triage",
1182
+ kind: "cone",
1183
+ parentId: null,
1184
+ status: "waiting",
1185
+ model: "claude-sonnet-5-5",
1186
+ contextFill: 0.95,
1187
+ unread: 2
1188
+ },
1189
+ {
1190
+ id: "scoop-wren",
1191
+ name: "tidal-wren",
1192
+ kind: "scoop",
1193
+ parentId: "cone-sliccy",
1194
+ status: "idle",
1195
+ model: "claude-haiku-4-5",
1196
+ contextFill: 0.02,
1197
+ unread: 0
1198
+ },
1199
+ {
1200
+ id: "cone-tides",
1201
+ name: "tide-tables",
1202
+ kind: "cone",
1203
+ parentId: null,
1204
+ status: "idle",
1205
+ model: "claude-sonnet-5-5",
1206
+ contextFill: 0.31,
1207
+ unread: 0,
1208
+ frozen: true
1209
+ }
1210
+ ];
1211
+ var start2 = Date.UTC(2026, 9, 5, 8, 30);
1212
+ var at2 = (minutes) => start2 + minutes * 6e4;
1213
+ var queues = { "cone-kitchen": showcaseQueue() };
1214
+ var conversations = {
1215
+ "cone-kitchen": showcase(),
1216
+ "cone-sliccy": [
1217
+ {
1218
+ id: "m-s0",
1219
+ role: "assistant",
1220
+ status: "done",
1221
+ createdAt: at2(-24 * 60),
1222
+ parts: [
1223
+ {
1224
+ type: "text",
1225
+ text: "Hi, I am sliccy. Tell me a little about yourself, and I will set things up for you."
1226
+ },
1227
+ { type: "sprinkle", sprinkle: "welcome" }
1228
+ ]
1229
+ },
1230
+ {
1231
+ id: "m-s1",
1232
+ role: "user",
1233
+ text: "What is on my plate today?",
1234
+ createdAt: at2(0)
1235
+ },
1236
+ {
1237
+ id: "m-s2",
1238
+ role: "assistant",
1239
+ status: "done",
1240
+ createdAt: at2(1),
1241
+ parts: [
1242
+ {
1243
+ type: "tool",
1244
+ tool: {
1245
+ id: "t-s1",
1246
+ name: "read_file",
1247
+ title: "Read the standup notes",
1248
+ input: "/shared/notes/2026-10-04-standup.md",
1249
+ output: standup,
1250
+ status: "done",
1251
+ paths: ["/shared/notes/2026-10-04-standup.md"]
1252
+ }
1253
+ },
1254
+ {
1255
+ type: "text",
1256
+ text: "Three things carry over from yesterday:\n\n1. **harbor**: the retry change is in review, and the midnight cache bug is still open.\n2. **Docs**: the forecast page still shows Fahrenheit examples.\n3. **release-notes** has a draft waiting for you.\n\nThe `harbor` cone is the one to start with."
1257
+ }
1258
+ ]
1259
+ }
1260
+ ],
1261
+ "cone-harbor": [
1262
+ {
1263
+ id: "m-h1",
1264
+ role: "lick",
1265
+ channel: "webhook",
1266
+ title: "GitHub \xB7 issue opened",
1267
+ text: "example/harbor#57: Forecast stays on yesterday after midnight UTC",
1268
+ createdAt: at2(2)
1269
+ },
1270
+ {
1271
+ id: "m-h2",
1272
+ role: "user",
1273
+ text: "The forecast endpoint serves stale data after midnight UTC. Find out why and fix it.",
1274
+ createdAt: at2(3)
1275
+ },
1276
+ {
1277
+ id: "m-h3",
1278
+ role: "assistant",
1279
+ status: "done",
1280
+ createdAt: at2(4),
1281
+ parts: [
1282
+ {
1283
+ type: "thinking",
1284
+ text: "Stale after midnight points at the cache expiry. Check how the cache compares days."
1285
+ },
1286
+ {
1287
+ type: "tool",
1288
+ tool: {
1289
+ id: "t-h1",
1290
+ name: "bash",
1291
+ title: "Search for the cache expiry",
1292
+ input: 'rg -n "getDate|TTL" src',
1293
+ output: "src/lib/cache.ts:6:const TTL = 60 * 60 * 1000;\nsrc/lib/cache.ts:14: const day = new Date(entry.storedAt).getDate();\nsrc/lib/cache.ts:16: if (now - entry.storedAt > TTL) return undefined;",
1294
+ status: "done",
1295
+ paths: ["/workspace/harbor/src/lib/cache.ts"]
1296
+ }
1297
+ },
1298
+ {
1299
+ type: "tool",
1300
+ tool: {
1301
+ id: "t-h2",
1302
+ name: "read_file",
1303
+ title: "Read the cache",
1304
+ input: "/workspace/harbor/src/lib/cache.ts",
1305
+ output: cacheBefore,
1306
+ status: "done",
1307
+ paths: ["/workspace/harbor/src/lib/cache.ts"]
1308
+ }
1309
+ },
1310
+ {
1311
+ type: "tool",
1312
+ tool: {
1313
+ id: "t-h3",
1314
+ name: "edit_file",
1315
+ title: "Expire entries from the previous day",
1316
+ input: "/workspace/harbor/src/lib/cache.ts",
1317
+ output: "Replaced 1 block (4 lines \u2192 7 lines), added dayOf()",
1318
+ status: "done",
1319
+ paths: ["/workspace/harbor/src/lib/cache.ts"],
1320
+ diff: { before: cacheBefore, after: cacheAfter }
1321
+ }
1322
+ },
1323
+ {
1324
+ type: "tool",
1325
+ tool: {
1326
+ id: "t-h4",
1327
+ name: "bash",
1328
+ title: "Run the tests",
1329
+ input: "npm test",
1330
+ meta: "timeout 120s",
1331
+ output: "> harbor@0.0.0 test\n> node --test test/\n\n\u2714 converts both ways (0.6ms)\n\u2714 expires entries from the previous day (0.4ms)\n\u2139 tests 2\n\u2139 pass 2\n\u2139 fail 0",
1332
+ status: "done",
1333
+ paths: []
1334
+ }
1335
+ },
1336
+ {
1337
+ type: "text",
1338
+ text: "Found it. `ForecastCache.get` compared **local** calendar days, and when the day changed it returned the old entry instead of dropping it:\n\n```ts\nif (day !== new Date(now).getDate()) return entry.value;\n```\n\nI changed it to compare UTC dates and delete the entry, so the first request after midnight goes upstream. The tests pass.\n\n| | Before | After |\n| --- | --- | --- |\n| Day compared | local `getDate()` | UTC `toISOString()` |\n| On a new day | returns stale entry | deletes and refetches |\n\nquiet-otter is adding retries for upstream timeouts in a scoop."
1339
+ },
1340
+ {
1341
+ type: "delegation",
1342
+ kind: "scoop",
1343
+ scoop: "quiet-otter",
1344
+ text: "Started a scoop to add retries around the upstream fetch"
1345
+ }
1346
+ ]
1347
+ },
1348
+ {
1349
+ id: "m-h4",
1350
+ role: "lick",
1351
+ channel: "scoop-notify",
1352
+ title: "quiet-otter",
1353
+ text: "Started: add retries with backoff around the upstream fetch",
1354
+ createdAt: at2(9)
1355
+ }
1356
+ ],
1357
+ "scoop-otter": [
1358
+ {
1359
+ id: "m-o1",
1360
+ role: "user",
1361
+ origin: "agent",
1362
+ from: "harbor",
1363
+ text: "Wrap the upstream fetch in src/routes/forecast.ts with three retries and exponential backoff.",
1364
+ createdAt: at2(9)
1365
+ },
1366
+ {
1367
+ id: "m-o2",
1368
+ role: "assistant",
1369
+ status: "done",
1370
+ createdAt: at2(10),
1371
+ parts: [
1372
+ {
1373
+ type: "tool",
1374
+ tool: {
1375
+ id: "t-o1",
1376
+ name: "write_file",
1377
+ title: "Add retry()",
1378
+ input: "/workspace/harbor/src/lib/retry.ts",
1379
+ output: "Wrote 12 lines",
1380
+ status: "done",
1381
+ paths: ["/workspace/harbor/src/lib/retry.ts"]
1382
+ }
1383
+ },
1384
+ {
1385
+ type: "tool",
1386
+ tool: {
1387
+ id: "t-o2",
1388
+ name: "edit_file",
1389
+ title: "Use retry() for the upstream call",
1390
+ input: "/workspace/harbor/src/routes/forecast.ts",
1391
+ output: "Replaced 1 block",
1392
+ status: "done",
1393
+ paths: ["/workspace/harbor/src/routes/forecast.ts"]
1394
+ }
1395
+ },
1396
+ { type: "text", text: "Both edits are in. Running the tests next." }
1397
+ ]
1398
+ }
1399
+ ],
1400
+ "scoop-heron": [],
1401
+ "scoop-wren": [],
1402
+ "cone-tides": [
1403
+ {
1404
+ id: "m-f1",
1405
+ role: "user",
1406
+ text: "Add a tide table endpoint next to the forecast, with high and low water per day.",
1407
+ createdAt: at2(-2900)
1408
+ },
1409
+ {
1410
+ id: "m-f2",
1411
+ role: "assistant",
1412
+ status: "done",
1413
+ createdAt: at2(-2898),
1414
+ parts: [
1415
+ {
1416
+ type: "text",
1417
+ text: "Added `/v1/tides?city=` with the next seven days of high and low water. It reads from the same upstream as the forecast and shares its cache."
1418
+ }
1419
+ ]
1420
+ }
1421
+ ],
1422
+ "cone-release": [
1423
+ {
1424
+ id: "m-r1",
1425
+ role: "lick",
1426
+ channel: "cron",
1427
+ title: "Every Friday 09:00",
1428
+ text: "Draft the release notes for example/harbor",
1429
+ createdAt: at2(-1440)
1430
+ },
1431
+ {
1432
+ id: "m-r2",
1433
+ role: "assistant",
1434
+ status: "done",
1435
+ createdAt: at2(-1438),
1436
+ parts: [
1437
+ {
1438
+ type: "tool",
1439
+ tool: {
1440
+ id: "t-r1",
1441
+ name: "bash",
1442
+ title: "Collect merged pull requests",
1443
+ input: "collect.jsh example/harbor v1.8.0",
1444
+ output: "41 fix Return 400 when the city is missing\n43 feature Add /health\n44 chore Update wrangler to 4.40.0",
1445
+ status: "done",
1446
+ paths: ["/workspace/skills/release-notes/scripts/collect.jsh"]
1447
+ }
1448
+ },
1449
+ {
1450
+ type: "text",
1451
+ text: "## harbor 1.9.0\n\n**Features**\n- Added a `/health` endpoint.\n\n**Fixes**\n- Requests without a city now get a 400 instead of a 500.\n\n**Chores**\n- Updated wrangler to 4.40.0."
1452
+ }
1453
+ ]
1454
+ },
1455
+ {
1456
+ id: "m-r3",
1457
+ role: "lick",
1458
+ channel: "cron",
1459
+ severity: "error",
1460
+ title: "Crontab",
1461
+ text: "Crontab line 3 is invalid",
1462
+ body: "30 25 * * * sweep-drafts\nThe hour is 25. It must be 0 to 23.",
1463
+ createdAt: at2(-1430)
1464
+ },
1465
+ {
1466
+ id: "m-r4",
1467
+ role: "lick",
1468
+ channel: "fswatch",
1469
+ severity: "warn",
1470
+ title: "/workspace/notes",
1471
+ text: "File watch on /workspace/notes stopped; retrying",
1472
+ createdAt: at2(-1429)
1473
+ },
1474
+ {
1475
+ id: "m-r5",
1476
+ role: "lick",
1477
+ channel: "workflow",
1478
+ title: "Release report",
1479
+ text: "The weekly release report is ready",
1480
+ body: "Summary: harbor 1.9.0 shipped three changes this week, the /health endpoint, a 400 for requests without a city and a wrangler update, with no failed deploys, no rollbacks and two flaky test runs in the forecast suite that passed on retry, so the next release can go out on Friday as planned once the docs pass lands.",
1481
+ createdAt: at2(-1428)
1482
+ }
1483
+ ],
1484
+ "cone-triage": [
1485
+ {
1486
+ id: "m-t1",
1487
+ role: "system",
1488
+ kind: "compaction",
1489
+ trigger: "threshold",
1490
+ state: "summarized",
1491
+ title: "Context compacted",
1492
+ text: "41 earlier messages were summarized to stay under the context limit.",
1493
+ createdAt: at2(-30)
1494
+ },
1495
+ {
1496
+ id: "m-t2",
1497
+ role: "assistant",
1498
+ status: "done",
1499
+ createdAt: at2(-20),
1500
+ parts: [
1501
+ {
1502
+ type: "text",
1503
+ text: "Two messages need a decision from you:\n\n- A vendor asks to move Thursday\u2019s call to Friday.\n- The staging certificate expires in 6 days. Should I open a ticket?"
1504
+ }
1505
+ ]
1506
+ },
1507
+ {
1508
+ id: "m-t3",
1509
+ role: "system",
1510
+ kind: "error",
1511
+ action: "login",
1512
+ title: "Mail connector",
1513
+ text: "The connector returned 401. Reconnect the account in Settings.",
1514
+ createdAt: at2(-19)
1515
+ },
1516
+ {
1517
+ id: "m-t4",
1518
+ role: "system",
1519
+ kind: "error",
1520
+ action: "login",
1521
+ account: "mcp:tickets",
1522
+ title: "Tickets connector",
1523
+ text: "The tickets server rejected its token.",
1524
+ createdAt: at2(-18)
1525
+ }
1526
+ ]
1527
+ };
1528
+ var tabs = [
1529
+ {
1530
+ id: "tab-preview",
1531
+ title: "harbor \xB7 localhost",
1532
+ url: "http://localhost:8787/forecast?city=Lisbon",
1533
+ status: "complete",
1534
+ agentId: "cone-harbor"
1535
+ },
1536
+ {
1537
+ id: "tab-docs",
1538
+ title: "Forecast API reference",
1539
+ url: "https://api.example.com/docs/v2/forecast",
1540
+ status: "complete",
1541
+ agentId: null,
1542
+ favicon: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect width="16" height="16" rx="3" fill="#7a5af8"/><text x="8" y="12" font-family="sans-serif" font-size="11" font-weight="700" fill="#ffffff" text-anchor="middle">A</text></svg>')}`
1543
+ },
1544
+ {
1545
+ id: "tab-pull",
1546
+ title: "Add retries for upstream timeouts \xB7 Pull request #58",
1547
+ url: "https://git.example.com/example/harbor/pull/58",
1548
+ status: "complete",
1549
+ agentId: "scoop-otter",
1550
+ favicon: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect width="16" height="16" rx="3" fill="#0a7a43"/><text x="8" y="12" font-family="sans-serif" font-size="11" font-weight="700" fill="#ffffff" text-anchor="middle">G</text></svg>')}`
1551
+ }
1552
+ ];
1553
+ var terminals = [
1554
+ { id: "term-1", title: "bash", cwd: "/workspace/harbor", agentId: null }
1555
+ ];
1556
+
1557
+ // src/dummy/providers.ts
1558
+ var options = [
1559
+ {
1560
+ id: "ai-adobe",
1561
+ name: "Adobe AI",
1562
+ description: "Models from your Adobe plan",
1563
+ auth: "oauth"
1564
+ },
1565
+ {
1566
+ id: "amazon-bedrock",
1567
+ name: "Amazon Bedrock",
1568
+ description: "Claude and more through AWS",
1569
+ auth: "fields",
1570
+ keyUrl: "https://console.aws.example/bedrock/api-keys",
1571
+ needs: "cors-free-transport",
1572
+ fields: accounts.find((account) => account.id === "amazon-bedrock")?.fields
1573
+ },
1574
+ {
1575
+ id: "anthropic",
1576
+ name: "Anthropic",
1577
+ description: "Claude models with your own key",
1578
+ auth: "api-key",
1579
+ keyUrl: "https://console.anthropic.example/keys"
1580
+ },
1581
+ {
1582
+ id: "openai",
1583
+ name: "OpenAI",
1584
+ description: "GPT models with your own key",
1585
+ auth: "api-key",
1586
+ keyUrl: "https://platform.openai.example/api-keys"
1587
+ },
1588
+ {
1589
+ id: "mistral",
1590
+ name: "Mistral",
1591
+ description: "Mistral and Codestral models",
1592
+ auth: "api-key",
1593
+ keyUrl: "https://console.mistral.example/keys"
1594
+ },
1595
+ {
1596
+ id: "groq",
1597
+ name: "Groq",
1598
+ description: "Fast open models",
1599
+ auth: "api-key",
1600
+ keyUrl: "https://console.groq.example/keys"
1601
+ },
1602
+ {
1603
+ id: "openrouter",
1604
+ name: "OpenRouter",
1605
+ description: "Many providers behind one key",
1606
+ auth: "api-key",
1607
+ keyUrl: "https://openrouter.example/keys"
1608
+ },
1609
+ {
1610
+ id: "cerebras",
1611
+ name: "Cerebras",
1612
+ description: "Open models on wafer-scale chips",
1613
+ auth: "api-key",
1614
+ keyUrl: "https://cloud.cerebras.example/keys"
1615
+ },
1616
+ {
1617
+ id: "github-copilot",
1618
+ name: "GitHub Copilot",
1619
+ description: "Models from your Copilot plan",
1620
+ auth: "oauth"
1621
+ },
1622
+ {
1623
+ id: "openai-codex",
1624
+ name: "OpenAI Codex",
1625
+ description: "Models from your ChatGPT plan",
1626
+ auth: "oauth"
1627
+ },
1628
+ {
1629
+ id: "azure-openai",
1630
+ name: "Azure OpenAI",
1631
+ description: "Your own Azure deployment",
1632
+ auth: "fields",
1633
+ keyUrl: "https://portal.azure.example/openai",
1634
+ fields: [
1635
+ {
1636
+ name: "endpoint",
1637
+ label: "Endpoint",
1638
+ required: true,
1639
+ customBase: true,
1640
+ placeholder: "https://harbor.openai.azure.example",
1641
+ help: "The resource\u2019s endpoint, from Keys and endpoint."
1642
+ },
1643
+ { name: "deployment", label: "Deployment", required: true, placeholder: "harbor-gpt" }
1644
+ ],
1645
+ vaultName: "AZURE_OPENAI_API_KEY"
1646
+ },
1647
+ {
1648
+ id: "cloudflare",
1649
+ name: "Cloudflare",
1650
+ description: "Workers AI models",
1651
+ auth: "fields",
1652
+ keyUrl: "https://dash.cloudflare.example/api-tokens",
1653
+ needs: "cors-free-transport",
1654
+ fields: [
1655
+ {
1656
+ name: "account",
1657
+ label: "Account ID",
1658
+ required: true,
1659
+ placeholder: "3f2a9c\u2026",
1660
+ help: "On the right of the Workers AI page."
1661
+ }
1662
+ ],
1663
+ vaultName: "CLOUDFLARE_WORKERS_AI_API_KEY"
1664
+ },
1665
+ {
1666
+ id: "ollama",
1667
+ name: "Ollama",
1668
+ description: "Models running on this device",
1669
+ auth: "local"
1670
+ },
1671
+ {
1672
+ id: "lm-studio",
1673
+ name: "LM Studio",
1674
+ description: "Models served by LM Studio on this device",
1675
+ auth: "local"
1676
+ }
1677
+ ];
1678
+ var providerFixtures = options.sort(
1679
+ (a, b) => a.name.localeCompare(b.name)
1680
+ );
1681
+ function providerAccount(option) {
1682
+ const keyed = option.auth === "api-key" || option.auth === "fields";
1683
+ return {
1684
+ id: option.id,
1685
+ provider: option.name,
1686
+ identity: "",
1687
+ status: "disconnected",
1688
+ group: "providers",
1689
+ auth: keyed ? "api-key" : option.auth,
1690
+ ...keyed ? { store: "vault" } : {},
1691
+ ...option.fields ? { fields: option.fields } : {},
1692
+ ...option.keyUrl ? { keyUrl: option.keyUrl } : {},
1693
+ ...option.description ? { description: option.description } : {},
1694
+ ...option.needs ? { needs: option.needs } : {},
1695
+ ...option.vaultName ? { vaultName: option.vaultName } : {}
1696
+ };
1697
+ }
1698
+ function providerAccounts(rows) {
1699
+ const added = (id, patch) => ({
1700
+ ...providerAccount(providerFixtures.find((option) => option.id === id)),
1701
+ ...patch
1702
+ });
1703
+ const offered = (account) => providerFixtures.find((option) => option.id === account.id);
1704
+ const providers = rows.filter((account) => account.group !== "mcp").map((account) => {
1705
+ const option = offered(account);
1706
+ return option ? { ...providerAccount(option), ...account } : account;
1707
+ });
1708
+ const servers = rows.filter((account) => account.group === "mcp");
1709
+ return [
1710
+ ...providers,
1711
+ added("ollama", { status: "connected", identity: "On this device" }),
1712
+ ...servers
1713
+ ];
1714
+ }
1715
+
1716
+ // src/dummy/clock.ts
1717
+ function clock(delay) {
1718
+ return {
1719
+ delay,
1720
+ sleep: (ticks = 1) => new Promise((resolve) => setTimeout(resolve, delay * ticks))
1721
+ };
1722
+ }
1723
+
1724
+ // src/dummy/settings.ts
1725
+ var dummySettingsKey = "slicc-ui.settings";
1726
+ var signInClosed = "The sign-in window was closed before it finished.";
1727
+ var DummySettings = class extends Emitter {
1728
+ #settings;
1729
+ #accounts;
1730
+ #models;
1731
+ #storage;
1732
+ #clock;
1733
+ #keys;
1734
+ #signIns = /* @__PURE__ */ new Map();
1735
+ #gated;
1736
+ #fails;
1737
+ connects = [];
1738
+ providers;
1739
+ remove;
1740
+ constructor(defaults2, models2, accounts2, storage, clock2, keys = [dummySettingsKey], gated = false, login, providers) {
1741
+ super();
1742
+ if (providers) {
1743
+ this.providers = () => providers;
1744
+ this.remove = async (id) => this.#drop(id);
1745
+ }
1746
+ this.#fails = login === "fail";
1747
+ this.#gated = gated;
1748
+ this.#keys = keys;
1749
+ this.#storage = storage;
1750
+ this.#clock = clock2;
1751
+ this.#models = models2;
1752
+ this.#accounts = accounts2.map((account) => ({ ...account }));
1753
+ this.#settings = { ...defaults2, ...this.#load() };
1754
+ }
1755
+ #load() {
1756
+ try {
1757
+ const saved = this.#keys.map((key) => this.#storage?.getItem(key)).find((value) => value != null);
1758
+ return JSON.parse(saved ?? "{}");
1759
+ } catch {
1760
+ return {};
1761
+ }
1762
+ }
1763
+ get() {
1764
+ return { ...this.#settings };
1765
+ }
1766
+ update(patch) {
1767
+ this.#settings = { ...this.#settings, ...patch };
1768
+ this.#storage?.setItem(this.#keys[0], JSON.stringify(this.#settings));
1769
+ this.emit("settings", this.get());
1770
+ }
1771
+ models() {
1772
+ if (!this.#gated) return this.#models;
1773
+ const live = new Set(
1774
+ this.#accounts.filter((account) => account.status === "connected").map((account) => account.provider)
1775
+ );
1776
+ return this.#models.filter((option) => live.has(option.provider));
1777
+ }
1778
+ accounts() {
1779
+ return this.#accounts.map((account) => ({ ...account }));
1780
+ }
1781
+ #set(id, patch) {
1782
+ this.#accounts = this.#accounts.map(
1783
+ (account) => account.id === id ? { ...account, ...patch } : account
1784
+ );
1785
+ this.emit("accounts", this.accounts());
1786
+ }
1787
+ #drop(id) {
1788
+ this.#signIns.get(id)?.stop();
1789
+ this.#accounts = this.#accounts.filter((account) => account.id !== id);
1790
+ this.emit("accounts", this.accounts());
1791
+ }
1792
+ #add(id) {
1793
+ const option = this.providers?.().find((candidate) => candidate.id === id);
1794
+ if (!option || this.#accounts.some((account) => account.id === id)) return false;
1795
+ this.#accounts = [...this.#accounts, providerAccount(option)];
1796
+ this.emit("accounts", this.accounts());
1797
+ return true;
1798
+ }
1799
+ async connect(id, secret, fields) {
1800
+ const activation = globalThis.navigator?.userActivation;
1801
+ this.connects.push({
1802
+ id,
1803
+ gesture: activation ? activation.isActive : null,
1804
+ ...fields ? { fields } : {}
1805
+ });
1806
+ const fresh = this.#add(id);
1807
+ try {
1808
+ if (!await this.#connect(id, secret, fields) && fresh) this.#drop(id);
1809
+ } catch (error) {
1810
+ if (fresh) this.#drop(id);
1811
+ throw error;
1812
+ }
1813
+ }
1814
+ async #connect(id, secret, fields) {
1815
+ const account = this.#accounts.find((candidate) => candidate.id === id);
1816
+ const local = account?.auth === "local";
1817
+ const window = !!account && account.auth !== "api-key" && !local;
1818
+ if (window && !await this.#signIn(account)) return false;
1819
+ if (window && this.#fails) {
1820
+ this.#set(id, { status: account.status });
1821
+ throw new Error(signInClosed);
1822
+ }
1823
+ if (!window) await this.#clock.sleep(10);
1824
+ if (account?.auth === "api-key" && account.store !== "vault" && !secret)
1825
+ throw new Error(
1826
+ `${account.provider} needs ${account.secret === "token" ? "a token" : "an API key"}`
1827
+ );
1828
+ if ([secret, ...Object.values(fields ?? {})].some((value) => value?.startsWith("bad")))
1829
+ throw new Error(`${account?.provider} didn\u2019t accept that key.`);
1830
+ if (fields?.endpoint?.includes("://") && !fields.endpoint.startsWith("https://"))
1831
+ throw new Error("The endpoint must use https://.");
1832
+ const identity = account?.group === "mcp" ? account.identity : local ? "On this device" : account?.auth === "api-key" ? "API key" : account?.identity || `${id}-user@example.com`;
1833
+ this.#set(id, { status: "connected", identity });
1834
+ return true;
1835
+ }
1836
+ #signIn(account) {
1837
+ const before = this.#signIns.get(account.id)?.before ?? account.status;
1838
+ this.#signIns.get(account.id)?.stop();
1839
+ this.#set(account.id, { status: "signing-in" });
1840
+ return new Promise((resolve) => {
1841
+ const stop = () => {
1842
+ this.#signIns.delete(account.id);
1843
+ this.#set(account.id, { status: before });
1844
+ resolve(false);
1845
+ };
1846
+ this.#signIns.set(account.id, { before, stop });
1847
+ void this.#clock.sleep(100).then(() => {
1848
+ if (this.#signIns.get(account.id)?.stop !== stop) return;
1849
+ this.#signIns.delete(account.id);
1850
+ resolve(true);
1851
+ });
1852
+ });
1853
+ }
1854
+ cancel(id) {
1855
+ this.#signIns.get(id)?.stop();
1856
+ }
1857
+ disconnect(id) {
1858
+ this.#set(id, { status: "disconnected" });
1859
+ }
1860
+ };
1861
+
1862
+ export {
1863
+ clock,
1864
+ files,
1865
+ directories,
1866
+ mountPoint,
1867
+ mounted,
1868
+ pending,
1869
+ skillEdit,
1870
+ models,
1871
+ laterModels,
1872
+ defaults,
1873
+ accounts,
1874
+ agents,
1875
+ queues,
1876
+ conversations,
1877
+ tabs,
1878
+ terminals,
1879
+ providerFixtures,
1880
+ providerAccount,
1881
+ providerAccounts,
1882
+ dummySettingsKey,
1883
+ DummySettings
1884
+ };
1885
+ //# sourceMappingURL=chunk-RYA3OPGE.js.map