@ai-ecoverse/slicc-agent 3.43.1 → 3.44.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 (61) hide show
  1. package/README.md +12 -2
  2. package/dist/agent.js +1 -0
  3. package/dist/agent.js.map +2 -2
  4. package/dist/client.js +3 -1
  5. package/dist/client.js.map +2 -2
  6. package/dist/codemode/index.js +2 -1
  7. package/dist/codemode/index.js.map +2 -2
  8. package/dist/control.js +13 -3
  9. package/dist/control.js.map +2 -2
  10. package/dist/freezer/index.js +5 -4
  11. package/dist/freezer/index.js.map +2 -2
  12. package/dist/host.js +13 -2
  13. package/dist/host.js.map +2 -2
  14. package/dist/kernel/env.js +55 -4
  15. package/dist/kernel/env.js.map +2 -2
  16. package/dist/kernel/exec.js +84 -16
  17. package/dist/kernel/exec.js.map +3 -3
  18. package/dist/licks/licks.js +17 -1
  19. package/dist/licks/licks.js.map +2 -2
  20. package/dist/main.js +29 -10
  21. package/dist/main.js.map +2 -2
  22. package/dist/mcp/content.js +3 -1
  23. package/dist/mcp/content.js.map +2 -2
  24. package/dist/mcp/tools.js +2 -1
  25. package/dist/mcp/tools.js.map +2 -2
  26. package/dist/memory/prompt.js +3 -2
  27. package/dist/memory/prompt.js.map +2 -2
  28. package/dist/memory/store.js +5 -3
  29. package/dist/memory/store.js.map +2 -2
  30. package/dist/remote/host.js +21 -1
  31. package/dist/remote/host.js.map +2 -2
  32. package/dist/secrets/extension.js +150 -0
  33. package/dist/secrets/extension.js.map +7 -0
  34. package/dist/secrets/names.js +72 -0
  35. package/dist/secrets/names.js.map +7 -0
  36. package/dist/secrets/scrub.js +36 -0
  37. package/dist/secrets/scrub.js.map +7 -0
  38. package/dist/secrets/vault.js +161 -0
  39. package/dist/secrets/vault.js.map +7 -0
  40. package/dist/services.js.map +2 -2
  41. package/dist/skills/index.js +8 -1
  42. package/dist/skills/index.js.map +2 -2
  43. package/dist/spectrum/agent-port.js +6 -2
  44. package/dist/spectrum/agent-port.js.map +2 -2
  45. package/dist/types/agent.d.ts +1 -0
  46. package/dist/types/client.d.ts +2 -0
  47. package/dist/types/control.d.ts +2 -1
  48. package/dist/types/host.d.ts +9 -1
  49. package/dist/types/kernel/env.d.ts +6 -0
  50. package/dist/types/kernel/exec.d.ts +8 -1
  51. package/dist/types/licks/licks.d.ts +1 -0
  52. package/dist/types/mcp/content.d.ts +1 -1
  53. package/dist/types/remote/host.d.ts +5 -1
  54. package/dist/types/secrets/extension.d.ts +31 -0
  55. package/dist/types/secrets/names.d.ts +24 -0
  56. package/dist/types/secrets/scrub.d.ts +21 -0
  57. package/dist/types/secrets/vault.d.ts +30 -0
  58. package/dist/types/services.d.ts +3 -1
  59. package/dist/types/skills/index.d.ts +1 -0
  60. package/dist/types/spectrum/agent-port.d.ts +2 -0
  61. package/package.json +3 -3
@@ -0,0 +1,150 @@
1
+ import {
2
+ defineExtension,
3
+ GenerationTask,
4
+ hook,
5
+ section,
6
+ ToolTask
7
+ } from "@earendil-works/pi-durable";
8
+ const SECRETS = "slicc-secrets";
9
+ const SECRETS_KIND = "slicc.secrets";
10
+ const SECRETS_COMMAND = "/secrets";
11
+ const SHELL_GRANTS = ["fnox", "exec", "--shell-grants", "--"];
12
+ const MODEL_LINE = "Credentials are env vars (`$NAME`) holding placeholders; `secret list` shows them and where each works. The vault fills in the real value only in the listed header, for the listed domain, method and path, over HTTPS (curl, git, fetch). Adding a rule means editing `/etc/fnox/config.toml` with sudo, which the user approves. `fnox exec` gives real values only for secrets the user enabled for exec, and the secrets listed as 'in every bash' already hold real values in your commands. Everything is logged, and stored values are replaced by their placeholder in everything you see; edit around placeholders, since files show them too. Never ask the user to paste a credential into the chat.";
13
+ function leaves(value, out, set) {
14
+ if (typeof value === "string") {
15
+ out.push({ get: () => value, set });
16
+ return;
17
+ }
18
+ if (Array.isArray(value)) {
19
+ value.forEach((item, at) => {
20
+ leaves(item, out, (next) => {
21
+ value[at] = next;
22
+ });
23
+ });
24
+ return;
25
+ }
26
+ if (value && typeof value === "object")
27
+ for (const [key, item] of Object.entries(value))
28
+ leaves(item, out, (next) => {
29
+ value[key] = next;
30
+ });
31
+ }
32
+ async function scrubResult(result, scrub) {
33
+ const content = [...result.content ?? []];
34
+ const diagnostics = [...result.diagnostics ?? []];
35
+ const details = result.details === void 0 ? void 0 : structuredClone(result.details);
36
+ const found = [];
37
+ content.forEach((item, at) => {
38
+ if (item.type === "text")
39
+ found.push({ get: () => item.text, set: (text) => void (content[at] = { ...item, text }) });
40
+ });
41
+ diagnostics.forEach((item, at) => {
42
+ found.push({
43
+ get: () => item.message,
44
+ set: (message) => void (diagnostics[at] = { ...item, message })
45
+ });
46
+ });
47
+ let replaced = details;
48
+ if (details !== void 0)
49
+ leaves(details, found, (next) => {
50
+ replaced = next;
51
+ });
52
+ const before = found.map((leaf) => leaf.get());
53
+ const after = await scrub.texts(before);
54
+ if (after.every((text, at) => text === before[at])) return void 0;
55
+ found.forEach((leaf, at) => {
56
+ leaf.set(after[at]);
57
+ });
58
+ const kept = result.content;
59
+ if (kept) kept.splice(0, kept.length, ...content);
60
+ return {
61
+ ...result,
62
+ ...kept ? { content: kept } : {},
63
+ ...result.diagnostics ? { diagnostics } : {},
64
+ ...replaced === void 0 ? {} : { details: replaced }
65
+ };
66
+ }
67
+ function where(name) {
68
+ const rules = name.rules.map(
69
+ (rule) => [
70
+ rule.domain,
71
+ rule.header ? `header ${rule.header}` : "",
72
+ rule.methods?.length ? rule.methods.join(" ") : "",
73
+ rule.paths?.length ? rule.paths.join(" ") : ""
74
+ ].filter(Boolean).join(", ")
75
+ );
76
+ const shell = name.exec === "shell" ? ["in every bash"] : [];
77
+ const parts = [...rules, ...shell];
78
+ return parts.length ? `: ${parts.join("; ")}` : "";
79
+ }
80
+ function renderSecrets(names) {
81
+ if (!names.length) return void 0;
82
+ const lines = names.map((name) => `- ${name.name} = ${name.placeholder}${where(name)}`);
83
+ return [MODEL_LINE, "", ...lines].join("\n");
84
+ }
85
+ function listedLine(names) {
86
+ return `The user listed secrets: ${names.length ? names.map((name) => name.name).join(", ") : "none"}.`;
87
+ }
88
+ function secretsEntry(names) {
89
+ return {
90
+ kind: SECRETS_KIND,
91
+ data: { names: structuredClone(names) },
92
+ model: [{ role: "user", content: listedLine(names), timestamp: Date.now() }]
93
+ };
94
+ }
95
+ function shellGrants(names) {
96
+ return (argv) => names.state.value.some((name) => name.exec === "shell") ? [...SHELL_GRANTS, ...argv] : argv;
97
+ }
98
+ function pinned(change) {
99
+ const list = change.extensions;
100
+ if (!Array.isArray(list) || list.some((extension) => extension.name === SECRETS)) return change;
101
+ return { ...change, extensions: [...list, { name: SECRETS }] };
102
+ }
103
+ async function repin(harness, storage, context) {
104
+ let fixed = 0;
105
+ let cursor;
106
+ do {
107
+ const page = await storage.scanConversations({}, 100, cursor, context);
108
+ for (const record of page.items) {
109
+ const conversation = await harness.conversation(record.id, context);
110
+ if (!conversation) continue;
111
+ const agent = await conversation.agent(context);
112
+ if (agent.extensions.some((extension) => extension.name === SECRETS)) continue;
113
+ await conversation.configure(pinned({ extensions: agent.extensions }), context);
114
+ fixed++;
115
+ }
116
+ cursor = page.next;
117
+ } while (cursor);
118
+ return fixed;
119
+ }
120
+ function secretsExtension(deps) {
121
+ return defineExtension({
122
+ name: SECRETS,
123
+ sections: [section("secrets", () => renderSecrets(deps.names.state.value))],
124
+ hooks: [
125
+ hook(ToolTask, { afterTool: (_call, result) => scrubResult(result, deps.vault) }),
126
+ hook(GenerationTask, {
127
+ beforeRequest: async () => {
128
+ await deps.vault.ready();
129
+ return void 0;
130
+ }
131
+ })
132
+ ]
133
+ });
134
+ }
135
+ export {
136
+ MODEL_LINE,
137
+ SECRETS,
138
+ SECRETS_COMMAND,
139
+ SECRETS_KIND,
140
+ SHELL_GRANTS,
141
+ listedLine,
142
+ pinned,
143
+ renderSecrets,
144
+ repin,
145
+ scrubResult,
146
+ secretsEntry,
147
+ secretsExtension,
148
+ shellGrants
149
+ };
150
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/secrets/extension.ts"],
4
+ "sourcesContent": ["import type { Context, JsonValue } from '@earendil-works/chord';\nimport {\n type AgentChange,\n type Cursor,\n defineExtension,\n type Extension,\n GenerationTask,\n type Harness,\n hook,\n type Storage,\n section,\n type ToolExecutionResult,\n ToolTask,\n} from '@earendil-works/pi-durable';\nimport type { Names, SecretName } from './names.ts';\nimport type { Scrub } from './scrub.ts';\nimport type { Vault } from './vault.ts';\n\nexport const SECRETS = 'slicc-secrets';\nexport const SECRETS_KIND = 'slicc.secrets';\nexport const SECRETS_COMMAND = '/secrets';\nexport const SHELL_GRANTS = ['fnox', 'exec', '--shell-grants', '--'] as const;\n\nexport const MODEL_LINE =\n \"Credentials are env vars (`$NAME`) holding placeholders; `secret list` shows them and where each works. The vault fills in the real value only in the listed header, for the listed domain, method and path, over HTTPS (curl, git, fetch). Adding a rule means editing `/etc/fnox/config.toml` with sudo, which the user approves. `fnox exec` gives real values only for secrets the user enabled for exec, and the secrets listed as 'in every bash' already hold real values in your commands. Everything is logged, and stored values are replaced by their placeholder in everything you see; edit around placeholders, since files show them too. Never ask the user to paste a credential into the chat.\";\n\ntype Leaf = { get(): string; set(text: string): void };\n\nfunction leaves(value: JsonValue, out: Leaf[], set: (next: JsonValue) => void): void {\n if (typeof value === 'string') {\n out.push({ get: () => value, set });\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((item, at) => {\n leaves(item, out, (next) => {\n value[at] = next;\n });\n });\n return;\n }\n if (value && typeof value === 'object')\n for (const [key, item] of Object.entries(value))\n leaves(item as JsonValue, out, (next) => {\n (value as Record<string, JsonValue>)[key] = next;\n });\n}\n\nexport async function scrubResult(\n result: ToolExecutionResult,\n scrub: Pick<Scrub, 'texts'>\n): Promise<ToolExecutionResult | undefined> {\n const content = [...(result.content ?? [])];\n const diagnostics = [...(result.diagnostics ?? [])];\n const details =\n result.details === undefined ? undefined : (structuredClone(result.details) as JsonValue);\n const found: Leaf[] = [];\n content.forEach((item, at) => {\n if (item.type === 'text')\n found.push({ get: () => item.text, set: (text) => void (content[at] = { ...item, text }) });\n });\n diagnostics.forEach((item, at) => {\n found.push({\n get: () => item.message,\n set: (message) => void (diagnostics[at] = { ...item, message }),\n });\n });\n let replaced = details;\n if (details !== undefined)\n leaves(details, found, (next) => {\n replaced = next;\n });\n const before = found.map((leaf) => leaf.get());\n const after = await scrub.texts(before);\n if (after.every((text, at) => text === before[at])) return undefined;\n found.forEach((leaf, at) => {\n leaf.set(after[at] as string);\n });\n const kept = result.content as ToolExecutionResult['content'] & unknown[];\n if (kept) kept.splice(0, kept.length, ...content);\n return {\n ...result,\n ...(kept ? { content: kept } : {}),\n ...(result.diagnostics ? { diagnostics } : {}),\n ...(replaced === undefined ? {} : { details: replaced }),\n };\n}\n\nfunction where(name: SecretName): string {\n const rules = name.rules.map((rule) =>\n [\n rule.domain,\n rule.header ? `header ${rule.header}` : '',\n rule.methods?.length ? rule.methods.join(' ') : '',\n rule.paths?.length ? rule.paths.join(' ') : '',\n ]\n .filter(Boolean)\n .join(', ')\n );\n const shell = name.exec === 'shell' ? ['in every bash'] : [];\n const parts = [...rules, ...shell];\n return parts.length ? `: ${parts.join('; ')}` : '';\n}\n\nexport function renderSecrets(names: readonly SecretName[]): string | undefined {\n if (!names.length) return undefined;\n const lines = names.map((name) => `- ${name.name} = ${name.placeholder}${where(name)}`);\n return [MODEL_LINE, '', ...lines].join('\\n');\n}\n\nexport function listedLine(names: readonly SecretName[]): string {\n return `The user listed secrets: ${names.length ? names.map((name) => name.name).join(', ') : 'none'}.`;\n}\n\nexport function secretsEntry(names: readonly SecretName[]) {\n return {\n kind: SECRETS_KIND,\n data: { names: structuredClone(names) as unknown as JsonValue },\n model: [{ role: 'user' as const, content: listedLine(names), timestamp: Date.now() }],\n };\n}\n\nexport function shellGrants(names: Pick<Names, 'state'>) {\n return (argv: readonly string[]): readonly string[] =>\n names.state.value.some((name) => name.exec === 'shell') ? [...SHELL_GRANTS, ...argv] : argv;\n}\n\nexport function pinned<T extends AgentChange>(change: T): T {\n const list = change.extensions;\n if (!Array.isArray(list) || list.some((extension) => extension.name === SECRETS)) return change;\n return { ...change, extensions: [...list, { name: SECRETS }] };\n}\n\nexport async function repin(\n harness: Pick<Harness, 'conversation'>,\n storage: Pick<Storage, 'scanConversations'>,\n context: Context\n): Promise<number> {\n let fixed = 0;\n let cursor: Cursor | undefined;\n do {\n const page = await storage.scanConversations({}, 100, cursor, context);\n for (const record of page.items) {\n const conversation = await harness.conversation(record.id, context);\n if (!conversation) continue;\n const agent = await conversation.agent(context);\n if (agent.extensions.some((extension) => extension.name === SECRETS)) continue;\n await conversation.configure(pinned({ extensions: agent.extensions }), context);\n fixed++;\n }\n cursor = page.next;\n } while (cursor);\n return fixed;\n}\n\nexport function secretsExtension(deps: {\n names: Pick<Names, 'state'>;\n vault: Pick<Vault, 'ready' | 'texts'>;\n}): Extension {\n return defineExtension({\n name: SECRETS,\n sections: [section('secrets', () => renderSecrets(deps.names.state.value))],\n hooks: [\n hook(ToolTask, { afterTool: (_call, result) => scrubResult(result, deps.vault) }),\n hook(GenerationTask, {\n beforeRequest: async () => {\n await deps.vault.ready();\n return undefined;\n },\n }),\n ],\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,72 @@
1
+ import { replicatedState } from "@earendil-works/chord";
2
+ const LIST = "secret list --json";
3
+ function strings(value) {
4
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : void 0;
5
+ }
6
+ function ruleOf(value) {
7
+ const rule = value;
8
+ if (typeof rule?.domain !== "string") return [];
9
+ const methods = strings(rule.methods);
10
+ const paths = strings(rule.paths);
11
+ return [
12
+ {
13
+ domain: rule.domain,
14
+ ...typeof rule.header === "string" ? { header: rule.header } : {},
15
+ ...methods ? { methods } : {},
16
+ ...paths ? { paths } : {}
17
+ }
18
+ ];
19
+ }
20
+ const EXEC = /* @__PURE__ */ new Set(["any", "argv", "shell"]);
21
+ function parseNames(text) {
22
+ let parsed;
23
+ try {
24
+ parsed = JSON.parse(text);
25
+ } catch {
26
+ return [];
27
+ }
28
+ const list = parsed?.secrets;
29
+ if (!Array.isArray(list)) return [];
30
+ return list.flatMap((item) => {
31
+ if (typeof item?.name !== "string" || typeof item.placeholder !== "string") return [];
32
+ const exec = EXEC.has(item.exec) ? item.exec : false;
33
+ const rules = Array.isArray(item.rules) ? item.rules.flatMap(ruleOf) : [];
34
+ return [{ name: item.name, placeholder: item.placeholder, exec, rules }];
35
+ });
36
+ }
37
+ async function listNames(env, context) {
38
+ let out = "";
39
+ const ran = await env.exec(
40
+ LIST,
41
+ {
42
+ onOutput: (text, _using, info) => {
43
+ if (info?.stream !== "stderr") out += text;
44
+ }
45
+ },
46
+ context
47
+ ).catch(() => void 0);
48
+ return ran?.ok && ran.value.exitCode === 0 ? parseNames(out) : [];
49
+ }
50
+ function secretNames(env, vault) {
51
+ const state = replicatedState([]);
52
+ let version = vault.state.value.version;
53
+ let lock = Promise.resolve();
54
+ const refresh = (context) => {
55
+ const run = lock.then(async () => state.replace(context, await listNames(env, context)));
56
+ lock = run.catch(() => void 0);
57
+ return run;
58
+ };
59
+ const off = vault.state.subscribe((value, context) => {
60
+ if (value.version === version) return;
61
+ version = value.version;
62
+ void refresh(context).catch(() => void 0);
63
+ });
64
+ return { state, refresh, close: off };
65
+ }
66
+ export {
67
+ LIST,
68
+ listNames,
69
+ parseNames,
70
+ secretNames
71
+ };
72
+ //# sourceMappingURL=names.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/secrets/names.ts"],
4
+ "sourcesContent": ["import { type Context, type MutableReplicatedState, replicatedState } from '@earendil-works/chord';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport type { Vault } from './vault.ts';\n\nexport interface SecretRule {\n domain: string;\n header?: string;\n methods?: string[];\n paths?: string[];\n}\n\nexport interface SecretName {\n name: string;\n placeholder: string;\n exec: false | 'any' | 'argv' | 'shell';\n rules: SecretRule[];\n}\n\nexport const LIST = 'secret list --json';\n\nfunction strings(value: unknown): string[] | undefined {\n return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : undefined;\n}\n\nfunction ruleOf(value: unknown): SecretRule[] {\n const rule = value as Record<string, unknown> | null;\n if (typeof rule?.domain !== 'string') return [];\n const methods = strings(rule.methods);\n const paths = strings(rule.paths);\n return [\n {\n domain: rule.domain,\n ...(typeof rule.header === 'string' ? { header: rule.header } : {}),\n ...(methods ? { methods } : {}),\n ...(paths ? { paths } : {}),\n },\n ];\n}\n\nconst EXEC = new Set(['any', 'argv', 'shell']);\n\nexport function parseNames(text: string): SecretName[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return [];\n }\n const list = (parsed as { secrets?: unknown } | null)?.secrets;\n if (!Array.isArray(list)) return [];\n return list.flatMap((item: Record<string, unknown> | null) => {\n if (typeof item?.name !== 'string' || typeof item.placeholder !== 'string') return [];\n const exec = EXEC.has(item.exec as string) ? (item.exec as SecretName['exec']) : false;\n const rules = Array.isArray(item.rules) ? item.rules.flatMap(ruleOf) : [];\n return [{ name: item.name, placeholder: item.placeholder, exec, rules }];\n });\n}\n\nexport async function listNames(env: ExecutionEnv, context: Context): Promise<SecretName[]> {\n let out = '';\n const ran = await env\n .exec(\n LIST,\n {\n onOutput: (text, _using, info) => {\n if (info?.stream !== 'stderr') out += text;\n },\n },\n context\n )\n .catch(() => undefined);\n return ran?.ok && ran.value.exitCode === 0 ? parseNames(out) : [];\n}\n\nexport interface Names {\n readonly state: MutableReplicatedState<SecretName[]>;\n refresh(context: Context): Promise<void>;\n close(): void;\n}\n\nexport function secretNames(env: ExecutionEnv, vault: Pick<Vault, 'state'>): Names {\n const state = replicatedState<SecretName[]>([]);\n let version = vault.state.value.version;\n let lock: Promise<unknown> = Promise.resolve();\n const refresh = (context: Context) => {\n const run = lock.then(async () => state.replace(context, await listNames(env, context)));\n lock = run.catch(() => undefined);\n return run;\n };\n const off = vault.state.subscribe((value, context) => {\n if (value.version === version) return;\n version = value.version;\n void refresh(context).catch(() => undefined);\n });\n return { state, refresh, close: off };\n}\n"],
5
+ "mappings": "AAAA,SAAoD,uBAAuB;AAkBpE,MAAM,OAAO;AAEpB,SAAS,QAAQ,OAAsC;AACrD,SAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,OAAO,CAAC,SAAS,OAAO,SAAS,QAAQ,IAAI;AACnF;AAEA,SAAS,OAAO,OAA8B;AAC5C,QAAM,OAAO;AACb,MAAI,OAAO,MAAM,WAAW,SAAU,QAAO,CAAC;AAC9C,QAAM,UAAU,QAAQ,KAAK,OAAO;AACpC,QAAM,QAAQ,QAAQ,KAAK,KAAK;AAChC,SAAO;AAAA,IACL;AAAA,MACE,QAAQ,KAAK;AAAA,MACb,GAAI,OAAO,KAAK,WAAW,WAAW,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,MACjE,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC7B,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,MAAM,OAAO,oBAAI,IAAI,CAAC,OAAO,QAAQ,OAAO,CAAC;AAEtC,SAAS,WAAW,MAA4B;AACrD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,OAAQ,QAAyC;AACvD,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC;AAClC,SAAO,KAAK,QAAQ,CAAC,SAAyC;AAC5D,QAAI,OAAO,MAAM,SAAS,YAAY,OAAO,KAAK,gBAAgB,SAAU,QAAO,CAAC;AACpF,UAAM,OAAO,KAAK,IAAI,KAAK,IAAc,IAAK,KAAK,OAA8B;AACjF,UAAM,QAAQ,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,MAAM,QAAQ,MAAM,IAAI,CAAC;AACxE,WAAO,CAAC,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,MAAM,CAAC;AAAA,EACzE,CAAC;AACH;AAEA,eAAsB,UAAU,KAAmB,SAAyC;AAC1F,MAAI,MAAM;AACV,QAAM,MAAM,MAAM,IACf;AAAA,IACC;AAAA,IACA;AAAA,MACE,UAAU,CAAC,MAAM,QAAQ,SAAS;AAChC,YAAI,MAAM,WAAW,SAAU,QAAO;AAAA,MACxC;AAAA,IACF;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AACxB,SAAO,KAAK,MAAM,IAAI,MAAM,aAAa,IAAI,WAAW,GAAG,IAAI,CAAC;AAClE;AAQO,SAAS,YAAY,KAAmB,OAAoC;AACjF,QAAM,QAAQ,gBAA8B,CAAC,CAAC;AAC9C,MAAI,UAAU,MAAM,MAAM,MAAM;AAChC,MAAI,OAAyB,QAAQ,QAAQ;AAC7C,QAAM,UAAU,CAAC,YAAqB;AACpC,UAAM,MAAM,KAAK,KAAK,YAAY,MAAM,QAAQ,SAAS,MAAM,UAAU,KAAK,OAAO,CAAC,CAAC;AACvF,WAAO,IAAI,MAAM,MAAM,MAAS;AAChC,WAAO;AAAA,EACT;AACA,QAAM,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,YAAY;AACpD,QAAI,MAAM,YAAY,QAAS;AAC/B,cAAU,MAAM;AAChB,SAAK,QAAQ,OAAO,EAAE,MAAM,MAAM,MAAS;AAAA,EAC7C,CAAC;AACD,SAAO,EAAE,OAAO,SAAS,OAAO,IAAI;AACtC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,36 @@
1
+ const EMPTY = new Uint8Array();
2
+ const IDENTITY = {
3
+ texts: async (texts) => [...texts],
4
+ stream: () => ({ push: async (chunk) => chunk, end: async () => EMPTY, drop: () => void 0 })
5
+ };
6
+ let current = IDENTITY;
7
+ function scrubWith(next) {
8
+ current = next;
9
+ }
10
+ function scrubTexts(texts) {
11
+ return texts.length ? current.texts(texts) : Promise.resolve([]);
12
+ }
13
+ async function scrubText(text) {
14
+ return (await scrubTexts([text]))[0];
15
+ }
16
+ async function scrubItems(items) {
17
+ const scrubbed = await scrubTexts(items.map((item) => item.text ?? ""));
18
+ return items.map((item, at) => item.type === "text" ? { ...item, text: scrubbed[at] } : item);
19
+ }
20
+ async function scrubbedOutput(api, text) {
21
+ try {
22
+ api.output(await scrubText(text));
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+ export {
29
+ IDENTITY,
30
+ scrubItems,
31
+ scrubText,
32
+ scrubTexts,
33
+ scrubWith,
34
+ scrubbedOutput
35
+ };
36
+ //# sourceMappingURL=scrub.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/secrets/scrub.ts"],
4
+ "sourcesContent": ["export type Scrubbable = string | Uint8Array;\n\nexport interface ScrubStream {\n push(chunk: Uint8Array): Promise<Uint8Array>;\n end(): Promise<Uint8Array>;\n drop(): void;\n}\n\nexport interface Scrub {\n texts<T extends Scrubbable>(texts: readonly T[]): Promise<T[]>;\n stream(): ScrubStream;\n}\n\nconst EMPTY = new Uint8Array();\n\nexport const IDENTITY: Scrub = {\n texts: async (texts) => [...texts],\n stream: () => ({ push: async (chunk) => chunk, end: async () => EMPTY, drop: () => undefined }),\n};\n\nlet current: Scrub = IDENTITY;\n\nexport function scrubWith(next: Scrub): void {\n current = next;\n}\n\nexport function scrubTexts<T extends Scrubbable>(texts: readonly T[]): Promise<T[]> {\n return texts.length ? current.texts(texts) : Promise.resolve([]);\n}\n\nexport async function scrubText(text: string): Promise<string> {\n return (await scrubTexts([text]))[0] as string;\n}\n\nexport async function scrubItems<T extends { type: string; text?: string }>(\n items: readonly T[]\n): Promise<T[]> {\n const scrubbed = await scrubTexts(items.map((item) => item.text ?? ''));\n return items.map((item, at) => (item.type === 'text' ? { ...item, text: scrubbed[at] } : item));\n}\n\nexport async function scrubbedOutput(\n api: { output(text: string): unknown },\n text: string\n): Promise<boolean> {\n try {\n api.output(await scrubText(text));\n return true;\n } catch {\n return false;\n }\n}\n"],
5
+ "mappings": "AAaA,MAAM,QAAQ,IAAI,WAAW;AAEtB,MAAM,WAAkB;AAAA,EAC7B,OAAO,OAAO,UAAU,CAAC,GAAG,KAAK;AAAA,EACjC,QAAQ,OAAO,EAAE,MAAM,OAAO,UAAU,OAAO,KAAK,YAAY,OAAO,MAAM,MAAM,OAAU;AAC/F;AAEA,IAAI,UAAiB;AAEd,SAAS,UAAU,MAAmB;AAC3C,YAAU;AACZ;AAEO,SAAS,WAAiC,OAAmC;AAClF,SAAO,MAAM,SAAS,QAAQ,MAAM,KAAK,IAAI,QAAQ,QAAQ,CAAC,CAAC;AACjE;AAEA,eAAsB,UAAU,MAA+B;AAC7D,UAAQ,MAAM,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;AACrC;AAEA,eAAsB,WACpB,OACc;AACd,QAAM,WAAW,MAAM,WAAW,MAAM,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;AACtE,SAAO,MAAM,IAAI,CAAC,MAAM,OAAQ,KAAK,SAAS,SAAS,EAAE,GAAG,MAAM,MAAM,SAAS,EAAE,EAAE,IAAI,IAAK;AAChG;AAEA,eAAsB,eACpB,KACA,MACkB;AAClB,MAAI;AACF,QAAI,OAAO,MAAM,UAAU,IAAI,CAAC;AAChC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,161 @@
1
+ import { replicatedState } from "@earendil-works/chord";
2
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3
+ const WAITING_MS = 2e3;
4
+ const WAITING = "waiting for the vault";
5
+ const LOCKS = ["no", "scrubbing", "passkey"];
6
+ const EMPTY = new Uint8Array();
7
+ function pairOf(value) {
8
+ const pair = value;
9
+ if (pair === null) return { needed: false, version: 0 };
10
+ if (typeof pair?.needed !== "boolean" || typeof pair.version !== "number") return void 0;
11
+ return { needed: pair.needed, version: pair.version };
12
+ }
13
+ class Vault {
14
+ state;
15
+ #pending = /* @__PURE__ */ new Map();
16
+ #streams = /* @__PURE__ */ new Set();
17
+ #waiters = /* @__PURE__ */ new Set();
18
+ #now;
19
+ #port;
20
+ #pair = { needed: false, version: 0 };
21
+ #locked = "no";
22
+ #seen = 0;
23
+ #serial = 0;
24
+ #streamSerial = 0;
25
+ constructor(now = Date.now) {
26
+ this.#now = now;
27
+ this.state = replicatedState(this.#snapshot());
28
+ }
29
+ get needed() {
30
+ return this.#pair.needed || this.#seen > this.#pair.version;
31
+ }
32
+ connect(port, scrub) {
33
+ const pair = scrub === void 0 ? void 0 : pairOf(scrub);
34
+ if (pair) this.#pair = pair;
35
+ if (port && port !== this.#port) {
36
+ if (this.#port) this.#port.onmessage = null;
37
+ this.#port = port;
38
+ port.onmessage = (event) => this.#answer(event.data);
39
+ port.start?.();
40
+ const resend = [...this.#pending.values()];
41
+ const opening = new Set(
42
+ resend.filter((item) => item.message.op === "redactStream").map((item) => item.message.stream)
43
+ );
44
+ for (const stream of this.#streams)
45
+ if (!opening.has(stream)) void this.#call("redactStream", { stream });
46
+ for (const item of resend) port.postMessage(item.message);
47
+ }
48
+ this.#refresh();
49
+ }
50
+ ready() {
51
+ if (!this.#holding()) return Promise.resolve();
52
+ return new Promise((resolve) => this.#waiters.add(resolve));
53
+ }
54
+ async texts(texts) {
55
+ if (!this.needed) return [...texts];
56
+ const answer = await this.#call("redact", { texts: [...texts] });
57
+ return answer.texts;
58
+ }
59
+ stream() {
60
+ let stream;
61
+ return {
62
+ push: (chunk) => {
63
+ if (stream === void 0 && !this.needed) return Promise.resolve(chunk);
64
+ if (stream === void 0) {
65
+ stream = ++this.#streamSerial;
66
+ this.#streams.add(stream);
67
+ void this.#call("redactStream", { stream });
68
+ }
69
+ return this.#call("push", { stream, chunk });
70
+ },
71
+ end: async () => {
72
+ if (stream === void 0) return EMPTY;
73
+ const tail = await this.#call("end", { stream });
74
+ this.#streams.delete(stream);
75
+ return tail;
76
+ },
77
+ drop: () => {
78
+ if (stream === void 0) return;
79
+ const dropped = stream;
80
+ this.#streams.delete(dropped);
81
+ for (const [id, item] of this.#pending)
82
+ if (item.message.stream === dropped && item.message.op !== "redactStream") {
83
+ this.#pending.delete(id);
84
+ item.resolve(EMPTY);
85
+ }
86
+ void this.#call("drop", { stream: dropped });
87
+ }
88
+ };
89
+ }
90
+ #call(op, fields) {
91
+ const message = { id: ++this.#serial, op, ...fields };
92
+ const result = new Promise((resolve, reject) => {
93
+ this.#pending.set(message.id, { message, at: this.#now(), resolve, reject });
94
+ });
95
+ this.#port?.postMessage(message);
96
+ setTimeout(() => this.#refresh(), WAITING_MS);
97
+ this.#refresh();
98
+ return result;
99
+ }
100
+ #answer(data) {
101
+ if (!data || typeof data !== "object") return;
102
+ const pair = "scrub" in data ? pairOf(data.scrub) : void 0;
103
+ if (pair) this.#pair = pair;
104
+ if (LOCKS.includes(data.locked)) this.#locked = data.locked;
105
+ const item = typeof data.id === "number" ? this.#pending.get(data.id) : void 0;
106
+ if (item) {
107
+ this.#pending.delete(data.id);
108
+ const version = data.ok?.version;
109
+ if (typeof version === "number") this.#seen = Math.max(this.#seen, version);
110
+ if (data.error) item.reject(new Error(data.error.message ?? "the vault refused"));
111
+ else item.resolve(data.ok);
112
+ }
113
+ this.#refresh();
114
+ }
115
+ #waiting() {
116
+ const now = this.#now();
117
+ return [...this.#pending.values()].some((item) => now - item.at >= WAITING_MS);
118
+ }
119
+ #holding() {
120
+ return this.needed && (!this.#port || this.#waiting() || this.#locked === "passkey");
121
+ }
122
+ #snapshot() {
123
+ return {
124
+ port: !this.#port ? "none" : this.#waiting() ? "waiting" : "ready",
125
+ needed: this.needed,
126
+ version: this.#pair.version,
127
+ locked: this.#locked,
128
+ held: this.#pending.size
129
+ };
130
+ }
131
+ #refresh() {
132
+ const next = this.#snapshot();
133
+ const before = this.state.value;
134
+ if (Object.entries(next).some(([key, value]) => before[key] !== value))
135
+ this.state.replace(BACKGROUND_CONTEXT, next);
136
+ if (this.#holding()) return;
137
+ for (const resolve of this.#waiters) resolve();
138
+ this.#waiters.clear();
139
+ }
140
+ }
141
+ function holdText(state) {
142
+ if (!state.needed) return void 0;
143
+ if (state.locked === "passkey") return "unlock the vault to continue";
144
+ return state.port === "ready" ? void 0 : WAITING;
145
+ }
146
+ function listenVault(scope, vault) {
147
+ scope.addEventListener("message", ({ data }) => {
148
+ const message = data;
149
+ if (!message || typeof message !== "object" || !("vault" in message || "scrub" in message))
150
+ return;
151
+ vault.connect(message.vault, message.scrub);
152
+ });
153
+ }
154
+ export {
155
+ Vault,
156
+ WAITING,
157
+ WAITING_MS,
158
+ holdText,
159
+ listenVault
160
+ };
161
+ //# sourceMappingURL=vault.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/secrets/vault.ts"],
4
+ "sourcesContent": ["import { type MutableReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Scrub, Scrubbable, ScrubStream } from './scrub.ts';\n\nexport type ScrubPair = { needed: boolean; version: number };\nexport type Locked = 'no' | 'scrubbing' | 'passkey';\n\nexport interface VaultState {\n port: 'none' | 'ready' | 'waiting';\n needed: boolean;\n version: number;\n locked: Locked;\n held: number;\n}\n\nexport const WAITING_MS = 2000;\nexport const WAITING = 'waiting for the vault';\n\ntype Message = Record<string, unknown> & { id: number; op: string };\n\ninterface Pending {\n message: Message;\n at: number;\n resolve(value: unknown): void;\n reject(error: Error): void;\n}\n\ntype Answer = {\n id?: unknown;\n ok?: unknown;\n error?: { message?: string };\n scrub?: unknown;\n locked?: unknown;\n};\n\nconst LOCKS: readonly unknown[] = ['no', 'scrubbing', 'passkey'];\n\nconst EMPTY = new Uint8Array();\n\nfunction pairOf(value: unknown): ScrubPair | undefined {\n const pair = value as Partial<ScrubPair> | null;\n if (pair === null) return { needed: false, version: 0 };\n if (typeof pair?.needed !== 'boolean' || typeof pair.version !== 'number') return undefined;\n return { needed: pair.needed, version: pair.version };\n}\n\nexport class Vault implements Scrub {\n readonly state: MutableReplicatedState<VaultState>;\n readonly #pending = new Map<number, Pending>();\n readonly #streams = new Set<number>();\n readonly #waiters = new Set<() => void>();\n readonly #now: () => number;\n #port: MessagePort | undefined;\n #pair: ScrubPair = { needed: false, version: 0 };\n #locked: Locked = 'no';\n #seen = 0;\n #serial = 0;\n #streamSerial = 0;\n\n constructor(now: () => number = Date.now) {\n this.#now = now;\n this.state = replicatedState<VaultState>(this.#snapshot());\n }\n\n get needed(): boolean {\n return this.#pair.needed || this.#seen > this.#pair.version;\n }\n\n connect(port: MessagePort | undefined, scrub: unknown): void {\n const pair = scrub === undefined ? undefined : pairOf(scrub);\n if (pair) this.#pair = pair;\n if (port && port !== this.#port) {\n if (this.#port) this.#port.onmessage = null;\n this.#port = port;\n port.onmessage = (event) => this.#answer(event.data as Answer);\n port.start?.();\n const resend = [...this.#pending.values()];\n const opening = new Set(\n resend\n .filter((item) => item.message.op === 'redactStream')\n .map((item) => item.message.stream)\n );\n for (const stream of this.#streams)\n if (!opening.has(stream)) void this.#call('redactStream', { stream });\n for (const item of resend) port.postMessage(item.message);\n }\n this.#refresh();\n }\n\n ready(): Promise<void> {\n if (!this.#holding()) return Promise.resolve();\n return new Promise((resolve) => this.#waiters.add(resolve));\n }\n\n async texts<T extends Scrubbable>(texts: readonly T[]): Promise<T[]> {\n if (!this.needed) return [...texts];\n const answer = (await this.#call('redact', { texts: [...texts] })) as {\n texts: T[];\n version?: number;\n };\n return answer.texts;\n }\n\n stream(): ScrubStream {\n let stream: number | undefined;\n return {\n push: (chunk) => {\n if (stream === undefined && !this.needed) return Promise.resolve(chunk);\n if (stream === undefined) {\n stream = ++this.#streamSerial;\n this.#streams.add(stream);\n void this.#call('redactStream', { stream });\n }\n return this.#call('push', { stream, chunk }) as Promise<Uint8Array>;\n },\n end: async () => {\n if (stream === undefined) return EMPTY;\n const tail = (await this.#call('end', { stream })) as Uint8Array;\n this.#streams.delete(stream);\n return tail;\n },\n drop: () => {\n if (stream === undefined) return;\n const dropped = stream;\n this.#streams.delete(dropped);\n for (const [id, item] of this.#pending)\n if (item.message.stream === dropped && item.message.op !== 'redactStream') {\n this.#pending.delete(id);\n item.resolve(EMPTY);\n }\n void this.#call('drop', { stream: dropped });\n },\n };\n }\n\n #call(op: string, fields: Record<string, unknown>): Promise<unknown> {\n const message: Message = { id: ++this.#serial, op, ...fields };\n const result = new Promise((resolve, reject) => {\n this.#pending.set(message.id, { message, at: this.#now(), resolve, reject });\n });\n this.#port?.postMessage(message);\n setTimeout(() => this.#refresh(), WAITING_MS);\n this.#refresh();\n return result;\n }\n\n #answer(data: Answer): void {\n if (!data || typeof data !== 'object') return;\n const pair = 'scrub' in data ? pairOf(data.scrub) : undefined;\n if (pair) this.#pair = pair;\n if (LOCKS.includes(data.locked)) this.#locked = data.locked as Locked;\n const item = typeof data.id === 'number' ? this.#pending.get(data.id) : undefined;\n if (item) {\n this.#pending.delete(data.id as number);\n const version = (data.ok as { version?: unknown } | undefined)?.version;\n if (typeof version === 'number') this.#seen = Math.max(this.#seen, version);\n if (data.error) item.reject(new Error(data.error.message ?? 'the vault refused'));\n else item.resolve(data.ok);\n }\n this.#refresh();\n }\n\n #waiting(): boolean {\n const now = this.#now();\n return [...this.#pending.values()].some((item) => now - item.at >= WAITING_MS);\n }\n\n #holding(): boolean {\n return this.needed && (!this.#port || this.#waiting() || this.#locked === 'passkey');\n }\n\n #snapshot(): VaultState {\n return {\n port: !this.#port ? 'none' : this.#waiting() ? 'waiting' : 'ready',\n needed: this.needed,\n version: this.#pair.version,\n locked: this.#locked,\n held: this.#pending.size,\n };\n }\n\n #refresh(): void {\n const next = this.#snapshot();\n const before = this.state.value;\n if (Object.entries(next).some(([key, value]) => before[key as keyof VaultState] !== value))\n this.state.replace(BACKGROUND_CONTEXT, next);\n if (this.#holding()) return;\n for (const resolve of this.#waiters) resolve();\n this.#waiters.clear();\n }\n}\n\nexport function holdText(state: VaultState): string | undefined {\n if (!state.needed) return undefined;\n if (state.locked === 'passkey') return 'unlock the vault to continue';\n return state.port === 'ready' ? undefined : WAITING;\n}\n\nexport function listenVault(\n scope: { addEventListener(type: 'message', listener: (event: MessageEvent) => void): void },\n vault: Vault\n): void {\n scope.addEventListener('message', ({ data }) => {\n const message = data as { vault?: MessagePort; scrub?: unknown } | null;\n if (!message || typeof message !== 'object' || !('vault' in message || 'scrub' in message))\n return;\n vault.connect(message.vault, message.scrub);\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/services.ts"],
4
- "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: 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 compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n monitorOpen(lease: string | null, resync: boolean, context: Context): Promise<MonitorLease>;\n monitorClose(lease: string, context: Context): Promise<void>;\n}\n\nexport interface MonitorLease {\n lease: string;\n ttlMs: number;\n}\n\nexport interface MonitorAgent {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n model: string | null;\n running: boolean;\n queued: number;\n tool: string | null;\n retry: { attempt: number; since: number; error: string } | null;\n failed: string | null;\n context: number | null;\n}\n\nexport interface MonitorTask {\n id: string;\n kind: string;\n detail: string | null;\n agentId: string | null;\n owner: string | null;\n status: 'pending' | 'running' | 'waiting' | 'completing';\n phase: string;\n background: boolean;\n aborting: number | null;\n since: number;\n waits: number;\n}\n\nexport interface MonitorProcess {\n pid: number;\n ppid: number;\n pgid: number;\n user: string | null;\n agentId: string | null;\n command: string;\n state: 'S' | 'Z';\n started: number;\n zombie: number | null;\n tty: boolean;\n rss: number | null;\n}\n\nexport interface MonitorProblem {\n id: string;\n at: number;\n agentId: string | null;\n kind: 'task' | 'retry' | 'turn';\n message: string;\n}\n\nexport interface MonitorFeed {\n open: boolean;\n at: number;\n usage: { tokens: number; cost: number; free: boolean } | null;\n agents: MonitorAgent[];\n tasks: MonitorTask[];\n processes: MonitorProcess[] | null;\n problems: MonitorProblem[];\n paused: boolean;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n readonly monitor: ReplicatedState<MonitorFeed>;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill';\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 AgentRemoteService = defineService<AgentRemote>('slicc.agent.remote');\n\nexport const AgentPagesService = defineService<AgentPages>('slicc.agent.pages');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected' | 'expired';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n group?: 'providers' | 'mcp';\n secret?: 'api-key' | 'token';\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface McpSignIn {\n url: string;\n state: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
- "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AAiNA,MAAM,eAAe,cAA4B,qBAAqB;AAwBtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,qBAAqB,cAA2B,oBAAoB;AAE1E,MAAM,oBAAoB,cAA0B,mBAAmB;AAEvE,MAAM,gBAAgB;AA2DtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
4
+ "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: 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 compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n monitorOpen(lease: string | null, resync: boolean, context: Context): Promise<MonitorLease>;\n monitorClose(lease: string, context: Context): Promise<void>;\n}\n\nexport interface MonitorLease {\n lease: string;\n ttlMs: number;\n}\n\nexport interface MonitorAgent {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n model: string | null;\n running: boolean;\n queued: number;\n tool: string | null;\n retry: { attempt: number; since: number; error: string } | null;\n failed: string | null;\n context: number | null;\n}\n\nexport interface MonitorTask {\n id: string;\n kind: string;\n detail: string | null;\n agentId: string | null;\n owner: string | null;\n status: 'pending' | 'running' | 'waiting' | 'completing';\n phase: string;\n background: boolean;\n aborting: number | null;\n since: number;\n waits: number;\n}\n\nexport interface MonitorProcess {\n pid: number;\n ppid: number;\n pgid: number;\n user: string | null;\n agentId: string | null;\n command: string;\n state: 'S' | 'Z';\n started: number;\n zombie: number | null;\n tty: boolean;\n rss: number | null;\n}\n\nexport interface MonitorProblem {\n id: string;\n at: number;\n agentId: string | null;\n kind: 'task' | 'retry' | 'turn';\n message: string;\n}\n\nexport interface MonitorFeed {\n open: boolean;\n at: number;\n usage: { tokens: number; cost: number; free: boolean } | null;\n agents: MonitorAgent[];\n tasks: MonitorTask[];\n processes: MonitorProcess[] | null;\n problems: MonitorProblem[];\n paused: boolean;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n readonly monitor: ReplicatedState<MonitorFeed>;\n readonly vault: ReplicatedState<VaultState>;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill' | 'action';\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 AgentRemoteService = defineService<AgentRemote>('slicc.agent.remote');\n\nexport const AgentPagesService = defineService<AgentPages>('slicc.agent.pages');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected' | 'expired';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n group?: 'providers' | 'mcp';\n secret?: 'api-key' | 'token';\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface McpSignIn {\n url: string;\n state: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
+ "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AAkNA,MAAM,eAAe,cAA4B,qBAAqB;AAyBtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,qBAAqB,cAA2B,oBAAoB;AAE1E,MAAM,oBAAoB,cAA0B,mBAAmB;AAEvE,MAAM,gBAAgB;AA2DtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
6
6
  "names": []
7
7
  }
@@ -73,6 +73,11 @@ function problemLick(problem, home) {
73
73
  ${problem.message}`
74
74
  };
75
75
  }
76
+ const SECRETS_ACTION = {
77
+ name: "secrets",
78
+ description: "List the secrets the agent can use, and where each works",
79
+ kind: "action"
80
+ };
76
81
  function commandsOf(skills, templates) {
77
82
  return [
78
83
  ...templates.map((template) => ({
@@ -84,7 +89,8 @@ function commandsOf(skills, templates) {
84
89
  name: `skill:${skill.name}`,
85
90
  description: skill.description,
86
91
  kind: "skill"
87
- }))
92
+ })),
93
+ SECRETS_ACTION
88
94
  ];
89
95
  }
90
96
  function setupSkills(registry, licks) {
@@ -173,6 +179,7 @@ export {
173
179
  GLOBAL_DIR,
174
180
  PROMPTS_DIR,
175
181
  RELOAD_MS,
182
+ SECRETS_ACTION,
176
183
  SKILLS_DIR,
177
184
  commandsOf,
178
185
  piResources,