@ai-ecoverse/slicc-agent 3.31.0 → 3.33.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 +6 -1
- package/dist/host.js +35 -15
- package/dist/host.js.map +2 -2
- package/dist/link.js +146 -0
- package/dist/link.js.map +7 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +2 -2
- package/dist/mcp/config.js +2 -2
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/connection.js +2 -2
- package/dist/mcp/connection.js.map +1 -1
- package/dist/mcp/index.js +6 -0
- package/dist/mcp/index.js.map +2 -2
- package/dist/mcp/label.js +14 -0
- package/dist/mcp/label.js.map +7 -0
- package/dist/mcp/runtime.js +65 -4
- package/dist/mcp/runtime.js.map +2 -2
- package/dist/owner.js +5 -4
- package/dist/owner.js.map +2 -2
- package/dist/page.js +14 -0
- package/dist/page.js.map +2 -2
- package/dist/readonly.js +54 -0
- package/dist/readonly.js.map +7 -0
- package/dist/remote/events.js +2 -1
- package/dist/remote/events.js.map +2 -2
- package/dist/remote/host.js +1 -1
- package/dist/remote/host.js.map +2 -2
- package/dist/services.js.map +2 -2
- package/dist/settings.js +14 -2
- package/dist/settings.js.map +2 -2
- package/dist/spectrum/messages.js +2 -1
- package/dist/spectrum/messages.js.map +2 -2
- package/dist/types/host.d.ts +3 -1
- package/dist/types/link.d.ts +17 -0
- package/dist/types/mcp/index.d.ts +1 -1
- package/dist/types/mcp/label.d.ts +2 -0
- package/dist/types/mcp/runtime.d.ts +9 -1
- package/dist/types/owner.d.ts +5 -2
- package/dist/types/page.d.ts +3 -1
- package/dist/types/readonly.d.ts +7 -0
- package/dist/types/services.d.ts +3 -1
- package/dist/types/settings.d.ts +2 -0
- package/dist/types/view-only.d.ts +1 -0
- package/dist/view-only.js +5 -0
- package/dist/view-only.js.map +7 -0
- package/dist/worker.js +3 -2
- package/dist/worker.js.map +2 -2
- package/package.json +2 -2
package/dist/mcp/index.js
CHANGED
|
@@ -2,6 +2,12 @@ import { parseMcpConfig } from "./config.js";
|
|
|
2
2
|
import { allows, McpAllowDoc, mcpEntries, serverAllowed } from "./allow.js";
|
|
3
3
|
const loadRuntime = () => import("./runtime.js");
|
|
4
4
|
const idle = {
|
|
5
|
+
accounts: async () => [],
|
|
6
|
+
connect: async (id) => {
|
|
7
|
+
throw new Error(`${id} is not an MCP server that takes a token`);
|
|
8
|
+
},
|
|
9
|
+
disconnect: async () => void 0,
|
|
10
|
+
onChange: () => () => void 0,
|
|
5
11
|
status: () => [],
|
|
6
12
|
reload: async () => void 0,
|
|
7
13
|
close: async () => void 0
|
package/dist/mcp/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type {\n Agent,\n Registry,\n ToolExecutionApi,\n ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport type { CodemodeExtra } from './allow.ts';\nimport type { McpCatalog } from './catalog.ts';\nimport { parseMcpConfig } from './config.ts';\nimport type { McpAttach, McpRuntime } from './runtime.ts';\n\nexport { allows, type CodemodeExtra, McpAllowDoc, mcpEntries, serverAllowed } from './allow.ts';\nexport type { McpAttach, McpRuntime, ServerStatus } from './runtime.ts';\n\nexport type McpLoader = () => Promise<typeof import('./runtime.ts')>;\n\nexport const loadRuntime: McpLoader = () => import('./runtime.ts');\n\nexport interface McpSetup {\n catalog(): McpCatalog | undefined;\n direct(names: readonly string[]): ToolRegistration[];\n codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra>;\n attach(options: McpAttach, context: Context): Promise<McpRuntime>;\n}\n\nconst idle: McpRuntime = {\n status: () => [],\n reload: async () => undefined,\n close: async () => undefined,\n};\n\nasync function unavailable(options: McpAttach, error: unknown, context: Context) {\n const file = `${options.home}/.pi/agent/mcp.json`;\n const read = await options.env.readTextFile(file, context);\n if (!parseMcpConfig(read.ok ? read.value : undefined).servers.length) return;\n await options.licks\n .deliver(\n {\n channel: 'fswatch',\n source: 'mcp:~/.pi/agent/mcp.json',\n title: \"MCP servers can't start in this SLICC\",\n text: error instanceof Error ? error.message : String(error),\n body: \"The MCP client didn't load, so the servers in ~/.pi/agent/mcp.json stay off until SLICC is updated.\",\n target: 'cone',\n severity: 'warn',\n eventId: 'mcp\\nunavailable',\n },\n context\n )\n .catch(() => undefined);\n}\n\nexport function setupMcp(registry: Registry, load: McpLoader = loadRuntime): McpSetup {\n let catalog: McpCatalog | undefined;\n return {\n catalog: () => catalog,\n direct: (names) => catalog?.direct(names) ?? [],\n codemode: async (api, agent, context) =>\n catalog ? catalog.codemode(api, agent, context) : { tools: [], globals: [] },\n async attach(options, context) {\n let started: Awaited<ReturnType<Awaited<ReturnType<McpLoader>>['startMcp']>>;\n try {\n started = await (await load()).startMcp(registry, options, context);\n } catch (error) {\n await unavailable(options, error, context);\n return idle;\n }\n catalog = started.catalog;\n return started.runtime;\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AASA,SAAS,sBAAsB;AAG/B,SAAS,QAA4B,aAAa,YAAY,qBAAqB;AAK5E,MAAM,cAAyB,MAAM,OAAO,cAAc;AASjE,MAAM,OAAmB;AAAA,EACvB,QAAQ,MAAM,CAAC;AAAA,EACf,QAAQ,YAAY;AAAA,EACpB,OAAO,YAAY;AACrB;AAEA,eAAe,YAAY,SAAoB,OAAgB,SAAkB;AAC/E,QAAM,OAAO,GAAG,QAAQ,IAAI;AAC5B,QAAM,OAAO,MAAM,QAAQ,IAAI,aAAa,MAAM,OAAO;AACzD,MAAI,CAAC,eAAe,KAAK,KAAK,KAAK,QAAQ,MAAS,EAAE,QAAQ,OAAQ;AACtE,QAAM,QAAQ,MACX;AAAA,IACC;AAAA,MACE,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC3D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AAC1B;AAEO,SAAS,SAAS,UAAoB,OAAkB,aAAuB;AACpF,MAAI;AACJ,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,CAAC,UAAU,SAAS,OAAO,KAAK,KAAK,CAAC;AAAA,IAC9C,UAAU,OAAO,KAAK,OAAO,YAC3B,UAAU,QAAQ,SAAS,KAAK,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,IAC7E,MAAM,OAAO,SAAS,SAAS;AAC7B,UAAI;AACJ,UAAI;AACF,kBAAU,OAAO,MAAM,KAAK,GAAG,SAAS,UAAU,SAAS,OAAO;AAAA,MACpE,SAAS,OAAO;AACd,cAAM,YAAY,SAAS,OAAO,OAAO;AACzC,eAAO;AAAA,MACT;AACA,gBAAU,QAAQ;AAClB,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type {\n Agent,\n Registry,\n ToolExecutionApi,\n ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport type { CodemodeExtra } from './allow.ts';\nimport type { McpCatalog } from './catalog.ts';\nimport { parseMcpConfig } from './config.ts';\nimport type { McpAttach, McpRuntime } from './runtime.ts';\n\nexport { allows, type CodemodeExtra, McpAllowDoc, mcpEntries, serverAllowed } from './allow.ts';\nexport type { McpAccounts, McpAttach, McpRuntime, ServerStatus } from './runtime.ts';\n\nexport type McpLoader = () => Promise<typeof import('./runtime.ts')>;\n\nexport const loadRuntime: McpLoader = () => import('./runtime.ts');\n\nexport interface McpSetup {\n catalog(): McpCatalog | undefined;\n direct(names: readonly string[]): ToolRegistration[];\n codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra>;\n attach(options: McpAttach, context: Context): Promise<McpRuntime>;\n}\n\nconst idle: McpRuntime = {\n accounts: async () => [],\n connect: async (id) => {\n throw new Error(`${id} is not an MCP server that takes a token`);\n },\n disconnect: async () => undefined,\n onChange: () => () => undefined,\n status: () => [],\n reload: async () => undefined,\n close: async () => undefined,\n};\n\nasync function unavailable(options: McpAttach, error: unknown, context: Context) {\n const file = `${options.home}/.pi/agent/mcp.json`;\n const read = await options.env.readTextFile(file, context);\n if (!parseMcpConfig(read.ok ? read.value : undefined).servers.length) return;\n await options.licks\n .deliver(\n {\n channel: 'fswatch',\n source: 'mcp:~/.pi/agent/mcp.json',\n title: \"MCP servers can't start in this SLICC\",\n text: error instanceof Error ? error.message : String(error),\n body: \"The MCP client didn't load, so the servers in ~/.pi/agent/mcp.json stay off until SLICC is updated.\",\n target: 'cone',\n severity: 'warn',\n eventId: 'mcp\\nunavailable',\n },\n context\n )\n .catch(() => undefined);\n}\n\nexport function setupMcp(registry: Registry, load: McpLoader = loadRuntime): McpSetup {\n let catalog: McpCatalog | undefined;\n return {\n catalog: () => catalog,\n direct: (names) => catalog?.direct(names) ?? [],\n codemode: async (api, agent, context) =>\n catalog ? catalog.codemode(api, agent, context) : { tools: [], globals: [] },\n async attach(options, context) {\n let started: Awaited<ReturnType<Awaited<ReturnType<McpLoader>>['startMcp']>>;\n try {\n started = await (await load()).startMcp(registry, options, context);\n } catch (error) {\n await unavailable(options, error, context);\n return idle;\n }\n catalog = started.catalog;\n return started.runtime;\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,sBAAsB;AAG/B,SAAS,QAA4B,aAAa,YAAY,qBAAqB;AAK5E,MAAM,cAAyB,MAAM,OAAO,cAAc;AASjE,MAAM,OAAmB;AAAA,EACvB,UAAU,YAAY,CAAC;AAAA,EACvB,SAAS,OAAO,OAAO;AACrB,UAAM,IAAI,MAAM,GAAG,EAAE,0CAA0C;AAAA,EACjE;AAAA,EACA,YAAY,YAAY;AAAA,EACxB,UAAU,MAAM,MAAM;AAAA,EACtB,QAAQ,MAAM,CAAC;AAAA,EACf,QAAQ,YAAY;AAAA,EACpB,OAAO,YAAY;AACrB;AAEA,eAAe,YAAY,SAAoB,OAAgB,SAAkB;AAC/E,QAAM,OAAO,GAAG,QAAQ,IAAI;AAC5B,QAAM,OAAO,MAAM,QAAQ,IAAI,aAAa,MAAM,OAAO;AACzD,MAAI,CAAC,eAAe,KAAK,KAAK,KAAK,QAAQ,MAAS,EAAE,QAAQ,OAAQ;AACtE,QAAM,QAAQ,MACX;AAAA,IACC;AAAA,MACE,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC3D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AAC1B;AAEO,SAAS,SAAS,UAAoB,OAAkB,aAAuB;AACpF,MAAI;AACJ,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,CAAC,UAAU,SAAS,OAAO,KAAK,KAAK,CAAC;AAAA,IAC9C,UAAU,OAAO,KAAK,OAAO,YAC3B,UAAU,QAAQ,SAAS,KAAK,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,IAC7E,MAAM,OAAO,SAAS,SAAS;AAC7B,UAAI;AACJ,UAAI;AACF,kBAAU,OAAO,MAAM,KAAK,GAAG,SAAS,UAAU,SAAS,OAAO;AAAA,MACpE,SAAS,OAAO;AACd,cAAM,YAAY,SAAS,OAAO,OAAO;AACzC,eAAO;AAAA,MACT;AACA,gBAAU,QAAQ;AAClB,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function mcpLabel(name) {
|
|
2
|
+
const parts = name.split("__");
|
|
3
|
+
if (parts.length !== 3 || parts[0] !== "mcp") return void 0;
|
|
4
|
+
const [, server, tool] = parts;
|
|
5
|
+
if (!/^[A-Za-z0-9](?:.*[A-Za-z0-9])?$/.test(server) || !/^[A-Za-z0-9]/.test(tool))
|
|
6
|
+
return void 0;
|
|
7
|
+
return `${server} \xB7 ${tool}`;
|
|
8
|
+
}
|
|
9
|
+
const MCP_ACCOUNT = "mcp:";
|
|
10
|
+
export {
|
|
11
|
+
MCP_ACCOUNT,
|
|
12
|
+
mcpLabel
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=label.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/mcp/label.ts"],
|
|
4
|
+
"sourcesContent": ["export function mcpLabel(name: string): string | undefined {\n const parts = name.split('__');\n if (parts.length !== 3 || parts[0] !== 'mcp') return undefined;\n const [, server, tool] = parts as [string, string, string];\n if (!/^[A-Za-z0-9](?:.*[A-Za-z0-9])?$/.test(server) || !/^[A-Za-z0-9]/.test(tool))\n return undefined;\n return `${server} \u00B7 ${tool}`;\n}\n\nexport const MCP_ACCOUNT = 'mcp:';\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,SAAS,MAAkC;AACzD,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,MAAI,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM,MAAO,QAAO;AACrD,QAAM,CAAC,EAAE,QAAQ,IAAI,IAAI;AACzB,MAAI,CAAC,kCAAkC,KAAK,MAAM,KAAK,CAAC,eAAe,KAAK,IAAI;AAC9E,WAAO;AACT,SAAO,GAAG,MAAM,SAAM,IAAI;AAC5B;AAEO,MAAM,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/mcp/runtime.js
CHANGED
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
sameConfig
|
|
6
6
|
} from "./config.js";
|
|
7
7
|
import { McpServerConnection } from "./connection.js";
|
|
8
|
-
import { resolveHeaders, scrub } from "./credentials.js";
|
|
8
|
+
import { credentialKey, resolveHeaders, scrub, secretNames } from "./credentials.js";
|
|
9
|
+
import { MCP_ACCOUNT } from "./label.js";
|
|
9
10
|
import { patchSse } from "./sse.js";
|
|
10
11
|
const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
11
12
|
function configPath(home) {
|
|
@@ -38,8 +39,8 @@ function stateLick(connection, cors) {
|
|
|
38
39
|
if (connection.state === "needs-secret")
|
|
39
40
|
return {
|
|
40
41
|
...base,
|
|
41
|
-
title: `MCP server "${name}" needs a
|
|
42
|
-
text: `Its headers use ${connection.missing.map((item) => `\${${item}}`).join(", ")}. Add the value in Settings \u203A
|
|
42
|
+
title: `MCP server "${name}" needs a token`,
|
|
43
|
+
text: `Its headers use ${connection.missing.map((item) => `\${${item}}`).join(", ")}. Add the value in Settings \u203A MCP servers.`,
|
|
43
44
|
severity: "warn",
|
|
44
45
|
eventId: `${name}
|
|
45
46
|
secret
|
|
@@ -48,7 +49,7 @@ ${connection.missing.join(",")}`
|
|
|
48
49
|
if (connection.state === "needs-auth")
|
|
49
50
|
return {
|
|
50
51
|
...base,
|
|
51
|
-
title: `MCP server "${name}"
|
|
52
|
+
title: `MCP server "${name}" rejected its token`,
|
|
52
53
|
text: connection.signInMessage(),
|
|
53
54
|
severity: "warn",
|
|
54
55
|
eventId: `${name}
|
|
@@ -103,6 +104,52 @@ function serverLog(options, context) {
|
|
|
103
104
|
settled: () => logging
|
|
104
105
|
};
|
|
105
106
|
}
|
|
107
|
+
function mcpAccounts(catalog, credentials, restart, listeners) {
|
|
108
|
+
const find = (id) => {
|
|
109
|
+
const connection = catalog.connections.get(id.slice(MCP_ACCOUNT.length));
|
|
110
|
+
if (!id.startsWith(MCP_ACCOUNT) || !connection || !secretNames(connection.entry.config).length)
|
|
111
|
+
throw new Error(`${id} is not an MCP server that takes a token`);
|
|
112
|
+
return connection;
|
|
113
|
+
};
|
|
114
|
+
const keyOf = (connection) => credentialKey(connection.name, connection.entry.config.url);
|
|
115
|
+
return {
|
|
116
|
+
async accounts() {
|
|
117
|
+
const rows = [];
|
|
118
|
+
for (const connection of catalog.connections.values()) {
|
|
119
|
+
if (!secretNames(connection.entry.config).length) continue;
|
|
120
|
+
const stored = await credentials.read(await keyOf(connection)) !== void 0;
|
|
121
|
+
rows.push({
|
|
122
|
+
id: `${MCP_ACCOUNT}${connection.name}`,
|
|
123
|
+
provider: connection.name,
|
|
124
|
+
identity: new URL(connection.entry.config.url).host,
|
|
125
|
+
status: connection.state === "needs-auth" ? "expired" : stored ? "connected" : "disconnected",
|
|
126
|
+
auth: "api-key",
|
|
127
|
+
needs: null,
|
|
128
|
+
group: "mcp",
|
|
129
|
+
secret: "token"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return rows;
|
|
133
|
+
},
|
|
134
|
+
async connect(id, secret) {
|
|
135
|
+
const connection = find(id);
|
|
136
|
+
await credentials.modify(await keyOf(connection), async () => ({
|
|
137
|
+
type: "api_key",
|
|
138
|
+
key: secret
|
|
139
|
+
}));
|
|
140
|
+
restart(connection);
|
|
141
|
+
},
|
|
142
|
+
async disconnect(id) {
|
|
143
|
+
const connection = find(id);
|
|
144
|
+
await credentials.delete(await keyOf(connection));
|
|
145
|
+
restart(connection);
|
|
146
|
+
},
|
|
147
|
+
onChange(listener) {
|
|
148
|
+
listeners.add(listener);
|
|
149
|
+
return () => listeners.delete(listener);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
106
153
|
async function attachMcp(catalog, options, context) {
|
|
107
154
|
const { env, home } = options;
|
|
108
155
|
catalog.codemodeOn = options.codemode;
|
|
@@ -112,6 +159,10 @@ async function attachMcp(catalog, options, context) {
|
|
|
112
159
|
reported.add(lick.eventId);
|
|
113
160
|
await options.licks.deliver(lick, using).catch(() => void 0);
|
|
114
161
|
};
|
|
162
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
163
|
+
const notify = () => {
|
|
164
|
+
for (const listener of listeners) listener();
|
|
165
|
+
};
|
|
115
166
|
let initial = /* @__PURE__ */ new Set();
|
|
116
167
|
let first = true;
|
|
117
168
|
const log = serverLog(options, context);
|
|
@@ -138,6 +189,7 @@ async function attachMcp(catalog, options, context) {
|
|
|
138
189
|
void report(connectedLick(changed), context);
|
|
139
190
|
void report(stateLick(changed, options.cors), context);
|
|
140
191
|
if (changed.state !== "connected") catalog.rebuild();
|
|
192
|
+
notify();
|
|
141
193
|
}
|
|
142
194
|
});
|
|
143
195
|
catalog.connections.set(entry.name, connection);
|
|
@@ -161,6 +213,7 @@ async function attachMcp(catalog, options, context) {
|
|
|
161
213
|
first = false;
|
|
162
214
|
for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);
|
|
163
215
|
catalog.rebuild();
|
|
216
|
+
notify();
|
|
164
217
|
await Promise.all(closing);
|
|
165
218
|
};
|
|
166
219
|
await reload(context);
|
|
@@ -171,7 +224,14 @@ async function attachMcp(catalog, options, context) {
|
|
|
171
224
|
};
|
|
172
225
|
const watched = await env.watch([{ path: configPath(home) }], schedule, context);
|
|
173
226
|
const watcher = watched.ok ? watched.value : void 0;
|
|
227
|
+
const restart = (connection) => {
|
|
228
|
+
catalog.connections.delete(connection.name);
|
|
229
|
+
void connection.close();
|
|
230
|
+
open(connection.entry);
|
|
231
|
+
catalog.rebuild();
|
|
232
|
+
};
|
|
174
233
|
return {
|
|
234
|
+
...mcpAccounts(catalog, options.credentials, restart, listeners),
|
|
175
235
|
status: () => [...catalog.connections.values()].map((connection) => ({
|
|
176
236
|
name: connection.name,
|
|
177
237
|
state: connection.state,
|
|
@@ -202,6 +262,7 @@ export {
|
|
|
202
262
|
connectedLick,
|
|
203
263
|
logLine,
|
|
204
264
|
logPath,
|
|
265
|
+
mcpAccounts,
|
|
205
266
|
problemLick,
|
|
206
267
|
startMcp,
|
|
207
268
|
stateLick
|
package/dist/mcp/runtime.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/runtime.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { Registry } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { StreamableHttpTransport } from '@earendil-works/pi-mcp';\nimport type { LickEvent, Licks } from '../licks/licks.ts';\nimport { McpCatalog } from './catalog.ts';\nimport {\n type McpConfigProblem,\n type McpServerEntry,\n parseMcpConfig,\n sameConfig,\n} from './config.ts';\nimport { McpServerConnection } from './connection.ts';\nimport { resolveHeaders, scrub } from './credentials.ts';\nimport { patchSse } from './sse.ts';\n\nexport const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;\n\nexport function configPath(home: string): string {\n return `${home}/.pi/agent/mcp.json`;\n}\n\nexport function logPath(home: string): string {\n return `${home}/.pi/agent/mcp.log`;\n}\n\nexport interface McpAttach {\n licks: Licks;\n env: ExecutionEnv;\n home: string;\n credentials: CredentialStore;\n fetch: typeof fetch;\n version: string;\n reloadMs: number;\n codemode: boolean;\n cors: boolean;\n}\n\nexport interface ServerStatus {\n name: string;\n state: string;\n tools: number;\n error?: string;\n}\n\nexport interface McpRuntime {\n status(): ServerStatus[];\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport function problemLick(problem: McpConfigProblem, home: string): LickEvent {\n const file = `~${configPath(home).slice(home.length)}`;\n const where = problem.server ? `server \"${problem.server}\"` : file;\n const field = problem.field ? `, field ${problem.field}` : '';\n return {\n channel: 'fswatch',\n source: `mcp:${file}`,\n title: `MCP config problem: ${where}${field}`,\n text: problem.message,\n body: `${file}: the entry is skipped until it is fixed. The format is in the slicc-agent README.`,\n target: 'cone',\n severity: 'warn',\n eventId: `${problem.server ?? ''}\\n${problem.field ?? ''}\\n${problem.message}`,\n };\n}\n\nconst CORS_ERROR = /failed to fetch|networkerror|load failed|cors/i;\n\nexport function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined {\n const name = connection.name;\n const base = { channel: 'fswatch' as const, source: `mcp:${name}`, target: 'cone' as const };\n if (connection.state === 'needs-secret')\n return {\n ...base,\n title: `MCP server \"${name}\" needs a secret`,\n text: `Its headers use ${connection.missing.map((item) => `\\${${item}}`).join(', ')}. Add the value in Settings \u203A Accounts.`,\n severity: 'warn',\n eventId: `${name}\\nsecret\\n${connection.missing.join(',')}`,\n };\n if (connection.state === 'needs-auth')\n return {\n ...base,\n title: `MCP server \"${name}\" needs sign-in`,\n text: connection.signInMessage(),\n severity: 'warn',\n eventId: `${name}\\nauth`,\n };\n if (connection.state !== 'failed') return undefined;\n const error = scrub(connection.error ?? 'unknown error', connection.secrets);\n const blocked = cors && CORS_ERROR.test(error);\n return {\n ...base,\n title: `MCP server \"${name}\" can't connect`,\n text: blocked\n ? \"Seven reaches it with the page's own fetch, and the server sends no CORS headers. Run slicc-node, slicc-swift or the SLICC extension, which fetch without CORS.\"\n : error,\n severity: 'warn',\n eventId: `${name}\\nfailed\\n${blocked ? 'cors' : error}`,\n };\n}\n\nexport function connectedLick(connection: McpServerConnection): LickEvent {\n const count = connection.tools.length;\n return {\n channel: 'fswatch',\n source: `mcp:${connection.name}`,\n title: `MCP server \"${connection.name}\" connected`,\n text: `${connection.entry.config.url} offers ${count} tool${count === 1 ? '' : 's'}.`,\n target: 'cone',\n eventId: `${connection.name}\\nconnected\\n${connection.entry.config.url}`,\n };\n}\n\nexport function logLine(\n server: string,\n params: unknown,\n secrets: readonly string[],\n at: Date\n): string {\n const { level, logger, data } = (params ?? {}) as {\n level?: string;\n logger?: string;\n data?: unknown;\n };\n const text = scrub(typeof data === 'string' ? data : (JSON.stringify(data) ?? ''), secrets);\n return `${at.toISOString()} [${server}] ${level ?? 'info'}${logger ? ` ${logger}` : ''}: ${text}\\n`;\n}\n\nfunction serverLog(options: McpAttach, context: Context) {\n let logging = Promise.resolve();\n const path = logPath(options.home);\n const { env } = options;\n return {\n write(connection: McpServerConnection, params: unknown) {\n const line = logLine(connection.name, params, connection.secrets, new Date());\n logging = logging.then(async () => {\n const info = await env.fileInfo(path, context);\n if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)\n await env.renameFile(path, `${path}.1`, context);\n await env.appendFile(path, line, context);\n });\n },\n settled: () => logging,\n };\n}\n\nexport async function attachMcp(\n catalog: McpCatalog,\n options: McpAttach,\n context: Context\n): Promise<McpRuntime> {\n const { env, home } = options;\n catalog.codemodeOn = options.codemode;\n const reported = new Set<string>();\n const report = async (lick: LickEvent | undefined, using: Context) => {\n if (!lick?.eventId || reported.has(lick.eventId)) return;\n reported.add(lick.eventId);\n await options.licks.deliver(lick, using).catch(() => undefined);\n };\n let initial: Set<string> = new Set();\n let first = true;\n const log = serverLog(options, context);\n const make = (url: string, headers: Record<string, string>) =>\n new StreamableHttpTransport({\n url,\n headers,\n fetch: (input, init) => options.fetch(input, init),\n });\n const transport = async (entry: McpServerEntry) => {\n const resolved = await resolveHeaders(entry.name, entry.config, options.credentials);\n if (!resolved.ok) return { missing: resolved.missing };\n return { transport: make(entry.config.url, resolved.headers), secrets: resolved.secrets };\n };\n const open = (entry: McpServerEntry) => {\n const connection = new McpServerConnection({\n entry,\n version: options.version,\n transport,\n onTools: () => catalog.rebuild(),\n onLog: log.write,\n onChange: (changed) => {\n if (catalog.connections.get(changed.name) !== changed) return;\n if (changed.state === 'connected' && !initial.has(changed.name))\n void report(connectedLick(changed), context);\n void report(stateLick(changed, options.cors), context);\n if (changed.state !== 'connected') catalog.rebuild();\n },\n });\n catalog.connections.set(entry.name, connection);\n void connection.getClient().catch(() => undefined);\n };\n const reload = async (using: Context) => {\n const read = await env.readTextFile(configPath(home), using);\n const parsed = parseMcpConfig(read.ok ? read.value : undefined);\n for (const problem of parsed.problems) await report(problemLick(problem, home), using);\n const wanted = new Map(\n parsed.servers\n .filter((entry) => entry.config.enabled !== false)\n .map((entry) => [entry.name, entry])\n );\n const closing: Promise<void>[] = [];\n for (const [name, connection] of [...catalog.connections]) {\n const next = wanted.get(name);\n if (next && sameConfig(next.config, connection.entry.config)) continue;\n catalog.connections.delete(name);\n closing.push(connection.close());\n }\n if (first) initial = new Set(wanted.keys());\n first = false;\n for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);\n catalog.rebuild();\n await Promise.all(closing);\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch([{ path: configPath(home) }], schedule, context);\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n return {\n status: () =>\n [...catalog.connections.values()].map((connection) => ({\n name: connection.name,\n state: connection.state,\n tools: connection.tools.length,\n ...(connection.error ? { error: scrub(connection.error, connection.secrets) } : {}),\n })),\n reload,\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n const all = [...catalog.connections.values()];\n catalog.connections.clear();\n await Promise.all(all.map((connection) => connection.close()));\n catalog.rebuild();\n await log.settled();\n },\n };\n}\n\nexport async function startMcp(\n registry: Registry,\n options: McpAttach,\n context: Context\n): Promise<{ catalog: McpCatalog; runtime: McpRuntime }> {\n patchSse();\n const catalog = new McpCatalog(registry);\n return { catalog, runtime: await attachMcp(catalog, options, context) };\n}\n"],
|
|
5
|
-
"mappings": "AAIA,SAAS,+BAA+B;
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { Registry } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { StreamableHttpTransport } from '@earendil-works/pi-mcp';\nimport type { LickEvent, Licks } from '../licks/licks.ts';\nimport type { AccountState } from '../services.ts';\nimport { McpCatalog } from './catalog.ts';\nimport {\n type McpConfigProblem,\n type McpServerEntry,\n parseMcpConfig,\n sameConfig,\n} from './config.ts';\nimport { McpServerConnection } from './connection.ts';\nimport { credentialKey, resolveHeaders, scrub, secretNames } from './credentials.ts';\nimport { MCP_ACCOUNT } from './label.ts';\nimport { patchSse } from './sse.ts';\n\nexport const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;\n\nexport function configPath(home: string): string {\n return `${home}/.pi/agent/mcp.json`;\n}\n\nexport function logPath(home: string): string {\n return `${home}/.pi/agent/mcp.log`;\n}\n\nexport interface McpAttach {\n licks: Licks;\n env: ExecutionEnv;\n home: string;\n credentials: CredentialStore;\n fetch: typeof fetch;\n version: string;\n reloadMs: number;\n codemode: boolean;\n cors: boolean;\n}\n\nexport interface ServerStatus {\n name: string;\n state: string;\n tools: number;\n error?: string;\n}\n\nexport interface McpAccounts {\n accounts(): Promise<AccountState[]>;\n connect(id: string, secret: string): Promise<void>;\n disconnect(id: string): Promise<void>;\n onChange(listener: () => void): () => void;\n}\n\nexport interface McpRuntime extends McpAccounts {\n status(): ServerStatus[];\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport function problemLick(problem: McpConfigProblem, home: string): LickEvent {\n const file = `~${configPath(home).slice(home.length)}`;\n const where = problem.server ? `server \"${problem.server}\"` : file;\n const field = problem.field ? `, field ${problem.field}` : '';\n return {\n channel: 'fswatch',\n source: `mcp:${file}`,\n title: `MCP config problem: ${where}${field}`,\n text: problem.message,\n body: `${file}: the entry is skipped until it is fixed. The format is in the slicc-agent README.`,\n target: 'cone',\n severity: 'warn',\n eventId: `${problem.server ?? ''}\\n${problem.field ?? ''}\\n${problem.message}`,\n };\n}\n\nconst CORS_ERROR = /failed to fetch|networkerror|load failed|cors/i;\n\nexport function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined {\n const name = connection.name;\n const base = { channel: 'fswatch' as const, source: `mcp:${name}`, target: 'cone' as const };\n if (connection.state === 'needs-secret')\n return {\n ...base,\n title: `MCP server \"${name}\" needs a token`,\n text: `Its headers use ${connection.missing.map((item) => `\\${${item}}`).join(', ')}. Add the value in Settings \u203A MCP servers.`,\n severity: 'warn',\n eventId: `${name}\\nsecret\\n${connection.missing.join(',')}`,\n };\n if (connection.state === 'needs-auth')\n return {\n ...base,\n title: `MCP server \"${name}\" rejected its token`,\n text: connection.signInMessage(),\n severity: 'warn',\n eventId: `${name}\\nauth`,\n };\n if (connection.state !== 'failed') return undefined;\n const error = scrub(connection.error ?? 'unknown error', connection.secrets);\n const blocked = cors && CORS_ERROR.test(error);\n return {\n ...base,\n title: `MCP server \"${name}\" can't connect`,\n text: blocked\n ? \"Seven reaches it with the page's own fetch, and the server sends no CORS headers. Run slicc-node, slicc-swift or the SLICC extension, which fetch without CORS.\"\n : error,\n severity: 'warn',\n eventId: `${name}\\nfailed\\n${blocked ? 'cors' : error}`,\n };\n}\n\nexport function connectedLick(connection: McpServerConnection): LickEvent {\n const count = connection.tools.length;\n return {\n channel: 'fswatch',\n source: `mcp:${connection.name}`,\n title: `MCP server \"${connection.name}\" connected`,\n text: `${connection.entry.config.url} offers ${count} tool${count === 1 ? '' : 's'}.`,\n target: 'cone',\n eventId: `${connection.name}\\nconnected\\n${connection.entry.config.url}`,\n };\n}\n\nexport function logLine(\n server: string,\n params: unknown,\n secrets: readonly string[],\n at: Date\n): string {\n const { level, logger, data } = (params ?? {}) as {\n level?: string;\n logger?: string;\n data?: unknown;\n };\n const text = scrub(typeof data === 'string' ? data : (JSON.stringify(data) ?? ''), secrets);\n return `${at.toISOString()} [${server}] ${level ?? 'info'}${logger ? ` ${logger}` : ''}: ${text}\\n`;\n}\n\nfunction serverLog(options: McpAttach, context: Context) {\n let logging = Promise.resolve();\n const path = logPath(options.home);\n const { env } = options;\n return {\n write(connection: McpServerConnection, params: unknown) {\n const line = logLine(connection.name, params, connection.secrets, new Date());\n logging = logging.then(async () => {\n const info = await env.fileInfo(path, context);\n if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)\n await env.renameFile(path, `${path}.1`, context);\n await env.appendFile(path, line, context);\n });\n },\n settled: () => logging,\n };\n}\n\nexport function mcpAccounts(\n catalog: McpCatalog,\n credentials: CredentialStore,\n restart: (connection: McpServerConnection) => void,\n listeners: Set<() => void>\n): McpAccounts {\n const find = (id: string) => {\n const connection = catalog.connections.get(id.slice(MCP_ACCOUNT.length));\n if (!id.startsWith(MCP_ACCOUNT) || !connection || !secretNames(connection.entry.config).length)\n throw new Error(`${id} is not an MCP server that takes a token`);\n return connection;\n };\n const keyOf = (connection: McpServerConnection) =>\n credentialKey(connection.name, connection.entry.config.url);\n return {\n async accounts() {\n const rows: AccountState[] = [];\n for (const connection of catalog.connections.values()) {\n if (!secretNames(connection.entry.config).length) continue;\n const stored = (await credentials.read(await keyOf(connection))) !== undefined;\n rows.push({\n id: `${MCP_ACCOUNT}${connection.name}`,\n provider: connection.name,\n identity: new URL(connection.entry.config.url).host,\n status:\n connection.state === 'needs-auth' ? 'expired' : stored ? 'connected' : 'disconnected',\n auth: 'api-key',\n needs: null,\n group: 'mcp',\n secret: 'token',\n });\n }\n return rows;\n },\n async connect(id, secret) {\n const connection = find(id);\n await credentials.modify(await keyOf(connection), async () => ({\n type: 'api_key',\n key: secret,\n }));\n restart(connection);\n },\n async disconnect(id) {\n const connection = find(id);\n await credentials.delete(await keyOf(connection));\n restart(connection);\n },\n onChange(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n\nexport async function attachMcp(\n catalog: McpCatalog,\n options: McpAttach,\n context: Context\n): Promise<McpRuntime> {\n const { env, home } = options;\n catalog.codemodeOn = options.codemode;\n const reported = new Set<string>();\n const report = async (lick: LickEvent | undefined, using: Context) => {\n if (!lick?.eventId || reported.has(lick.eventId)) return;\n reported.add(lick.eventId);\n await options.licks.deliver(lick, using).catch(() => undefined);\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of listeners) listener();\n };\n let initial: Set<string> = new Set();\n let first = true;\n const log = serverLog(options, context);\n const make = (url: string, headers: Record<string, string>) =>\n new StreamableHttpTransport({\n url,\n headers,\n fetch: (input, init) => options.fetch(input, init),\n });\n const transport = async (entry: McpServerEntry) => {\n const resolved = await resolveHeaders(entry.name, entry.config, options.credentials);\n if (!resolved.ok) return { missing: resolved.missing };\n return { transport: make(entry.config.url, resolved.headers), secrets: resolved.secrets };\n };\n const open = (entry: McpServerEntry) => {\n const connection = new McpServerConnection({\n entry,\n version: options.version,\n transport,\n onTools: () => catalog.rebuild(),\n onLog: log.write,\n onChange: (changed) => {\n if (catalog.connections.get(changed.name) !== changed) return;\n if (changed.state === 'connected' && !initial.has(changed.name))\n void report(connectedLick(changed), context);\n void report(stateLick(changed, options.cors), context);\n if (changed.state !== 'connected') catalog.rebuild();\n notify();\n },\n });\n catalog.connections.set(entry.name, connection);\n void connection.getClient().catch(() => undefined);\n };\n const reload = async (using: Context) => {\n const read = await env.readTextFile(configPath(home), using);\n const parsed = parseMcpConfig(read.ok ? read.value : undefined);\n for (const problem of parsed.problems) await report(problemLick(problem, home), using);\n const wanted = new Map(\n parsed.servers\n .filter((entry) => entry.config.enabled !== false)\n .map((entry) => [entry.name, entry])\n );\n const closing: Promise<void>[] = [];\n for (const [name, connection] of [...catalog.connections]) {\n const next = wanted.get(name);\n if (next && sameConfig(next.config, connection.entry.config)) continue;\n catalog.connections.delete(name);\n closing.push(connection.close());\n }\n if (first) initial = new Set(wanted.keys());\n first = false;\n for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);\n catalog.rebuild();\n notify();\n await Promise.all(closing);\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch([{ path: configPath(home) }], schedule, context);\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const restart = (connection: McpServerConnection) => {\n catalog.connections.delete(connection.name);\n void connection.close();\n open(connection.entry);\n catalog.rebuild();\n };\n return {\n ...mcpAccounts(catalog, options.credentials, restart, listeners),\n status: () =>\n [...catalog.connections.values()].map((connection) => ({\n name: connection.name,\n state: connection.state,\n tools: connection.tools.length,\n ...(connection.error ? { error: scrub(connection.error, connection.secrets) } : {}),\n })),\n reload,\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n const all = [...catalog.connections.values()];\n catalog.connections.clear();\n await Promise.all(all.map((connection) => connection.close()));\n catalog.rebuild();\n await log.settled();\n },\n };\n}\n\nexport async function startMcp(\n registry: Registry,\n options: McpAttach,\n context: Context\n): Promise<{ catalog: McpCatalog; runtime: McpRuntime }> {\n patchSse();\n const catalog = new McpCatalog(registry);\n return { catalog, runtime: await attachMcp(catalog, options, context) };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/owner.js
CHANGED
|
@@ -24,16 +24,17 @@ async function startAgent(options) {
|
|
|
24
24
|
release();
|
|
25
25
|
throw error;
|
|
26
26
|
}
|
|
27
|
-
const open = async () => {
|
|
27
|
+
const open = async (portOptions = {}) => {
|
|
28
28
|
const ready = await worker;
|
|
29
29
|
if (!ready) throw new Error("the agent was released");
|
|
30
30
|
const { port1, port2 } = channel();
|
|
31
|
-
|
|
31
|
+
const readOnly = portOptions.readOnly === true ? { readOnly: true } : {};
|
|
32
|
+
ready.postMessage({ connect: port2, ...readOnly }, [port2]);
|
|
32
33
|
return port1;
|
|
33
34
|
};
|
|
34
35
|
return {
|
|
35
|
-
async connect() {
|
|
36
|
-
return connectAgent(await open());
|
|
36
|
+
async connect(portOptions) {
|
|
37
|
+
return connectAgent(await open(portOptions));
|
|
37
38
|
},
|
|
38
39
|
port: open,
|
|
39
40
|
restart() {
|
package/dist/owner.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/owner.ts"],
|
|
4
|
-
"sourcesContent": ["import { type AgentConnection, connectAgent } from './client.ts';\nimport { holdLock } from './opfs-pool.ts';\nimport type { PortEndpoint } from './wire.ts';\n\nexport const AGENT_LOCK = 'slicc-agent';\n\nexport interface AgentWorker {\n postMessage(message: unknown, transfer: Transferable[]): void;\n terminate(): void;\n}\n\nexport interface AgentOwner {\n connect(): Promise<AgentConnection>;\n port(): Promise<Transferable>;\n restart(): Promise<void>;\n release(): Promise<void>;\n}\n\nexport interface KernelSource {\n connect(): Promise<Transferable>;\n}\n\nexport interface OwnerOptions {\n worker: () => AgentWorker;\n kernel?: KernelSource;\n locks?: LockManager;\n held?: boolean;\n channel?: () => { port1: PortEndpoint & Transferable; port2: Transferable };\n}\n\nexport async function startAgent(options: OwnerOptions): Promise<AgentOwner> {\n const release = options.held\n ? () => undefined\n : await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);\n const channel = options.channel ?? (() => new MessageChannel());\n const spawn = async (): Promise<AgentWorker> => {\n const created = options.worker();\n try {\n if (options.kernel) {\n const port = await options.kernel.connect();\n created.postMessage({ kernel: port }, [port]);\n }\n } catch (error) {\n created.terminate();\n throw error;\n }\n return created;\n };\n let worker: Promise<AgentWorker | undefined> = Promise.resolve().then(spawn);\n try {\n await worker;\n } catch (error) {\n release();\n throw error;\n }\n const open = async (): Promise<PortEndpoint & Transferable> => {\n const ready = await worker;\n if (!ready) throw new Error('the agent was released');\n const { port1, port2 } = channel();\n ready.postMessage({ connect: port2 }, [port2]);\n return port1;\n };\n return {\n async connect() {\n return connectAgent(await open());\n },\n port: open,\n restart() {\n worker = worker\n .catch(() => undefined)\n .then((old) => {\n old?.terminate();\n return spawn();\n });\n return worker.then(() => undefined);\n },\n async release() {\n const previous = worker;\n worker = Promise.resolve(undefined);\n try {\n (await previous.catch(() => undefined))?.terminate();\n } finally {\n release();\n }\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAA+B,oBAAoB;AACnD,SAAS,gBAAgB;AAGlB,MAAM,aAAa;
|
|
4
|
+
"sourcesContent": ["import { type AgentConnection, connectAgent } from './client.ts';\nimport { holdLock } from './opfs-pool.ts';\nimport type { PortEndpoint } from './wire.ts';\n\nexport const AGENT_LOCK = 'slicc-agent';\n\nexport interface AgentWorker {\n postMessage(message: unknown, transfer: Transferable[]): void;\n terminate(): void;\n}\n\nexport interface PortOptions {\n readOnly?: boolean;\n}\n\nexport interface AgentOwner {\n connect(options?: PortOptions): Promise<AgentConnection>;\n port(options?: PortOptions): Promise<PortEndpoint & Transferable>;\n restart(): Promise<void>;\n release(): Promise<void>;\n}\n\nexport interface KernelSource {\n connect(): Promise<Transferable>;\n}\n\nexport interface OwnerOptions {\n worker: () => AgentWorker;\n kernel?: KernelSource;\n locks?: LockManager;\n held?: boolean;\n channel?: () => { port1: PortEndpoint & Transferable; port2: Transferable };\n}\n\nexport async function startAgent(options: OwnerOptions): Promise<AgentOwner> {\n const release = options.held\n ? () => undefined\n : await holdLock(options.locks ?? navigator.locks, AGENT_LOCK);\n const channel = options.channel ?? (() => new MessageChannel());\n const spawn = async (): Promise<AgentWorker> => {\n const created = options.worker();\n try {\n if (options.kernel) {\n const port = await options.kernel.connect();\n created.postMessage({ kernel: port }, [port]);\n }\n } catch (error) {\n created.terminate();\n throw error;\n }\n return created;\n };\n let worker: Promise<AgentWorker | undefined> = Promise.resolve().then(spawn);\n try {\n await worker;\n } catch (error) {\n release();\n throw error;\n }\n const open = async (portOptions: PortOptions = {}): Promise<PortEndpoint & Transferable> => {\n const ready = await worker;\n if (!ready) throw new Error('the agent was released');\n const { port1, port2 } = channel();\n const readOnly = portOptions.readOnly === true ? { readOnly: true } : {};\n ready.postMessage({ connect: port2, ...readOnly }, [port2]);\n return port1;\n };\n return {\n async connect(portOptions) {\n return connectAgent(await open(portOptions));\n },\n port: open,\n restart() {\n worker = worker\n .catch(() => undefined)\n .then((old) => {\n old?.terminate();\n return spawn();\n });\n return worker.then(() => undefined);\n },\n async release() {\n const previous = worker;\n worker = Promise.resolve(undefined);\n try {\n (await previous.catch(() => undefined))?.terminate();\n } finally {\n release();\n }\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAA+B,oBAAoB;AACnD,SAAS,gBAAgB;AAGlB,MAAM,aAAa;AA8B1B,eAAsB,WAAW,SAA4C;AAC3E,QAAM,UAAU,QAAQ,OACpB,MAAM,SACN,MAAM,SAAS,QAAQ,SAAS,UAAU,OAAO,UAAU;AAC/D,QAAM,UAAU,QAAQ,YAAY,MAAM,IAAI,eAAe;AAC7D,QAAM,QAAQ,YAAkC;AAC9C,UAAM,UAAU,QAAQ,OAAO;AAC/B,QAAI;AACF,UAAI,QAAQ,QAAQ;AAClB,cAAM,OAAO,MAAM,QAAQ,OAAO,QAAQ;AAC1C,gBAAQ,YAAY,EAAE,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC;AAAA,MAC9C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,UAAU;AAClB,YAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AACA,MAAI,SAA2C,QAAQ,QAAQ,EAAE,KAAK,KAAK;AAC3E,MAAI;AACF,UAAM;AAAA,EACR,SAAS,OAAO;AACd,YAAQ;AACR,UAAM;AAAA,EACR;AACA,QAAM,OAAO,OAAO,cAA2B,CAAC,MAA4C;AAC1F,UAAM,QAAQ,MAAM;AACpB,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,wBAAwB;AACpD,UAAM,EAAE,OAAO,MAAM,IAAI,QAAQ;AACjC,UAAM,WAAW,YAAY,aAAa,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;AACvE,UAAM,YAAY,EAAE,SAAS,OAAO,GAAG,SAAS,GAAG,CAAC,KAAK,CAAC;AAC1D,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,QAAQ,aAAa;AACzB,aAAO,aAAa,MAAM,KAAK,WAAW,CAAC;AAAA,IAC7C;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AACR,eAAS,OACN,MAAM,MAAM,MAAS,EACrB,KAAK,CAAC,QAAQ;AACb,aAAK,UAAU;AACf,eAAO,MAAM;AAAA,MACf,CAAC;AACH,aAAO,OAAO,KAAK,MAAM,MAAS;AAAA,IACpC;AAAA,IACA,MAAM,UAAU;AACd,YAAM,WAAW;AACjB,eAAS,QAAQ,QAAQ,MAAS;AAClC,UAAI;AACF,SAAC,MAAM,SAAS,MAAM,MAAM,MAAS,IAAI,UAAU;AAAA,MACrD,UAAE;AACA,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/page.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { connectAgent } from "./client.js";
|
|
2
|
+
import {
|
|
3
|
+
bridgePort,
|
|
4
|
+
encodeFrame,
|
|
5
|
+
FrameReader,
|
|
6
|
+
linkEndpoint,
|
|
7
|
+
MAX_FRAME
|
|
8
|
+
} from "./link.js";
|
|
2
9
|
import {
|
|
3
10
|
AGENT_LOCK,
|
|
4
11
|
startAgent
|
|
@@ -6,9 +13,16 @@ import {
|
|
|
6
13
|
import {
|
|
7
14
|
remoteControl
|
|
8
15
|
} from "./remote/client.js";
|
|
16
|
+
import { VIEW_ONLY } from "./view-only.js";
|
|
9
17
|
export {
|
|
10
18
|
AGENT_LOCK,
|
|
19
|
+
FrameReader,
|
|
20
|
+
MAX_FRAME,
|
|
21
|
+
VIEW_ONLY,
|
|
22
|
+
bridgePort,
|
|
11
23
|
connectAgent,
|
|
24
|
+
encodeFrame,
|
|
25
|
+
linkEndpoint,
|
|
12
26
|
remoteControl,
|
|
13
27
|
startAgent
|
|
14
28
|
};
|
package/dist/page.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/page.ts"],
|
|
4
|
-
"sourcesContent": ["export { type AgentConnection, connectAgent } from './client.ts';\nexport {\n AGENT_LOCK,\n type AgentOwner,\n type AgentWorker,\n type KernelSource,\n type OwnerOptions,\n startAgent,\n} from './owner.ts';\nexport {\n type RemoteControl,\n type RemoteEvent,\n type RemoteOptions,\n remoteControl,\n} from './remote/client.ts';\n"],
|
|
5
|
-
"mappings": "AAAA,SAA+B,oBAAoB;AACnD;AAAA,EACE;AAAA,
|
|
4
|
+
"sourcesContent": ["export { type AgentConnection, connectAgent } from './client.ts';\nexport {\n type ByteSink,\n bridgePort,\n encodeFrame,\n FrameReader,\n type LinkEnd,\n linkEndpoint,\n MAX_FRAME,\n} from './link.ts';\nexport {\n AGENT_LOCK,\n type AgentOwner,\n type AgentWorker,\n type KernelSource,\n type OwnerOptions,\n type PortOptions,\n startAgent,\n} from './owner.ts';\nexport {\n type RemoteControl,\n type RemoteEvent,\n type RemoteOptions,\n remoteControl,\n} from './remote/client.ts';\nexport { VIEW_ONLY } from './view-only.ts';\n"],
|
|
5
|
+
"mappings": "AAAA,SAA+B,oBAAoB;AACnD;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAMA;AAAA,OACK;AACP;AAAA,EAIE;AAAA,OACK;AACP,SAAS,iBAAiB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/readonly.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ServerError } from "@earendil-works/pi-server";
|
|
2
|
+
import { VIEW_ONLY } from "./view-only.js";
|
|
3
|
+
const READS = /* @__PURE__ */ new Set(["wait", "changesOpen"]);
|
|
4
|
+
function refuse() {
|
|
5
|
+
return Promise.reject(new ServerError("EACCES", VIEW_ONLY));
|
|
6
|
+
}
|
|
7
|
+
const refused = async () => ({
|
|
8
|
+
accepted: false,
|
|
9
|
+
submissionId: null,
|
|
10
|
+
error: { code: "EACCES", message: VIEW_ONLY }
|
|
11
|
+
});
|
|
12
|
+
const ANSWERS = {
|
|
13
|
+
send: refused,
|
|
14
|
+
compact: refused,
|
|
15
|
+
compactAgent: refused
|
|
16
|
+
};
|
|
17
|
+
function viewControl(control) {
|
|
18
|
+
const view = {};
|
|
19
|
+
for (const [key, value] of Object.entries(control))
|
|
20
|
+
view[key] = typeof value !== "function" || READS.has(key) ? value : ANSWERS[key] ?? refuse;
|
|
21
|
+
return view;
|
|
22
|
+
}
|
|
23
|
+
function viewSettings(settings) {
|
|
24
|
+
return { state: settings.state, connect: refuse, disconnect: refuse, signIn: refuse };
|
|
25
|
+
}
|
|
26
|
+
function viewRemote(remote) {
|
|
27
|
+
const mine = /* @__PURE__ */ new Set();
|
|
28
|
+
return {
|
|
29
|
+
prompt: async () => ({ ok: false, code: "EACCES", message: VIEW_ONLY }),
|
|
30
|
+
async watch(request, context) {
|
|
31
|
+
const opened = await remote.watch(request, context);
|
|
32
|
+
if (opened.ok) mine.add(opened.stream);
|
|
33
|
+
return opened;
|
|
34
|
+
},
|
|
35
|
+
async next(stream, waitMs, context) {
|
|
36
|
+
if (!mine.has(stream)) return { events: [], done: true };
|
|
37
|
+
const batch = await remote.next(stream, waitMs, context);
|
|
38
|
+
if (batch.done) mine.delete(stream);
|
|
39
|
+
return batch;
|
|
40
|
+
},
|
|
41
|
+
async close(stream, context) {
|
|
42
|
+
if (!mine.delete(stream)) return;
|
|
43
|
+
await remote.close(stream, context);
|
|
44
|
+
},
|
|
45
|
+
abort: refuse
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
VIEW_ONLY,
|
|
50
|
+
viewControl,
|
|
51
|
+
viewRemote,
|
|
52
|
+
viewSettings
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=readonly.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/readonly.ts"],
|
|
4
|
+
"sourcesContent": ["import { ServerError } from '@earendil-works/pi-server';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentControl, AgentSettings } from './services.ts';\nimport { VIEW_ONLY } from './view-only.ts';\n\nexport { VIEW_ONLY };\n\nconst READS = new Set<keyof AgentControl>(['wait', 'changesOpen']);\n\nfunction refuse(): Promise<never> {\n return Promise.reject(new ServerError('EACCES' as never, VIEW_ONLY));\n}\n\nconst refused = async () => ({\n accepted: false as const,\n submissionId: null,\n error: { code: 'EACCES', message: VIEW_ONLY },\n});\n\nconst ANSWERS: Partial<Record<keyof AgentControl, () => Promise<unknown>>> = {\n send: refused,\n compact: refused,\n compactAgent: refused,\n};\n\nexport function viewControl(control: AgentControl): AgentControl {\n const view: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(control))\n view[key] =\n typeof value !== 'function' || READS.has(key as keyof AgentControl)\n ? value\n : (ANSWERS[key as keyof AgentControl] ?? refuse);\n return view as unknown as AgentControl;\n}\n\nexport function viewSettings(settings: AgentSettings): AgentSettings {\n return { state: settings.state, connect: refuse, disconnect: refuse, signIn: refuse };\n}\n\nexport function viewRemote(remote: AgentRemote): AgentRemote {\n const mine = new Set<string>();\n return {\n prompt: async () => ({ ok: false, code: 'EACCES', message: VIEW_ONLY }),\n async watch(request, context) {\n const opened = await remote.watch(request, context);\n if (opened.ok) mine.add(opened.stream);\n return opened;\n },\n async next(stream, waitMs, context) {\n if (!mine.has(stream)) return { events: [], done: true };\n const batch = await remote.next(stream, waitMs, context);\n if (batch.done) mine.delete(stream);\n return batch;\n },\n async close(stream, context) {\n if (!mine.delete(stream)) return;\n await remote.close(stream, context);\n },\n abort: refuse,\n };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,mBAAmB;AAG5B,SAAS,iBAAiB;AAI1B,MAAM,QAAQ,oBAAI,IAAwB,CAAC,QAAQ,aAAa,CAAC;AAEjE,SAAS,SAAyB;AAChC,SAAO,QAAQ,OAAO,IAAI,YAAY,UAAmB,SAAS,CAAC;AACrE;AAEA,MAAM,UAAU,aAAa;AAAA,EAC3B,UAAU;AAAA,EACV,cAAc;AAAA,EACd,OAAO,EAAE,MAAM,UAAU,SAAS,UAAU;AAC9C;AAEA,MAAM,UAAuE;AAAA,EAC3E,MAAM;AAAA,EACN,SAAS;AAAA,EACT,cAAc;AAChB;AAEO,SAAS,YAAY,SAAqC;AAC/D,QAAM,OAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO;AAC/C,SAAK,GAAG,IACN,OAAO,UAAU,cAAc,MAAM,IAAI,GAAyB,IAC9D,QACC,QAAQ,GAAyB,KAAK;AAC/C,SAAO;AACT;AAEO,SAAS,aAAa,UAAwC;AACnE,SAAO,EAAE,OAAO,SAAS,OAAO,SAAS,QAAQ,YAAY,QAAQ,QAAQ,OAAO;AACtF;AAEO,SAAS,WAAW,QAAkC;AAC3D,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO;AAAA,IACL,QAAQ,aAAa,EAAE,IAAI,OAAO,MAAM,UAAU,SAAS,UAAU;AAAA,IACrE,MAAM,MAAM,SAAS,SAAS;AAC5B,YAAM,SAAS,MAAM,OAAO,MAAM,SAAS,OAAO;AAClD,UAAI,OAAO,GAAI,MAAK,IAAI,OAAO,MAAM;AACrC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,KAAK,QAAQ,QAAQ,SAAS;AAClC,UAAI,CAAC,KAAK,IAAI,MAAM,EAAG,QAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,KAAK;AACvD,YAAM,QAAQ,MAAM,OAAO,KAAK,QAAQ,QAAQ,OAAO;AACvD,UAAI,MAAM,KAAM,MAAK,OAAO,MAAM;AAClC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,MAAM,QAAQ,SAAS;AAC3B,UAAI,CAAC,KAAK,OAAO,MAAM,EAAG;AAC1B,YAAM,OAAO,MAAM,QAAQ,OAAO;AAAA,IACpC;AAAA,IACA,OAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/remote/events.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { entryLick } from "../licks/state.js";
|
|
2
|
+
import { mcpLabel } from "../mcp/label.js";
|
|
2
3
|
import { redactSecrets } from "../memory/format.js";
|
|
3
4
|
const SUMMARY = 200;
|
|
4
5
|
function clip(text, limit = SUMMARY) {
|
|
@@ -13,7 +14,7 @@ const SUBJECTS = ["command", "path", "pattern", "url", "query", "code", "name"];
|
|
|
13
14
|
function toolSummary(name, args) {
|
|
14
15
|
const subject = SUBJECTS.map((key) => args?.[key]).find((value) => typeof value === "string");
|
|
15
16
|
const detail = typeof subject === "string" ? subject : JSON.stringify(args ?? {});
|
|
16
|
-
return clip(`${name} ${detail}`);
|
|
17
|
+
return clip(`${mcpLabel(name) ?? name} ${detail}`);
|
|
17
18
|
}
|
|
18
19
|
function resultSummary(message) {
|
|
19
20
|
const text = textOf(message.content);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/remote/events.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AgentEvent, EntryRecord } from '@earendil-works/pi-durable';\nimport { entryLick } from '../licks/state.ts';\nimport { redactSecrets } from '../memory/format.ts';\nimport type { Delivered } from '../services.ts';\n\nexport const SUMMARY = 200;\n\nexport type EndReason = 'stop' | 'aborted' | 'error';\n\nexport type RemoteEvent =\n | { t: 'user'; id: string; text: string; delivered: Delivered }\n | { t: 'text'; id: string; delta: string }\n | { t: 'thinking'; id: string; delta: string }\n | { t: 'tool'; id: string; call: string; name: string; summary: string }\n | { t: 'tool-result'; id: string; call: string; ok: boolean; summary: string }\n | { t: 'run'; state: 'start' | 'end'; reason?: EndReason }\n | { t: 'notice'; id: string; text: string }\n | { t: 'error'; id: string; message: string; detail?: string }\n | { t: 'end'; reason: EndReason; message?: string };\n\ntype Json = Record<string, unknown>;\n\ninterface Block {\n type: string;\n text?: string;\n thinking?: string;\n id?: string;\n name?: string;\n arguments?: Json;\n}\n\ninterface ModelMessage {\n role?: string;\n content?: string | Block[];\n timestamp?: number;\n stopReason?: string;\n errorMessage?: string;\n toolCallId?: string;\n toolName?: string;\n isError?: boolean;\n}\n\nexport function clip(text: string, limit = SUMMARY): string {\n const flat = redactSecrets(text).text.replace(/\\s+/g, ' ').trim();\n return flat.length > limit ? `${flat.slice(0, limit - 1)}\u2026` : flat;\n}\n\nfunction textOf(content: ModelMessage['content']): string {\n if (typeof content === 'string') return content;\n return (content ?? []).map((block) => (block.type === 'text' ? (block.text ?? '') : '')).join('');\n}\n\nconst SUBJECTS = ['command', 'path', 'pattern', 'url', 'query', 'code', 'name'];\n\nexport function toolSummary(name: string, args: Json | undefined): string {\n const subject = SUBJECTS.map((key) => args?.[key]).find((value) => typeof value === 'string');\n const detail = typeof subject === 'string' ? subject : JSON.stringify(args ?? {});\n return clip(`${name} ${detail}`);\n}\n\nexport function resultSummary(message: ModelMessage): string {\n const text = textOf(message.content);\n if (message.isError)\n return clip(\n text.split('\\n').find((line) => line.trim() && !/^<\\/?harness>$/.test(line.trim())) ??\n 'failed'\n );\n const lines = text ? text.split('\\n').length : 0;\n return lines === 1 ? '1 line' : `${lines} lines`;\n}\n\nexport function keyOf(timestamp: number | undefined, at: number): string {\n return at > 0 ? `a${timestamp ?? 0}~${at}` : `a${timestamp ?? 0}`;\n}\n\nfunction reasonOf(stopReason: string | undefined): EndReason {\n if (stopReason === 'aborted') return 'aborted';\n return stopReason === 'error' ? 'error' : 'stop';\n}\n\nexport class Mapper {\n readonly #thinking: boolean;\n readonly #emitted = new Map<string, number>();\n readonly #seen = new Set<number>();\n #current: string | undefined;\n #previous: string | undefined;\n #ending: readonly number[] = [];\n readonly #stamps = new Map<number, number[]>();\n reason: EndReason = 'stop';\n error: string | undefined;\n\n constructor(thinking: boolean) {\n this.#thinking = thinking;\n }\n\n #key(message: ModelMessage | undefined, entry?: number): string {\n const stamp = message?.timestamp ?? 0;\n const list = this.#stamps.get(stamp) ?? [];\n this.#stamps.set(stamp, list);\n if (entry === undefined) return keyOf(stamp, list.length);\n if (!list.includes(entry)) list.push(entry);\n return keyOf(stamp, list.indexOf(entry));\n }\n\n #delta(t: 'text' | 'thinking', id: string, full: string): RemoteEvent[] {\n if (t === 'thinking' && !this.#thinking) return [];\n const done = this.#emitted.get(id) ?? 0;\n if (full.length <= done) return [];\n this.#emitted.set(id, full.length);\n return [{ t, id, delta: full.slice(done) }];\n }\n\n partial(message: ModelMessage | undefined): RemoteEvent[] {\n if (!message || !Array.isArray(message.content)) return [];\n const key = this.#key(message);\n this.#current = key;\n return message.content.flatMap((block, at) => {\n if (block.type === 'text') return this.#delta('text', `${key}.${at}`, block.text ?? '');\n if (block.type === 'thinking')\n return this.#delta('thinking', `${key}.${at}`, block.thinking ?? '');\n return [];\n });\n }\n\n #assistant(message: ModelMessage, entry: EntryRecord): RemoteEvent[] {\n const key = this.#key(message, entry.id);\n const blocks = Array.isArray(message.content) ? message.content : [];\n const out = blocks.flatMap((block, at): RemoteEvent[] => {\n const id = `${key}.${at}`;\n if (block.type === 'text') return this.#delta('text', id, block.text ?? '');\n if (block.type === 'thinking') return this.#delta('thinking', id, block.thinking ?? '');\n if (block.type !== 'toolCall') return [];\n const name = block.name ?? 'tool';\n return [\n { t: 'tool', id, call: block.id ?? '', name, summary: toolSummary(name, block.arguments) },\n ];\n });\n if (typeof message.content === 'string')\n out.push(...this.#delta('text', `${key}.0`, message.content));\n this.reason = reasonOf(message.stopReason);\n this.error =\n message.stopReason === 'error' ? clip(message.errorMessage ?? 'error', 1000) : undefined;\n if (this.error) out.push({ t: 'error', id: String(entry.id), message: this.error });\n return out;\n }\n\n #delivered(entry: EntryRecord): Delivered {\n if (entry.byTaskId === undefined) return 'run';\n return this.#previous === 'pi.tool-result' ? 'steer' : 'follow-up';\n }\n\n entry(entry: EntryRecord, delivered: Delivered = this.#delivered(entry)): RemoteEvent[] {\n if (this.#seen.has(entry.id)) return [];\n this.#seen.add(entry.id);\n this.#previous = entry.kind;\n const id = String(entry.id);\n const message = entry.model?.[0] as ModelMessage | undefined;\n if (entry.kind === 'pi.user' && message) {\n const lick = entryLick(entry);\n if (lick) return [{ t: 'notice', id, text: clip(`lick ${lick.channel}: ${lick.title}`) }];\n return [{ t: 'user', id, text: redactSecrets(textOf(message.content)).text, delivered }];\n }\n if (entry.kind === 'pi.assistant' && message) return this.#assistant(message, entry);\n if (entry.kind === 'pi.tool-result' && message)\n return [\n {\n t: 'tool-result',\n id,\n call: message.toolCallId ?? '',\n ok: !message.isError,\n summary: resultSummary(message),\n },\n ];\n if (entry.kind === 'pi.compaction') return [{ t: 'notice', id, text: 'compacted' }];\n if (entry.kind === 'slicc.rewound') return [{ t: 'notice', id, text: 'rewound' }];\n return [];\n }\n\n #ended(reason: EndReason): RemoteEvent[] {\n if (!this.#ending.length) return [];\n this.#ending = [];\n this.reason = reason;\n return [{ t: 'run', state: 'end', reason }];\n }\n\n #settled(record: { id: number; status: string; reason?: unknown }): RemoteEvent[] {\n if (!this.#ending.includes(record.id) || ['queued', 'placed'].includes(record.status))\n return [];\n if (record.status !== 'unanswered') return this.#ended(this.reason);\n if (record.reason === 'aborted') return this.#ended('aborted');\n this.error ??= clip(String(record.reason ?? 'error'), 1000);\n return this.#ended('error');\n }\n\n last(): number {\n return Math.max(0, ...this.#seen);\n }\n\n event(event: AgentEvent): RemoteEvent[] {\n switch (event.type) {\n case 'run_start': {\n const ended = this.#ended(this.reason);\n this.reason = 'stop';\n this.error = undefined;\n return [...ended, { t: 'run', state: 'start' }];\n }\n case 'run_end':\n this.#ending = event.inputs;\n return [];\n case 'submission':\n return this.#settled(event.record);\n case 'message_start':\n if ((event.message as ModelMessage).role === 'assistant')\n this.#current = this.#key(event.message as ModelMessage);\n return [];\n case 'message_update':\n return event.changes.flatMap((change) => {\n if (change.type !== 'text_delta' && change.type !== 'thinking_delta') return [];\n const id = `${this.#current}.${change.contentIndex}`;\n const t = change.type === 'text_delta' ? 'text' : 'thinking';\n const done = this.#emitted.get(id) ?? 0;\n if (t === 'thinking' && !this.#thinking) return [];\n this.#emitted.set(id, done + change.delta.length);\n return [{ t, id, delta: change.delta }];\n });\n case 'message_end':\n return this.entry(event.entry);\n case 'tool_execution_end':\n return event.entry\n ? this.entry(event.entry)\n : [\n {\n t: 'tool-result',\n id: `x${event.toolCallId}`,\n call: event.toolCallId,\n ok: false,\n summary: 'the tool did not finish',\n },\n ];\n case 'auto_retry_start':\n return [\n {\n t: 'notice',\n id: `retry-${event.at}`,\n text: clip(`retrying (attempt ${event.attempt}): ${event.errorMessage}`),\n },\n ];\n case 'compaction_start':\n return [{ t: 'notice', id: `compaction-${event.taskId}`, text: 'compacting' }];\n case 'task_failed':\n return [{ t: 'error', id: `task-${event.taskId}`, message: clip(event.message, 1000) }];\n default:\n return [];\n }\n }\n}\n\nexport function replayOf(\n entries: readonly EntryRecord[],\n partial: ModelMessage | undefined,\n thinking: boolean\n): RemoteEvent[] {\n const mapper = new Mapper(thinking);\n const out: RemoteEvent[] = [];\n for (const entry of entries) out.push(...mapper.entry(entry));\n out.push(...mapper.partial(partial));\n return out;\n}\n\nexport function idOf(event: RemoteEvent): string | undefined {\n return 'id' in event ? event.id : undefined;\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAGvB,MAAM,UAAU;AAqChB,SAAS,KAAK,MAAc,QAAQ,SAAiB;AAC1D,QAAM,OAAO,cAAc,IAAI,EAAE,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAChE,SAAO,KAAK,SAAS,QAAQ,GAAG,KAAK,MAAM,GAAG,QAAQ,CAAC,CAAC,WAAM;AAChE;AAEA,SAAS,OAAO,SAA0C;AACxD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,UAAQ,WAAW,CAAC,GAAG,IAAI,CAAC,UAAW,MAAM,SAAS,SAAU,MAAM,QAAQ,KAAM,EAAG,EAAE,KAAK,EAAE;AAClG;AAEA,MAAM,WAAW,CAAC,WAAW,QAAQ,WAAW,OAAO,SAAS,QAAQ,MAAM;AAEvE,SAAS,YAAY,MAAc,MAAgC;AACxE,QAAM,UAAU,SAAS,IAAI,CAAC,QAAQ,OAAO,GAAG,CAAC,EAAE,KAAK,CAAC,UAAU,OAAO,UAAU,QAAQ;AAC5F,QAAM,SAAS,OAAO,YAAY,WAAW,UAAU,KAAK,UAAU,QAAQ,CAAC,CAAC;AAChF,SAAO,KAAK,GAAG,IAAI,IAAI,MAAM,EAAE;
|
|
4
|
+
"sourcesContent": ["import type { AgentEvent, EntryRecord } from '@earendil-works/pi-durable';\nimport { entryLick } from '../licks/state.ts';\nimport { mcpLabel } from '../mcp/label.ts';\nimport { redactSecrets } from '../memory/format.ts';\nimport type { Delivered } from '../services.ts';\n\nexport const SUMMARY = 200;\n\nexport type EndReason = 'stop' | 'aborted' | 'error';\n\nexport type RemoteEvent =\n | { t: 'user'; id: string; text: string; delivered: Delivered }\n | { t: 'text'; id: string; delta: string }\n | { t: 'thinking'; id: string; delta: string }\n | { t: 'tool'; id: string; call: string; name: string; summary: string }\n | { t: 'tool-result'; id: string; call: string; ok: boolean; summary: string }\n | { t: 'run'; state: 'start' | 'end'; reason?: EndReason }\n | { t: 'notice'; id: string; text: string }\n | { t: 'error'; id: string; message: string; detail?: string }\n | { t: 'end'; reason: EndReason; message?: string };\n\ntype Json = Record<string, unknown>;\n\ninterface Block {\n type: string;\n text?: string;\n thinking?: string;\n id?: string;\n name?: string;\n arguments?: Json;\n}\n\ninterface ModelMessage {\n role?: string;\n content?: string | Block[];\n timestamp?: number;\n stopReason?: string;\n errorMessage?: string;\n toolCallId?: string;\n toolName?: string;\n isError?: boolean;\n}\n\nexport function clip(text: string, limit = SUMMARY): string {\n const flat = redactSecrets(text).text.replace(/\\s+/g, ' ').trim();\n return flat.length > limit ? `${flat.slice(0, limit - 1)}\u2026` : flat;\n}\n\nfunction textOf(content: ModelMessage['content']): string {\n if (typeof content === 'string') return content;\n return (content ?? []).map((block) => (block.type === 'text' ? (block.text ?? '') : '')).join('');\n}\n\nconst SUBJECTS = ['command', 'path', 'pattern', 'url', 'query', 'code', 'name'];\n\nexport function toolSummary(name: string, args: Json | undefined): string {\n const subject = SUBJECTS.map((key) => args?.[key]).find((value) => typeof value === 'string');\n const detail = typeof subject === 'string' ? subject : JSON.stringify(args ?? {});\n return clip(`${mcpLabel(name) ?? name} ${detail}`);\n}\n\nexport function resultSummary(message: ModelMessage): string {\n const text = textOf(message.content);\n if (message.isError)\n return clip(\n text.split('\\n').find((line) => line.trim() && !/^<\\/?harness>$/.test(line.trim())) ??\n 'failed'\n );\n const lines = text ? text.split('\\n').length : 0;\n return lines === 1 ? '1 line' : `${lines} lines`;\n}\n\nexport function keyOf(timestamp: number | undefined, at: number): string {\n return at > 0 ? `a${timestamp ?? 0}~${at}` : `a${timestamp ?? 0}`;\n}\n\nfunction reasonOf(stopReason: string | undefined): EndReason {\n if (stopReason === 'aborted') return 'aborted';\n return stopReason === 'error' ? 'error' : 'stop';\n}\n\nexport class Mapper {\n readonly #thinking: boolean;\n readonly #emitted = new Map<string, number>();\n readonly #seen = new Set<number>();\n #current: string | undefined;\n #previous: string | undefined;\n #ending: readonly number[] = [];\n readonly #stamps = new Map<number, number[]>();\n reason: EndReason = 'stop';\n error: string | undefined;\n\n constructor(thinking: boolean) {\n this.#thinking = thinking;\n }\n\n #key(message: ModelMessage | undefined, entry?: number): string {\n const stamp = message?.timestamp ?? 0;\n const list = this.#stamps.get(stamp) ?? [];\n this.#stamps.set(stamp, list);\n if (entry === undefined) return keyOf(stamp, list.length);\n if (!list.includes(entry)) list.push(entry);\n return keyOf(stamp, list.indexOf(entry));\n }\n\n #delta(t: 'text' | 'thinking', id: string, full: string): RemoteEvent[] {\n if (t === 'thinking' && !this.#thinking) return [];\n const done = this.#emitted.get(id) ?? 0;\n if (full.length <= done) return [];\n this.#emitted.set(id, full.length);\n return [{ t, id, delta: full.slice(done) }];\n }\n\n partial(message: ModelMessage | undefined): RemoteEvent[] {\n if (!message || !Array.isArray(message.content)) return [];\n const key = this.#key(message);\n this.#current = key;\n return message.content.flatMap((block, at) => {\n if (block.type === 'text') return this.#delta('text', `${key}.${at}`, block.text ?? '');\n if (block.type === 'thinking')\n return this.#delta('thinking', `${key}.${at}`, block.thinking ?? '');\n return [];\n });\n }\n\n #assistant(message: ModelMessage, entry: EntryRecord): RemoteEvent[] {\n const key = this.#key(message, entry.id);\n const blocks = Array.isArray(message.content) ? message.content : [];\n const out = blocks.flatMap((block, at): RemoteEvent[] => {\n const id = `${key}.${at}`;\n if (block.type === 'text') return this.#delta('text', id, block.text ?? '');\n if (block.type === 'thinking') return this.#delta('thinking', id, block.thinking ?? '');\n if (block.type !== 'toolCall') return [];\n const name = block.name ?? 'tool';\n return [\n { t: 'tool', id, call: block.id ?? '', name, summary: toolSummary(name, block.arguments) },\n ];\n });\n if (typeof message.content === 'string')\n out.push(...this.#delta('text', `${key}.0`, message.content));\n this.reason = reasonOf(message.stopReason);\n this.error =\n message.stopReason === 'error' ? clip(message.errorMessage ?? 'error', 1000) : undefined;\n if (this.error) out.push({ t: 'error', id: String(entry.id), message: this.error });\n return out;\n }\n\n #delivered(entry: EntryRecord): Delivered {\n if (entry.byTaskId === undefined) return 'run';\n return this.#previous === 'pi.tool-result' ? 'steer' : 'follow-up';\n }\n\n entry(entry: EntryRecord, delivered: Delivered = this.#delivered(entry)): RemoteEvent[] {\n if (this.#seen.has(entry.id)) return [];\n this.#seen.add(entry.id);\n this.#previous = entry.kind;\n const id = String(entry.id);\n const message = entry.model?.[0] as ModelMessage | undefined;\n if (entry.kind === 'pi.user' && message) {\n const lick = entryLick(entry);\n if (lick) return [{ t: 'notice', id, text: clip(`lick ${lick.channel}: ${lick.title}`) }];\n return [{ t: 'user', id, text: redactSecrets(textOf(message.content)).text, delivered }];\n }\n if (entry.kind === 'pi.assistant' && message) return this.#assistant(message, entry);\n if (entry.kind === 'pi.tool-result' && message)\n return [\n {\n t: 'tool-result',\n id,\n call: message.toolCallId ?? '',\n ok: !message.isError,\n summary: resultSummary(message),\n },\n ];\n if (entry.kind === 'pi.compaction') return [{ t: 'notice', id, text: 'compacted' }];\n if (entry.kind === 'slicc.rewound') return [{ t: 'notice', id, text: 'rewound' }];\n return [];\n }\n\n #ended(reason: EndReason): RemoteEvent[] {\n if (!this.#ending.length) return [];\n this.#ending = [];\n this.reason = reason;\n return [{ t: 'run', state: 'end', reason }];\n }\n\n #settled(record: { id: number; status: string; reason?: unknown }): RemoteEvent[] {\n if (!this.#ending.includes(record.id) || ['queued', 'placed'].includes(record.status))\n return [];\n if (record.status !== 'unanswered') return this.#ended(this.reason);\n if (record.reason === 'aborted') return this.#ended('aborted');\n this.error ??= clip(String(record.reason ?? 'error'), 1000);\n return this.#ended('error');\n }\n\n last(): number {\n return Math.max(0, ...this.#seen);\n }\n\n event(event: AgentEvent): RemoteEvent[] {\n switch (event.type) {\n case 'run_start': {\n const ended = this.#ended(this.reason);\n this.reason = 'stop';\n this.error = undefined;\n return [...ended, { t: 'run', state: 'start' }];\n }\n case 'run_end':\n this.#ending = event.inputs;\n return [];\n case 'submission':\n return this.#settled(event.record);\n case 'message_start':\n if ((event.message as ModelMessage).role === 'assistant')\n this.#current = this.#key(event.message as ModelMessage);\n return [];\n case 'message_update':\n return event.changes.flatMap((change) => {\n if (change.type !== 'text_delta' && change.type !== 'thinking_delta') return [];\n const id = `${this.#current}.${change.contentIndex}`;\n const t = change.type === 'text_delta' ? 'text' : 'thinking';\n const done = this.#emitted.get(id) ?? 0;\n if (t === 'thinking' && !this.#thinking) return [];\n this.#emitted.set(id, done + change.delta.length);\n return [{ t, id, delta: change.delta }];\n });\n case 'message_end':\n return this.entry(event.entry);\n case 'tool_execution_end':\n return event.entry\n ? this.entry(event.entry)\n : [\n {\n t: 'tool-result',\n id: `x${event.toolCallId}`,\n call: event.toolCallId,\n ok: false,\n summary: 'the tool did not finish',\n },\n ];\n case 'auto_retry_start':\n return [\n {\n t: 'notice',\n id: `retry-${event.at}`,\n text: clip(`retrying (attempt ${event.attempt}): ${event.errorMessage}`),\n },\n ];\n case 'compaction_start':\n return [{ t: 'notice', id: `compaction-${event.taskId}`, text: 'compacting' }];\n case 'task_failed':\n return [{ t: 'error', id: `task-${event.taskId}`, message: clip(event.message, 1000) }];\n default:\n return [];\n }\n }\n}\n\nexport function replayOf(\n entries: readonly EntryRecord[],\n partial: ModelMessage | undefined,\n thinking: boolean\n): RemoteEvent[] {\n const mapper = new Mapper(thinking);\n const out: RemoteEvent[] = [];\n for (const entry of entries) out.push(...mapper.entry(entry));\n out.push(...mapper.partial(partial));\n return out;\n}\n\nexport function idOf(event: RemoteEvent): string | undefined {\n return 'id' in event ? event.id : undefined;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/remote/host.js
CHANGED
|
@@ -159,7 +159,7 @@ function createRemote(host) {
|
|
|
159
159
|
now(),
|
|
160
160
|
host.limit ?? MAX_QUEUED
|
|
161
161
|
);
|
|
162
|
-
const id = `s${++serial}`;
|
|
162
|
+
const id = `s${++serial}-${crypto.randomUUID()}`;
|
|
163
163
|
track(id, stream);
|
|
164
164
|
events.start(async (batch) => onEvents(batch));
|
|
165
165
|
return { id, stream };
|