@ai-ecoverse/slicc-agent 3.52.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -13
- package/dist/client.js +3 -1
- package/dist/client.js.map +2 -2
- package/dist/control.js +4 -1
- package/dist/control.js.map +2 -2
- package/dist/freezer/index.js +27 -68
- package/dist/freezer/index.js.map +2 -2
- package/dist/host.js +2 -0
- package/dist/host.js.map +2 -2
- package/dist/index.js +0 -3
- package/dist/index.js.map +2 -2
- package/dist/kernel/agent-users.js +3 -0
- package/dist/kernel/agent-users.js.map +2 -2
- package/dist/main.js +111 -26
- package/dist/main.js.map +2 -2
- package/dist/mcp/accounts.js +18 -14
- package/dist/mcp/accounts.js.map +2 -2
- package/dist/mcp/connection.js +0 -2
- package/dist/mcp/connection.js.map +2 -2
- package/dist/mcp/credentials.js +6 -15
- package/dist/mcp/credentials.js.map +2 -2
- package/dist/mcp/index.js +0 -1
- package/dist/mcp/index.js.map +2 -2
- package/dist/mcp/oauth.js +15 -20
- package/dist/mcp/oauth.js.map +2 -2
- package/dist/mcp/runtime.js +36 -14
- package/dist/mcp/runtime.js.map +2 -2
- package/dist/mcp/tools.js +4 -6
- package/dist/mcp/tools.js.map +2 -2
- package/dist/models/index.js +7 -4
- package/dist/models/index.js.map +2 -2
- package/dist/monitor/index.js +5 -10
- package/dist/monitor/index.js.map +2 -2
- package/dist/monitor/redact.js +4 -5
- package/dist/monitor/redact.js.map +2 -2
- package/dist/secrets/credentials.js +32 -0
- package/dist/secrets/credentials.js.map +7 -0
- package/dist/secrets/endpoints.js +111 -0
- package/dist/secrets/endpoints.js.map +7 -0
- package/dist/secrets/extension.js +6 -3
- package/dist/secrets/extension.js.map +2 -2
- package/dist/secrets/fetch.js +160 -0
- package/dist/secrets/fetch.js.map +7 -0
- package/dist/secrets/records.js +35 -0
- package/dist/secrets/records.js.map +7 -0
- package/dist/secrets/store.js +50 -0
- package/dist/secrets/store.js.map +7 -0
- package/dist/secrets/vault.js +58 -13
- package/dist/secrets/vault.js.map +2 -2
- package/dist/services.js.map +2 -2
- package/dist/settings.js +85 -23
- package/dist/settings.js.map +2 -2
- package/dist/skills/index.js +20 -3
- package/dist/skills/index.js.map +3 -3
- package/dist/spectrum/agent-port.js +29 -1
- package/dist/spectrum/agent-port.js.map +2 -2
- package/dist/spectrum/settings-port.js +24 -6
- package/dist/spectrum/settings-port.js.map +2 -2
- package/dist/titles/extension.js +110 -0
- package/dist/titles/extension.js.map +7 -0
- package/dist/{freezer/title.js → titles/generate.js} +21 -2
- package/dist/titles/generate.js.map +7 -0
- package/dist/types/client.d.ts +2 -1
- package/dist/types/control.d.ts +1 -1
- package/dist/types/freezer/index.d.ts +2 -5
- package/dist/types/host.d.ts +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/main.d.ts +7 -2
- package/dist/types/mcp/accounts.d.ts +10 -4
- package/dist/types/mcp/connection.d.ts +0 -2
- package/dist/types/mcp/credentials.d.ts +6 -4
- package/dist/types/mcp/oauth.d.ts +3 -2
- package/dist/types/mcp/runtime.d.ts +18 -4
- package/dist/types/models/index.d.ts +2 -0
- package/dist/types/monitor/index.d.ts +0 -1
- package/dist/types/monitor/redact.d.ts +2 -2
- package/dist/types/secrets/credentials.d.ts +5 -0
- package/dist/types/secrets/endpoints.d.ts +27 -0
- package/dist/types/secrets/fetch.d.ts +13 -0
- package/dist/types/secrets/records.d.ts +13 -0
- package/dist/types/secrets/store.d.ts +7 -0
- package/dist/types/secrets/vault.d.ts +31 -1
- package/dist/types/services.d.ts +13 -1
- package/dist/types/settings.d.ts +17 -2
- package/dist/types/skills/index.d.ts +4 -1
- package/dist/types/spectrum/agent-port.d.ts +1 -1
- package/dist/types/spectrum/settings-port.d.ts +6 -4
- package/dist/types/titles/extension.d.ts +19 -0
- package/dist/types/{freezer/title.d.ts → titles/generate.d.ts} +4 -0
- package/package.json +6 -4
- package/dist/credentials.js +0 -100
- package/dist/credentials.js.map +0 -7
- package/dist/freezer/title.js.map +0 -7
- package/dist/types/credentials.d.ts +0 -12
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const BEDROCK_ENDPOINTS = [
|
|
2
|
+
"https://bedrock-runtime.{region}.amazonaws.com",
|
|
3
|
+
"https://bedrock-runtime-fips.{region}.amazonaws.com"
|
|
4
|
+
];
|
|
5
|
+
const NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6
|
+
const KERNEL = /^(\d{1,5})\.kernel\.localhost$/;
|
|
7
|
+
const FACADE = 9222;
|
|
8
|
+
function plain(url) {
|
|
9
|
+
return !url.username && !url.password && (url.port === "" || url.port === "443");
|
|
10
|
+
}
|
|
11
|
+
function modelUrl(raw) {
|
|
12
|
+
const url = URL.parse(raw);
|
|
13
|
+
return !!url && url.protocol === "https:" && plain(url) && !raw.includes("{");
|
|
14
|
+
}
|
|
15
|
+
function kernelUrl(url) {
|
|
16
|
+
const port = Number(KERNEL.exec(url.hostname)?.[1] ?? 0);
|
|
17
|
+
return url.protocol === "http:" && url.port === "" && port >= 1 && port <= 65535 && port !== FACADE;
|
|
18
|
+
}
|
|
19
|
+
function mcpUrl(raw) {
|
|
20
|
+
const url = URL.parse(raw);
|
|
21
|
+
return !!url && !url.username && !url.password && (kernelUrl(url) || url.protocol === "https:" && plain(url));
|
|
22
|
+
}
|
|
23
|
+
const escaped = (text) => text.replace(/[.*+?^$()|[\]\\]/g, "\\$&");
|
|
24
|
+
function pattern(base) {
|
|
25
|
+
const trimmed = base.replace(/\/+$/, "");
|
|
26
|
+
const source = trimmed.split(/(\{[^}]+\})/).map((part) => /^\{[^}]+\}$/.test(part) ? "[^./]+" : escaped(part)).join("");
|
|
27
|
+
return new RegExp(`^${source}(?:[/?#]|$)`, "i");
|
|
28
|
+
}
|
|
29
|
+
function modelRoute(bases, records) {
|
|
30
|
+
return (request) => {
|
|
31
|
+
const url = request.url;
|
|
32
|
+
const matched = bases().filter(({ urls }) => urls.some((base) => pattern(base).test(url))).map(({ provider }) => provider);
|
|
33
|
+
const head = [url, ...[...request.headers].map(([, value]) => value)].join("\n");
|
|
34
|
+
const held = records().filter((record) => record.kind === "model" && head.includes(record.placeholder)).map((record) => record.provider);
|
|
35
|
+
const owned = matched.filter((provider) => held.includes(provider));
|
|
36
|
+
if (owned.length === 1) return owned[0];
|
|
37
|
+
if (matched.length === 1 && !held.length) return matched[0];
|
|
38
|
+
return matched.length || held.length ? null : void 0;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function modelEndpoints(bases) {
|
|
42
|
+
const seen = /* @__PURE__ */ new Set();
|
|
43
|
+
return bases.flatMap(
|
|
44
|
+
({ provider, urls }) => urls.filter(modelUrl).flatMap((url) => {
|
|
45
|
+
const key = `${provider} ${url}`;
|
|
46
|
+
if (seen.has(key)) return [];
|
|
47
|
+
seen.add(key);
|
|
48
|
+
return [{ provider, url }];
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function mcpEndpoints(servers, tokens) {
|
|
53
|
+
return servers.filter((entry) => mcpUrl(entry.url)).map(({ server, url, names }) => {
|
|
54
|
+
const tokenUrl = tokens.get(server);
|
|
55
|
+
const valid = names.filter((name) => NAME.test(name));
|
|
56
|
+
return {
|
|
57
|
+
server,
|
|
58
|
+
url,
|
|
59
|
+
...valid.length ? { names: valid } : {},
|
|
60
|
+
...tokenUrl && modelUrl(tokenUrl) ? { tokenUrl } : {}
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function vaultEndpoints(vault) {
|
|
65
|
+
let model = [];
|
|
66
|
+
let servers = [];
|
|
67
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
68
|
+
let last = "";
|
|
69
|
+
const send = async (force = false) => {
|
|
70
|
+
const set = { model, mcp: mcpEndpoints(servers, tokens) };
|
|
71
|
+
const key = JSON.stringify(set);
|
|
72
|
+
if (!vault.connected || !force && key === last) return;
|
|
73
|
+
last = key;
|
|
74
|
+
await vault.endpoints(set).catch((error) => {
|
|
75
|
+
if (last === key) last = "";
|
|
76
|
+
throw error;
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
let port = vault.state.value.port;
|
|
80
|
+
const off = vault.state.subscribe((value) => {
|
|
81
|
+
const opened = port === "none" && value.port !== "none";
|
|
82
|
+
port = value.port;
|
|
83
|
+
if (opened) void send(true).catch(() => void 0);
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
models(bases) {
|
|
87
|
+
model = modelEndpoints(bases);
|
|
88
|
+
return send();
|
|
89
|
+
},
|
|
90
|
+
servers(next) {
|
|
91
|
+
servers = next;
|
|
92
|
+
return send();
|
|
93
|
+
},
|
|
94
|
+
token(server, url) {
|
|
95
|
+
if (!tokens.has(server)) tokens.set(server, new URL(url).href);
|
|
96
|
+
return send();
|
|
97
|
+
},
|
|
98
|
+
close: off
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
BEDROCK_ENDPOINTS,
|
|
103
|
+
mcpEndpoints,
|
|
104
|
+
mcpUrl,
|
|
105
|
+
modelEndpoints,
|
|
106
|
+
modelRoute,
|
|
107
|
+
modelUrl,
|
|
108
|
+
pattern,
|
|
109
|
+
vaultEndpoints
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=endpoints.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/secrets/endpoints.ts"],
|
|
4
|
+
"sourcesContent": ["import type { McpServerEntry, ModelEndpoint } from '@ai-ecoverse/slicc-vault';\nimport type { ModelRoute } from './fetch.ts';\nimport type { AgentRecord } from './records.ts';\nimport type { Vault } from './vault.ts';\n\nexport const BEDROCK_ENDPOINTS = [\n 'https://bedrock-runtime.{region}.amazonaws.com',\n 'https://bedrock-runtime-fips.{region}.amazonaws.com',\n];\n\nconst NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst KERNEL = /^(\\d{1,5})\\.kernel\\.localhost$/;\nconst FACADE = 9222;\n\nfunction plain(url: URL): boolean {\n return !url.username && !url.password && (url.port === '' || url.port === '443');\n}\n\nexport function modelUrl(raw: string): boolean {\n const url = URL.parse(raw);\n return !!url && url.protocol === 'https:' && plain(url) && !raw.includes('{');\n}\n\nfunction kernelUrl(url: URL): boolean {\n const port = Number(KERNEL.exec(url.hostname)?.[1] ?? 0);\n return (\n url.protocol === 'http:' && url.port === '' && port >= 1 && port <= 65535 && port !== FACADE\n );\n}\n\nexport function mcpUrl(raw: string): boolean {\n const url = URL.parse(raw);\n return (\n !!url &&\n !url.username &&\n !url.password &&\n (kernelUrl(url) || (url.protocol === 'https:' && plain(url)))\n );\n}\n\nconst escaped = (text: string) => text.replace(/[.*+?^$()|[\\]\\\\]/g, '\\\\$&');\n\nexport function pattern(base: string): RegExp {\n const trimmed = base.replace(/\\/+$/, '');\n const source = trimmed\n .split(/(\\{[^}]+\\})/)\n .map((part) => (/^\\{[^}]+\\}$/.test(part) ? '[^./]+' : escaped(part)))\n .join('');\n return new RegExp(`^${source}(?:[/?#]|$)`, 'i');\n}\n\nexport interface ModelBases {\n provider: string;\n urls: readonly string[];\n}\n\nexport function modelRoute(\n bases: () => readonly ModelBases[],\n records: () => readonly AgentRecord[]\n): ModelRoute {\n return (request) => {\n const url = request.url;\n const matched = bases()\n .filter(({ urls }) => urls.some((base) => pattern(base).test(url)))\n .map(({ provider }) => provider);\n const head = [url, ...[...request.headers].map(([, value]) => value)].join('\\n');\n const held = records()\n .filter((record) => record.kind === 'model' && head.includes(record.placeholder))\n .map((record) => record.provider);\n const owned = matched.filter((provider) => held.includes(provider));\n if (owned.length === 1) return owned[0];\n if (matched.length === 1 && !held.length) return matched[0];\n return matched.length || held.length ? null : undefined;\n };\n}\n\nexport function modelEndpoints(bases: readonly ModelBases[]): ModelEndpoint[] {\n const seen = new Set<string>();\n return bases.flatMap(({ provider, urls }) =>\n urls.filter(modelUrl).flatMap((url) => {\n const key = `${provider} ${url}`;\n if (seen.has(key)) return [];\n seen.add(key);\n return [{ provider, url }];\n })\n );\n}\n\nexport interface McpServerSource {\n server: string;\n url: string;\n names: readonly string[];\n}\n\nexport function mcpEndpoints(\n servers: readonly McpServerSource[],\n tokens: ReadonlyMap<string, string>\n): McpServerEntry[] {\n return servers\n .filter((entry) => mcpUrl(entry.url))\n .map(({ server, url, names }) => {\n const tokenUrl = tokens.get(server);\n const valid = names.filter((name) => NAME.test(name));\n return {\n server,\n url,\n ...(valid.length ? { names: valid } : {}),\n ...(tokenUrl && modelUrl(tokenUrl) ? { tokenUrl } : {}),\n };\n });\n}\n\nexport interface Endpoints {\n models(bases: readonly ModelBases[]): Promise<void>;\n servers(servers: readonly McpServerSource[]): Promise<void>;\n token(server: string, url: string): Promise<void>;\n close(): void;\n}\n\nexport function vaultEndpoints(vault: Pick<Vault, 'state' | 'connected' | 'endpoints'>): Endpoints {\n let model: ModelEndpoint[] = [];\n let servers: readonly McpServerSource[] = [];\n const tokens = new Map<string, string>();\n let last = '';\n const send = async (force = false) => {\n const set = { model, mcp: mcpEndpoints(servers, tokens) };\n const key = JSON.stringify(set);\n if (!vault.connected || (!force && key === last)) return;\n last = key;\n await vault.endpoints(set).catch((error: unknown) => {\n if (last === key) last = '';\n throw error;\n });\n };\n let port = vault.state.value.port;\n const off = vault.state.subscribe((value) => {\n const opened = port === 'none' && value.port !== 'none';\n port = value.port;\n if (opened) void send(true).catch(() => undefined);\n });\n return {\n models(bases) {\n model = modelEndpoints(bases);\n return send();\n },\n servers(next) {\n servers = next;\n return send();\n },\n token(server, url) {\n if (!tokens.has(server)) tokens.set(server, new URL(url).href);\n return send();\n },\n close: off,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
section,
|
|
6
6
|
ToolTask
|
|
7
7
|
} from "@earendil-works/pi-durable";
|
|
8
|
+
import { TITLES } from "../titles/extension.js";
|
|
8
9
|
import { listedLine, SECRETS_KIND } from "./view.js";
|
|
9
10
|
const SECRETS = "slicc-secrets";
|
|
10
11
|
const SECRETS_COMMAND = "/secrets";
|
|
@@ -92,10 +93,12 @@ function secretsEntry(names) {
|
|
|
92
93
|
function shellGrants(names) {
|
|
93
94
|
return (argv) => names.state.value.some((name) => name.exec === "shell") ? [...SHELL_GRANTS, ...argv] : argv;
|
|
94
95
|
}
|
|
96
|
+
const missing = (list) => [SECRETS, TITLES].filter((name) => !list.some((extension) => extension.name === name));
|
|
95
97
|
function pinned(change) {
|
|
96
98
|
const list = change.extensions;
|
|
97
|
-
if (!Array.isArray(list)
|
|
98
|
-
|
|
99
|
+
if (!Array.isArray(list)) return change;
|
|
100
|
+
const added = missing(list);
|
|
101
|
+
return added.length ? { ...change, extensions: [...list, ...added.map((name) => ({ name }))] } : change;
|
|
99
102
|
}
|
|
100
103
|
async function repin(harness, storage, context) {
|
|
101
104
|
let fixed = 0;
|
|
@@ -106,7 +109,7 @@ async function repin(harness, storage, context) {
|
|
|
106
109
|
const conversation = await harness.conversation(record.id, context);
|
|
107
110
|
if (!conversation) continue;
|
|
108
111
|
const agent = await conversation.agent(context);
|
|
109
|
-
if (agent.extensions
|
|
112
|
+
if (!missing(agent.extensions).length) continue;
|
|
110
113
|
await conversation.configure(pinned({ extensions: agent.extensions }), context);
|
|
111
114
|
fixed++;
|
|
112
115
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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';\nimport { listedLine, SECRETS_KIND } from './view.ts';\n\nexport { listedLine, SECRETS_KIND };\n\nexport const SECRETS = '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 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)
|
|
5
|
-
"mappings": "AACA;AAAA,EAGE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OACK;
|
|
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 { TITLES } from '../titles/extension.ts';\nimport type { Names, SecretName } from './names.ts';\nimport type { Scrub } from './scrub.ts';\nimport type { Vault } from './vault.ts';\nimport { listedLine, SECRETS_KIND } from './view.ts';\n\nexport { listedLine, SECRETS_KIND };\n\nexport const SECRETS = '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 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\nconst missing = (list: readonly { name: string }[]) =>\n [SECRETS, TITLES].filter((name) => !list.some((extension) => extension.name === name));\n\nexport function pinned<T extends AgentChange>(change: T): T {\n const list = change.extensions;\n if (!Array.isArray(list)) return change;\n const added = missing(list);\n return added.length\n ? { ...change, extensions: [...list, ...added.map((name) => ({ name }))] }\n : change;\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 (!missing(agent.extensions).length) 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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { VaultRefusal } from "./vault.js";
|
|
2
|
+
const BODYLESS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
3
|
+
const MIN_PART = 8;
|
|
4
|
+
function absent(vault) {
|
|
5
|
+
return !vault.connected && !vault.needed;
|
|
6
|
+
}
|
|
7
|
+
async function outgoing(request) {
|
|
8
|
+
const body = BODYLESS.has(request.method) ? void 0 : new Uint8Array(await request.arrayBuffer());
|
|
9
|
+
return {
|
|
10
|
+
method: request.method,
|
|
11
|
+
url: request.url,
|
|
12
|
+
headers: [...request.headers],
|
|
13
|
+
...body ? { body } : {}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function refusal(error) {
|
|
17
|
+
const code = error instanceof VaultRefusal ? error.code : void 0;
|
|
18
|
+
const status = code === "EACCES" ? 403 : code === "EINVAL" ? 400 : 503;
|
|
19
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
20
|
+
return new Response(text, {
|
|
21
|
+
status,
|
|
22
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async function prepared(op, request) {
|
|
26
|
+
try {
|
|
27
|
+
return await op.prepare(request);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof VaultRefusal && error.code === "EAGAIN") return op.prepare(request);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function filled(original, ready) {
|
|
34
|
+
const sent2 = new Set(original.headers.map(([, value]) => value));
|
|
35
|
+
const values = ready.headers.map(([, value]) => value).filter((value) => !sent2.has(value));
|
|
36
|
+
if (ready.url !== original.url) values.push(ready.url, decodeURIComponent(ready.url));
|
|
37
|
+
return values.flatMap((value) => [
|
|
38
|
+
value,
|
|
39
|
+
...value.split(/[\s=&?]+/).filter((part) => part.length >= MIN_PART)
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
function safeError(error, original, ready) {
|
|
43
|
+
if (error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError"))
|
|
44
|
+
return error;
|
|
45
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
46
|
+
const parts = filled(original, ready);
|
|
47
|
+
const shown = parts.some((part) => text.includes(part)) ? "the request failed" : text;
|
|
48
|
+
return new TypeError(`${ready.method} ${new URL(ready.url).origin}: ${shown}`);
|
|
49
|
+
}
|
|
50
|
+
async function sent(inner, request, original, ready) {
|
|
51
|
+
const init = {
|
|
52
|
+
method: ready.method,
|
|
53
|
+
headers: ready.headers,
|
|
54
|
+
signal: request.signal,
|
|
55
|
+
redirect: request.redirect,
|
|
56
|
+
credentials: request.credentials,
|
|
57
|
+
...ready.body === void 0 ? {} : { body: ready.body }
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
return await inner(ready.url, init);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw safeError(error, original, ready);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function through(op, inner, request) {
|
|
66
|
+
const original = await outgoing(request);
|
|
67
|
+
let refreshed = false;
|
|
68
|
+
for (; ; ) {
|
|
69
|
+
let ready;
|
|
70
|
+
try {
|
|
71
|
+
ready = await prepared(op, original);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return refusal(error);
|
|
74
|
+
}
|
|
75
|
+
const response = await sent(inner, request, original, ready);
|
|
76
|
+
if (response.status !== 401 || refreshed || !op.refresh) return response;
|
|
77
|
+
refreshed = true;
|
|
78
|
+
if (!await op.refresh().then(
|
|
79
|
+
() => true,
|
|
80
|
+
() => false
|
|
81
|
+
))
|
|
82
|
+
return response;
|
|
83
|
+
await response.body?.cancel().catch(() => void 0);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function vaultFetch(vault, inner, route) {
|
|
87
|
+
return async (input, init) => {
|
|
88
|
+
const request = new Request(input, init);
|
|
89
|
+
const provider = absent(vault) ? void 0 : route(request);
|
|
90
|
+
if (provider === void 0) return inner(request);
|
|
91
|
+
const target = provider === null ? void 0 : { provider };
|
|
92
|
+
return through(
|
|
93
|
+
{
|
|
94
|
+
prepare: (outgoing2) => vault.model(provider ?? void 0, outgoing2),
|
|
95
|
+
...target ? { refresh: () => vault.refresh(target) } : {}
|
|
96
|
+
},
|
|
97
|
+
inner,
|
|
98
|
+
request
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function mcpFetch(vault, inner, server) {
|
|
103
|
+
return async (input, init) => {
|
|
104
|
+
const request = new Request(input, init);
|
|
105
|
+
if (absent(vault)) return inner(request);
|
|
106
|
+
return through(
|
|
107
|
+
{
|
|
108
|
+
prepare: (outgoing2) => vault.mcp(server, outgoing2),
|
|
109
|
+
refresh: () => vault.refresh({ server })
|
|
110
|
+
},
|
|
111
|
+
inner,
|
|
112
|
+
request
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const NO_VAULT_SIGN_IN = "MCP sign-in needs seven's vault: update seven";
|
|
117
|
+
function oauthOf(url, params) {
|
|
118
|
+
const scope = params.get("scope");
|
|
119
|
+
const resource = params.get("resource");
|
|
120
|
+
return {
|
|
121
|
+
tokenUrl: url,
|
|
122
|
+
clientId: params.get("client_id") ?? "",
|
|
123
|
+
...scope ? { scope } : {},
|
|
124
|
+
...resource ? { resource } : {}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function grantFetch(vault, server, base) {
|
|
128
|
+
return async (input, init) => {
|
|
129
|
+
const request = new Request(input, init);
|
|
130
|
+
const params = request.method === "POST" ? new URLSearchParams(await request.clone().text()) : void 0;
|
|
131
|
+
if (params?.get("grant_type") !== "authorization_code") return base(request);
|
|
132
|
+
if (absent(vault)) throw new Error(NO_VAULT_SIGN_IN);
|
|
133
|
+
const response = await base(request);
|
|
134
|
+
if (!response.ok) return response;
|
|
135
|
+
const raw = new Uint8Array(await response.arrayBuffer());
|
|
136
|
+
try {
|
|
137
|
+
const granted = await vault.grant(server, raw, oauthOf(request.url, params));
|
|
138
|
+
return new Response(granted.response, {
|
|
139
|
+
status: response.status,
|
|
140
|
+
headers: { "content-type": "application/json" }
|
|
141
|
+
});
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`the vault didn't keep the sign-in: ${error instanceof Error ? error.message : String(error)}`
|
|
145
|
+
);
|
|
146
|
+
} finally {
|
|
147
|
+
raw.fill(0);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
NO_VAULT_SIGN_IN,
|
|
153
|
+
absent,
|
|
154
|
+
grantFetch,
|
|
155
|
+
mcpFetch,
|
|
156
|
+
refusal,
|
|
157
|
+
safeError,
|
|
158
|
+
vaultFetch
|
|
159
|
+
};
|
|
160
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/secrets/fetch.ts"],
|
|
4
|
+
"sourcesContent": ["import type { OAuthConfig, OutgoingRequest } from '@ai-ecoverse/slicc-vault';\nimport { type RefreshTarget, type Vault, VaultRefusal } from './vault.ts';\n\ntype Fetch = typeof fetch;\n\ntype Port = Pick<Vault, 'connected' | 'needed' | 'model' | 'mcp' | 'refresh' | 'grant'>;\n\nexport type ModelRoute = (request: Request) => string | null | undefined;\n\ninterface Op {\n prepare(request: OutgoingRequest): Promise<OutgoingRequest>;\n refresh?(): Promise<unknown>;\n}\n\nconst BODYLESS = new Set(['GET', 'HEAD']);\nconst MIN_PART = 8;\n\nexport function absent(vault: Pick<Vault, 'connected' | 'needed'>): boolean {\n return !vault.connected && !vault.needed;\n}\n\nasync function outgoing(request: Request): Promise<OutgoingRequest> {\n const body = BODYLESS.has(request.method)\n ? undefined\n : new Uint8Array(await request.arrayBuffer());\n return {\n method: request.method,\n url: request.url,\n headers: [...request.headers],\n ...(body ? { body } : {}),\n };\n}\n\nexport function refusal(error: unknown): Response {\n const code = error instanceof VaultRefusal ? error.code : undefined;\n const status = code === 'EACCES' ? 403 : code === 'EINVAL' ? 400 : 503;\n const text = error instanceof Error ? error.message : String(error);\n return new Response(text, {\n status,\n headers: { 'content-type': 'text/plain; charset=utf-8' },\n });\n}\n\nasync function prepared(op: Op, request: OutgoingRequest): Promise<OutgoingRequest> {\n try {\n return await op.prepare(request);\n } catch (error) {\n if (error instanceof VaultRefusal && error.code === 'EAGAIN') return op.prepare(request);\n throw error;\n }\n}\n\nfunction filled(original: OutgoingRequest, ready: OutgoingRequest): string[] {\n const sent = new Set(original.headers.map(([, value]) => value));\n const values = ready.headers.map(([, value]) => value).filter((value) => !sent.has(value));\n if (ready.url !== original.url) values.push(ready.url, decodeURIComponent(ready.url));\n return values.flatMap((value) => [\n value,\n ...value.split(/[\\s=&?]+/).filter((part) => part.length >= MIN_PART),\n ]);\n}\n\nexport function safeError(error: unknown, original: OutgoingRequest, ready: OutgoingRequest) {\n if (\n error instanceof DOMException &&\n (error.name === 'AbortError' || error.name === 'TimeoutError')\n )\n return error;\n const text = error instanceof Error ? error.message : String(error);\n const parts = filled(original, ready);\n const shown = parts.some((part) => text.includes(part)) ? 'the request failed' : text;\n return new TypeError(`${ready.method} ${new URL(ready.url).origin}: ${shown}`);\n}\n\nasync function sent(\n inner: Fetch,\n request: Request,\n original: OutgoingRequest,\n ready: OutgoingRequest\n) {\n const init: RequestInit = {\n method: ready.method,\n headers: ready.headers,\n signal: request.signal,\n redirect: request.redirect,\n credentials: request.credentials,\n ...(ready.body === undefined ? {} : { body: ready.body as BodyInit }),\n };\n try {\n return await inner(ready.url, init);\n } catch (error) {\n throw safeError(error, original, ready);\n }\n}\n\nasync function through(op: Op, inner: Fetch, request: Request): Promise<Response> {\n const original = await outgoing(request);\n let refreshed = false;\n for (;;) {\n let ready: OutgoingRequest;\n try {\n ready = await prepared(op, original);\n } catch (error) {\n return refusal(error);\n }\n const response = await sent(inner, request, original, ready);\n if (response.status !== 401 || refreshed || !op.refresh) return response;\n refreshed = true;\n if (\n !(await op.refresh().then(\n () => true,\n () => false\n ))\n )\n return response;\n await response.body?.cancel().catch(() => undefined);\n }\n}\n\nexport function vaultFetch(vault: Port, inner: Fetch, route: ModelRoute): Fetch {\n return async (input, init) => {\n const request = new Request(input, init);\n const provider = absent(vault) ? undefined : route(request);\n if (provider === undefined) return inner(request);\n const target: RefreshTarget | undefined = provider === null ? undefined : { provider };\n return through(\n {\n prepare: (outgoing) => vault.model(provider ?? undefined, outgoing),\n ...(target ? { refresh: () => vault.refresh(target) } : {}),\n },\n inner,\n request\n );\n };\n}\n\nexport function mcpFetch(vault: Port, inner: Fetch, server: string): Fetch {\n return async (input, init) => {\n const request = new Request(input, init);\n if (absent(vault)) return inner(request);\n return through(\n {\n prepare: (outgoing) => vault.mcp(server, outgoing),\n refresh: () => vault.refresh({ server }),\n },\n inner,\n request\n );\n };\n}\n\nexport const NO_VAULT_SIGN_IN = \"MCP sign-in needs seven's vault: update seven\";\n\nfunction oauthOf(url: string, params: URLSearchParams): OAuthConfig {\n const scope = params.get('scope');\n const resource = params.get('resource');\n return {\n tokenUrl: url,\n clientId: params.get('client_id') ?? '',\n ...(scope ? { scope } : {}),\n ...(resource ? { resource } : {}),\n };\n}\n\nexport function grantFetch(vault: Port, server: string, base: Fetch): Fetch {\n return async (input, init) => {\n const request = new Request(input, init);\n const params =\n request.method === 'POST' ? new URLSearchParams(await request.clone().text()) : undefined;\n if (params?.get('grant_type') !== 'authorization_code') return base(request);\n if (absent(vault)) throw new Error(NO_VAULT_SIGN_IN);\n const response = await base(request);\n if (!response.ok) return response;\n const raw = new Uint8Array(await response.arrayBuffer());\n try {\n const granted = await vault.grant(server, raw, oauthOf(request.url, params));\n return new Response(granted.response as BodyInit, {\n status: response.status,\n headers: { 'content-type': 'application/json' },\n });\n } catch (error) {\n throw new Error(\n `the vault didn't keep the sign-in: ${error instanceof Error ? error.message : String(error)}`\n );\n } finally {\n raw.fill(0);\n }\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["sent", "outgoing"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { replicatedState } from "@earendil-works/chord";
|
|
2
|
+
function vaultRecords(vault) {
|
|
3
|
+
const state = replicatedState([]);
|
|
4
|
+
let version = vault.state.value.version;
|
|
5
|
+
let port = vault.state.value.port;
|
|
6
|
+
let lock = Promise.resolve();
|
|
7
|
+
const read = async () => vault.connected || vault.needed ? vault.records() : [];
|
|
8
|
+
const refresh = (context) => {
|
|
9
|
+
const run = lock.then(async () => state.replace(context, await read()));
|
|
10
|
+
lock = run.catch(() => void 0);
|
|
11
|
+
return run;
|
|
12
|
+
};
|
|
13
|
+
const off = vault.state.subscribe((value, context) => {
|
|
14
|
+
const bumped = value.version !== version || port === "none" && value.port !== "none";
|
|
15
|
+
version = value.version;
|
|
16
|
+
port = value.port;
|
|
17
|
+
if (bumped) void refresh(context).catch(() => void 0);
|
|
18
|
+
});
|
|
19
|
+
const of = (kind) => state.value.filter((record) => record.kind === kind);
|
|
20
|
+
return {
|
|
21
|
+
state,
|
|
22
|
+
refresh,
|
|
23
|
+
model: (provider) => of("model").find((record) => record.provider === provider),
|
|
24
|
+
mcp: (server, name) => {
|
|
25
|
+
const own = of("mcp").filter((record) => record.server === server);
|
|
26
|
+
if (name === void 0) return own[0];
|
|
27
|
+
return own.find((record) => record.name === name) ?? of("mcp").find((record) => record.server === void 0 && record.name === name);
|
|
28
|
+
},
|
|
29
|
+
close: off
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
vaultRecords
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/secrets/records.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AgentRecord } from '@ai-ecoverse/slicc-vault';\nimport { type Context, type MutableReplicatedState, replicatedState } from '@earendil-works/chord';\nimport type { Vault } from './vault.ts';\n\nexport type { AgentRecord } from '@ai-ecoverse/slicc-vault';\n\nexport interface VaultRecords {\n readonly state: MutableReplicatedState<AgentRecord[]>;\n refresh(context: Context): Promise<void>;\n model(provider: string): AgentRecord | undefined;\n mcp(server: string, name?: string): AgentRecord | undefined;\n close(): void;\n}\n\ntype Source = Pick<Vault, 'state' | 'records' | 'connected' | 'needed'>;\n\nexport function vaultRecords(vault: Source): VaultRecords {\n const state = replicatedState<AgentRecord[]>([]);\n let version = vault.state.value.version;\n let port = vault.state.value.port;\n let lock: Promise<unknown> = Promise.resolve();\n const read = async () => (vault.connected || vault.needed ? vault.records() : []);\n const refresh = (context: Context) => {\n const run = lock.then(async () => state.replace(context, await read()));\n lock = run.catch(() => undefined);\n return run;\n };\n const off = vault.state.subscribe((value, context) => {\n const bumped = value.version !== version || (port === 'none' && value.port !== 'none');\n version = value.version;\n port = value.port;\n if (bumped) void refresh(context).catch(() => undefined);\n });\n const of = (kind: AgentRecord['kind']) => state.value.filter((record) => record.kind === kind);\n return {\n state,\n refresh,\n model: (provider) => of('model').find((record) => record.provider === provider),\n mcp: (server, name) => {\n const own = of('mcp').filter((record) => record.server === server);\n if (name === undefined) return own[0];\n return (\n own.find((record) => record.name === name) ??\n of('mcp').find((record) => record.server === undefined && record.name === name)\n );\n },\n close: off,\n };\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAoD,uBAAuB;AAepE,SAAS,aAAa,OAA6B;AACxD,QAAM,QAAQ,gBAA+B,CAAC,CAAC;AAC/C,MAAI,UAAU,MAAM,MAAM,MAAM;AAChC,MAAI,OAAO,MAAM,MAAM,MAAM;AAC7B,MAAI,OAAyB,QAAQ,QAAQ;AAC7C,QAAM,OAAO,YAAa,MAAM,aAAa,MAAM,SAAS,MAAM,QAAQ,IAAI,CAAC;AAC/E,QAAM,UAAU,CAAC,YAAqB;AACpC,UAAM,MAAM,KAAK,KAAK,YAAY,MAAM,QAAQ,SAAS,MAAM,KAAK,CAAC,CAAC;AACtE,WAAO,IAAI,MAAM,MAAM,MAAS;AAChC,WAAO;AAAA,EACT;AACA,QAAM,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,YAAY;AACpD,UAAM,SAAS,MAAM,YAAY,WAAY,SAAS,UAAU,MAAM,SAAS;AAC/E,cAAU,MAAM;AAChB,WAAO,MAAM;AACb,QAAI,OAAQ,MAAK,QAAQ,OAAO,EAAE,MAAM,MAAM,MAAS;AAAA,EACzD,CAAC;AACD,QAAM,KAAK,CAAC,SAA8B,MAAM,MAAM,OAAO,CAAC,WAAW,OAAO,SAAS,IAAI;AAC7F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,CAAC,aAAa,GAAG,OAAO,EAAE,KAAK,CAAC,WAAW,OAAO,aAAa,QAAQ;AAAA,IAC9E,KAAK,CAAC,QAAQ,SAAS;AACrB,YAAM,MAAM,GAAG,KAAK,EAAE,OAAO,CAAC,WAAW,OAAO,WAAW,MAAM;AACjE,UAAI,SAAS,OAAW,QAAO,IAAI,CAAC;AACpC,aACE,IAAI,KAAK,CAAC,WAAW,OAAO,SAAS,IAAI,KACzC,GAAG,KAAK,EAAE,KAAK,CAAC,WAAW,OAAO,WAAW,UAAa,OAAO,SAAS,IAAI;AAAA,IAElF;AAAA,IACA,OAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
2
|
+
function memoryStore() {
|
|
3
|
+
const values = /* @__PURE__ */ new Map();
|
|
4
|
+
return {
|
|
5
|
+
get: async (key) => structuredClone(values.get(key)),
|
|
6
|
+
async set(key, value) {
|
|
7
|
+
if (value === void 0) values.delete(key);
|
|
8
|
+
else values.set(key, structuredClone(value));
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function fileStore(env, path) {
|
|
13
|
+
let cache;
|
|
14
|
+
let writes = Promise.resolve();
|
|
15
|
+
const load = () => {
|
|
16
|
+
cache ??= env.readTextFile(path, BACKGROUND_CONTEXT).then((read) => {
|
|
17
|
+
if (!read.ok) return {};
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(read.value);
|
|
20
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
21
|
+
} catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return cache;
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
async get(key) {
|
|
29
|
+
await writes;
|
|
30
|
+
return structuredClone((await load())[key]);
|
|
31
|
+
},
|
|
32
|
+
set(key, value) {
|
|
33
|
+
const write = writes.then(async () => {
|
|
34
|
+
const all = { ...await load() };
|
|
35
|
+
if (value === void 0) delete all[key];
|
|
36
|
+
else all[key] = structuredClone(value);
|
|
37
|
+
cache = Promise.resolve(all);
|
|
38
|
+
const wrote = await env.writeFile(path, JSON.stringify(all), BACKGROUND_CONTEXT);
|
|
39
|
+
if (!wrote.ok) throw new Error(`can't write ${path}`);
|
|
40
|
+
});
|
|
41
|
+
writes = write.catch(() => void 0);
|
|
42
|
+
return write;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
fileStore,
|
|
48
|
+
memoryStore
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/secrets/store.ts"],
|
|
4
|
+
"sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\n\nexport interface PlainStore {\n get(key: string): Promise<unknown>;\n set(key: string, value: unknown): Promise<void>;\n}\n\nexport function memoryStore(): PlainStore {\n const values = new Map<string, unknown>();\n return {\n get: async (key) => structuredClone(values.get(key)),\n async set(key, value) {\n if (value === undefined) values.delete(key);\n else values.set(key, structuredClone(value));\n },\n };\n}\n\nexport function fileStore(env: ExecutionEnv, path: string): PlainStore {\n let cache: Promise<Record<string, unknown>> | undefined;\n let writes: Promise<unknown> = Promise.resolve();\n const load = () => {\n cache ??= env.readTextFile(path, BACKGROUND_CONTEXT).then((read) => {\n if (!read.ok) return {};\n try {\n const parsed: unknown = JSON.parse(read.value);\n return parsed && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n });\n return cache;\n };\n return {\n async get(key) {\n await writes;\n return structuredClone((await load())[key]);\n },\n set(key, value) {\n const write = writes.then(async () => {\n const all = { ...(await load()) };\n if (value === undefined) delete all[key];\n else all[key] = structuredClone(value);\n cache = Promise.resolve(all);\n const wrote = await env.writeFile(path, JSON.stringify(all), BACKGROUND_CONTEXT);\n if (!wrote.ok) throw new Error(`can't write ${path}`);\n });\n writes = write.catch(() => undefined);\n return write;\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,0BAA0B;AAQ5B,SAAS,cAA0B;AACxC,QAAM,SAAS,oBAAI,IAAqB;AACxC,SAAO;AAAA,IACL,KAAK,OAAO,QAAQ,gBAAgB,OAAO,IAAI,GAAG,CAAC;AAAA,IACnD,MAAM,IAAI,KAAK,OAAO;AACpB,UAAI,UAAU,OAAW,QAAO,OAAO,GAAG;AAAA,UACrC,QAAO,IAAI,KAAK,gBAAgB,KAAK,CAAC;AAAA,IAC7C;AAAA,EACF;AACF;AAEO,SAAS,UAAU,KAAmB,MAA0B;AACrE,MAAI;AACJ,MAAI,SAA2B,QAAQ,QAAQ;AAC/C,QAAM,OAAO,MAAM;AACjB,cAAU,IAAI,aAAa,MAAM,kBAAkB,EAAE,KAAK,CAAC,SAAS;AAClE,UAAI,CAAC,KAAK,GAAI,QAAO,CAAC;AACtB,UAAI;AACF,cAAM,SAAkB,KAAK,MAAM,KAAK,KAAK;AAC7C,eAAO,UAAU,OAAO,WAAW,WAAY,SAAqC,CAAC;AAAA,MACvF,QAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,IAAI,KAAK;AACb,YAAM;AACN,aAAO,iBAAiB,MAAM,KAAK,GAAG,GAAG,CAAC;AAAA,IAC5C;AAAA,IACA,IAAI,KAAK,OAAO;AACd,YAAM,QAAQ,OAAO,KAAK,YAAY;AACpC,cAAM,MAAM,EAAE,GAAI,MAAM,KAAK,EAAG;AAChC,YAAI,UAAU,OAAW,QAAO,IAAI,GAAG;AAAA,YAClC,KAAI,GAAG,IAAI,gBAAgB,KAAK;AACrC,gBAAQ,QAAQ,QAAQ,GAAG;AAC3B,cAAM,QAAQ,MAAM,IAAI,UAAU,MAAM,KAAK,UAAU,GAAG,GAAG,kBAAkB;AAC/E,YAAI,CAAC,MAAM,GAAI,OAAM,IAAI,MAAM,eAAe,IAAI,EAAE;AAAA,MACtD,CAAC;AACD,eAAS,MAAM,MAAM,MAAM,MAAS;AACpC,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|