@ai-ecoverse/slicc-agent 2.0.0 → 2.2.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 (48) hide show
  1. package/README.md +39 -0
  2. package/dist/agent.js +2 -1
  3. package/dist/agent.js.map +2 -2
  4. package/dist/control.js +9 -1
  5. package/dist/control.js.map +2 -2
  6. package/dist/index.js +6 -1
  7. package/dist/index.js.map +2 -2
  8. package/dist/kernel/client.js +1 -0
  9. package/dist/kernel/client.js.map +7 -0
  10. package/dist/kernel/env.js +204 -0
  11. package/dist/kernel/env.js.map +7 -0
  12. package/dist/kernel/exec.js +115 -0
  13. package/dist/kernel/exec.js.map +7 -0
  14. package/dist/kernel/files.js +153 -0
  15. package/dist/kernel/files.js.map +7 -0
  16. package/dist/kernel/paths.js +31 -0
  17. package/dist/kernel/paths.js.map +7 -0
  18. package/dist/kernel/watch.js +71 -0
  19. package/dist/kernel/watch.js.map +7 -0
  20. package/dist/owner.js +32 -9
  21. package/dist/owner.js.map +2 -2
  22. package/dist/services.js.map +2 -2
  23. package/dist/spectrum/agent-port.js +156 -0
  24. package/dist/spectrum/agent-port.js.map +7 -0
  25. package/dist/spectrum/emitter.js +20 -0
  26. package/dist/spectrum/emitter.js.map +7 -0
  27. package/dist/spectrum/index.js +17 -0
  28. package/dist/spectrum/index.js.map +7 -0
  29. package/dist/spectrum/messages.js +161 -0
  30. package/dist/spectrum/messages.js.map +7 -0
  31. package/dist/types/agent.d.ts +1 -0
  32. package/dist/types/index.d.ts +4 -2
  33. package/dist/types/kernel/client.d.ts +39 -0
  34. package/dist/types/kernel/env.d.ts +57 -0
  35. package/dist/types/kernel/exec.d.ts +5 -0
  36. package/dist/types/kernel/files.d.ts +34 -0
  37. package/dist/types/kernel/paths.d.ts +4 -0
  38. package/dist/types/kernel/watch.d.ts +10 -0
  39. package/dist/types/owner.d.ts +6 -2
  40. package/dist/types/services.d.ts +9 -0
  41. package/dist/types/spectrum/agent-port.d.ts +32 -0
  42. package/dist/types/spectrum/emitter.d.ts +7 -0
  43. package/dist/types/spectrum/index.d.ts +7 -0
  44. package/dist/types/spectrum/messages.d.ts +37 -0
  45. package/dist/types/worker.d.ts +1 -0
  46. package/dist/worker.js +9 -0
  47. package/dist/worker.js.map +2 -2
  48. package/package.json +13 -2
@@ -0,0 +1,153 @@
1
+ import {
2
+ err,
3
+ FileError,
4
+ LineScanner,
5
+ ok
6
+ } from "@earendil-works/pi-durable/env";
7
+ import { basename } from "./paths.js";
8
+ const codes = {
9
+ ENOENT: "not_found",
10
+ EACCES: "permission_denied",
11
+ EPERM: "permission_denied",
12
+ ENOTDIR: "not_directory",
13
+ EISDIR: "is_directory",
14
+ EINVAL: "invalid",
15
+ ELOOP: "invalid",
16
+ ENOSYS: "not_supported",
17
+ ENOTSUP: "not_supported"
18
+ };
19
+ function fileError(error, path) {
20
+ if (error instanceof FileError) return error;
21
+ const code = error?.code;
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ const cause = error instanceof Error ? error : void 0;
24
+ return new FileError(code && codes[code] || "unknown", message, path, cause);
25
+ }
26
+ function aborted(context, path) {
27
+ if (!context.abortSignal?.aborted) return void 0;
28
+ return err(new FileError("aborted", "The operation was aborted", path));
29
+ }
30
+ async function attempt(context, path, run) {
31
+ const before = aborted(context, path);
32
+ if (before) return before;
33
+ try {
34
+ const value = await run();
35
+ return aborted(context, path) ?? ok(value);
36
+ } catch (error) {
37
+ return err(fileError(error, path));
38
+ }
39
+ }
40
+ function info(path, stat) {
41
+ const kind = stat.isSymbolicLink ? "symlink" : stat.isDirectory ? "directory" : "file";
42
+ return {
43
+ name: basename(path),
44
+ path,
45
+ kind,
46
+ size: stat.size,
47
+ mtimeMs: new Date(stat.mtime).getTime()
48
+ };
49
+ }
50
+ function closed(what, path) {
51
+ return err(new FileError("invalid", `${what} is closed`, path));
52
+ }
53
+ class SnapshotReader {
54
+ #bytes;
55
+ #info;
56
+ #closed = false;
57
+ constructor(bytes, fileInfo) {
58
+ this.#bytes = bytes;
59
+ this.#info = fileInfo;
60
+ }
61
+ async info() {
62
+ return this.#closed ? closed("Binary reader", this.#info.path) : ok(this.#info);
63
+ }
64
+ async read(offset, length, context) {
65
+ const path = this.#info.path;
66
+ const stop = aborted(context, path);
67
+ if (stop) return stop;
68
+ if (this.#closed) return closed("Binary reader", path);
69
+ if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 0) {
70
+ return err(new FileError("invalid", "Offset and length must be non-negative integers", path));
71
+ }
72
+ return ok(this.#bytes.slice(offset, offset + length));
73
+ }
74
+ async scanLines(options, context) {
75
+ const path = this.#info.path;
76
+ const stop = aborted(context, path);
77
+ if (stop) return stop;
78
+ if (this.#closed) return closed("Binary reader", path);
79
+ let scanner;
80
+ try {
81
+ scanner = new LineScanner(options.startLine, options.endLine);
82
+ } catch {
83
+ return err(new FileError("invalid", "Invalid line range", path));
84
+ }
85
+ scanner.push(this.#bytes);
86
+ return ok(scanner.finish());
87
+ }
88
+ async close() {
89
+ this.#closed = true;
90
+ }
91
+ }
92
+ class SnapshotLines {
93
+ #lines;
94
+ #next = 0;
95
+ constructor(text) {
96
+ const parts = text.split("\n");
97
+ const last = parts.pop();
98
+ this.#lines = parts.map((part) => ({ text: part, terminated: true }));
99
+ if (last !== "") this.#lines.push({ text: last, terminated: false });
100
+ }
101
+ async readLine() {
102
+ return ok(this.#lines[this.#next++]);
103
+ }
104
+ async close() {
105
+ this.#next = this.#lines.length;
106
+ }
107
+ }
108
+ class KernelDirReader {
109
+ #fs;
110
+ #path;
111
+ #names;
112
+ #closed = false;
113
+ constructor(fs, path, names) {
114
+ this.#fs = fs;
115
+ this.#path = path;
116
+ this.#names = names;
117
+ }
118
+ async next(maxEntries, context) {
119
+ if (this.#closed) return closed("Directory reader", this.#path);
120
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
121
+ return err(new FileError("invalid", "maxEntries must be a positive integer", this.#path));
122
+ }
123
+ const stop = aborted(context, this.#path);
124
+ if (stop) return stop;
125
+ const entries = await this.#take(maxEntries);
126
+ return ok({ entries, done: this.#names.length === 0 });
127
+ }
128
+ rest() {
129
+ return this.#take(Number.MAX_SAFE_INTEGER);
130
+ }
131
+ async #take(max) {
132
+ const entries = [];
133
+ while (entries.length < max && this.#names.length > 0) {
134
+ const path = `${this.#path === "/" ? "" : this.#path}/${this.#names.shift()}`;
135
+ const stat = await this.#fs.lstat(path).catch(() => void 0);
136
+ if (stat) entries.push(info(path, stat));
137
+ }
138
+ return entries;
139
+ }
140
+ async close() {
141
+ this.#closed = true;
142
+ }
143
+ }
144
+ export {
145
+ KernelDirReader,
146
+ SnapshotLines,
147
+ SnapshotReader,
148
+ aborted,
149
+ attempt,
150
+ fileError,
151
+ info
152
+ };
153
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/files.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type BinaryReader,\n type DirReader,\n err,\n FileError,\n type FileErrorCode,\n type FileInfo,\n type LineScan,\n LineScanner,\n ok,\n type Result,\n type TextLine,\n type TextLineReader,\n} from '@earendil-works/pi-durable/env';\nimport type { KernelFs, KernelStat } from './client.ts';\nimport { basename } from './paths.ts';\n\nconst codes: Record<string, FileErrorCode> = {\n ENOENT: 'not_found',\n EACCES: 'permission_denied',\n EPERM: 'permission_denied',\n ENOTDIR: 'not_directory',\n EISDIR: 'is_directory',\n EINVAL: 'invalid',\n ELOOP: 'invalid',\n ENOSYS: 'not_supported',\n ENOTSUP: 'not_supported',\n};\n\nexport function fileError(error: unknown, path?: string): FileError {\n if (error instanceof FileError) return error;\n const code = (error as { code?: string } | null)?.code;\n const message = error instanceof Error ? error.message : String(error);\n const cause = error instanceof Error ? error : undefined;\n return new FileError((code && codes[code]) || 'unknown', message, path, cause);\n}\n\nexport function aborted(context: Context, path?: string): Result<never, FileError> | undefined {\n if (!context.abortSignal?.aborted) return undefined;\n return err(new FileError('aborted', 'The operation was aborted', path));\n}\n\nexport async function attempt<T>(\n context: Context,\n path: string,\n run: () => Promise<T>\n): Promise<Result<T, FileError>> {\n const before = aborted(context, path);\n if (before) return before;\n try {\n const value = await run();\n return aborted(context, path) ?? ok(value);\n } catch (error) {\n return err(fileError(error, path));\n }\n}\n\nexport function info(path: string, stat: KernelStat): FileInfo {\n const kind = stat.isSymbolicLink ? 'symlink' : stat.isDirectory ? 'directory' : 'file';\n return {\n name: basename(path),\n path,\n kind,\n size: stat.size,\n mtimeMs: new Date(stat.mtime).getTime(),\n };\n}\n\nfunction closed<T>(what: string, path: string): Result<T, FileError> {\n return err(new FileError('invalid', `${what} is closed`, path));\n}\n\nexport class SnapshotReader implements BinaryReader {\n readonly #bytes: Uint8Array;\n readonly #info: FileInfo;\n #closed = false;\n\n constructor(bytes: Uint8Array, fileInfo: FileInfo) {\n this.#bytes = bytes;\n this.#info = fileInfo;\n }\n\n async info(): Promise<Result<FileInfo, FileError>> {\n return this.#closed ? closed('Binary reader', this.#info.path) : ok(this.#info);\n }\n\n async read(\n offset: number,\n length: number,\n context: Context\n ): Promise<Result<Uint8Array, FileError>> {\n const path = this.#info.path;\n const stop = aborted(context, path);\n if (stop) return stop;\n if (this.#closed) return closed('Binary reader', path);\n if (\n !Number.isSafeInteger(offset) ||\n offset < 0 ||\n !Number.isSafeInteger(length) ||\n length < 0\n ) {\n return err(new FileError('invalid', 'Offset and length must be non-negative integers', path));\n }\n return ok(this.#bytes.slice(offset, offset + length));\n }\n\n async scanLines(\n options: { startLine: number; endLine?: number },\n context: Context\n ): Promise<Result<LineScan, FileError>> {\n const path = this.#info.path;\n const stop = aborted(context, path);\n if (stop) return stop;\n if (this.#closed) return closed('Binary reader', path);\n let scanner: LineScanner;\n try {\n scanner = new LineScanner(options.startLine, options.endLine);\n } catch {\n return err(new FileError('invalid', 'Invalid line range', path));\n }\n scanner.push(this.#bytes);\n return ok(scanner.finish());\n }\n\n async close(): Promise<void> {\n this.#closed = true;\n }\n}\n\nexport class SnapshotLines implements TextLineReader {\n readonly #lines: TextLine[];\n #next = 0;\n\n constructor(text: string) {\n const parts = text.split('\\n');\n const last = parts.pop() as string;\n this.#lines = parts.map((part) => ({ text: part, terminated: true }));\n if (last !== '') this.#lines.push({ text: last, terminated: false });\n }\n\n async readLine(): Promise<Result<TextLine | undefined, FileError>> {\n return ok(this.#lines[this.#next++]);\n }\n\n async close(): Promise<void> {\n this.#next = this.#lines.length;\n }\n}\n\nexport class KernelDirReader implements DirReader {\n readonly #fs: KernelFs;\n readonly #path: string;\n readonly #names: string[];\n #closed = false;\n\n constructor(fs: KernelFs, path: string, names: string[]) {\n this.#fs = fs;\n this.#path = path;\n this.#names = names;\n }\n\n async next(\n maxEntries: number,\n context: Context\n ): Promise<Result<{ entries: FileInfo[]; done: boolean }, FileError>> {\n if (this.#closed) return closed('Directory reader', this.#path);\n if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {\n return err(new FileError('invalid', 'maxEntries must be a positive integer', this.#path));\n }\n const stop = aborted(context, this.#path);\n if (stop) return stop;\n const entries = await this.#take(maxEntries);\n return ok({ entries, done: this.#names.length === 0 });\n }\n\n rest(): Promise<FileInfo[]> {\n return this.#take(Number.MAX_SAFE_INTEGER);\n }\n\n async #take(max: number): Promise<FileInfo[]> {\n const entries: FileInfo[] = [];\n while (entries.length < max && this.#names.length > 0) {\n const path = `${this.#path === '/' ? '' : this.#path}/${this.#names.shift()}`;\n const stat = await this.#fs.lstat(path).catch(() => undefined);\n if (stat) entries.push(info(path, stat));\n }\n return entries;\n }\n\n async close(): Promise<void> {\n this.#closed = true;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,31 @@
1
+ function normalize(path) {
2
+ const absolute = path.startsWith("/");
3
+ const parts = [];
4
+ for (const part of path.split("/")) {
5
+ if (part === "" || part === ".") continue;
6
+ if (part === "..") {
7
+ if (parts.length > 0 && parts.at(-1) !== "..") parts.pop();
8
+ else if (!absolute) parts.push(part);
9
+ } else parts.push(part);
10
+ }
11
+ const joined = parts.join("/");
12
+ if (absolute) return `/${joined}`;
13
+ return joined || ".";
14
+ }
15
+ function resolve(cwd, path) {
16
+ return normalize(path.startsWith("/") ? path : `${cwd}/${path}`);
17
+ }
18
+ function dirname(path) {
19
+ const index = path.lastIndexOf("/");
20
+ return index <= 0 ? "/" : path.slice(0, index);
21
+ }
22
+ function basename(path) {
23
+ return path.slice(path.lastIndexOf("/") + 1);
24
+ }
25
+ export {
26
+ basename,
27
+ dirname,
28
+ normalize,
29
+ resolve
30
+ };
31
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/paths.ts"],
4
+ "sourcesContent": ["export function normalize(path: string): string {\n const absolute = path.startsWith('/');\n const parts: string[] = [];\n for (const part of path.split('/')) {\n if (part === '' || part === '.') continue;\n if (part === '..') {\n if (parts.length > 0 && parts.at(-1) !== '..') parts.pop();\n else if (!absolute) parts.push(part);\n } else parts.push(part);\n }\n const joined = parts.join('/');\n if (absolute) return `/${joined}`;\n return joined || '.';\n}\n\nexport function resolve(cwd: string, path: string): string {\n return normalize(path.startsWith('/') ? path : `${cwd}/${path}`);\n}\n\nexport function dirname(path: string): string {\n const index = path.lastIndexOf('/');\n return index <= 0 ? '/' : path.slice(0, index);\n}\n\nexport function basename(path: string): string {\n return path.slice(path.lastIndexOf('/') + 1);\n}\n"],
5
+ "mappings": "AAAO,SAAS,UAAU,MAAsB;AAC9C,QAAM,WAAW,KAAK,WAAW,GAAG;AACpC,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,KAAK,MAAM,GAAG,GAAG;AAClC,QAAI,SAAS,MAAM,SAAS,IAAK;AACjC,QAAI,SAAS,MAAM;AACjB,UAAI,MAAM,SAAS,KAAK,MAAM,GAAG,EAAE,MAAM,KAAM,OAAM,IAAI;AAAA,eAChD,CAAC,SAAU,OAAM,KAAK,IAAI;AAAA,IACrC,MAAO,OAAM,KAAK,IAAI;AAAA,EACxB;AACA,QAAM,SAAS,MAAM,KAAK,GAAG;AAC7B,MAAI,SAAU,QAAO,IAAI,MAAM;AAC/B,SAAO,UAAU;AACnB;AAEO,SAAS,QAAQ,KAAa,MAAsB;AACzD,SAAO,UAAU,KAAK,WAAW,GAAG,IAAI,OAAO,GAAG,GAAG,IAAI,IAAI,EAAE;AACjE;AAEO,SAAS,QAAQ,MAAsB;AAC5C,QAAM,QAAQ,KAAK,YAAY,GAAG;AAClC,SAAO,SAAS,IAAI,MAAM,KAAK,MAAM,GAAG,KAAK;AAC/C;AAEO,SAAS,SAAS,MAAsB;AAC7C,SAAO,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC;AAC7C;",
6
+ "names": []
7
+ }
@@ -0,0 +1,71 @@
1
+ import { dirname, resolve } from "./paths.js";
2
+ function excluded(name, target) {
3
+ const rule = target.exclude;
4
+ if (!rule) return false;
5
+ return rule.hidden === true && name.startsWith(".") || (rule.names?.includes(name) ?? false);
6
+ }
7
+ async function record(fs, path, into) {
8
+ const stat = await fs.stat(path).catch(() => void 0);
9
+ if (!stat) return false;
10
+ into.set(
11
+ path,
12
+ `${stat.isDirectory ? "d" : "f"}:${stat.ino}:${stat.size}:${new Date(stat.mtime).getTime()}`
13
+ );
14
+ return stat.isDirectory;
15
+ }
16
+ async function scan(fs, path, target, deep, into) {
17
+ const directory = await record(fs, path, into);
18
+ if (!directory || !deep) return;
19
+ const names = await fs.readdir(path).catch(() => []);
20
+ for (const name of names) {
21
+ if (excluded(name, target)) continue;
22
+ const child = `${path === "/" ? "" : path}/${name}`;
23
+ await scan(fs, child, target, target.recursive === true, into);
24
+ }
25
+ }
26
+ async function snapshot(fs, cwd, targets) {
27
+ const all = /* @__PURE__ */ new Map();
28
+ for (const target of targets) await scan(fs, resolve(cwd, target.path), target, true, all);
29
+ return all;
30
+ }
31
+ function differences(before, after) {
32
+ const changed = /* @__PURE__ */ new Set();
33
+ const moved = (path) => {
34
+ changed.add(path);
35
+ changed.add(dirname(path));
36
+ };
37
+ for (const [path, value] of after) {
38
+ if (!before.has(path)) moved(path);
39
+ else if (before.get(path) !== value) changed.add(path);
40
+ }
41
+ for (const path of before.keys()) if (!after.has(path)) moved(path);
42
+ return [...changed];
43
+ }
44
+ async function pollWatch(fs, cwd, targets, onChange, options = {}) {
45
+ let current = await snapshot(fs, cwd, targets);
46
+ let open = true;
47
+ let running = Promise.resolve();
48
+ const tick = async () => {
49
+ const next = await snapshot(fs, cwd, targets);
50
+ const paths = differences(current, next);
51
+ current = next;
52
+ if (open && paths.length > 0) onChange({ paths });
53
+ };
54
+ const timer = setInterval(() => {
55
+ running = running.then(tick);
56
+ }, options.intervalMs ?? 100);
57
+ return {
58
+ mode: "polling",
59
+ async close(_context) {
60
+ open = false;
61
+ clearInterval(timer);
62
+ await running;
63
+ }
64
+ };
65
+ }
66
+ export {
67
+ differences,
68
+ pollWatch,
69
+ snapshot
70
+ };
71
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/watch.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { FileWatcher, WatchChange, WatchTarget } from '@earendil-works/pi-durable/env';\nimport type { KernelFs } from './client.ts';\nimport { dirname, resolve } from './paths.ts';\n\ntype Snapshot = Map<string, string>;\n\nfunction excluded(name: string, target: WatchTarget): boolean {\n const rule = target.exclude;\n if (!rule) return false;\n return (rule.hidden === true && name.startsWith('.')) || (rule.names?.includes(name) ?? false);\n}\n\nasync function record(fs: KernelFs, path: string, into: Snapshot): Promise<boolean> {\n const stat = await fs.stat(path).catch(() => undefined);\n if (!stat) return false;\n into.set(\n path,\n `${stat.isDirectory ? 'd' : 'f'}:${stat.ino}:${stat.size}:${new Date(stat.mtime).getTime()}`\n );\n return stat.isDirectory;\n}\n\nasync function scan(\n fs: KernelFs,\n path: string,\n target: WatchTarget,\n deep: boolean,\n into: Snapshot\n) {\n const directory = await record(fs, path, into);\n if (!directory || !deep) return;\n const names = await fs.readdir(path).catch(() => [] as string[]);\n for (const name of names) {\n if (excluded(name, target)) continue;\n const child = `${path === '/' ? '' : path}/${name}`;\n await scan(fs, child, target, target.recursive === true, into);\n }\n}\n\nexport async function snapshot(\n fs: KernelFs,\n cwd: string,\n targets: readonly WatchTarget[]\n): Promise<Snapshot> {\n const all: Snapshot = new Map();\n for (const target of targets) await scan(fs, resolve(cwd, target.path), target, true, all);\n return all;\n}\n\nexport function differences(before: Snapshot, after: Snapshot): string[] {\n const changed = new Set<string>();\n const moved = (path: string) => {\n changed.add(path);\n changed.add(dirname(path));\n };\n for (const [path, value] of after) {\n if (!before.has(path)) moved(path);\n else if (before.get(path) !== value) changed.add(path);\n }\n for (const path of before.keys()) if (!after.has(path)) moved(path);\n return [...changed];\n}\n\nexport interface PollOptions {\n intervalMs?: number;\n}\n\nexport async function pollWatch(\n fs: KernelFs,\n cwd: string,\n targets: readonly WatchTarget[],\n onChange: (change: WatchChange) => void,\n options: PollOptions = {}\n): Promise<FileWatcher> {\n let current = await snapshot(fs, cwd, targets);\n let open = true;\n let running = Promise.resolve();\n const tick = async () => {\n const next = await snapshot(fs, cwd, targets);\n const paths = differences(current, next);\n current = next;\n if (open && paths.length > 0) onChange({ paths });\n };\n const timer = setInterval(() => {\n running = running.then(tick);\n }, options.intervalMs ?? 100);\n return {\n mode: 'polling',\n async close(_context: Context) {\n open = false;\n clearInterval(timer);\n await running;\n },\n };\n}\n"],
5
+ "mappings": "AAGA,SAAS,SAAS,eAAe;AAIjC,SAAS,SAAS,MAAc,QAA8B;AAC5D,QAAM,OAAO,OAAO;AACpB,MAAI,CAAC,KAAM,QAAO;AAClB,SAAQ,KAAK,WAAW,QAAQ,KAAK,WAAW,GAAG,MAAO,KAAK,OAAO,SAAS,IAAI,KAAK;AAC1F;AAEA,eAAe,OAAO,IAAc,MAAc,MAAkC;AAClF,QAAM,OAAO,MAAM,GAAG,KAAK,IAAI,EAAE,MAAM,MAAM,MAAS;AACtD,MAAI,CAAC,KAAM,QAAO;AAClB,OAAK;AAAA,IACH;AAAA,IACA,GAAG,KAAK,cAAc,MAAM,GAAG,IAAI,KAAK,GAAG,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,EAAE,QAAQ,CAAC;AAAA,EAC5F;AACA,SAAO,KAAK;AACd;AAEA,eAAe,KACb,IACA,MACA,QACA,MACA,MACA;AACA,QAAM,YAAY,MAAM,OAAO,IAAI,MAAM,IAAI;AAC7C,MAAI,CAAC,aAAa,CAAC,KAAM;AACzB,QAAM,QAAQ,MAAM,GAAG,QAAQ,IAAI,EAAE,MAAM,MAAM,CAAC,CAAa;AAC/D,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS,MAAM,MAAM,EAAG;AAC5B,UAAM,QAAQ,GAAG,SAAS,MAAM,KAAK,IAAI,IAAI,IAAI;AACjD,UAAM,KAAK,IAAI,OAAO,QAAQ,OAAO,cAAc,MAAM,IAAI;AAAA,EAC/D;AACF;AAEA,eAAsB,SACpB,IACA,KACA,SACmB;AACnB,QAAM,MAAgB,oBAAI,IAAI;AAC9B,aAAW,UAAU,QAAS,OAAM,KAAK,IAAI,QAAQ,KAAK,OAAO,IAAI,GAAG,QAAQ,MAAM,GAAG;AACzF,SAAO;AACT;AAEO,SAAS,YAAY,QAAkB,OAA2B;AACvE,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,QAAQ,CAAC,SAAiB;AAC9B,YAAQ,IAAI,IAAI;AAChB,YAAQ,IAAI,QAAQ,IAAI,CAAC;AAAA,EAC3B;AACA,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO;AACjC,QAAI,CAAC,OAAO,IAAI,IAAI,EAAG,OAAM,IAAI;AAAA,aACxB,OAAO,IAAI,IAAI,MAAM,MAAO,SAAQ,IAAI,IAAI;AAAA,EACvD;AACA,aAAW,QAAQ,OAAO,KAAK,EAAG,KAAI,CAAC,MAAM,IAAI,IAAI,EAAG,OAAM,IAAI;AAClE,SAAO,CAAC,GAAG,OAAO;AACpB;AAMA,eAAsB,UACpB,IACA,KACA,SACA,UACA,UAAuB,CAAC,GACF;AACtB,MAAI,UAAU,MAAM,SAAS,IAAI,KAAK,OAAO;AAC7C,MAAI,OAAO;AACX,MAAI,UAAU,QAAQ,QAAQ;AAC9B,QAAM,OAAO,YAAY;AACvB,UAAM,OAAO,MAAM,SAAS,IAAI,KAAK,OAAO;AAC5C,UAAM,QAAQ,YAAY,SAAS,IAAI;AACvC,cAAU;AACV,QAAI,QAAQ,MAAM,SAAS,EAAG,UAAS,EAAE,MAAM,CAAC;AAAA,EAClD;AACA,QAAM,QAAQ,YAAY,MAAM;AAC9B,cAAU,QAAQ,KAAK,IAAI;AAAA,EAC7B,GAAG,QAAQ,cAAc,GAAG;AAC5B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,MAAM,UAAmB;AAC7B,aAAO;AACP,oBAAc,KAAK;AACnB,YAAM;AAAA,IACR;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
package/dist/owner.js CHANGED
@@ -4,26 +4,49 @@ const AGENT_LOCK = "slicc-agent";
4
4
  async function startAgent(options) {
5
5
  const release = await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);
6
6
  const channel = options.channel ?? (() => new MessageChannel());
7
- let worker;
7
+ const spawn = async () => {
8
+ const created = options.worker();
9
+ try {
10
+ if (options.kernel) {
11
+ const port = await options.kernel.connect();
12
+ created.postMessage({ kernel: port }, [port]);
13
+ }
14
+ } catch (error) {
15
+ created.terminate();
16
+ throw error;
17
+ }
18
+ return created;
19
+ };
20
+ let worker = Promise.resolve().then(spawn);
8
21
  try {
9
- worker = options.worker();
22
+ await worker;
10
23
  } catch (error) {
11
24
  release();
12
25
  throw error;
13
26
  }
14
27
  return {
15
- connect() {
28
+ async connect() {
29
+ const ready = await worker;
30
+ if (!ready) throw new Error("the agent was released");
16
31
  const { port1, port2 } = channel();
17
- worker.postMessage({ connect: port2 }, [port2]);
32
+ ready.postMessage({ connect: port2 }, [port2]);
18
33
  return connectAgent(port1);
19
34
  },
20
35
  restart() {
21
- worker.terminate();
22
- worker = options.worker();
36
+ worker = worker.catch(() => void 0).then((old) => {
37
+ old?.terminate();
38
+ return spawn();
39
+ });
40
+ return worker.then(() => void 0);
23
41
  },
24
- release() {
25
- worker.terminate();
26
- release();
42
+ async release() {
43
+ const previous = worker;
44
+ worker = Promise.resolve(void 0);
45
+ try {
46
+ (await previous.catch(() => void 0))?.terminate();
47
+ } finally {
48
+ release();
49
+ }
27
50
  }
28
51
  };
29
52
  }
package/dist/owner.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/owner.ts"],
4
- "sourcesContent": ["import { type AgentConnection, connectAgent } from './client.ts';\nimport { holdLock } from './opfs-pool.ts';\nimport type { PortEndpoint } from './wire.ts';\n\nexport const AGENT_LOCK = 'slicc-agent';\n\nexport interface AgentWorker {\n postMessage(message: unknown, transfer: Transferable[]): void;\n terminate(): void;\n}\n\nexport interface AgentOwner {\n connect(): Promise<AgentConnection>;\n restart(): void;\n release(): void;\n}\n\nexport interface OwnerOptions {\n worker: () => AgentWorker;\n locks?: LockManager;\n channel?: () => { port1: PortEndpoint; port2: Transferable };\n}\n\nexport async function startAgent(options: OwnerOptions): Promise<AgentOwner> {\n const release = await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);\n const channel = options.channel ?? (() => new MessageChannel());\n let worker: AgentWorker;\n try {\n worker = options.worker();\n } catch (error) {\n release();\n throw error;\n }\n return {\n connect() {\n const { port1, port2 } = channel();\n worker.postMessage({ connect: port2 }, [port2]);\n return connectAgent(port1);\n },\n restart() {\n worker.terminate();\n worker = options.worker();\n },\n release() {\n worker.terminate();\n release();\n },\n };\n}\n"],
5
- "mappings": "AAAA,SAA+B,oBAAoB;AACnD,SAAS,gBAAgB;AAGlB,MAAM,aAAa;AAmB1B,eAAsB,WAAW,SAA4C;AAC3E,QAAM,UAAU,MAAM,SAAS,QAAQ,SAAS,UAAU,OAAO,UAAU;AAC3E,QAAM,UAAU,QAAQ,YAAY,MAAM,IAAI,eAAe;AAC7D,MAAI;AACJ,MAAI;AACF,aAAS,QAAQ,OAAO;AAAA,EAC1B,SAAS,OAAO;AACd,YAAQ;AACR,UAAM;AAAA,EACR;AACA,SAAO;AAAA,IACL,UAAU;AACR,YAAM,EAAE,OAAO,MAAM,IAAI,QAAQ;AACjC,aAAO,YAAY,EAAE,SAAS,MAAM,GAAG,CAAC,KAAK,CAAC;AAC9C,aAAO,aAAa,KAAK;AAAA,IAC3B;AAAA,IACA,UAAU;AACR,aAAO,UAAU;AACjB,eAAS,QAAQ,OAAO;AAAA,IAC1B;AAAA,IACA,UAAU;AACR,aAAO,UAAU;AACjB,cAAQ;AAAA,IACV;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { type AgentConnection, connectAgent } from './client.ts';\nimport { holdLock } from './opfs-pool.ts';\nimport type { PortEndpoint } from './wire.ts';\n\nexport const AGENT_LOCK = 'slicc-agent';\n\nexport interface AgentWorker {\n postMessage(message: unknown, transfer: Transferable[]): void;\n terminate(): void;\n}\n\nexport interface AgentOwner {\n connect(): Promise<AgentConnection>;\n restart(): Promise<void>;\n release(): Promise<void>;\n}\n\nexport interface KernelSource {\n connect(): Promise<Transferable>;\n}\n\nexport interface OwnerOptions {\n worker: () => AgentWorker;\n kernel?: KernelSource;\n locks?: LockManager;\n channel?: () => { port1: PortEndpoint; port2: Transferable };\n}\n\nexport async function startAgent(options: OwnerOptions): Promise<AgentOwner> {\n const release = await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);\n const channel = options.channel ?? (() => new MessageChannel());\n const spawn = async (): Promise<AgentWorker> => {\n const created = options.worker();\n try {\n if (options.kernel) {\n const port = await options.kernel.connect();\n created.postMessage({ kernel: port }, [port]);\n }\n } catch (error) {\n created.terminate();\n throw error;\n }\n return created;\n };\n let worker: Promise<AgentWorker | undefined> = Promise.resolve().then(spawn);\n try {\n await worker;\n } catch (error) {\n release();\n throw error;\n }\n return {\n async connect() {\n const ready = await worker;\n if (!ready) throw new Error('the agent was released');\n const { port1, port2 } = channel();\n ready.postMessage({ connect: port2 }, [port2]);\n return connectAgent(port1);\n },\n restart() {\n worker = worker\n .catch(() => undefined)\n .then((old) => {\n old?.terminate();\n return spawn();\n });\n return worker.then(() => undefined);\n },\n async release() {\n const previous = worker;\n worker = Promise.resolve(undefined);\n try {\n (await previous.catch(() => undefined))?.terminate();\n } finally {\n release();\n }\n },\n };\n}\n"],
5
+ "mappings": "AAAA,SAA+B,oBAAoB;AACnD,SAAS,gBAAgB;AAGlB,MAAM,aAAa;AAwB1B,eAAsB,WAAW,SAA4C;AAC3E,QAAM,UAAU,MAAM,SAAS,QAAQ,SAAS,UAAU,OAAO,UAAU;AAC3E,QAAM,UAAU,QAAQ,YAAY,MAAM,IAAI,eAAe;AAC7D,QAAM,QAAQ,YAAkC;AAC9C,UAAM,UAAU,QAAQ,OAAO;AAC/B,QAAI;AACF,UAAI,QAAQ,QAAQ;AAClB,cAAM,OAAO,MAAM,QAAQ,OAAO,QAAQ;AAC1C,gBAAQ,YAAY,EAAE,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC;AAAA,MAC9C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,UAAU;AAClB,YAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AACA,MAAI,SAA2C,QAAQ,QAAQ,EAAE,KAAK,KAAK;AAC3E,MAAI;AACF,UAAM;AAAA,EACR,SAAS,OAAO;AACd,YAAQ;AACR,UAAM;AAAA,EACR;AACA,SAAO;AAAA,IACL,MAAM,UAAU;AACd,YAAM,QAAQ,MAAM;AACpB,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,wBAAwB;AACpD,YAAM,EAAE,OAAO,MAAM,IAAI,QAAQ;AACjC,YAAM,YAAY,EAAE,SAAS,MAAM,GAAG,CAAC,KAAK,CAAC;AAC7C,aAAO,aAAa,KAAK;AAAA,IAC3B;AAAA,IACA,UAAU;AACR,eAAS,OACN,MAAM,MAAM,MAAS,EACrB,KAAK,CAAC,QAAQ;AACb,aAAK,UAAU;AACf,eAAO,MAAM;AAAA,MACf,CAAC;AACH,aAAO,OAAO,KAAK,MAAM,MAAS;AAAA,IACpC;AAAA,IACA,MAAM,UAAU;AACd,YAAM,WAAW;AACjB,eAAS,QAAQ,QAAQ,MAAS;AAClC,UAAI;AACF,SAAC,MAAM,SAAS,MAAM,MAAM,MAAS,IAAI,UAAU;AAAA,MACrD,UAAE;AACA,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/services.ts"],
4
- "sourcesContent": ["import { type Context, defineService, type ReplicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AGENT_SESSION = 'agent';\n"],
5
- "mappings": "AAAA,SAAuB,qBAA2C;AAmC3D,MAAM,eAAe,cAA4B,qBAAqB;AAMtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,gBAAgB;",
4
+ "sourcesContent": ["import { type Context, defineService, type ReplicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n}\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AGENT_SESSION = 'agent';\n"],
5
+ "mappings": "AAAA,SAAuB,qBAA2C;AA0C3D,MAAM,eAAe,cAA4B,qBAAqB;AAMtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,gBAAgB;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,156 @@
1
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ import { Emitter } from "./emitter.js";
3
+ import { isBusy, queued, toMessages } from "./messages.js";
4
+ const CONE = "cone";
5
+ const thinking = {
6
+ off: "off",
7
+ minimal: "low",
8
+ low: "low",
9
+ medium: "medium",
10
+ high: "high",
11
+ xhigh: "high",
12
+ max: "high"
13
+ };
14
+ function status(view, messages) {
15
+ if (isBusy(view)) {
16
+ const live = view?.docs["pi.live"];
17
+ return live?.tools?.some((slot) => slot.status === "running") ? "working" : "thinking";
18
+ }
19
+ const last = messages.at(-1);
20
+ return last?.role === "assistant" && last.status === "error" ? "error" : "idle";
21
+ }
22
+ class AgentAdapter extends Emitter {
23
+ #connection;
24
+ #ready;
25
+ #messages = [];
26
+ constructor(connection) {
27
+ super();
28
+ this.#connection = connection;
29
+ this.#ready = new Promise((resolve) => {
30
+ connection.transcript.subscribe((view) => {
31
+ this.#update(view);
32
+ resolve();
33
+ });
34
+ });
35
+ }
36
+ #view() {
37
+ return this.#connection.transcript.value;
38
+ }
39
+ #update(view) {
40
+ const before = this.#messages;
41
+ this.#messages = toMessages(view);
42
+ this.emit("agents", this.list());
43
+ if (before.length !== this.#messages.length) {
44
+ this.emit("messages", CONE);
45
+ return;
46
+ }
47
+ const changed = this.#messages.filter(
48
+ (message, index) => JSON.stringify(message) !== JSON.stringify(before[index])
49
+ );
50
+ if (changed.length === 1)
51
+ this.emit("message", { agentId: CONE, message: changed[0] });
52
+ else if (changed.length > 1) this.emit("messages", CONE);
53
+ }
54
+ ready() {
55
+ return this.#ready;
56
+ }
57
+ list() {
58
+ const doc = this.#view()?.docs["pi.agent"];
59
+ return [
60
+ {
61
+ id: CONE,
62
+ name: "sliccy",
63
+ kind: "cone",
64
+ parentId: null,
65
+ status: status(this.#view(), this.#messages),
66
+ model: doc?.model ? `${doc.model.provider}/${doc.model.modelId}` : "",
67
+ contextFill: 0,
68
+ unread: 0,
69
+ thinking: thinking[doc?.thinkingLevel ?? "off"] ?? "off"
70
+ }
71
+ ];
72
+ }
73
+ active() {
74
+ return CONE;
75
+ }
76
+ select() {
77
+ }
78
+ messages() {
79
+ return this.#messages;
80
+ }
81
+ async send(_agentId, input) {
82
+ const outgoing = typeof input === "string" ? { text: input } : input;
83
+ const sent = await this.#connection.control.send(
84
+ {
85
+ text: outgoing.text,
86
+ whenBusy: outgoing.mode === "steer" ? "steer" : "followUp",
87
+ requestId: crypto.randomUUID()
88
+ },
89
+ BACKGROUND_CONTEXT
90
+ );
91
+ if (!sent.accepted) throw new Error(sent.error.message);
92
+ }
93
+ stop() {
94
+ void this.#connection.control.abort(BACKGROUND_CONTEXT);
95
+ }
96
+ busy() {
97
+ return isBusy(this.#view());
98
+ }
99
+ queue() {
100
+ return queued(this.#view());
101
+ }
102
+ unqueue(_agentId, messageId) {
103
+ void this.#connection.control.withdraw(messageId.replace(/^q/, ""), BACKGROUND_CONTEXT);
104
+ }
105
+ suggestion() {
106
+ return null;
107
+ }
108
+ answer() {
109
+ }
110
+ resolveLick() {
111
+ }
112
+ compact() {
113
+ void this.#connection.control.compact(null, BACKGROUND_CONTEXT);
114
+ }
115
+ clear() {
116
+ void this.#connection.control.reset(null, BACKGROUND_CONTEXT);
117
+ }
118
+ #configure(change) {
119
+ void this.#connection.control.configure(
120
+ { model: change.model ?? null, thinkingLevel: change.thinkingLevel ?? null },
121
+ BACKGROUND_CONTEXT
122
+ );
123
+ }
124
+ setModel(_agentId, model) {
125
+ const at = model.indexOf("/");
126
+ if (at <= 0) return;
127
+ this.#configure({ model: { provider: model.slice(0, at), modelId: model.slice(at + 1) } });
128
+ }
129
+ setThinking(_agentId, level) {
130
+ this.#configure({ thinkingLevel: level });
131
+ }
132
+ async older() {
133
+ return [];
134
+ }
135
+ commands() {
136
+ return [];
137
+ }
138
+ createScoop() {
139
+ throw new Error("Scoops come with a later slicc-agent");
140
+ }
141
+ frozen() {
142
+ return [];
143
+ }
144
+ freeze() {
145
+ }
146
+ thaw() {
147
+ return null;
148
+ }
149
+ discard() {
150
+ }
151
+ }
152
+ export {
153
+ AgentAdapter,
154
+ CONE
155
+ };
156
+ //# sourceMappingURL=agent-port.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/spectrum/agent-port.ts"],
4
+ "sourcesContent": ["import type {\n Agent,\n AgentEvents,\n AgentPort,\n AgentStatus,\n FrozenCone,\n Message,\n Outgoing,\n SlashCommand,\n Thinking,\n UserMessage,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { AgentConnection } from '../client.ts';\nimport type { AgentSettingsChange } from '../services.ts';\nimport { Emitter } from './emitter.ts';\nimport { isBusy, queued, toMessages } from './messages.ts';\n\nexport const CONE = 'cone';\n\nconst thinking: Record<string, Thinking> = {\n off: 'off',\n minimal: 'low',\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'high',\n max: 'high',\n};\n\ninterface AgentDoc {\n model?: { provider: string; modelId: string };\n thinkingLevel?: string;\n}\n\nfunction status(view: ConversationView | undefined, messages: readonly Message[]): AgentStatus {\n if (isBusy(view)) {\n const live = view?.docs['pi.live'] as { tools?: { status: string }[] } | undefined;\n return live?.tools?.some((slot) => slot.status === 'running') ? 'working' : 'thinking';\n }\n const last = messages.at(-1);\n return last?.role === 'assistant' && last.status === 'error' ? 'error' : 'idle';\n}\n\nexport class AgentAdapter extends Emitter<AgentEvents> implements AgentPort {\n readonly #connection: AgentConnection;\n readonly #ready: Promise<void>;\n #messages: Message[] = [];\n\n constructor(connection: AgentConnection) {\n super();\n this.#connection = connection;\n this.#ready = new Promise((resolve) => {\n connection.transcript.subscribe((view) => {\n this.#update(view);\n resolve();\n });\n });\n }\n\n #view(): ConversationView | undefined {\n return this.#connection.transcript.value;\n }\n\n #update(view: ConversationView): void {\n const before = this.#messages;\n this.#messages = toMessages(view);\n this.emit('agents', this.list());\n if (before.length !== this.#messages.length) {\n this.emit('messages', CONE);\n return;\n }\n const changed = this.#messages.filter(\n (message, index) => JSON.stringify(message) !== JSON.stringify(before[index])\n );\n if (changed.length === 1)\n this.emit('message', { agentId: CONE, message: changed[0] as Message });\n else if (changed.length > 1) this.emit('messages', CONE);\n }\n\n ready(): Promise<void> {\n return this.#ready;\n }\n\n list(): readonly Agent[] {\n const doc = this.#view()?.docs['pi.agent'] as AgentDoc | undefined;\n return [\n {\n id: CONE,\n name: 'sliccy',\n kind: 'cone',\n parentId: null,\n status: status(this.#view(), this.#messages),\n model: doc?.model ? `${doc.model.provider}/${doc.model.modelId}` : '',\n contextFill: 0,\n unread: 0,\n thinking: thinking[doc?.thinkingLevel ?? 'off'] ?? 'off',\n },\n ];\n }\n\n active(): string {\n return CONE;\n }\n\n select(): void {}\n\n messages(): readonly Message[] {\n return this.#messages;\n }\n\n async send(_agentId: string, input: string | Outgoing): Promise<void> {\n const outgoing = typeof input === 'string' ? { text: input } : input;\n const sent = await this.#connection.control.send(\n {\n text: outgoing.text,\n whenBusy: outgoing.mode === 'steer' ? 'steer' : 'followUp',\n requestId: crypto.randomUUID(),\n },\n BACKGROUND_CONTEXT\n );\n if (!sent.accepted) throw new Error(sent.error.message);\n }\n\n stop(): void {\n void this.#connection.control.abort(BACKGROUND_CONTEXT);\n }\n\n busy(): boolean {\n return isBusy(this.#view());\n }\n\n queue(): readonly UserMessage[] {\n return queued(this.#view());\n }\n\n unqueue(_agentId: string, messageId: string): void {\n void this.#connection.control.withdraw(messageId.replace(/^q/, ''), BACKGROUND_CONTEXT);\n }\n\n suggestion(): string | null {\n return null;\n }\n\n answer(): void {}\n\n resolveLick(): void {}\n\n compact(): void {\n void this.#connection.control.compact(null, BACKGROUND_CONTEXT);\n }\n\n clear(): void {\n void this.#connection.control.reset(null, BACKGROUND_CONTEXT);\n }\n\n #configure(change: Partial<AgentSettingsChange>): void {\n void this.#connection.control.configure(\n { model: change.model ?? null, thinkingLevel: change.thinkingLevel ?? null },\n BACKGROUND_CONTEXT\n );\n }\n\n setModel(_agentId: string, model: string): void {\n const at = model.indexOf('/');\n if (at <= 0) return;\n this.#configure({ model: { provider: model.slice(0, at), modelId: model.slice(at + 1) } });\n }\n\n setThinking(_agentId: string, level: Thinking): void {\n this.#configure({ thinkingLevel: level });\n }\n\n async older(): Promise<readonly Message[]> {\n return [];\n }\n\n commands(): readonly SlashCommand[] {\n return [];\n }\n\n createScoop(): Agent {\n throw new Error('Scoops come with a later slicc-agent');\n }\n\n frozen(): readonly FrozenCone[] {\n return [];\n }\n\n freeze(): void {}\n\n thaw(): Agent | null {\n return null;\n }\n\n discard(): void {}\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,20 @@
1
+ class Emitter {
2
+ #listeners = /* @__PURE__ */ new Map();
3
+ on(type, listener) {
4
+ let set = this.#listeners.get(type);
5
+ if (!set) {
6
+ set = /* @__PURE__ */ new Set();
7
+ this.#listeners.set(type, set);
8
+ }
9
+ set.add(listener);
10
+ return () => set.delete(listener);
11
+ }
12
+ emit(type, detail) {
13
+ for (const listener of [...this.#listeners.get(type) ?? []])
14
+ listener(detail);
15
+ }
16
+ }
17
+ export {
18
+ Emitter
19
+ };
20
+ //# sourceMappingURL=emitter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/spectrum/emitter.ts"],
4
+ "sourcesContent": ["type Listener<T> = (detail: T) => void;\n\nexport class Emitter<Events extends object> {\n readonly #listeners = new Map<keyof Events, Set<Listener<never>>>();\n\n on<K extends keyof Events>(type: K, listener: Listener<Events[K]>): () => void {\n let set = this.#listeners.get(type);\n if (!set) {\n set = new Set();\n this.#listeners.set(type, set);\n }\n set.add(listener as Listener<never>);\n return () => set.delete(listener as Listener<never>);\n }\n\n emit<K extends keyof Events>(type: K, detail: Events[K]): void {\n for (const listener of [...(this.#listeners.get(type) ?? [])])\n (listener as Listener<Events[K]>)(detail);\n }\n}\n"],
5
+ "mappings": "AAEO,MAAM,QAA+B;AAAA,EACjC,aAAa,oBAAI,IAAwC;AAAA,EAElE,GAA2B,MAAS,UAA2C;AAC7E,QAAI,MAAM,KAAK,WAAW,IAAI,IAAI;AAClC,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,WAAK,WAAW,IAAI,MAAM,GAAG;AAAA,IAC/B;AACA,QAAI,IAAI,QAA2B;AACnC,WAAO,MAAM,IAAI,OAAO,QAA2B;AAAA,EACrD;AAAA,EAEA,KAA6B,MAAS,QAAyB;AAC7D,eAAW,YAAY,CAAC,GAAI,KAAK,WAAW,IAAI,IAAI,KAAK,CAAC,CAAE;AAC1D,MAAC,SAAiC,MAAM;AAAA,EAC5C;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ import { AgentAdapter } from "./agent-port.js";
2
+ import { AgentAdapter as AgentAdapter2, CONE } from "./agent-port.js";
3
+ import { assistant, isBusy, queued, textOf, toMessages } from "./messages.js";
4
+ function createAgentModel(connection) {
5
+ return { agent: new AgentAdapter(connection) };
6
+ }
7
+ export {
8
+ AgentAdapter2 as AgentAdapter,
9
+ CONE,
10
+ assistant,
11
+ createAgentModel,
12
+ isBusy,
13
+ queued,
14
+ textOf,
15
+ toMessages
16
+ };
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/spectrum/index.ts"],
4
+ "sourcesContent": ["import type { AgentConnection } from '../client.ts';\nimport { AgentAdapter } from './agent-port.ts';\n\nexport { AgentAdapter, CONE } from './agent-port.ts';\nexport { assistant, isBusy, queued, textOf, toMessages } from './messages.ts';\n\nexport function createAgentModel(connection: AgentConnection): { agent: AgentAdapter } {\n return { agent: new AgentAdapter(connection) };\n}\n"],
5
+ "mappings": "AACA,SAAS,oBAAoB;AAE7B,SAAS,gBAAAA,eAAc,YAAY;AACnC,SAAS,WAAW,QAAQ,QAAQ,QAAQ,kBAAkB;AAEvD,SAAS,iBAAiB,YAAsD;AACrF,SAAO,EAAE,OAAO,IAAI,aAAa,UAAU,EAAE;AAC/C;",
6
+ "names": ["AgentAdapter"]
7
+ }