@ai-ecoverse/slicc-agent 3.40.1 → 3.42.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.
Files changed (66) hide show
  1. package/README.md +25 -4
  2. package/bin/agent +9 -4
  3. package/dist/client.js +4 -1
  4. package/dist/client.js.map +2 -2
  5. package/dist/host.js +59 -21
  6. package/dist/host.js.map +3 -3
  7. package/dist/index.js +6 -0
  8. package/dist/index.js.map +2 -2
  9. package/dist/licks/config.js +20 -4
  10. package/dist/licks/config.js.map +2 -2
  11. package/dist/licks/fswatch.js +2 -1
  12. package/dist/licks/fswatch.js.map +2 -2
  13. package/dist/licks/licks.js +31 -4
  14. package/dist/licks/licks.js.map +3 -3
  15. package/dist/licks/sources.js +2 -1
  16. package/dist/licks/sources.js.map +2 -2
  17. package/dist/licks/triage.js +86 -0
  18. package/dist/licks/triage.js.map +7 -0
  19. package/dist/main.js +6 -0
  20. package/dist/main.js.map +2 -2
  21. package/dist/owner.js +2 -1
  22. package/dist/owner.js.map +2 -2
  23. package/dist/readonly.js +13 -0
  24. package/dist/readonly.js.map +2 -2
  25. package/dist/remote/host.js +10 -4
  26. package/dist/remote/host.js.map +2 -2
  27. package/dist/remote/pages.js +216 -0
  28. package/dist/remote/pages.js.map +7 -0
  29. package/dist/scoops/cli.js +17 -0
  30. package/dist/scoops/cli.js.map +2 -2
  31. package/dist/scoops/index.js +4 -2
  32. package/dist/scoops/index.js.map +2 -2
  33. package/dist/scoops/requests.js +14 -2
  34. package/dist/scoops/requests.js.map +2 -2
  35. package/dist/scoops/service.js.map +1 -1
  36. package/dist/services.js +2 -0
  37. package/dist/services.js.map +2 -2
  38. package/dist/spectrum/index.js +7 -0
  39. package/dist/spectrum/index.js.map +3 -3
  40. package/dist/spectrum/page-port.js +132 -0
  41. package/dist/spectrum/page-port.js.map +7 -0
  42. package/dist/sprinkles/index.js +1 -2
  43. package/dist/sprinkles/index.js.map +2 -2
  44. package/dist/types/client.d.ts +2 -0
  45. package/dist/types/host.d.ts +8 -3
  46. package/dist/types/index.d.ts +3 -2
  47. package/dist/types/licks/config.d.ts +2 -0
  48. package/dist/types/licks/licks.d.ts +3 -0
  49. package/dist/types/licks/triage.d.ts +42 -0
  50. package/dist/types/owner.d.ts +1 -0
  51. package/dist/types/readonly.d.ts +2 -0
  52. package/dist/types/remote/host.d.ts +10 -0
  53. package/dist/types/remote/pages.d.ts +76 -0
  54. package/dist/types/scoops/cli.d.ts +7 -0
  55. package/dist/types/scoops/index.d.ts +1 -0
  56. package/dist/types/scoops/requests.d.ts +2 -1
  57. package/dist/types/scoops/service.d.ts +1 -0
  58. package/dist/types/services.d.ts +2 -0
  59. package/dist/types/spectrum/index.d.ts +4 -0
  60. package/dist/types/spectrum/page-port.d.ts +32 -0
  61. package/dist/types/sprinkles/index.d.ts +1 -1
  62. package/dist/worker.js +2 -1
  63. package/dist/worker.js.map +2 -2
  64. package/package.json +1 -1
  65. package/packages/vfs-root/skills/agent/SKILL.md +2 -0
  66. package/packages/vfs-root/skills/sprinkles/SKILL.md +1 -1
@@ -2,6 +2,7 @@ import { AgentAdapter } from "./agent-port.js";
2
2
  import { ChangesAdapter } from "./changes-port.js";
3
3
  import { MemoryAdapter } from "./memory-port.js";
4
4
  import { MonitorAdapter } from "./monitor-port.js";
5
+ import { PageAdapter } from "./page-port.js";
5
6
  import { SettingsAdapter } from "./settings-port.js";
6
7
  import { SprinkleAdapter } from "./sprinkle-port.js";
7
8
  import { TrayAdapter } from "./tray-port.js";
@@ -21,13 +22,18 @@ import {
21
22
  MonitorAdapter as MonitorAdapter2,
22
23
  monitorSnapshot
23
24
  } from "./monitor-port.js";
25
+ import {
26
+ PageAdapter as PageAdapter2
27
+ } from "./page-port.js";
24
28
  import { SETTINGS_KEY, SettingsAdapter as SettingsAdapter2 } from "./settings-port.js";
25
29
  import { SprinkleAdapter as SprinkleAdapter2 } from "./sprinkle-port.js";
26
30
  import { TrayAdapter as TrayAdapter2 } from "./tray-port.js";
27
31
  function createAgentModel(connection, options = {}) {
28
32
  const agent = new AgentAdapter(connection);
33
+ const run = options.page?.run;
29
34
  return {
30
35
  agent,
36
+ page: options.page && run ? new PageAdapter(connection, agent, { ...options.page, run }) : null,
31
37
  settings: new SettingsAdapter(
32
38
  connection.settings,
33
39
  agent,
@@ -50,6 +56,7 @@ export {
50
56
  ChangesAdapter2 as ChangesAdapter,
51
57
  MemoryAdapter2 as MemoryAdapter,
52
58
  MonitorAdapter2 as MonitorAdapter,
59
+ PageAdapter2 as PageAdapter,
53
60
  SETTINGS_KEY,
54
61
  SettingsAdapter2 as SettingsAdapter,
55
62
  SprinkleAdapter2 as SprinkleAdapter,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/spectrum/index.ts"],
4
- "sourcesContent": ["import type { MonitorSection } from '@ai-ecoverse/slicc-spectrum/ui';\nimport type { AgentConnection } from '../client.ts';\nimport { AgentAdapter } from './agent-port.ts';\nimport { ChangesAdapter } from './changes-port.ts';\nimport { MemoryAdapter } from './memory-port.ts';\nimport { MonitorAdapter } from './monitor-port.ts';\nimport { type Login, SettingsAdapter } from './settings-port.ts';\nimport { SprinkleAdapter } from './sprinkle-port.ts';\nimport { TrayAdapter } from './tray-port.ts';\n\nexport { AgentAdapter, CONE } from './agent-port.ts';\nexport { ChangesAdapter, type ChangesEvents } from './changes-port.ts';\nexport { MemoryAdapter } from './memory-port.ts';\nexport {\n assistant,\n errorAction,\n errorPart,\n isBusy,\n queued,\n textOf,\n toMessages,\n} from './messages.ts';\nexport {\n MonitorAdapter,\n type MonitorAdapterOptions,\n type MonitorInput,\n monitorSnapshot,\n} from './monitor-port.ts';\nexport { type Login, SETTINGS_KEY, SettingsAdapter } from './settings-port.ts';\nexport { SprinkleAdapter } from './sprinkle-port.ts';\nexport { TrayAdapter } from './tray-port.ts';\n\nexport function createAgentModel(\n connection: AgentConnection,\n options: {\n storage?: Pick<Storage, 'getItem' | 'setItem'> | null;\n login?: Login;\n monitor?: { extra?: () => readonly MonitorSection[] };\n } = {}\n): {\n agent: AgentAdapter;\n settings: SettingsAdapter;\n tray: TrayAdapter;\n sprinkles: SprinkleAdapter;\n memory: MemoryAdapter;\n changes: ChangesAdapter;\n monitor: MonitorAdapter;\n} {\n const agent = new AgentAdapter(connection);\n return {\n agent,\n settings: new SettingsAdapter(\n connection.settings,\n agent,\n options.storage ?? null,\n options.login ?? null\n ),\n tray: new TrayAdapter(connection.settings),\n sprinkles: new SprinkleAdapter(connection),\n memory: new MemoryAdapter(connection),\n changes: new ChangesAdapter(connection),\n monitor: new MonitorAdapter(\n connection,\n options.monitor?.extra ? { extra: options.monitor.extra } : {}\n ),\n };\n}\n"],
5
- "mappings": "AAEA,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAC/B,SAAqB,uBAAuB;AAC5C,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAE5B,SAAS,gBAAAA,eAAc,YAAY;AACnC,SAAS,kBAAAC,uBAA0C;AACnD,SAAS,iBAAAC,sBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE,kBAAAC;AAAA,EAGA;AAAA,OACK;AACP,SAAqB,cAAc,mBAAAC,wBAAuB;AAC1D,SAAS,mBAAAC,wBAAuB;AAChC,SAAS,eAAAC,oBAAmB;AAErB,SAAS,iBACd,YACA,UAII,CAAC,GASL;AACA,QAAM,QAAQ,IAAI,aAAa,UAAU;AACzC,SAAO;AAAA,IACL;AAAA,IACA,UAAU,IAAI;AAAA,MACZ,WAAW;AAAA,MACX;AAAA,MACA,QAAQ,WAAW;AAAA,MACnB,QAAQ,SAAS;AAAA,IACnB;AAAA,IACA,MAAM,IAAI,YAAY,WAAW,QAAQ;AAAA,IACzC,WAAW,IAAI,gBAAgB,UAAU;AAAA,IACzC,QAAQ,IAAI,cAAc,UAAU;AAAA,IACpC,SAAS,IAAI,eAAe,UAAU;AAAA,IACtC,SAAS,IAAI;AAAA,MACX;AAAA,MACA,QAAQ,SAAS,QAAQ,EAAE,OAAO,QAAQ,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;",
6
- "names": ["AgentAdapter", "ChangesAdapter", "MemoryAdapter", "MonitorAdapter", "SettingsAdapter", "SprinkleAdapter", "TrayAdapter"]
4
+ "sourcesContent": ["import type { MonitorSection } from '@ai-ecoverse/slicc-spectrum/ui';\nimport type { AgentConnection } from '../client.ts';\nimport { AgentAdapter } from './agent-port.ts';\nimport { ChangesAdapter } from './changes-port.ts';\nimport { MemoryAdapter } from './memory-port.ts';\nimport { MonitorAdapter } from './monitor-port.ts';\nimport { PageAdapter, type PageOptions } from './page-port.ts';\nimport { type Login, SettingsAdapter } from './settings-port.ts';\nimport { SprinkleAdapter } from './sprinkle-port.ts';\nimport { TrayAdapter } from './tray-port.ts';\n\nexport { AgentAdapter, CONE } from './agent-port.ts';\nexport { ChangesAdapter, type ChangesEvents } from './changes-port.ts';\nexport { MemoryAdapter } from './memory-port.ts';\nexport {\n assistant,\n errorAction,\n errorPart,\n isBusy,\n queued,\n textOf,\n toMessages,\n} from './messages.ts';\nexport {\n MonitorAdapter,\n type MonitorAdapterOptions,\n type MonitorInput,\n monitorSnapshot,\n} from './monitor-port.ts';\nexport {\n PageAdapter,\n type PageDocument,\n type PageOptions,\n type PageRun,\n type PageTarget,\n} from './page-port.ts';\nexport { type Login, SETTINGS_KEY, SettingsAdapter } from './settings-port.ts';\nexport { SprinkleAdapter } from './sprinkle-port.ts';\nexport { TrayAdapter } from './tray-port.ts';\n\nexport function createAgentModel(\n connection: AgentConnection,\n options: {\n storage?: Pick<Storage, 'getItem' | 'setItem'> | null;\n login?: Login;\n monitor?: { extra?: () => readonly MonitorSection[] };\n page?: PageOptions;\n } = {}\n): {\n agent: AgentAdapter;\n settings: SettingsAdapter;\n tray: TrayAdapter;\n sprinkles: SprinkleAdapter;\n memory: MemoryAdapter;\n changes: ChangesAdapter;\n monitor: MonitorAdapter;\n page: PageAdapter | null;\n} {\n const agent = new AgentAdapter(connection);\n const run = options.page?.run;\n return {\n agent,\n page: options.page && run ? new PageAdapter(connection, agent, { ...options.page, run }) : null,\n settings: new SettingsAdapter(\n connection.settings,\n agent,\n options.storage ?? null,\n options.login ?? null\n ),\n tray: new TrayAdapter(connection.settings),\n sprinkles: new SprinkleAdapter(connection),\n memory: new MemoryAdapter(connection),\n changes: new ChangesAdapter(connection),\n monitor: new MonitorAdapter(\n connection,\n options.monitor?.extra ? { extra: options.monitor.extra } : {}\n ),\n };\n}\n"],
5
+ "mappings": "AAEA,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAC/B,SAAS,mBAAqC;AAC9C,SAAqB,uBAAuB;AAC5C,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAE5B,SAAS,gBAAAA,eAAc,YAAY;AACnC,SAAS,kBAAAC,uBAA0C;AACnD,SAAS,iBAAAC,sBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE,kBAAAC;AAAA,EAGA;AAAA,OACK;AACP;AAAA,EACE,eAAAC;AAAA,OAKK;AACP,SAAqB,cAAc,mBAAAC,wBAAuB;AAC1D,SAAS,mBAAAC,wBAAuB;AAChC,SAAS,eAAAC,oBAAmB;AAErB,SAAS,iBACd,YACA,UAKI,CAAC,GAUL;AACA,QAAM,QAAQ,IAAI,aAAa,UAAU;AACzC,QAAM,MAAM,QAAQ,MAAM;AAC1B,SAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ,QAAQ,MAAM,IAAI,YAAY,YAAY,OAAO,EAAE,GAAG,QAAQ,MAAM,IAAI,CAAC,IAAI;AAAA,IAC3F,UAAU,IAAI;AAAA,MACZ,WAAW;AAAA,MACX;AAAA,MACA,QAAQ,WAAW;AAAA,MACnB,QAAQ,SAAS;AAAA,IACnB;AAAA,IACA,MAAM,IAAI,YAAY,WAAW,QAAQ;AAAA,IACzC,WAAW,IAAI,gBAAgB,UAAU;AAAA,IACzC,QAAQ,IAAI,cAAc,UAAU;AAAA,IACpC,SAAS,IAAI,eAAe,UAAU;AAAA,IACtC,SAAS,IAAI;AAAA,MACX;AAAA,MACA,QAAQ,SAAS,QAAQ,EAAE,OAAO,QAAQ,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;",
6
+ "names": ["AgentAdapter", "ChangesAdapter", "MemoryAdapter", "MonitorAdapter", "PageAdapter", "SettingsAdapter", "SprinkleAdapter", "TrayAdapter"]
7
7
  }
@@ -0,0 +1,132 @@
1
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ const REPORT_MS = 250;
3
+ const POLL_MS = 1e4;
4
+ async function quietly(call) {
5
+ try {
6
+ await call();
7
+ } catch {
8
+ }
9
+ }
10
+ const ACTIVITY = ["pointerdown", "keydown"];
11
+ const FOCUS = ["focus", "blur"];
12
+ class PageAdapter {
13
+ #connection;
14
+ #agent;
15
+ #options;
16
+ #run;
17
+ #now;
18
+ #off = [];
19
+ #stream = null;
20
+ #lastActive;
21
+ #timer;
22
+ #disposed = false;
23
+ done;
24
+ constructor(connection, agent, options) {
25
+ this.#connection = connection;
26
+ this.#agent = agent;
27
+ this.#options = options;
28
+ this.#run = options.run;
29
+ this.#now = options.now ?? Date.now;
30
+ this.#lastActive = this.#now();
31
+ const doc = options.document ?? globalThis.document;
32
+ const win = options.window ?? globalThis.window;
33
+ const schedule = () => this.#schedule();
34
+ const active = () => {
35
+ this.#lastActive = this.#now();
36
+ this.#schedule();
37
+ };
38
+ if (doc) this.#listen(doc, "visibilitychange", schedule);
39
+ if (win) {
40
+ for (const type of FOCUS) this.#listen(win, type, schedule);
41
+ for (const type of ACTIVITY) this.#listen(win, type, active);
42
+ }
43
+ this.#off.push(agent.on("active", schedule));
44
+ const agents = connection.agents?.subscribe(schedule);
45
+ if (agents) this.#off.push(agents);
46
+ this.done = this.#loop().catch(() => void 0);
47
+ }
48
+ #listen(target, type, listener) {
49
+ target.addEventListener(type, listener, true);
50
+ this.#off.push(() => target.removeEventListener(type, listener, true));
51
+ }
52
+ #cone() {
53
+ const summary = this.#connection.agents?.value;
54
+ const agents = summary?.agents ?? [];
55
+ let id = this.#agent.active();
56
+ for (let hops = 0; id && hops <= agents.length; hops++) {
57
+ const found = agents.find((agent) => agent.id === id);
58
+ if (!found || found.kind === "cone") return id;
59
+ id = found.parentId;
60
+ }
61
+ return summary?.active ?? id;
62
+ }
63
+ state() {
64
+ const doc = this.#options.document ?? globalThis.document;
65
+ return {
66
+ tab: this.#options.tab,
67
+ kinds: ["panel"],
68
+ cone: this.#cone(),
69
+ visible: doc ? doc.visibilityState === "visible" : false,
70
+ focused: doc ? doc.hasFocus() : false,
71
+ lastActive: this.#lastActive
72
+ };
73
+ }
74
+ #schedule() {
75
+ if (this.#disposed || this.#timer !== void 0) return;
76
+ this.#timer = setTimeout(() => {
77
+ this.#timer = void 0;
78
+ const stream = this.#stream;
79
+ if (!stream || this.#disposed) return;
80
+ void quietly(() => this.#connection.pages?.report(stream, this.state(), BACKGROUND_CONTEXT));
81
+ }, this.#options.reportMs ?? REPORT_MS);
82
+ }
83
+ async #loop() {
84
+ const pages = this.#connection.pages;
85
+ if (!pages) return;
86
+ while (!this.#disposed) {
87
+ const opened = await pages.open(this.state(), BACKGROUND_CONTEXT);
88
+ if ("error" in opened) return;
89
+ this.#stream = opened.stream;
90
+ if (this.#disposed) break;
91
+ let done = false;
92
+ while (!done && !this.#disposed) {
93
+ const batch = await pages.next(
94
+ opened.stream,
95
+ this.#options.pollMs ?? POLL_MS,
96
+ BACKGROUND_CONTEXT
97
+ );
98
+ for (const request of batch.requests) void this.#handle(opened.stream, request);
99
+ done = batch.done;
100
+ }
101
+ }
102
+ const stream = this.#stream;
103
+ if (stream) await pages.close(stream, BACKGROUND_CONTEXT);
104
+ }
105
+ async #handle(stream, request) {
106
+ let result;
107
+ try {
108
+ result = await this.#run(request.argv, request.caller);
109
+ } catch (error) {
110
+ result = { stdout: "", stderr: `panel: ${error.message}
111
+ `, code: 1 };
112
+ }
113
+ await quietly(
114
+ () => this.#connection.pages?.answer(stream, request.id, result, BACKGROUND_CONTEXT)
115
+ );
116
+ }
117
+ dispose() {
118
+ this.#disposed = true;
119
+ clearTimeout(this.#timer);
120
+ this.#timer = void 0;
121
+ for (const off of this.#off.splice(0)) off();
122
+ const stream = this.#stream;
123
+ if (stream) void quietly(() => this.#connection.pages?.close(stream, BACKGROUND_CONTEXT));
124
+ return this.done;
125
+ }
126
+ }
127
+ export {
128
+ POLL_MS,
129
+ PageAdapter,
130
+ REPORT_MS
131
+ };
132
+ //# sourceMappingURL=page-port.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/spectrum/page-port.ts"],
4
+ "sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AgentConnection } from '../client.ts';\nimport type { PageCaller, PageRequest, PageResult, PageTab } from '../remote/pages.ts';\nimport type { AgentAdapter } from './agent-port.ts';\n\nexport const REPORT_MS = 250;\nexport const POLL_MS = 10000;\n\nexport type PageRun = (\n argv: readonly string[],\n caller: PageCaller\n) => PageResult | Promise<PageResult>;\n\nexport interface PageTarget {\n addEventListener(type: string, listener: () => void, options?: boolean): void;\n removeEventListener(type: string, listener: () => void, options?: boolean): void;\n}\n\nexport interface PageDocument extends PageTarget {\n readonly visibilityState: string;\n hasFocus(): boolean;\n}\n\nexport interface PageOptions {\n tab: string;\n run?: PageRun;\n document?: PageDocument;\n window?: PageTarget;\n now?: () => number;\n reportMs?: number;\n pollMs?: number;\n}\n\nasync function quietly(call: () => Promise<void> | undefined): Promise<void> {\n try {\n await call();\n } catch {}\n}\n\nconst ACTIVITY = ['pointerdown', 'keydown'];\nconst FOCUS = ['focus', 'blur'];\n\nexport class PageAdapter {\n readonly #connection: AgentConnection;\n readonly #agent: AgentAdapter;\n readonly #options: PageOptions;\n readonly #run: PageRun;\n readonly #now: () => number;\n readonly #off: (() => void)[] = [];\n #stream: string | null = null;\n #lastActive: number;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #disposed = false;\n readonly done: Promise<void>;\n\n constructor(\n connection: AgentConnection,\n agent: AgentAdapter,\n options: PageOptions & { run: PageRun }\n ) {\n this.#connection = connection;\n this.#agent = agent;\n this.#options = options;\n this.#run = options.run;\n this.#now = options.now ?? Date.now;\n this.#lastActive = this.#now();\n const doc = options.document ?? (globalThis as { document?: PageDocument }).document;\n const win = options.window ?? (globalThis as { window?: PageTarget }).window;\n const schedule = () => this.#schedule();\n const active = () => {\n this.#lastActive = this.#now();\n this.#schedule();\n };\n if (doc) this.#listen(doc, 'visibilitychange', schedule);\n if (win) {\n for (const type of FOCUS) this.#listen(win, type, schedule);\n for (const type of ACTIVITY) this.#listen(win, type, active);\n }\n this.#off.push(agent.on('active', schedule));\n const agents = connection.agents?.subscribe(schedule);\n if (agents) this.#off.push(agents);\n this.done = this.#loop().catch(() => undefined);\n }\n\n #listen(target: PageTarget, type: string, listener: () => void): void {\n target.addEventListener(type, listener, true);\n this.#off.push(() => target.removeEventListener(type, listener, true));\n }\n\n #cone(): string | null {\n const summary = this.#connection.agents?.value;\n const agents = summary?.agents ?? [];\n let id: string | null = this.#agent.active();\n for (let hops = 0; id && hops <= agents.length; hops++) {\n const found = agents.find((agent) => agent.id === id);\n if (!found || found.kind === 'cone') return id;\n id = found.parentId;\n }\n return summary?.active ?? id;\n }\n\n state(): PageTab {\n const doc = this.#options.document ?? (globalThis as { document?: PageDocument }).document;\n return {\n tab: this.#options.tab,\n kinds: ['panel'],\n cone: this.#cone(),\n visible: doc ? doc.visibilityState === 'visible' : false,\n focused: doc ? doc.hasFocus() : false,\n lastActive: this.#lastActive,\n };\n }\n\n #schedule(): void {\n if (this.#disposed || this.#timer !== undefined) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n const stream = this.#stream;\n if (!stream || this.#disposed) return;\n void quietly(() => this.#connection.pages?.report(stream, this.state(), BACKGROUND_CONTEXT));\n }, this.#options.reportMs ?? REPORT_MS);\n }\n\n async #loop(): Promise<void> {\n const pages = this.#connection.pages;\n if (!pages) return;\n while (!this.#disposed) {\n const opened = await pages.open(this.state(), BACKGROUND_CONTEXT);\n if ('error' in opened) return;\n this.#stream = opened.stream;\n if (this.#disposed) break;\n let done = false;\n while (!done && !this.#disposed) {\n const batch = await pages.next(\n opened.stream,\n this.#options.pollMs ?? POLL_MS,\n BACKGROUND_CONTEXT\n );\n for (const request of batch.requests) void this.#handle(opened.stream, request);\n done = batch.done;\n }\n }\n const stream = this.#stream;\n if (stream) await pages.close(stream, BACKGROUND_CONTEXT);\n }\n\n async #handle(stream: string, request: PageRequest): Promise<void> {\n let result: PageResult;\n try {\n result = await this.#run(request.argv, request.caller);\n } catch (error) {\n result = { stdout: '', stderr: `panel: ${(error as Error).message}\\n`, code: 1 };\n }\n await quietly(() =>\n this.#connection.pages?.answer(stream, request.id, result, BACKGROUND_CONTEXT)\n );\n }\n\n dispose(): Promise<void> {\n this.#disposed = true;\n clearTimeout(this.#timer);\n this.#timer = undefined;\n for (const off of this.#off.splice(0)) off();\n const stream = this.#stream;\n if (stream) void quietly(() => this.#connection.pages?.close(stream, BACKGROUND_CONTEXT));\n return this.done;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -21,14 +21,13 @@ const USAGE = `usage:
21
21
  sprinkle list every sprinkle in /home/sprinkles, with its owner
22
22
  sprinkle show <name> show a sprinkle in your chat
23
23
  sprinkle own <name> [<agent>] send its messages to <agent> (cone, cone-<n> or a scoop's handle)
24
+ sprinkle open|close <name> the same as panel open|close sprinkle:<name>
24
25
 
25
26
  A sprinkle is an .shtml file in /home/sprinkles, as <name>.shtml or <name>/<name>.shtml.
26
27
  Every one of them has a button in the rail; edit the file and the open panel reloads.
27
28
  `;
28
29
  const RETIRED = {
29
- open: "every sprinkle in /home/sprinkles already has a button in the rail; sprinkle show <name> shows one in the chat",
30
30
  reload: "editing the .shtml file reloads the sprinkle",
31
- close: "delete the .shtml file to retire a sprinkle; the user closes panels",
32
31
  send: "a sprinkle reads data with slicc.readFile() or keeps it with slicc.setState(); write the file it reads",
33
32
  chat: "show the sprinkle with sprinkle show <name> and wait for its lick"
34
33
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/sprinkles/index.ts"],
4
- "sourcesContent": ["import {\n type Context,\n type JsonValue,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport type { Harness } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { LickTarget } from '../licks/lick.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport {\n describe,\n homePath,\n NO_EXEC,\n SPRINKLE_KIND,\n SPRINKLES_DIR,\n type Sprinkle,\n type SprinkleMethod,\n SprinklesDoc,\n SUGGESTIONS,\n SUGGESTIONS_DIR,\n SUGGESTIONS_STORE,\n sprinkleFiles,\n WELCOME,\n WELCOME_PROFILE,\n WELCOMED,\n} from './sprinkles.ts';\n\nexport type Answer = { code: number; out: string };\n\nexport interface SprinklePayload {\n action: string;\n data: JsonValue | null;\n target: string | null;\n}\n\nexport interface SprinklesRuntime {\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n send(id: string, payload: SprinklePayload, context: Context): Promise<{ delivered: boolean }>;\n call(id: string, method: string, args: readonly unknown[], context: Context): Promise<JsonValue>;\n command(argv: readonly string[], caller: string | null, context: Context): Promise<Answer>;\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface SprinklesAttach {\n harness: Harness;\n agents: Agents;\n licks: Licks;\n env: ExecutionEnv;\n assets?: (path: string) => Promise<string>;\n reloadMs: number;\n dir?: string;\n intercept?: (id: string, payload: SprinklePayload, context: Context) => Promise<boolean>;\n}\n\nexport const USAGE = `usage:\n sprinkle list every sprinkle in /home/sprinkles, with its owner\n sprinkle show <name> show a sprinkle in your chat\n sprinkle own <name> [<agent>] send its messages to <agent> (cone, cone-<n> or a scoop's handle)\n\nA sprinkle is an .shtml file in /home/sprinkles, as <name>.shtml or <name>/<name>.shtml.\nEvery one of them has a button in the rail; edit the file and the open panel reloads.\n`;\n\nconst RETIRED: Record<string, string> = {\n open: 'every sprinkle in /home/sprinkles already has a button in the rail; sprinkle show <name> shows one in the chat',\n reload: 'editing the .shtml file reloads the sprinkle',\n close: 'delete the .shtml file to retire a sprinkle; the user closes panels',\n send: 'a sprinkle reads data with slicc.readFile() or keeps it with slicc.setState(); write the file it reads',\n chat: 'show the sprinkle with sprinkle show <name> and wait for its lick',\n};\n\nfunction targetOf(agentId: string): LickTarget {\n return agentId.startsWith('scoop:') ? (agentId as LickTarget) : `cone:${agentId}`;\n}\n\nfunction agentOf(caller: string | null, agents: Agents): string {\n if (caller?.startsWith('cone:')) return caller.slice('cone:'.length);\n return caller ?? agents.activeCone();\n}\n\nfunction named(agents: Agents, value: string): string | undefined {\n const state = agents.state();\n if (value in state.cones) return value;\n const id = value.startsWith('scoop:') ? value : `scoop:${value}`;\n return id in state.scoops ? id : undefined;\n}\n\nexport function ownerOf(agents: Agents, owner: string | undefined): string {\n const state = agents.state();\n if (owner && state.cones[owner]) return owner;\n const scoop = owner ? state.scoops[owner] : undefined;\n return scoop && live(scoop) ? (owner as string) : agents.activeCone();\n}\n\nfunction entry(name: string, html: string, agentId: string): Sprinkle {\n return { id: name, name, ...describe(name, html), agentId, html };\n}\n\nfunction reloader(\n options: SprinklesAttach,\n dir: string,\n builtins: { welcome: string | undefined; suggestions: string | undefined },\n sprinkles: ReplicatedState<Sprinkle[]> & { replace(context: Context, value: Sprinkle[]): void },\n owners: (context: Context) => Promise<Record<string, string>>\n) {\n const { env, agents } = options;\n const { welcome, suggestions } = builtins;\n return async (using: Context) => {\n const owned = await owners(using);\n const stored =\n suggestions === undefined ? undefined : await env.exists(SUGGESTIONS_STORE, using);\n const offered = stored?.ok === true && stored.value;\n const reserved = new Set([WELCOME, ...(offered ? [SUGGESTIONS] : [])]);\n const out: Sprinkle[] = [];\n for (const { name, path } of await sprinkleFiles(env, dir, using)) {\n if (reserved.has(name)) continue;\n const read = await env.readTextFile(path, using);\n if (read.ok) out.push(entry(name, read.value, ownerOf(agents, owned[name])));\n }\n if (offered)\n out.push(entry(SUGGESTIONS, suggestions as string, ownerOf(agents, owned[SUGGESTIONS])));\n if (welcome !== undefined)\n out.push({\n ...entry(WELCOME, welcome, ownerOf(agents, owned[WELCOME] ?? 'cone')),\n inline: true,\n });\n sprinkles.replace(using, out);\n };\n}\n\nexport function setupSprinkles() {\n return {\n async attach(options: SprinklesAttach, context: Context): Promise<SprinklesRuntime> {\n const { harness, agents, licks, env } = options;\n const dir = options.dir ?? SPRINKLES_DIR;\n const sprinkles = replicatedState<Sprinkle[]>([]);\n const builtin = (name: string) =>\n options.assets\n ? options.assets(`packages/vfs-root/sprinkles/${name}.shtml`).catch(() => undefined)\n : Promise.resolve(undefined);\n const welcome = await builtin(WELCOME);\n const suggestions = await builtin(SUGGESTIONS);\n const owners = async (using: Context) =>\n (await harness.snapshot(SprinklesDoc, using))?.owners ?? {};\n const reload = reloader(options, dir, { welcome, suggestions }, sprinkles, owners);\n const inside = async (path: unknown, using: Context) => {\n const asked = homePath(path);\n const real = await env.canonicalPath(asked, using);\n return real.ok ? homePath(real.value) : asked;\n };\n const offCone = agents.onCone(() => reload(context).catch(() => undefined));\n const find = (id: string) => sprinkles.value.find((item) => item.id === id);\n const post = async (id: string, agentId: string, using: Context) => {\n const conversation = await agents.conversation(agentId, using);\n if (!conversation) throw new Error(`there is no agent ${agentId}`);\n await conversation.submit(\n { type: 'write', entry: { kind: SPRINKLE_KIND, data: { sprinkle: id, at: Date.now() } } },\n using\n );\n };\n const setOwner = async (name: string, agentId: string, using: Context) => {\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).owners[name] = agentId;\n }, using);\n await reload(using);\n };\n await reload(context);\n const state = await harness.snapshot(SprinklesDoc, context);\n if (welcome !== undefined && !state?.welcomed) {\n await post(WELCOME, 'cone', context);\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).welcomed = true;\n }, context);\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const watched = await env.watch(\n [{ path: dir, recursive: true }, { path: SUGGESTIONS_DIR }],\n () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n },\n context\n );\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const commands: Record<\n string,\n (argv: readonly string[], caller: string | null, using: Context) => Promise<Answer>\n > = {\n async list(_argv, _caller, using) {\n await reload(using);\n const rows = sprinkles.value\n .filter((item) => !item.inline)\n .map((item) => `${item.name}\\t${item.title}\\t${item.agentId}`);\n return { code: 0, out: rows.length ? `${rows.join('\\n')}\\n` : 'no sprinkles\\n' };\n },\n async show(argv, caller, using) {\n await reload(using);\n const name = argv[1] ?? '';\n if (!find(name)) return { code: 1, out: `sprinkle: there is no sprinkle ${name}\\n` };\n const agentId = agentOf(caller, agents);\n await post(name, agentId, using);\n if (name !== SUGGESTIONS && !(await owners(using))[name])\n await setOwner(name, agentId, using);\n return { code: 0, out: `showed ${name} in the chat of ${agentId}\\n` };\n },\n async own(argv, caller, using) {\n const name = argv[1] ?? '';\n if (!find(name)) return { code: 1, out: `sprinkle: there is no sprinkle ${name}\\n` };\n const agentId = argv[2] ? named(agents, argv[2]) : agentOf(caller, agents);\n if (!agentId) return { code: 1, out: `sprinkle: there is no agent ${argv[2]}\\n` };\n await setOwner(name, agentId, using);\n return { code: 0, out: `${name} now talks to ${agentId}\\n` };\n },\n };\n return {\n sprinkles,\n async send(id, payload, using) {\n const sprinkle = find(id);\n if (!sprinkle) return { delivered: false };\n if (await options.intercept?.(id, payload, using)) return { delivered: true };\n if (id === WELCOME && payload.action === 'onboarding-complete') {\n await env.writeFile(WELCOMED, `${new Date().toISOString()}\\n`, using);\n if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data))\n await env.writeFile(\n WELCOME_PROFILE,\n `${JSON.stringify(payload.data, null, 2)}\\n`,\n using\n );\n }\n await licks.deliver(\n {\n channel: 'sprinkle',\n source: `sprinkle:${id}`,\n title: sprinkle.title,\n text: payload.action || '(no action)',\n ...(payload.data === null ? {} : { body: JSON.stringify(payload.data, null, 2) }),\n target: targetOf(ownerOf(agents, (await owners(using))[id])),\n },\n using\n );\n return { delivered: true };\n },\n async call(id, method, args, using) {\n if (!find(id)) throw new Error(`there is no sprinkle ${id}`);\n const handlers: Record<SprinkleMethod, () => Promise<JsonValue>> = {\n async readFile() {\n const read = await env.readTextFile(await inside(args[0], using), using);\n if (!read.ok) throw new Error(read.error.message);\n return read.value;\n },\n async exists() {\n const found = await env.exists(await inside(args[0], using), using);\n return found.ok && found.value;\n },\n async getState() {\n return (await harness.snapshot(SprinklesDoc, using))?.state[id] ?? null;\n },\n async setState() {\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).state[id] = (args[0] ?? null) as JsonValue;\n }, using);\n return null;\n },\n };\n const handler = handlers[method as SprinkleMethod];\n if (!handler) throw new Error(NO_EXEC);\n return handler();\n },\n async command(argv, caller, using) {\n const verb = argv[0] ?? 'help';\n const run = commands[verb];\n if (run) return run(argv, caller, using);\n if (verb in RETIRED)\n return { code: 2, out: `sprinkle ${verb} isn't in SLICC: ${RETIRED[verb]}\\n` };\n return { code: verb === 'help' || verb === '--help' ? 0 : 2, out: USAGE };\n },\n reload,\n async close(using) {\n offCone();\n clearTimeout(timer);\n await watcher?.close(using);\n },\n };\n },\n };\n}\n"],
5
- "mappings": "AAAA;AAAA,EAIE;AAAA,OACK;AAGP,SAAsB,YAAY;AAGlC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA8BA,MAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASrB,MAAM,UAAkC;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AACR;AAEA,SAAS,SAAS,SAA6B;AAC7C,SAAO,QAAQ,WAAW,QAAQ,IAAK,UAAyB,QAAQ,OAAO;AACjF;AAEA,SAAS,QAAQ,QAAuB,QAAwB;AAC9D,MAAI,QAAQ,WAAW,OAAO,EAAG,QAAO,OAAO,MAAM,QAAQ,MAAM;AACnE,SAAO,UAAU,OAAO,WAAW;AACrC;AAEA,SAAS,MAAM,QAAgB,OAAmC;AAChE,QAAM,QAAQ,OAAO,MAAM;AAC3B,MAAI,SAAS,MAAM,MAAO,QAAO;AACjC,QAAM,KAAK,MAAM,WAAW,QAAQ,IAAI,QAAQ,SAAS,KAAK;AAC9D,SAAO,MAAM,MAAM,SAAS,KAAK;AACnC;AAEO,SAAS,QAAQ,QAAgB,OAAmC;AACzE,QAAM,QAAQ,OAAO,MAAM;AAC3B,MAAI,SAAS,MAAM,MAAM,KAAK,EAAG,QAAO;AACxC,QAAM,QAAQ,QAAQ,MAAM,OAAO,KAAK,IAAI;AAC5C,SAAO,SAAS,KAAK,KAAK,IAAK,QAAmB,OAAO,WAAW;AACtE;AAEA,SAAS,MAAM,MAAc,MAAc,SAA2B;AACpE,SAAO,EAAE,IAAI,MAAM,MAAM,GAAG,SAAS,MAAM,IAAI,GAAG,SAAS,KAAK;AAClE;AAEA,SAAS,SACP,SACA,KACA,UACA,WACA,QACA;AACA,QAAM,EAAE,KAAK,OAAO,IAAI;AACxB,QAAM,EAAE,SAAS,YAAY,IAAI;AACjC,SAAO,OAAO,UAAmB;AAC/B,UAAM,QAAQ,MAAM,OAAO,KAAK;AAChC,UAAM,SACJ,gBAAgB,SAAY,SAAY,MAAM,IAAI,OAAO,mBAAmB,KAAK;AACnF,UAAM,UAAU,QAAQ,OAAO,QAAQ,OAAO;AAC9C,UAAM,WAAW,oBAAI,IAAI,CAAC,SAAS,GAAI,UAAU,CAAC,WAAW,IAAI,CAAC,CAAE,CAAC;AACrE,UAAM,MAAkB,CAAC;AACzB,eAAW,EAAE,MAAM,KAAK,KAAK,MAAM,cAAc,KAAK,KAAK,KAAK,GAAG;AACjE,UAAI,SAAS,IAAI,IAAI,EAAG;AACxB,YAAM,OAAO,MAAM,IAAI,aAAa,MAAM,KAAK;AAC/C,UAAI,KAAK,GAAI,KAAI,KAAK,MAAM,MAAM,KAAK,OAAO,QAAQ,QAAQ,MAAM,IAAI,CAAC,CAAC,CAAC;AAAA,IAC7E;AACA,QAAI;AACF,UAAI,KAAK,MAAM,aAAa,aAAuB,QAAQ,QAAQ,MAAM,WAAW,CAAC,CAAC,CAAC;AACzF,QAAI,YAAY;AACd,UAAI,KAAK;AAAA,QACP,GAAG,MAAM,SAAS,SAAS,QAAQ,QAAQ,MAAM,OAAO,KAAK,MAAM,CAAC;AAAA,QACpE,QAAQ;AAAA,MACV,CAAC;AACH,cAAU,QAAQ,OAAO,GAAG;AAAA,EAC9B;AACF;AAEO,SAAS,iBAAiB;AAC/B,SAAO;AAAA,IACL,MAAM,OAAO,SAA0B,SAA6C;AAClF,YAAM,EAAE,SAAS,QAAQ,OAAO,IAAI,IAAI;AACxC,YAAM,MAAM,QAAQ,OAAO;AAC3B,YAAM,YAAY,gBAA4B,CAAC,CAAC;AAChD,YAAM,UAAU,CAAC,SACf,QAAQ,SACJ,QAAQ,OAAO,+BAA+B,IAAI,QAAQ,EAAE,MAAM,MAAM,MAAS,IACjF,QAAQ,QAAQ,MAAS;AAC/B,YAAM,UAAU,MAAM,QAAQ,OAAO;AACrC,YAAM,cAAc,MAAM,QAAQ,WAAW;AAC7C,YAAM,SAAS,OAAO,WACnB,MAAM,QAAQ,SAAS,cAAc,KAAK,IAAI,UAAU,CAAC;AAC5D,YAAM,SAAS,SAAS,SAAS,KAAK,EAAE,SAAS,YAAY,GAAG,WAAW,MAAM;AACjF,YAAM,SAAS,OAAO,MAAe,UAAmB;AACtD,cAAM,QAAQ,SAAS,IAAI;AAC3B,cAAM,OAAO,MAAM,IAAI,cAAc,OAAO,KAAK;AACjD,eAAO,KAAK,KAAK,SAAS,KAAK,KAAK,IAAI;AAAA,MAC1C;AACA,YAAM,UAAU,OAAO,OAAO,MAAM,OAAO,OAAO,EAAE,MAAM,MAAM,MAAS,CAAC;AAC1E,YAAM,OAAO,CAAC,OAAe,UAAU,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,EAAE;AAC1E,YAAM,OAAO,OAAO,IAAY,SAAiB,UAAmB;AAClE,cAAM,eAAe,MAAM,OAAO,aAAa,SAAS,KAAK;AAC7D,YAAI,CAAC,aAAc,OAAM,IAAI,MAAM,qBAAqB,OAAO,EAAE;AACjE,cAAM,aAAa;AAAA,UACjB,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,eAAe,MAAM,EAAE,UAAU,IAAI,IAAI,KAAK,IAAI,EAAE,EAAE,EAAE;AAAA,UACxF;AAAA,QACF;AAAA,MACF;AACA,YAAM,WAAW,OAAO,MAAc,SAAiB,UAAmB;AACxE,cAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,WAAC,MAAM,GAAG,IAAI,YAAY,GAAG,OAAO,IAAI,IAAI;AAAA,QAC9C,GAAG,KAAK;AACR,cAAM,OAAO,KAAK;AAAA,MACpB;AACA,YAAM,OAAO,OAAO;AACpB,YAAM,QAAQ,MAAM,QAAQ,SAAS,cAAc,OAAO;AAC1D,UAAI,YAAY,UAAa,CAAC,OAAO,UAAU;AAC7C,cAAM,KAAK,SAAS,QAAQ,OAAO;AACnC,cAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,WAAC,MAAM,GAAG,IAAI,YAAY,GAAG,WAAW;AAAA,QAC1C,GAAG,OAAO;AAAA,MACZ;AACA,UAAI;AACJ,YAAM,UAAU,MAAM,IAAI;AAAA,QACxB,CAAC,EAAE,MAAM,KAAK,WAAW,KAAK,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAAA,QAC1D,MAAM;AACJ,uBAAa,KAAK;AAClB,kBAAQ,WAAW,MAAM,KAAK,OAAO,OAAO,EAAE,MAAM,MAAM,MAAS,GAAG,QAAQ,QAAQ;AAAA,QACxF;AAAA,QACA;AAAA,MACF;AACA,YAAM,UAAmC,QAAQ,KAAK,QAAQ,QAAQ;AACtE,YAAM,WAGF;AAAA,QACF,MAAM,KAAK,OAAO,SAAS,OAAO;AAChC,gBAAM,OAAO,KAAK;AAClB,gBAAM,OAAO,UAAU,MACpB,OAAO,CAAC,SAAS,CAAC,KAAK,MAAM,EAC7B,IAAI,CAAC,SAAS,GAAG,KAAK,IAAI,IAAK,KAAK,KAAK,IAAK,KAAK,OAAO,EAAE;AAC/D,iBAAO,EAAE,MAAM,GAAG,KAAK,KAAK,SAAS,GAAG,KAAK,KAAK,IAAI,CAAC;AAAA,IAAO,iBAAiB;AAAA,QACjF;AAAA,QACA,MAAM,KAAK,MAAM,QAAQ,OAAO;AAC9B,gBAAM,OAAO,KAAK;AAClB,gBAAM,OAAO,KAAK,CAAC,KAAK;AACxB,cAAI,CAAC,KAAK,IAAI,EAAG,QAAO,EAAE,MAAM,GAAG,KAAK,kCAAkC,IAAI;AAAA,EAAK;AACnF,gBAAM,UAAU,QAAQ,QAAQ,MAAM;AACtC,gBAAM,KAAK,MAAM,SAAS,KAAK;AAC/B,cAAI,SAAS,eAAe,EAAE,MAAM,OAAO,KAAK,GAAG,IAAI;AACrD,kBAAM,SAAS,MAAM,SAAS,KAAK;AACrC,iBAAO,EAAE,MAAM,GAAG,KAAK,UAAU,IAAI,mBAAmB,OAAO;AAAA,EAAK;AAAA,QACtE;AAAA,QACA,MAAM,IAAI,MAAM,QAAQ,OAAO;AAC7B,gBAAM,OAAO,KAAK,CAAC,KAAK;AACxB,cAAI,CAAC,KAAK,IAAI,EAAG,QAAO,EAAE,MAAM,GAAG,KAAK,kCAAkC,IAAI;AAAA,EAAK;AACnF,gBAAM,UAAU,KAAK,CAAC,IAAI,MAAM,QAAQ,KAAK,CAAC,CAAC,IAAI,QAAQ,QAAQ,MAAM;AACzE,cAAI,CAAC,QAAS,QAAO,EAAE,MAAM,GAAG,KAAK,+BAA+B,KAAK,CAAC,CAAC;AAAA,EAAK;AAChF,gBAAM,SAAS,MAAM,SAAS,KAAK;AACnC,iBAAO,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,iBAAiB,OAAO;AAAA,EAAK;AAAA,QAC7D;AAAA,MACF;AACA,aAAO;AAAA,QACL;AAAA,QACA,MAAM,KAAK,IAAI,SAAS,OAAO;AAC7B,gBAAM,WAAW,KAAK,EAAE;AACxB,cAAI,CAAC,SAAU,QAAO,EAAE,WAAW,MAAM;AACzC,cAAI,MAAM,QAAQ,YAAY,IAAI,SAAS,KAAK,EAAG,QAAO,EAAE,WAAW,KAAK;AAC5E,cAAI,OAAO,WAAW,QAAQ,WAAW,uBAAuB;AAC9D,kBAAM,IAAI,UAAU,UAAU,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,GAAM,KAAK;AACpE,gBAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAS,YAAY,CAAC,MAAM,QAAQ,QAAQ,IAAI;AACjF,oBAAM,IAAI;AAAA,gBACR;AAAA,gBACA,GAAG,KAAK,UAAU,QAAQ,MAAM,MAAM,CAAC,CAAC;AAAA;AAAA,gBACxC;AAAA,cACF;AAAA,UACJ;AACA,gBAAM,MAAM;AAAA,YACV;AAAA,cACE,SAAS;AAAA,cACT,QAAQ,YAAY,EAAE;AAAA,cACtB,OAAO,SAAS;AAAA,cAChB,MAAM,QAAQ,UAAU;AAAA,cACxB,GAAI,QAAQ,SAAS,OAAO,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,QAAQ,MAAM,MAAM,CAAC,EAAE;AAAA,cAC/E,QAAQ,SAAS,QAAQ,SAAS,MAAM,OAAO,KAAK,GAAG,EAAE,CAAC,CAAC;AAAA,YAC7D;AAAA,YACA;AAAA,UACF;AACA,iBAAO,EAAE,WAAW,KAAK;AAAA,QAC3B;AAAA,QACA,MAAM,KAAK,IAAI,QAAQ,MAAM,OAAO;AAClC,cAAI,CAAC,KAAK,EAAE,EAAG,OAAM,IAAI,MAAM,wBAAwB,EAAE,EAAE;AAC3D,gBAAM,WAA6D;AAAA,YACjE,MAAM,WAAW;AACf,oBAAM,OAAO,MAAM,IAAI,aAAa,MAAM,OAAO,KAAK,CAAC,GAAG,KAAK,GAAG,KAAK;AACvE,kBAAI,CAAC,KAAK,GAAI,OAAM,IAAI,MAAM,KAAK,MAAM,OAAO;AAChD,qBAAO,KAAK;AAAA,YACd;AAAA,YACA,MAAM,SAAS;AACb,oBAAM,QAAQ,MAAM,IAAI,OAAO,MAAM,OAAO,KAAK,CAAC,GAAG,KAAK,GAAG,KAAK;AAClE,qBAAO,MAAM,MAAM,MAAM;AAAA,YAC3B;AAAA,YACA,MAAM,WAAW;AACf,sBAAQ,MAAM,QAAQ,SAAS,cAAc,KAAK,IAAI,MAAM,EAAE,KAAK;AAAA,YACrE;AAAA,YACA,MAAM,WAAW;AACf,oBAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,iBAAC,MAAM,GAAG,IAAI,YAAY,GAAG,MAAM,EAAE,IAAK,KAAK,CAAC,KAAK;AAAA,cACvD,GAAG,KAAK;AACR,qBAAO;AAAA,YACT;AAAA,UACF;AACA,gBAAM,UAAU,SAAS,MAAwB;AACjD,cAAI,CAAC,QAAS,OAAM,IAAI,MAAM,OAAO;AACrC,iBAAO,QAAQ;AAAA,QACjB;AAAA,QACA,MAAM,QAAQ,MAAM,QAAQ,OAAO;AACjC,gBAAM,OAAO,KAAK,CAAC,KAAK;AACxB,gBAAM,MAAM,SAAS,IAAI;AACzB,cAAI,IAAK,QAAO,IAAI,MAAM,QAAQ,KAAK;AACvC,cAAI,QAAQ;AACV,mBAAO,EAAE,MAAM,GAAG,KAAK,YAAY,IAAI,oBAAoB,QAAQ,IAAI,CAAC;AAAA,EAAK;AAC/E,iBAAO,EAAE,MAAM,SAAS,UAAU,SAAS,WAAW,IAAI,GAAG,KAAK,MAAM;AAAA,QAC1E;AAAA,QACA;AAAA,QACA,MAAM,MAAM,OAAO;AACjB,kBAAQ;AACR,uBAAa,KAAK;AAClB,gBAAM,SAAS,MAAM,KAAK;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import {\n type Context,\n type JsonValue,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport type { Harness } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { LickTarget } from '../licks/lick.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport {\n describe,\n homePath,\n NO_EXEC,\n SPRINKLE_KIND,\n SPRINKLES_DIR,\n type Sprinkle,\n type SprinkleMethod,\n SprinklesDoc,\n SUGGESTIONS,\n SUGGESTIONS_DIR,\n SUGGESTIONS_STORE,\n sprinkleFiles,\n WELCOME,\n WELCOME_PROFILE,\n WELCOMED,\n} from './sprinkles.ts';\n\nexport type Answer = { code: number; out: string };\n\nexport interface SprinklePayload {\n action: string;\n data: JsonValue | null;\n target: string | null;\n}\n\nexport interface SprinklesRuntime {\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n send(id: string, payload: SprinklePayload, context: Context): Promise<{ delivered: boolean }>;\n call(id: string, method: string, args: readonly unknown[], context: Context): Promise<JsonValue>;\n command(argv: readonly string[], caller: string | null, context: Context): Promise<Answer>;\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface SprinklesAttach {\n harness: Harness;\n agents: Agents;\n licks: Licks;\n env: ExecutionEnv;\n assets?: (path: string) => Promise<string>;\n reloadMs: number;\n dir?: string;\n intercept?: (id: string, payload: SprinklePayload, context: Context) => Promise<boolean>;\n}\n\nexport const USAGE = `usage:\n sprinkle list every sprinkle in /home/sprinkles, with its owner\n sprinkle show <name> show a sprinkle in your chat\n sprinkle own <name> [<agent>] send its messages to <agent> (cone, cone-<n> or a scoop's handle)\n sprinkle open|close <name> the same as panel open|close sprinkle:<name>\n\nA sprinkle is an .shtml file in /home/sprinkles, as <name>.shtml or <name>/<name>.shtml.\nEvery one of them has a button in the rail; edit the file and the open panel reloads.\n`;\n\nconst RETIRED: Record<string, string> = {\n reload: 'editing the .shtml file reloads the sprinkle',\n send: 'a sprinkle reads data with slicc.readFile() or keeps it with slicc.setState(); write the file it reads',\n chat: 'show the sprinkle with sprinkle show <name> and wait for its lick',\n};\n\nfunction targetOf(agentId: string): LickTarget {\n return agentId.startsWith('scoop:') ? (agentId as LickTarget) : `cone:${agentId}`;\n}\n\nfunction agentOf(caller: string | null, agents: Agents): string {\n if (caller?.startsWith('cone:')) return caller.slice('cone:'.length);\n return caller ?? agents.activeCone();\n}\n\nfunction named(agents: Agents, value: string): string | undefined {\n const state = agents.state();\n if (value in state.cones) return value;\n const id = value.startsWith('scoop:') ? value : `scoop:${value}`;\n return id in state.scoops ? id : undefined;\n}\n\nexport function ownerOf(agents: Agents, owner: string | undefined): string {\n const state = agents.state();\n if (owner && state.cones[owner]) return owner;\n const scoop = owner ? state.scoops[owner] : undefined;\n return scoop && live(scoop) ? (owner as string) : agents.activeCone();\n}\n\nfunction entry(name: string, html: string, agentId: string): Sprinkle {\n return { id: name, name, ...describe(name, html), agentId, html };\n}\n\nfunction reloader(\n options: SprinklesAttach,\n dir: string,\n builtins: { welcome: string | undefined; suggestions: string | undefined },\n sprinkles: ReplicatedState<Sprinkle[]> & { replace(context: Context, value: Sprinkle[]): void },\n owners: (context: Context) => Promise<Record<string, string>>\n) {\n const { env, agents } = options;\n const { welcome, suggestions } = builtins;\n return async (using: Context) => {\n const owned = await owners(using);\n const stored =\n suggestions === undefined ? undefined : await env.exists(SUGGESTIONS_STORE, using);\n const offered = stored?.ok === true && stored.value;\n const reserved = new Set([WELCOME, ...(offered ? [SUGGESTIONS] : [])]);\n const out: Sprinkle[] = [];\n for (const { name, path } of await sprinkleFiles(env, dir, using)) {\n if (reserved.has(name)) continue;\n const read = await env.readTextFile(path, using);\n if (read.ok) out.push(entry(name, read.value, ownerOf(agents, owned[name])));\n }\n if (offered)\n out.push(entry(SUGGESTIONS, suggestions as string, ownerOf(agents, owned[SUGGESTIONS])));\n if (welcome !== undefined)\n out.push({\n ...entry(WELCOME, welcome, ownerOf(agents, owned[WELCOME] ?? 'cone')),\n inline: true,\n });\n sprinkles.replace(using, out);\n };\n}\n\nexport function setupSprinkles() {\n return {\n async attach(options: SprinklesAttach, context: Context): Promise<SprinklesRuntime> {\n const { harness, agents, licks, env } = options;\n const dir = options.dir ?? SPRINKLES_DIR;\n const sprinkles = replicatedState<Sprinkle[]>([]);\n const builtin = (name: string) =>\n options.assets\n ? options.assets(`packages/vfs-root/sprinkles/${name}.shtml`).catch(() => undefined)\n : Promise.resolve(undefined);\n const welcome = await builtin(WELCOME);\n const suggestions = await builtin(SUGGESTIONS);\n const owners = async (using: Context) =>\n (await harness.snapshot(SprinklesDoc, using))?.owners ?? {};\n const reload = reloader(options, dir, { welcome, suggestions }, sprinkles, owners);\n const inside = async (path: unknown, using: Context) => {\n const asked = homePath(path);\n const real = await env.canonicalPath(asked, using);\n return real.ok ? homePath(real.value) : asked;\n };\n const offCone = agents.onCone(() => reload(context).catch(() => undefined));\n const find = (id: string) => sprinkles.value.find((item) => item.id === id);\n const post = async (id: string, agentId: string, using: Context) => {\n const conversation = await agents.conversation(agentId, using);\n if (!conversation) throw new Error(`there is no agent ${agentId}`);\n await conversation.submit(\n { type: 'write', entry: { kind: SPRINKLE_KIND, data: { sprinkle: id, at: Date.now() } } },\n using\n );\n };\n const setOwner = async (name: string, agentId: string, using: Context) => {\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).owners[name] = agentId;\n }, using);\n await reload(using);\n };\n await reload(context);\n const state = await harness.snapshot(SprinklesDoc, context);\n if (welcome !== undefined && !state?.welcomed) {\n await post(WELCOME, 'cone', context);\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).welcomed = true;\n }, context);\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const watched = await env.watch(\n [{ path: dir, recursive: true }, { path: SUGGESTIONS_DIR }],\n () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n },\n context\n );\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const commands: Record<\n string,\n (argv: readonly string[], caller: string | null, using: Context) => Promise<Answer>\n > = {\n async list(_argv, _caller, using) {\n await reload(using);\n const rows = sprinkles.value\n .filter((item) => !item.inline)\n .map((item) => `${item.name}\\t${item.title}\\t${item.agentId}`);\n return { code: 0, out: rows.length ? `${rows.join('\\n')}\\n` : 'no sprinkles\\n' };\n },\n async show(argv, caller, using) {\n await reload(using);\n const name = argv[1] ?? '';\n if (!find(name)) return { code: 1, out: `sprinkle: there is no sprinkle ${name}\\n` };\n const agentId = agentOf(caller, agents);\n await post(name, agentId, using);\n if (name !== SUGGESTIONS && !(await owners(using))[name])\n await setOwner(name, agentId, using);\n return { code: 0, out: `showed ${name} in the chat of ${agentId}\\n` };\n },\n async own(argv, caller, using) {\n const name = argv[1] ?? '';\n if (!find(name)) return { code: 1, out: `sprinkle: there is no sprinkle ${name}\\n` };\n const agentId = argv[2] ? named(agents, argv[2]) : agentOf(caller, agents);\n if (!agentId) return { code: 1, out: `sprinkle: there is no agent ${argv[2]}\\n` };\n await setOwner(name, agentId, using);\n return { code: 0, out: `${name} now talks to ${agentId}\\n` };\n },\n };\n return {\n sprinkles,\n async send(id, payload, using) {\n const sprinkle = find(id);\n if (!sprinkle) return { delivered: false };\n if (await options.intercept?.(id, payload, using)) return { delivered: true };\n if (id === WELCOME && payload.action === 'onboarding-complete') {\n await env.writeFile(WELCOMED, `${new Date().toISOString()}\\n`, using);\n if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data))\n await env.writeFile(\n WELCOME_PROFILE,\n `${JSON.stringify(payload.data, null, 2)}\\n`,\n using\n );\n }\n await licks.deliver(\n {\n channel: 'sprinkle',\n source: `sprinkle:${id}`,\n title: sprinkle.title,\n text: payload.action || '(no action)',\n ...(payload.data === null ? {} : { body: JSON.stringify(payload.data, null, 2) }),\n target: targetOf(ownerOf(agents, (await owners(using))[id])),\n },\n using\n );\n return { delivered: true };\n },\n async call(id, method, args, using) {\n if (!find(id)) throw new Error(`there is no sprinkle ${id}`);\n const handlers: Record<SprinkleMethod, () => Promise<JsonValue>> = {\n async readFile() {\n const read = await env.readTextFile(await inside(args[0], using), using);\n if (!read.ok) throw new Error(read.error.message);\n return read.value;\n },\n async exists() {\n const found = await env.exists(await inside(args[0], using), using);\n return found.ok && found.value;\n },\n async getState() {\n return (await harness.snapshot(SprinklesDoc, using))?.state[id] ?? null;\n },\n async setState() {\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).state[id] = (args[0] ?? null) as JsonValue;\n }, using);\n return null;\n },\n };\n const handler = handlers[method as SprinkleMethod];\n if (!handler) throw new Error(NO_EXEC);\n return handler();\n },\n async command(argv, caller, using) {\n const verb = argv[0] ?? 'help';\n const run = commands[verb];\n if (run) return run(argv, caller, using);\n if (verb in RETIRED)\n return { code: 2, out: `sprinkle ${verb} isn't in SLICC: ${RETIRED[verb]}\\n` };\n return { code: verb === 'help' || verb === '--help' ? 0 : 2, out: USAGE };\n },\n reload,\n async close(using) {\n offCone();\n clearTimeout(timer);\n await watcher?.close(using);\n },\n };\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,6 +4,7 @@ import type { ChangesView } from './changes/git.ts';
4
4
  import type { FrozenCone } from './freezer/index.ts';
5
5
  import type { MemoryEntry, MemoryScope } from './memory/format.ts';
6
6
  import type { AgentRemote } from './remote/host.ts';
7
+ import type { AgentPages } from './remote/pages.ts';
7
8
  import { AgentControl, AgentSettings, type AgentsSummary, type Command, type Delivered, type MonitorFeed, type SendMode } from './services.ts';
8
9
  import type { Sprinkle } from './sprinkles/kind.ts';
9
10
  import { type PortEndpoint } from './wire.ts';
@@ -23,6 +24,7 @@ export interface AgentConnection {
23
24
  readonly monitor?: ReplicatedState<MonitorFeed>;
24
25
  readonly settings: AgentSettings;
25
26
  readonly remote?: AgentRemote;
27
+ readonly pages?: AgentPages;
26
28
  readonly closed: Promise<Error | undefined>;
27
29
  prompt(text: string, whenBusy?: SendMode): Promise<string>;
28
30
  webhook(name: string, delivery: {
@@ -7,17 +7,21 @@ import type { UserClients } from './kernel/users.ts';
7
7
  import type { McpRuntime } from './mcp/index.ts';
8
8
  import type { MemoryRuntime } from './memory/index.ts';
9
9
  import { type MonitorOptions } from './monitor/index.ts';
10
+ import { type Pages } from './remote/pages.ts';
10
11
  import { AgentSettings } from './services.ts';
11
12
  import type { SkillsRuntime } from './skills/index.ts';
12
13
  import type { SprinklesRuntime } from './sprinkles/index.ts';
13
14
  import type { PortEndpoint } from './wire.ts';
15
+ export interface ConnectOptions {
16
+ readOnly?: boolean;
17
+ pages?: boolean;
18
+ }
14
19
  export interface AgentHost {
15
20
  readonly serverId: string;
16
21
  readonly agent: Agent;
17
22
  readonly users?: UserClients;
18
- connect(endpoint: PortEndpoint, options?: {
19
- readOnly?: boolean;
20
- }): void;
23
+ readonly pages: Pages;
24
+ connect(endpoint: PortEndpoint, options?: ConnectOptions): void;
21
25
  close(): Promise<void>;
22
26
  }
23
27
  export declare function hostAgent(agent: Agent, options?: {
@@ -34,4 +38,5 @@ export declare function hostAgent(agent: Agent, options?: {
34
38
  mcp?: Pick<McpRuntime, 'close'>;
35
39
  users?: UserClients;
36
40
  monitor?: Pick<MonitorOptions, 'kernel' | 'groups' | 'users' | 'secrets'>;
41
+ pages?: Pages;
37
42
  }): Promise<AgentHost>;
@@ -3,7 +3,7 @@ export { type Agent, type AgentOptions, answerText, openAgent } from './agent.ts
3
3
  export { type AgentConnection, connectAgent } from './client.ts';
4
4
  export { createAgentControl } from './control.ts';
5
5
  export { CREDENTIALS_DATABASE, EncryptedCredentialStore } from './credentials.ts';
6
- export { type AgentHost, hostAgent } from './host.ts';
6
+ export { type AgentHost, type ConnectOptions, hostAgent } from './host.ts';
7
7
  export type { KernelClient, KernelFs, KernelProcess, KernelStat, SpawnOptions, } from './kernel/client.ts';
8
8
  export { HOME, type KernelEnvOptions, kernelEnvironment, SliccKernelEnv } from './kernel/env.ts';
9
9
  export { adminOver, type KernelAdmin, type KernelUser, type KernelUsers, serveAdmin, type UserClient, type UserClients, type UserConnect, type UserSpec, userClients, } from './kernel/users.ts';
@@ -13,7 +13,8 @@ export { type Transport, type TransportResponse, transportFetch } from './net.ts
13
13
  export { AGENT_LOCK, type AgentOwner, type AgentWorker, type KernelSource, type OwnerOptions, startAgent, } from './owner.ts';
14
14
  export { applyPatches, patchServerAccept } from './patches.ts';
15
15
  export { SliccPrompt, sliccPrompt } from './prompt.ts';
16
- export { type AccountState, AGENT_SESSION, AgentControl, AgentSessions, AgentSettings, type AgentSettingsChange, AgentTranscript, type BudgetState, type ModelChoice, type OperationError, type PromptResult, type SendMode, type SendRequest, type SendResponse, type SettingsState, type SignIn, } from './services.ts';
16
+ export { type AgentPages, createPages, type PageBatch, type PageCaller, type PageRequest, type PageResult, type Pages, type PagesOpened, type PageTab, } from './remote/pages.ts';
17
+ export { type AccountState, AGENT_SESSION, AgentControl, AgentPagesService, AgentSessions, AgentSettings, type AgentSettingsChange, AgentTranscript, type BudgetState, type ModelChoice, type OperationError, type PromptResult, type SendMode, type SendRequest, type SendResponse, type SettingsState, type SignIn, } from './services.ts';
17
18
  export { BEDROCK, createAgentSettings, createSliccModels, DEFAULT_MODEL, DEFAULT_REGIONS, sliccProviders, withDefaultRegions, } from './settings.ts';
18
19
  export { AGENT_DIRECTORY, type OpfsSqliteOptions, openMemorySqliteStorage, openOpfsSqliteStorage, WasmSqliteDatabase, } from './sqlite.ts';
19
20
  export { type SystemFacts, systemSection } from './system.ts';
@@ -12,11 +12,13 @@ export interface WatchEntry {
12
12
  target: LickTarget;
13
13
  message: string;
14
14
  debounce: number;
15
+ triage?: string;
15
16
  }
16
17
  export interface WebhookEntry {
17
18
  name: string;
18
19
  target: LickTarget;
19
20
  message: string;
21
+ triage?: string;
20
22
  }
21
23
  export interface ConfigError {
22
24
  file: string;
@@ -2,6 +2,7 @@ import type { Context } from '@earendil-works/chord';
2
2
  import { type Conversation, type Harness } from '@earendil-works/pi-durable';
3
3
  import { type Lick, type LickAction, type LickChannel, type LickSeverity, type LickTarget } from './lick.ts';
4
4
  import { LICK_STATE_KIND, type LickDecision } from './state.ts';
5
+ import { type Triage } from './triage.ts';
5
6
  export interface LickEvent {
6
7
  channel: LickChannel;
7
8
  source: string;
@@ -15,6 +16,7 @@ export interface LickEvent {
15
16
  severity?: LickSeverity;
16
17
  eventId?: string;
17
18
  at?: number;
19
+ triage?: string;
18
20
  }
19
21
  type Pending = {
20
22
  lick: Lick;
@@ -47,6 +49,7 @@ export interface Licks {
47
49
  pending(context: Context): Promise<number>;
48
50
  forget(target: LickTarget, channel: LickChannel, source: string, context: Context): Promise<void>;
49
51
  guard(check: (target: LickTarget) => Blocked | undefined): void;
52
+ triage(judge: Triage): void;
50
53
  }
51
54
  export type Blocked = {
52
55
  id: string;
@@ -0,0 +1,42 @@
1
+ import type { Context } from '@earendil-works/chord';
2
+ import type { Models } from '@earendil-works/pi-ai/models';
3
+ import type { LickEvent } from './licks.ts';
4
+ export declare const TRIAGE_MODEL = "clef-flash";
5
+ export declare const TRIAGE_SKIP = 0.75;
6
+ export declare const TRIAGE_KIND = "slicc.lick-triage";
7
+ export declare const TRIAGE_LIMIT = 4000;
8
+ export interface Verdict {
9
+ decision: 'act' | 'ignore';
10
+ confidence: number;
11
+ probabilities: Record<string, number>;
12
+ model: string;
13
+ cost: number | null;
14
+ }
15
+ export type Triage = (event: LickEvent, context: Context) => Promise<Verdict | undefined>;
16
+ export declare function clip(text: string): string;
17
+ export declare function skips(verdict: Verdict | undefined): verdict is Verdict;
18
+ export declare function lickTriage(models: Pick<Models, 'getModelOfType' | 'classify'>): Triage;
19
+ export declare function skipEntry(event: LickEvent, verdict: Verdict, now: number): {
20
+ type: 'write';
21
+ requestId: string;
22
+ entry: {
23
+ kind: string;
24
+ data: {
25
+ decision: 'act' | 'ignore';
26
+ confidence: number;
27
+ probabilities: Record<string, number>;
28
+ model: string;
29
+ cost: number | null;
30
+ channel: "bash" | "cherry" | "cron" | "discovery" | "fswatch" | "jshd" | "navigate" | "preview" | "scoop-idle" | "scoop-notify" | "scoop-wait" | "session-reload" | "sprinkle" | "sudo-request" | "upgrade" | "webhook" | "workflow";
31
+ source: string;
32
+ title: string;
33
+ text: string;
34
+ body?: string | undefined;
35
+ items?: string[] | undefined;
36
+ lick: string;
37
+ count: number;
38
+ at: number;
39
+ triage: string;
40
+ };
41
+ };
42
+ };
@@ -8,6 +8,7 @@ export interface AgentWorker {
8
8
  }
9
9
  export interface PortOptions {
10
10
  readOnly?: boolean;
11
+ pages?: boolean;
11
12
  }
12
13
  export interface AgentOwner {
13
14
  connect(options?: PortOptions): Promise<AgentConnection>;
@@ -1,7 +1,9 @@
1
1
  import type { AgentRemote } from './remote/host.ts';
2
+ import type { AgentPages } from './remote/pages.ts';
2
3
  import type { AgentControl, AgentSettings } from './services.ts';
3
4
  import { VIEW_ONLY } from './view-only.ts';
4
5
  export { VIEW_ONLY };
5
6
  export declare function viewControl(control: AgentControl): AgentControl;
6
7
  export declare function viewSettings(settings: AgentSettings): AgentSettings;
8
+ export declare function viewPages(): AgentPages;
7
9
  export declare function viewRemote(remote: AgentRemote): AgentRemote;
@@ -45,6 +45,16 @@ export interface RemoteHost {
45
45
  limit?: number;
46
46
  sweepMs?: number;
47
47
  }
48
+ export declare class Stream<T = RemoteEvent> {
49
+ #private;
50
+ readonly queue: T[];
51
+ done: boolean;
52
+ touched: number;
53
+ constructor(stop: () => Promise<unknown>, now: number, limit: number, overflow: readonly T[]);
54
+ push(events: readonly T[]): void;
55
+ finish(): Promise<void>;
56
+ wait(ms: number, signal?: AbortSignal): Promise<void>;
57
+ }
48
58
  export declare function followed(mapper: Mapper, event: AgentEvent): RemoteEvent[];
49
59
  export declare function resync(mapper: Mapper, snapshot: SnapshotEvent): RemoteEvent[];
50
60
  export declare class Follower {
@@ -0,0 +1,76 @@
1
+ import type { Context } from '@earendil-works/chord';
2
+ import type { Answer } from '../scoops/service.ts';
3
+ export declare const PAGES_IDLE_MS = 30000;
4
+ export declare const PICK_MS = 5000;
5
+ export declare const ANSWER_MS = 5000;
6
+ export declare const MAX_PENDING = 100;
7
+ export interface PageTab {
8
+ tab: string;
9
+ kinds: string[];
10
+ cone: string | null;
11
+ visible: boolean;
12
+ focused: boolean;
13
+ lastActive: number;
14
+ }
15
+ export interface PageCaller {
16
+ agentId: string | null;
17
+ cone: string;
18
+ readOnly?: boolean;
19
+ }
20
+ export interface PageRequest {
21
+ id: string;
22
+ kind: 'panel';
23
+ argv: string[];
24
+ caller: PageCaller;
25
+ }
26
+ export interface PageResult {
27
+ stdout: string;
28
+ stderr: string;
29
+ code: number;
30
+ }
31
+ export type PagesOpened = {
32
+ stream: string;
33
+ } | {
34
+ error: string;
35
+ };
36
+ export interface PageBatch {
37
+ requests: PageRequest[];
38
+ done: boolean;
39
+ }
40
+ export interface AgentPages {
41
+ open(tab: PageTab, context: Context): Promise<PagesOpened>;
42
+ report(stream: string, tab: PageTab, context: Context): Promise<void>;
43
+ next(stream: string, waitMs: number, context: Context): Promise<PageBatch>;
44
+ answer(stream: string, id: string, result: PageResult, context: Context): Promise<void>;
45
+ close(stream: string, context: Context): Promise<void>;
46
+ }
47
+ export interface PageAsk {
48
+ kind: 'panel';
49
+ argv: readonly string[];
50
+ agentId: string | null;
51
+ cone: string;
52
+ tab: string | null;
53
+ }
54
+ export interface Pages extends AgentPages {
55
+ request(ask: PageAsk): Promise<PageResult>;
56
+ command(argv: readonly string[], caller: {
57
+ agentId: string | null;
58
+ cone: string;
59
+ tab: string | null;
60
+ }, context: Context): Promise<Answer>;
61
+ tabs(): PageTab[];
62
+ dispose(): Promise<void>;
63
+ }
64
+ export interface PagesOptions {
65
+ now?: () => number;
66
+ idleMs?: number;
67
+ sweepMs?: number;
68
+ pickMs?: number;
69
+ answerMs?: number;
70
+ }
71
+ export declare function noTab(cone: string): PageResult;
72
+ export declare function noAnswer(cone: string): PageResult;
73
+ export declare function ownedPages(pages: AgentPages): AgentPages & {
74
+ release(): void;
75
+ };
76
+ export declare function createPages(options?: PagesOptions): Pages;
@@ -24,6 +24,12 @@ export interface CliOptions {
24
24
  gelatiere?: (argv: readonly string[], caller: string | null, context: Context) => Promise<Answer>;
25
25
  freezer?: (argv: readonly string[], caller: string | null, context: Context) => Promise<Answer>;
26
26
  models?: (argv: readonly string[], caller: string | null, context: Context) => Promise<Answer>;
27
+ panel?: (argv: readonly string[], caller: PanelCaller, context: Context) => Promise<Answer>;
28
+ }
29
+ export interface PanelCaller {
30
+ agentId: string | null;
31
+ cone: string;
32
+ tab: string | null;
27
33
  }
28
34
  export type Deps = CliOptions & {
29
35
  now: () => number;
@@ -69,4 +75,5 @@ export declare function role(deps: Deps, parsed: Flags): Promise<{
69
75
  }>;
70
76
  export declare function unsupported(argv: readonly string[]): Answer | undefined;
71
77
  export declare function noScoop(state: Readonly<AgentsState>, name: string): string;
78
+ export declare function panelCaller(request: Request, who: Caller): PanelCaller;
72
79
  export declare function createCli(options: CliOptions): Runner;
@@ -30,6 +30,7 @@ export interface AttachOptions {
30
30
  gelatiere?: CliOptions['gelatiere'];
31
31
  freezer?: CliOptions['freezer'];
32
32
  models?: CliOptions['models'];
33
+ panel?: CliOptions['panel'];
33
34
  mcp?: (names: readonly string[]) => ToolRegistration[];
34
35
  }
35
36
  export interface ScoopsRuntime {
@@ -6,8 +6,9 @@ export declare const PROTOCOL = "slicc-agent/1";
6
6
  export declare const STALE_MS: number;
7
7
  export interface Request {
8
8
  id: string;
9
- as: 'agent' | 'subagent' | 'sprinkle' | 'memory' | 'gelatiere' | 'freezer' | 'models';
9
+ as: 'agent' | 'subagent' | 'sprinkle' | 'memory' | 'gelatiere' | 'freezer' | 'models' | 'panel';
10
10
  caller: string;
11
+ tab?: string | null;
11
12
  cwd: string;
12
13
  pid: number | null;
13
14
  argv: string[];