@ai-ecoverse/slicc-agent 3.36.0 → 3.36.2
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 +2 -2
- package/dist/mcp/oauth.js +15 -7
- package/dist/mcp/oauth.js.map +2 -2
- package/dist/scoops/index.js +27 -8
- package/dist/scoops/index.js.map +2 -2
- package/dist/spectrum/mcp-login.js +4 -4
- package/dist/spectrum/mcp-login.js.map +2 -2
- package/dist/types/mcp/oauth.d.ts +1 -0
- package/dist/types/scoops/index.d.ts +5 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -182,7 +182,7 @@ A **cone** is a top-level agent the user talks to; a **scoop** is a helper agent
|
|
|
182
182
|
|
|
183
183
|
Each scoop is owned by a background durable task, its **anchor**, so Stop in its cone doesn't end it; `agent stop` and Drop do. They keep its transcript in durable storage and its reports in `/scoops/<handle>/reports/`, which report licks point at, and remove its working folder `/scoops/<handle>/workspace`. Each request handed to a scoop gets a **reporter** task that waits for the run, writes the answer to `/scoops/<handle>/reports/<task id>.md` and reports it to whoever asked. Requests from a cone's or a scoop's bash report into that agent's `pi.inbox` as a lick, through the licks outbox, so they steer and coalesce like any lick; requests from a terminal report nowhere, and a user typing in a scoop's chat gets the answer there. In a scoop's chat, a request someone else sent shows who sent it (`UserMessage.from`): the asking cone's or scoop's name, with `origin: 'agent'` so spectrum (≥ 1.26.0) shows it as that agent's request with an "Agent" badge, or `terminal` without an origin. Once the request is placed, the reporter appends a `slicc.from` entry (`{ from, entry }`) without model messages, and the adapter sets `from` on that entry's message. A request withdrawn before it was placed leaves no entry. The report lick's text is `[scoop <name> (<role>) <status>]`, and its body has the report's path and line count and a 1,000-character preview. A scoop's prompt has a `scoop` section with its name, its cone, its folders and its limits. The `agent` command is taught by the built-in `agent` skill (below), whose description says that roles exist and that `agent list --agents` lists them.
|
|
184
184
|
|
|
185
|
-
**The `agent` command.** One bash script is installed
|
|
185
|
+
**The `agent` command.** One bash script is installed under seven names, `agent`, `subagent`, `sprinkle`, `memory`, `gelatiere`, `freezer` and `models`, in `/usr/local/bin`, mode `0755`, which is on every user's `PATH`, so the user, a cone or a scoop runs it in any bash. A file at one of those paths that isn't slicc-agent's (it lacks the `slicc-agent/1` request marker), such as a root global package's `memory`, is left alone, with one warning per boot in the worker's console. Root's `$PNPM_HOME/bin` (`/usr/local/share/pnpm/bin`) is on root's `PATH` only: other users find root's installed packages in `/usr/bin`, but not loose scripts. Copies that earlier versions put in the kernel's `$PNPM_HOME/bin` are removed, so they can't shadow the new ones for root. It writes a request (NUL-separated fields: `slicc-agent/1`, the name it was called by, `$SLICC_AGENT`, the working directory, its pid, argc, the arguments and stdin) to `/var/lib/slicc/agent/requests/in/` and polls `out/` for the answer and exit code; the worker writes `<id>.ack` when it picks a request up, and the script gives up after 20 s without one ("the SLICC agent isn't running", exit 1). The kernel environment exports `SLICC_AGENT`, `PI_PROVIDER`, `PI_MODEL` and `PI_REASONING_LEVEL` into every agent command, so the worker knows the caller. `/var/lib/slicc/agent/scoops/<handle>/metadata.json` mirrors each scoop's state for scripts.
|
|
186
186
|
- `agent [options] --prompt <text>` (or `--file <path>`, or `-` for stdin, or v6's `agent <cwd> <allowed-commands> <prompt>`) is **synchronous**: an ephemeral scoop runs, and the command prints its final answer and exits 0, or prints the reason on stderr and exits 1. Afterwards the scoop is stopped, its folder removed, its conversation marked dropped and its transcript archived as Markdown in `/tmp/agent-sessions/<handle>.md` (`/home/sessions/` with `--persist-session`, nowhere with `--no-persist-session`). The script traps TERM, INT and HUP and cancels the scoop, and the worker checks the script's pid in the kernel's process table every second for a SIGKILL. If the caller's run is still going when the bash call dies (a bash timeout), the scoop continues and its answer arrives as a `bash` lick; `--background-after <seconds>` does the same on purpose. Sync calls nest up to three deep. Allowed commands in v6's form are told to the scoop, not enforced.
|
|
187
187
|
- `agent --async …` (= `subagent spawn …`) starts a persistent scoop and prints its handle; only cones start them. `list [--agents]`, `status`, `rename`, `send [--follow-up]`, `wait [--timeout <s>] [--notify]` (30 s by default; `--notify` returns at once and brings the answers together in one `scoop-wait` lick) and `stop` manage them. From a scoop, `agent send parent "<note>"` posts a progress note to its cone as a lick, coalesced per scoop.
|
|
188
188
|
- Options: `--name`, `--agent <role>`, `--model <provider/model>`, `--thinking <level>` or `--effort low|medium|high|max`, `--tools <a,b>` (or `auto`/`full`), `--read-only <paths>`, `--system-prompt[-file]`. `--schema-b64`, `--tools output`, `--session`, `--resume`, `--image`, `--no-escalate`, `--minimal` and `--usage` answer "not supported in SLICC yet".
|
|
@@ -457,7 +457,7 @@ The built-in `sprinkles` skill teaches the files, the bridge and the command.
|
|
|
457
457
|
|
|
458
458
|
## The kernel
|
|
459
459
|
|
|
460
|
-
`@ai-ecoverse/slicc-kernel` is a peer dependency (`>=1.47.1 <2`, the first kernel with users and the H1 wasm commands; until 3.31.0 the floor was 1.26.3): the agent only calls `attachKernel`, the client end of the page kernel's port, so it must match the kernel the page runs. Seven chooses that version: slicc-bios pins the certified kernel next to the agent in `src/packages/agent/package.json`, and pnpm fills the peer from that pin. The devDependency is the lowest certified kernel the range allows (1.47.1), and the unit and integration tests run on it with the H1 wasm commands as devDependencies. A second CI job reruns the integration tests on the newest certified kernel (`NEWEST_CERTIFIED_KERNEL` in `.github/workflows/ci.yml`), with the wasm commands certified for it (`NEWEST_CERTIFIED_WASM`). Today the floor is 1.47.1 and the newest certified kernel 1.
|
|
460
|
+
`@ai-ecoverse/slicc-kernel` is a peer dependency (`>=1.47.1 <2`, the first kernel with users and the H1 wasm commands; until 3.31.0 the floor was 1.26.3): the agent only calls `attachKernel`, the client end of the page kernel's port, so it must match the kernel the page runs. Seven chooses that version: slicc-bios pins the certified kernel next to the agent in `src/packages/agent/package.json`, and pnpm fills the peer from that pin. The devDependency is the lowest certified kernel the range allows (1.47.1), and the unit and integration tests run on it with the H1 wasm commands as devDependencies. A second CI job reruns the integration tests on the newest certified kernel (`NEWEST_CERTIFIED_KERNEL` in `.github/workflows/ci.yml`), with the wasm commands certified for it (`NEWEST_CERTIFIED_WASM`). Today the floor is 1.47.1 and the newest certified kernel 1.56.0; the second job moves by hand to each newer certified kernel, so both ends of the range are tested. If the port protocol ever breaks within 1.x, a patch release narrows the upper bound. Renovate opens PRs for the kernel, `wasi-pnpm`, the `wasm-*` packages and `slicc-cdp` but never merges them, because they move only after the homescoop coordinator certifies them.
|
|
461
461
|
|
|
462
462
|
## Patched dependencies
|
|
463
463
|
|
package/dist/mcp/oauth.js
CHANGED
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
} from "@earendil-works/pi-mcp/oauth";
|
|
10
10
|
import { credentialKey } from "./credentials.js";
|
|
11
11
|
const CLIENT_NAME = "SLICC";
|
|
12
|
+
function sentence(text) {
|
|
13
|
+
const trimmed = text.trim();
|
|
14
|
+
const lowered = /^[A-Z][a-z]/.test(trimmed) ? `${trimmed[0]?.toLowerCase()}${trimmed.slice(1)}` : trimmed;
|
|
15
|
+
return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;
|
|
16
|
+
}
|
|
12
17
|
const LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
13
18
|
function secureUrl(url) {
|
|
14
19
|
return url.protocol === "https:" || url.protocol === "http:" && LOOPBACK.has(url.hostname);
|
|
@@ -184,9 +189,9 @@ async function beginSignIn(provider, server, options) {
|
|
|
184
189
|
if (!configured) await provider.saveDiscoveryState(info);
|
|
185
190
|
const metadata = info.authorizationServerMetadata;
|
|
186
191
|
if (metadata && !metadata.response_types_supported.includes("code"))
|
|
187
|
-
throw new Error("
|
|
192
|
+
throw new Error("The authorization server doesn't support authorization codes.");
|
|
188
193
|
if (metadata?.code_challenge_methods_supported && !metadata.code_challenge_methods_supported.includes("S256"))
|
|
189
|
-
throw new Error("
|
|
194
|
+
throw new Error("The authorization server doesn't support PKCE S256.");
|
|
190
195
|
const scope = server.config.oauth?.scope || info.resourceMetadata?.scopes_supported?.join(" ") || void 0;
|
|
191
196
|
let client = await provider.clientInformation();
|
|
192
197
|
if (!client) {
|
|
@@ -204,7 +209,7 @@ async function beginSignIn(provider, server, options) {
|
|
|
204
209
|
const url = new URL(
|
|
205
210
|
metadata?.authorization_endpoint ?? new URL("/authorize", info.authorizationServerUrl)
|
|
206
211
|
);
|
|
207
|
-
if (!secureUrl(url)) throw new Error("
|
|
212
|
+
if (!secureUrl(url)) throw new Error("The authorization server's sign-in address isn't https.");
|
|
208
213
|
url.searchParams.set("response_type", "code");
|
|
209
214
|
url.searchParams.set("client_id", client.client_id);
|
|
210
215
|
url.searchParams.set("code_challenge", challenge);
|
|
@@ -222,14 +227,16 @@ async function finishSignIn(provider, server, answer, fetch) {
|
|
|
222
227
|
try {
|
|
223
228
|
callback = JSON.parse(answer);
|
|
224
229
|
} catch {
|
|
225
|
-
throw new Error("
|
|
230
|
+
throw new Error("The sign-in answer isn't a callback.");
|
|
226
231
|
}
|
|
227
232
|
const expected = (await provider.load()).oauthState;
|
|
228
233
|
if (!expected || callback.state !== expected)
|
|
229
|
-
throw new Error("
|
|
234
|
+
throw new Error("The sign-in answer doesn't match this sign-in. Try again.");
|
|
230
235
|
if (callback.error)
|
|
231
|
-
throw new Error(
|
|
232
|
-
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Sign-in failed: ${sentence(callback.error_description?.trim() || callback.error)}`
|
|
238
|
+
);
|
|
239
|
+
if (!callback.code) throw new Error("The sign-in answer has no authorization code.");
|
|
233
240
|
const configured = server.config.oauth?.authServerMetadataUrl;
|
|
234
241
|
await authorizeMcp(provider, {
|
|
235
242
|
serverUrl: server.config.url,
|
|
@@ -255,6 +262,7 @@ export {
|
|
|
255
262
|
pkce,
|
|
256
263
|
randomState,
|
|
257
264
|
secureUrl,
|
|
265
|
+
sentence,
|
|
258
266
|
signInRequired
|
|
259
267
|
};
|
|
260
268
|
//# sourceMappingURL=oauth.js.map
|
package/dist/mcp/oauth.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/oauth.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { AuthProvider, McpFetch, UnauthorizedContext } from '@earendil-works/pi-mcp';\nimport {\n authorizeMcp,\n discoverOAuthServerInfo,\n McpOAuthAuthorizationRequiredError,\n type McpOAuthState,\n type OAuthChallenge,\n parseWwwAuthenticate,\n registerClient,\n selectResource,\n stepUpScope,\n} from '@earendil-works/pi-mcp/oauth';\nimport type { McpSignIn } from '../services.ts';\nimport type { McpServerConfig } from './config.ts';\nimport { credentialKey } from './credentials.ts';\n\ntype Provider = Parameters<typeof authorizeMcp>[0];\ntype Tokens = Awaited<ReturnType<Provider['tokens']>>;\ntype Client = Awaited<ReturnType<Provider['clientInformation']>>;\ntype Discovery = NonNullable<McpOAuthState['discovery']>;\n\nexport const CLIENT_NAME = 'SLICC';\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function secureUrl(url: URL): boolean {\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nexport function signInRequired(cause?: unknown): McpOAuthAuthorizationRequiredError {\n const error = new McpOAuthAuthorizationRequiredError();\n if (cause !== undefined) error.cause = cause;\n return error;\n}\n\nexport function authProvider(provider: StoredOAuth, server: OAuthServer): AuthProvider {\n const configured = server.config.oauth?.authServerMetadataUrl;\n let inFlight: Promise<void> | undefined;\n const authorize = async (\n context: UnauthorizedContext,\n step: boolean,\n challenge: OAuthChallenge\n ) => {\n const granted = step ? await provider.tokens() : undefined;\n try {\n await authorizeMcp(provider, {\n serverUrl: context.serverUrl,\n ...(challenge.resourceMetadataUrl\n ? { resourceMetadataUrl: challenge.resourceMetadataUrl }\n : {}),\n ...(step\n ? { scope: stepUpScope(granted?.scope, challenge.scope), skipRefresh: true }\n : challenge.scope\n ? { scope: challenge.scope }\n : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch: context.fetch,\n });\n } catch (error) {\n throw error instanceof McpOAuthAuthorizationRequiredError ? error : signInRequired(error);\n }\n };\n return {\n token: async () => (await provider.tokens())?.access_token,\n async onUnauthorized(context) {\n const challenge = parseWwwAuthenticate(context.response.headers.get('www-authenticate'));\n const step = challenge.error === 'insufficient_scope';\n if (!step && !inFlight && context.token !== undefined) {\n const current = (await provider.tokens())?.access_token;\n if (current !== undefined && current !== context.token) return;\n }\n inFlight ??= authorize(context, step, challenge).finally(() => {\n inFlight = undefined;\n });\n await inFlight;\n },\n };\n}\n\nconst bytesToBase64Url = (bytes: Uint8Array) =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nconst random = (length: number) => crypto.getRandomValues(new Uint8Array(length));\n\nexport async function pkce(): Promise<{ verifier: string; challenge: string }> {\n const verifier = bytesToBase64Url(random(32));\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };\n}\n\nexport function randomState(): string {\n return [...random(32)].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport interface OAuthServer {\n name: string;\n config: McpServerConfig;\n}\n\nexport class StoredOAuth implements Provider {\n readonly redirectUrl: string;\n readonly clientMetadata: Provider['clientMetadata'];\n readonly #credentials: CredentialStore;\n readonly #server: OAuthServer;\n #writes: Promise<unknown> = Promise.resolve();\n\n constructor(server: OAuthServer, credentials: CredentialStore, redirectUrl: string) {\n this.#server = server;\n this.#credentials = credentials;\n this.redirectUrl = redirectUrl;\n this.clientMetadata = {\n client_name: server.config.oauth?.clientName ?? CLIENT_NAME,\n redirect_uris: [redirectUrl],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n };\n }\n\n get serverUrl(): string {\n return String(new URL(this.#server.config.url));\n }\n\n async #key(): Promise<string> {\n return credentialKey(this.#server.name, this.#server.config.url);\n }\n\n async load(): Promise<McpOAuthState> {\n await this.#writes;\n const stored = await this.#credentials.read(await this.#key());\n const state =\n stored?.type === 'oauth' ? (stored.state as McpOAuthState | undefined) : undefined;\n return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };\n }\n\n async update(change: (state: McpOAuthState) => McpOAuthState): Promise<void> {\n const write = this.#writes.then(async () => {\n const key = await this.#key();\n await this.#credentials.modify(key, async (current) => {\n const before =\n current?.type === 'oauth' ? (current.state as McpOAuthState | undefined) : undefined;\n const next = change(\n before?.serverUrl === this.serverUrl ? before : { serverUrl: this.serverUrl }\n );\n return {\n type: 'oauth',\n access: next.tokens?.access_token ?? '',\n refresh: next.tokens?.refresh_token ?? '',\n expires: next.tokensExpireAt ?? 0,\n state: next,\n };\n });\n });\n this.#writes = write.catch(() => undefined);\n await write;\n }\n\n async signedIn(): Promise<boolean> {\n return (await this.load()).tokens !== undefined;\n }\n\n state(): string {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async clientInformation(): Promise<Client> {\n const configured = this.#server.config.oauth?.clientId;\n return configured ? { client_id: configured } : (await this.load()).clientInformation;\n }\n\n async saveClientInformation(information: NonNullable<Client>): Promise<void> {\n if (this.#server.config.oauth?.clientId) return;\n await this.update((state) => ({ ...state, clientInformation: information }));\n }\n\n async tokens(): Promise<Tokens> {\n return (await this.load()).tokens;\n }\n\n async saveTokens(tokens: NonNullable<Tokens>): Promise<void> {\n const expires =\n tokens.expires_in === undefined ? undefined : Date.now() + tokens.expires_in * 1000;\n await this.update((state) => {\n const next: McpOAuthState = { ...state, tokens };\n if (expires === undefined) delete next.tokensExpireAt;\n else next.tokensExpireAt = expires;\n return next;\n });\n }\n\n redirectToAuthorization(): void {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async saveCodeVerifier(verifier: string): Promise<void> {\n await this.update((state) => ({ ...state, codeVerifier: verifier }));\n }\n\n async codeVerifier(): Promise<string> {\n const verifier = (await this.load()).codeVerifier;\n if (!verifier) throw new Error('No OAuth PKCE code verifier is stored');\n return verifier;\n }\n\n async invalidateCredentials(\n kind: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'\n ): Promise<void> {\n await this.update((state) => {\n const next = { ...state };\n if (kind === 'all' || kind === 'client') delete next.clientInformation;\n if (kind === 'all' || kind === 'tokens') {\n delete next.tokens;\n delete next.tokensExpireAt;\n }\n if (kind === 'all' || kind === 'verifier') delete next.codeVerifier;\n if (kind === 'all' || kind === 'discovery') delete next.discovery;\n if (kind === 'all') delete next.oauthState;\n return next;\n });\n }\n\n async saveDiscoveryState(discovery: Discovery): Promise<void> {\n await this.update((state) => ({ ...state, discovery }));\n }\n\n async discoveryState(): Promise<Discovery | undefined> {\n return (await this.load()).discovery;\n }\n\n async forget(): Promise<void> {\n await this.#writes;\n await this.#credentials.delete(await this.#key());\n }\n}\n\nexport type { McpSignIn } from '../services.ts';\n\nexport async function beginSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n options: { fetch: McpFetch; resourceMetadataUrl?: URL }\n): Promise<McpSignIn> {\n const configured = server.config.oauth?.authServerMetadataUrl;\n const info = await discoverOAuthServerInfo(server.config.url, {\n fetch: options.fetch,\n ...(options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n });\n if (!configured) await provider.saveDiscoveryState(info);\n const metadata = info.authorizationServerMetadata;\n if (metadata && !metadata.response_types_supported.includes('code'))\n throw new Error('the authorization server does not support authorization codes');\n if (\n metadata?.code_challenge_methods_supported &&\n !metadata.code_challenge_methods_supported.includes('S256')\n )\n throw new Error('the authorization server does not support PKCE S256');\n const scope =\n server.config.oauth?.scope || info.resourceMetadata?.scopes_supported?.join(' ') || undefined;\n let client = await provider.clientInformation();\n if (!client) {\n client = await registerClient(info.authorizationServerUrl, {\n ...(metadata ? { metadata } : {}),\n clientMetadata: provider.clientMetadata,\n ...(scope ? { scope } : {}),\n fetch: options.fetch,\n });\n await provider.saveClientInformation(client);\n }\n const { verifier, challenge } = await pkce();\n const state = randomState();\n await provider.update((current) => ({ ...current, codeVerifier: verifier, oauthState: state }));\n const url = new URL(\n metadata?.authorization_endpoint ?? new URL('/authorize', info.authorizationServerUrl)\n );\n if (!secureUrl(url)) throw new Error('the authorization endpoint is not an https URL');\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', client.client_id);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('redirect_uri', provider.redirectUrl);\n url.searchParams.set('state', state);\n if (scope) url.searchParams.set('scope', scope);\n if (scope?.split(/\\s+/).includes('offline_access')) url.searchParams.set('prompt', 'consent');\n const resource = selectResource(server.config.url, info.resourceMetadata);\n if (resource) url.searchParams.set('resource', resource);\n return { url: url.href, state };\n}\n\nexport interface Callback {\n code?: string;\n state?: string;\n iss?: string;\n error?: string;\n error_description?: string;\n}\n\nexport async function finishSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n answer: string,\n fetch: McpFetch\n): Promise<void> {\n let callback: Callback;\n try {\n callback = JSON.parse(answer) as Callback;\n } catch {\n throw new Error('the sign-in answer is not a callback');\n }\n const expected = (await provider.load()).oauthState;\n if (!expected || callback.state !== expected)\n throw new Error('the sign-in answer does not match this sign-in');\n if (callback.error)\n throw new Error(`sign-in failed: ${callback.error_description ?? callback.error}`);\n if (!callback.code) throw new Error('the sign-in answer has no authorization code');\n const configured = server.config.oauth?.authServerMetadataUrl;\n await authorizeMcp(provider, {\n serverUrl: server.config.url,\n authorizationCode: callback.code,\n ...(callback.iss ? { iss: callback.iss } : {}),\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch,\n });\n await provider.update((state) => {\n const next = { ...state };\n delete next.oauthState;\n delete next.codeVerifier;\n return next;\n });\n}\n"],
|
|
5
|
-
"mappings": "AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,qBAAqB;AAOvB,MAAM,cAAc;
|
|
4
|
+
"sourcesContent": ["import type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { AuthProvider, McpFetch, UnauthorizedContext } from '@earendil-works/pi-mcp';\nimport {\n authorizeMcp,\n discoverOAuthServerInfo,\n McpOAuthAuthorizationRequiredError,\n type McpOAuthState,\n type OAuthChallenge,\n parseWwwAuthenticate,\n registerClient,\n selectResource,\n stepUpScope,\n} from '@earendil-works/pi-mcp/oauth';\nimport type { McpSignIn } from '../services.ts';\nimport type { McpServerConfig } from './config.ts';\nimport { credentialKey } from './credentials.ts';\n\ntype Provider = Parameters<typeof authorizeMcp>[0];\ntype Tokens = Awaited<ReturnType<Provider['tokens']>>;\ntype Client = Awaited<ReturnType<Provider['clientInformation']>>;\ntype Discovery = NonNullable<McpOAuthState['discovery']>;\n\nexport const CLIENT_NAME = 'SLICC';\n\nexport function sentence(text: string): string {\n const trimmed = text.trim();\n const lowered = /^[A-Z][a-z]/.test(trimmed)\n ? `${trimmed[0]?.toLowerCase()}${trimmed.slice(1)}`\n : trimmed;\n return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;\n}\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function secureUrl(url: URL): boolean {\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nexport function signInRequired(cause?: unknown): McpOAuthAuthorizationRequiredError {\n const error = new McpOAuthAuthorizationRequiredError();\n if (cause !== undefined) error.cause = cause;\n return error;\n}\n\nexport function authProvider(provider: StoredOAuth, server: OAuthServer): AuthProvider {\n const configured = server.config.oauth?.authServerMetadataUrl;\n let inFlight: Promise<void> | undefined;\n const authorize = async (\n context: UnauthorizedContext,\n step: boolean,\n challenge: OAuthChallenge\n ) => {\n const granted = step ? await provider.tokens() : undefined;\n try {\n await authorizeMcp(provider, {\n serverUrl: context.serverUrl,\n ...(challenge.resourceMetadataUrl\n ? { resourceMetadataUrl: challenge.resourceMetadataUrl }\n : {}),\n ...(step\n ? { scope: stepUpScope(granted?.scope, challenge.scope), skipRefresh: true }\n : challenge.scope\n ? { scope: challenge.scope }\n : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch: context.fetch,\n });\n } catch (error) {\n throw error instanceof McpOAuthAuthorizationRequiredError ? error : signInRequired(error);\n }\n };\n return {\n token: async () => (await provider.tokens())?.access_token,\n async onUnauthorized(context) {\n const challenge = parseWwwAuthenticate(context.response.headers.get('www-authenticate'));\n const step = challenge.error === 'insufficient_scope';\n if (!step && !inFlight && context.token !== undefined) {\n const current = (await provider.tokens())?.access_token;\n if (current !== undefined && current !== context.token) return;\n }\n inFlight ??= authorize(context, step, challenge).finally(() => {\n inFlight = undefined;\n });\n await inFlight;\n },\n };\n}\n\nconst bytesToBase64Url = (bytes: Uint8Array) =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nconst random = (length: number) => crypto.getRandomValues(new Uint8Array(length));\n\nexport async function pkce(): Promise<{ verifier: string; challenge: string }> {\n const verifier = bytesToBase64Url(random(32));\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };\n}\n\nexport function randomState(): string {\n return [...random(32)].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport interface OAuthServer {\n name: string;\n config: McpServerConfig;\n}\n\nexport class StoredOAuth implements Provider {\n readonly redirectUrl: string;\n readonly clientMetadata: Provider['clientMetadata'];\n readonly #credentials: CredentialStore;\n readonly #server: OAuthServer;\n #writes: Promise<unknown> = Promise.resolve();\n\n constructor(server: OAuthServer, credentials: CredentialStore, redirectUrl: string) {\n this.#server = server;\n this.#credentials = credentials;\n this.redirectUrl = redirectUrl;\n this.clientMetadata = {\n client_name: server.config.oauth?.clientName ?? CLIENT_NAME,\n redirect_uris: [redirectUrl],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n };\n }\n\n get serverUrl(): string {\n return String(new URL(this.#server.config.url));\n }\n\n async #key(): Promise<string> {\n return credentialKey(this.#server.name, this.#server.config.url);\n }\n\n async load(): Promise<McpOAuthState> {\n await this.#writes;\n const stored = await this.#credentials.read(await this.#key());\n const state =\n stored?.type === 'oauth' ? (stored.state as McpOAuthState | undefined) : undefined;\n return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };\n }\n\n async update(change: (state: McpOAuthState) => McpOAuthState): Promise<void> {\n const write = this.#writes.then(async () => {\n const key = await this.#key();\n await this.#credentials.modify(key, async (current) => {\n const before =\n current?.type === 'oauth' ? (current.state as McpOAuthState | undefined) : undefined;\n const next = change(\n before?.serverUrl === this.serverUrl ? before : { serverUrl: this.serverUrl }\n );\n return {\n type: 'oauth',\n access: next.tokens?.access_token ?? '',\n refresh: next.tokens?.refresh_token ?? '',\n expires: next.tokensExpireAt ?? 0,\n state: next,\n };\n });\n });\n this.#writes = write.catch(() => undefined);\n await write;\n }\n\n async signedIn(): Promise<boolean> {\n return (await this.load()).tokens !== undefined;\n }\n\n state(): string {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async clientInformation(): Promise<Client> {\n const configured = this.#server.config.oauth?.clientId;\n return configured ? { client_id: configured } : (await this.load()).clientInformation;\n }\n\n async saveClientInformation(information: NonNullable<Client>): Promise<void> {\n if (this.#server.config.oauth?.clientId) return;\n await this.update((state) => ({ ...state, clientInformation: information }));\n }\n\n async tokens(): Promise<Tokens> {\n return (await this.load()).tokens;\n }\n\n async saveTokens(tokens: NonNullable<Tokens>): Promise<void> {\n const expires =\n tokens.expires_in === undefined ? undefined : Date.now() + tokens.expires_in * 1000;\n await this.update((state) => {\n const next: McpOAuthState = { ...state, tokens };\n if (expires === undefined) delete next.tokensExpireAt;\n else next.tokensExpireAt = expires;\n return next;\n });\n }\n\n redirectToAuthorization(): void {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async saveCodeVerifier(verifier: string): Promise<void> {\n await this.update((state) => ({ ...state, codeVerifier: verifier }));\n }\n\n async codeVerifier(): Promise<string> {\n const verifier = (await this.load()).codeVerifier;\n if (!verifier) throw new Error('No OAuth PKCE code verifier is stored');\n return verifier;\n }\n\n async invalidateCredentials(\n kind: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'\n ): Promise<void> {\n await this.update((state) => {\n const next = { ...state };\n if (kind === 'all' || kind === 'client') delete next.clientInformation;\n if (kind === 'all' || kind === 'tokens') {\n delete next.tokens;\n delete next.tokensExpireAt;\n }\n if (kind === 'all' || kind === 'verifier') delete next.codeVerifier;\n if (kind === 'all' || kind === 'discovery') delete next.discovery;\n if (kind === 'all') delete next.oauthState;\n return next;\n });\n }\n\n async saveDiscoveryState(discovery: Discovery): Promise<void> {\n await this.update((state) => ({ ...state, discovery }));\n }\n\n async discoveryState(): Promise<Discovery | undefined> {\n return (await this.load()).discovery;\n }\n\n async forget(): Promise<void> {\n await this.#writes;\n await this.#credentials.delete(await this.#key());\n }\n}\n\nexport type { McpSignIn } from '../services.ts';\n\nexport async function beginSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n options: { fetch: McpFetch; resourceMetadataUrl?: URL }\n): Promise<McpSignIn> {\n const configured = server.config.oauth?.authServerMetadataUrl;\n const info = await discoverOAuthServerInfo(server.config.url, {\n fetch: options.fetch,\n ...(options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n });\n if (!configured) await provider.saveDiscoveryState(info);\n const metadata = info.authorizationServerMetadata;\n if (metadata && !metadata.response_types_supported.includes('code'))\n throw new Error(\"The authorization server doesn't support authorization codes.\");\n if (\n metadata?.code_challenge_methods_supported &&\n !metadata.code_challenge_methods_supported.includes('S256')\n )\n throw new Error(\"The authorization server doesn't support PKCE S256.\");\n const scope =\n server.config.oauth?.scope || info.resourceMetadata?.scopes_supported?.join(' ') || undefined;\n let client = await provider.clientInformation();\n if (!client) {\n client = await registerClient(info.authorizationServerUrl, {\n ...(metadata ? { metadata } : {}),\n clientMetadata: provider.clientMetadata,\n ...(scope ? { scope } : {}),\n fetch: options.fetch,\n });\n await provider.saveClientInformation(client);\n }\n const { verifier, challenge } = await pkce();\n const state = randomState();\n await provider.update((current) => ({ ...current, codeVerifier: verifier, oauthState: state }));\n const url = new URL(\n metadata?.authorization_endpoint ?? new URL('/authorize', info.authorizationServerUrl)\n );\n if (!secureUrl(url)) throw new Error(\"The authorization server's sign-in address isn't https.\");\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', client.client_id);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('redirect_uri', provider.redirectUrl);\n url.searchParams.set('state', state);\n if (scope) url.searchParams.set('scope', scope);\n if (scope?.split(/\\s+/).includes('offline_access')) url.searchParams.set('prompt', 'consent');\n const resource = selectResource(server.config.url, info.resourceMetadata);\n if (resource) url.searchParams.set('resource', resource);\n return { url: url.href, state };\n}\n\nexport interface Callback {\n code?: string;\n state?: string;\n iss?: string;\n error?: string;\n error_description?: string;\n}\n\nexport async function finishSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n answer: string,\n fetch: McpFetch\n): Promise<void> {\n let callback: Callback;\n try {\n callback = JSON.parse(answer) as Callback;\n } catch {\n throw new Error(\"The sign-in answer isn't a callback.\");\n }\n const expected = (await provider.load()).oauthState;\n if (!expected || callback.state !== expected)\n throw new Error(\"The sign-in answer doesn't match this sign-in. Try again.\");\n if (callback.error)\n throw new Error(\n `Sign-in failed: ${sentence(callback.error_description?.trim() || callback.error)}`\n );\n if (!callback.code) throw new Error('The sign-in answer has no authorization code.');\n const configured = server.config.oauth?.authServerMetadataUrl;\n await authorizeMcp(provider, {\n serverUrl: server.config.url,\n authorizationCode: callback.code,\n ...(callback.iss ? { iss: callback.iss } : {}),\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch,\n });\n await provider.update((state) => {\n const next = { ...state };\n delete next.oauthState;\n delete next.codeVerifier;\n return next;\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/scoops/index.js
CHANGED
|
@@ -36,20 +36,39 @@ async function pnpmHomeOf(env, context) {
|
|
|
36
36
|
).catch(() => void 0);
|
|
37
37
|
return ran?.ok && ran.value.exitCode === 0 && /^\/\S*$/.test(out) ? out : PNPM_HOME;
|
|
38
38
|
}
|
|
39
|
+
async function theirs(env, path, context) {
|
|
40
|
+
const found = await env.fileInfo(path, context);
|
|
41
|
+
if (!found.ok) return found.error.code !== "not_found";
|
|
42
|
+
if (found.value.kind !== "file") return true;
|
|
43
|
+
const read = await env.readTextFile(path, context);
|
|
44
|
+
return !read.ok || !read.value.includes(PROTOCOL);
|
|
45
|
+
}
|
|
39
46
|
async function install(env, script, binDir, context) {
|
|
40
|
-
|
|
47
|
+
const skipped = [];
|
|
48
|
+
if (!(await env.createDir(binDir, { recursive: true }, context)).ok)
|
|
49
|
+
return { done: false, skipped };
|
|
41
50
|
for (const name of COMMANDS) {
|
|
42
51
|
const path = `${binDir}/${name}`;
|
|
43
|
-
if (
|
|
52
|
+
if (await theirs(env, path, context)) {
|
|
53
|
+
skipped.push(name);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!(await env.writeFile(path, script, context)).ok) return { done: false, skipped };
|
|
44
57
|
const mode = await env.exec(["chmod", "755", path], void 0, context);
|
|
45
|
-
if (!mode.ok || mode.value.exitCode !== 0) return false;
|
|
58
|
+
if (!mode.ok || mode.value.exitCode !== 0) return { done: false, skipped };
|
|
46
59
|
}
|
|
47
|
-
return true;
|
|
60
|
+
return { done: true, skipped };
|
|
48
61
|
}
|
|
49
|
-
async function installCommands(env, script, binDir, context) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
async function installCommands(env, script, binDir, context, warn = (message) => console.warn(message)) {
|
|
63
|
+
const installed = await install(env, script, binDir, context).catch(
|
|
64
|
+
() => ({ done: false, skipped: [] })
|
|
65
|
+
);
|
|
66
|
+
if (installed.skipped.length) {
|
|
67
|
+
const paths = installed.skipped.map((name) => `${binDir}/${name}`).join(", ");
|
|
68
|
+
warn(`slicc-agent: left ${paths} alone, because it isn't slicc-agent's command`);
|
|
69
|
+
}
|
|
70
|
+
if (installed.done) await retire(env, binDir, context).catch(() => void 0);
|
|
71
|
+
return installed;
|
|
53
72
|
}
|
|
54
73
|
async function retire(env, binDir, context) {
|
|
55
74
|
const old = `${await pnpmHomeOf(env, context)}/bin`;
|
package/dist/scoops/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/scoops/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { Harness, Registry, ToolRegistration } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport { DEFAULT_LIMITS, loadRoles, packageDirs, type Roles } from '../roles/roles.ts';\nimport { type CliOptions, createCli, RUN_DIR } from './cli.ts';\nimport { guardExtension, scoopsExtension, scoopTasks } from './extension.ts';\nimport { type ControlPlane, controlPlane, PROTOCOL } from './requests.ts';\nimport { createScoops, type Scoops, type ScoopsHost } from './service.ts';\n\nexport const BUILTIN_ROLES = ['scout', 'worker', 'reviewer', 'oracle', 'delegate', 'gelatiere'];\nexport const PNPM_HOME = '/home/.local/share/pnpm';\nexport const BIN_DIR = '/usr/local/bin';\n\nexport type Assets = (path: string) => Promise<string>;\n\nexport function packageAssets(base: URL = new URL('../../', import.meta.url)): Assets {\n return async (path) => {\n const response = await fetch(new URL(path, base));\n if (!response.ok) throw new Error(`${path}: ${response.status}`);\n return response.text();\n };\n}\n\nexport interface AttachOptions {\n harness: Harness;\n agents: Agents;\n groups: ProcessGroups;\n env: ExecutionEnv;\n home: string;\n assets?: Assets;\n binDir?: string;\n controlDir?: string;\n sweepEvery?: number;\n alive?: (pid: number) => Promise<boolean>;\n reads: () => readonly string[];\n sprinkle?: CliOptions['sprinkle'];\n memory?: CliOptions['memory'];\n gelatiere?: CliOptions['gelatiere'];\n freezer?: CliOptions['freezer'];\n models?: CliOptions['models'];\n mcp?: (names: readonly string[]) => ToolRegistration[];\n}\n\nexport interface ScoopsRuntime {\n roles(context: Context): Promise<Roles>;\n plane: ControlPlane;\n close(context: Context): Promise<void>;\n}\n\nexport interface ScoopsSetup {\n scoops: Scoops;\n attach(options: AttachOptions, context: Context): Promise<ScoopsRuntime>;\n}\n\nexport const COMMANDS = [\n 'agent',\n 'subagent',\n 'sprinkle',\n 'memory',\n 'gelatiere',\n 'freezer',\n 'models',\n];\n\nexport async function pnpmHomeOf(env: ExecutionEnv, context: Context): Promise<string> {\n let out = '';\n const ran = await env\n .exec(\n 'printf %s \"$PNPM_HOME\"',\n {\n onOutput: (text) => {\n out += text;\n },\n },\n context\n )\n .catch(() => undefined);\n return ran?.ok && ran.value.exitCode === 0 && /^\\/\\S*$/.test(out) ? out : PNPM_HOME;\n}\n\nasync function
|
|
5
|
-
"mappings": "AAGA,SAAsB,YAAY;AAGlC,SAAS,gBAAgB,WAAW,mBAA+B;AACnE,SAA0B,WAAW,eAAe;AACpD,SAAS,gBAAgB,iBAAiB,kBAAkB;AAC5D,SAA4B,cAAc,gBAAgB;AAC1D,SAAS,oBAAkD;AAEpD,MAAM,gBAAgB,CAAC,SAAS,UAAU,YAAY,UAAU,YAAY,WAAW;AACvF,MAAM,YAAY;AAClB,MAAM,UAAU;AAIhB,SAAS,cAAc,OAAY,IAAI,IAAI,UAAU,YAAY,GAAG,GAAW;AACpF,SAAO,OAAO,SAAS;AACrB,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC;AAChD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,GAAG,IAAI,KAAK,SAAS,MAAM,EAAE;AAC/D,WAAO,SAAS,KAAK;AAAA,EACvB;AACF;AAiCO,MAAM,WAAW;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,eAAsB,WAAW,KAAmB,SAAmC;AACrF,MAAI,MAAM;AACV,QAAM,MAAM,MAAM,IACf;AAAA,IACC;AAAA,IACA;AAAA,MACE,UAAU,CAAC,SAAS;AAClB,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AACxB,SAAO,KAAK,MAAM,IAAI,MAAM,aAAa,KAAK,UAAU,KAAK,GAAG,IAAI,MAAM;AAC5E;
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { Harness, Registry, ToolRegistration } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport { DEFAULT_LIMITS, loadRoles, packageDirs, type Roles } from '../roles/roles.ts';\nimport { type CliOptions, createCli, RUN_DIR } from './cli.ts';\nimport { guardExtension, scoopsExtension, scoopTasks } from './extension.ts';\nimport { type ControlPlane, controlPlane, PROTOCOL } from './requests.ts';\nimport { createScoops, type Scoops, type ScoopsHost } from './service.ts';\n\nexport const BUILTIN_ROLES = ['scout', 'worker', 'reviewer', 'oracle', 'delegate', 'gelatiere'];\nexport const PNPM_HOME = '/home/.local/share/pnpm';\nexport const BIN_DIR = '/usr/local/bin';\n\nexport type Assets = (path: string) => Promise<string>;\n\nexport function packageAssets(base: URL = new URL('../../', import.meta.url)): Assets {\n return async (path) => {\n const response = await fetch(new URL(path, base));\n if (!response.ok) throw new Error(`${path}: ${response.status}`);\n return response.text();\n };\n}\n\nexport interface AttachOptions {\n harness: Harness;\n agents: Agents;\n groups: ProcessGroups;\n env: ExecutionEnv;\n home: string;\n assets?: Assets;\n binDir?: string;\n controlDir?: string;\n sweepEvery?: number;\n alive?: (pid: number) => Promise<boolean>;\n reads: () => readonly string[];\n sprinkle?: CliOptions['sprinkle'];\n memory?: CliOptions['memory'];\n gelatiere?: CliOptions['gelatiere'];\n freezer?: CliOptions['freezer'];\n models?: CliOptions['models'];\n mcp?: (names: readonly string[]) => ToolRegistration[];\n}\n\nexport interface ScoopsRuntime {\n roles(context: Context): Promise<Roles>;\n plane: ControlPlane;\n close(context: Context): Promise<void>;\n}\n\nexport interface ScoopsSetup {\n scoops: Scoops;\n attach(options: AttachOptions, context: Context): Promise<ScoopsRuntime>;\n}\n\nexport const COMMANDS = [\n 'agent',\n 'subagent',\n 'sprinkle',\n 'memory',\n 'gelatiere',\n 'freezer',\n 'models',\n];\n\nexport async function pnpmHomeOf(env: ExecutionEnv, context: Context): Promise<string> {\n let out = '';\n const ran = await env\n .exec(\n 'printf %s \"$PNPM_HOME\"',\n {\n onOutput: (text) => {\n out += text;\n },\n },\n context\n )\n .catch(() => undefined);\n return ran?.ok && ran.value.exitCode === 0 && /^\\/\\S*$/.test(out) ? out : PNPM_HOME;\n}\n\nexport interface Installed {\n done: boolean;\n skipped: string[];\n}\n\nasync function theirs(env: ExecutionEnv, path: string, context: Context): Promise<boolean> {\n const found = await env.fileInfo(path, context);\n if (!found.ok) return found.error.code !== 'not_found';\n if (found.value.kind !== 'file') return true;\n const read = await env.readTextFile(path, context);\n return !read.ok || !read.value.includes(PROTOCOL);\n}\n\nasync function install(\n env: ExecutionEnv,\n script: string,\n binDir: string,\n context: Context\n): Promise<Installed> {\n const skipped: string[] = [];\n if (!(await env.createDir(binDir, { recursive: true }, context)).ok)\n return { done: false, skipped };\n for (const name of COMMANDS) {\n const path = `${binDir}/${name}`;\n if (await theirs(env, path, context)) {\n skipped.push(name);\n continue;\n }\n if (!(await env.writeFile(path, script, context)).ok) return { done: false, skipped };\n const mode = await env.exec(['chmod', '755', path], undefined, context);\n if (!mode.ok || mode.value.exitCode !== 0) return { done: false, skipped };\n }\n return { done: true, skipped };\n}\n\nexport async function installCommands(\n env: ExecutionEnv,\n script: string,\n binDir: string,\n context: Context,\n warn: (message: string) => void = (message) => console.warn(message)\n): Promise<Installed> {\n const installed = await install(env, script, binDir, context).catch(\n (): Installed => ({ done: false, skipped: [] })\n );\n if (installed.skipped.length) {\n const paths = installed.skipped.map((name) => `${binDir}/${name}`).join(', ');\n warn(`slicc-agent: left ${paths} alone, because it isn't slicc-agent's command`);\n }\n if (installed.done) await retire(env, binDir, context).catch(() => undefined);\n return installed;\n}\n\nexport async function retire(env: ExecutionEnv, binDir: string, context: Context) {\n const old = `${await pnpmHomeOf(env, context)}/bin`;\n if (old === binDir) return;\n for (const name of COMMANDS) {\n const path = `${old}/${name}`;\n const text = await env.readTextFile(path, context);\n if (text.ok && text.value.includes(PROTOCOL)) await env.remove(path, { force: true }, context);\n }\n}\n\nfunction mirror(agents: Agents, env: ExecutionEnv) {\n return async (context: Context) => {\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n const state = record.gone ? 'gone' : live(record) ? 'live' : 'stopped';\n const metadata = {\n handle: record.folder,\n id,\n name: record.name,\n role: record.role,\n cone: record.cone,\n state,\n };\n await env.writeFile(\n `${RUN_DIR}/${record.folder}/metadata.json`,\n `${JSON.stringify(metadata, null, 2)}\\n`,\n context\n );\n }\n };\n}\n\nexport function setupScoops(\n registry: Registry,\n licks: Licks,\n fileTools: readonly ToolRegistration[] = []\n): ScoopsSetup {\n let bind: (host: ScoopsHost) => void = () => undefined;\n const host = new Promise<ScoopsHost>((resolve) => {\n bind = resolve;\n });\n const lookup = () => host;\n const tasks = scoopTasks(lookup);\n const scoops = createScoops(host, () => tasks);\n let cached: Roles = { roles: [], warnings: [], limits: { ...DEFAULT_LIMITS } };\n registry.install(scoopsExtension(tasks));\n registry.install(guardExtension(lookup, fileTools));\n return {\n scoops,\n async attach(options, context) {\n const { harness, agents, groups, env, home } = options;\n const assets = options.assets ?? packageAssets();\n bind({\n harness,\n agents,\n licks,\n tools: fileTools,\n groups,\n reads: options.reads,\n files: env,\n ...(options.mcp ? { mcp: options.mcp } : {}),\n });\n const builtin = async () => {\n const out: { path: string; text: string }[] = [];\n for (const name of BUILTIN_ROLES) {\n const path = `packages/vfs-root/agents/${name}.md`;\n out.push({ path: `builtin:${name}`, text: await assets(path) });\n }\n return out;\n };\n const roles = async (using: Context) => {\n cached = await loadRoles(\n {\n files: env,\n builtin,\n dirs: [\n ...(await packageDirs(env, '/node_modules', using)).map((dir) => ({\n source: 'package' as const,\n dir,\n })),\n { source: 'user', dir: `${home}/.pi/agent/agents` },\n ],\n settingsPath: `${home}/.pi/agent/settings.json`,\n },\n using\n );\n return cached;\n };\n await roles(context).catch(() => cached);\n const script = await assets('bin/agent').catch(() => undefined);\n if (script) {\n const binDir = options.binDir ?? BIN_DIR;\n await installCommands(env, script, binDir, context);\n }\n const cli = createCli({\n env,\n harness,\n agents,\n scoops,\n roles: () => roles(context),\n ...(options.alive ? { alive: options.alive } : {}),\n ...(options.sprinkle ? { sprinkle: options.sprinkle } : {}),\n ...(options.memory ? { memory: options.memory } : {}),\n ...(options.gelatiere ? { gelatiere: options.gelatiere } : {}),\n ...(options.freezer ? { freezer: options.freezer } : {}),\n ...(options.models ? { models: options.models } : {}),\n });\n const plane = controlPlane(env, cli, {\n ...(options.controlDir ? { dir: options.controlDir } : {}),\n ...(options.sweepEvery ? { sweepEvery: options.sweepEvery } : {}),\n mirror: mirror(agents, env),\n });\n await plane.start(context);\n return { roles, plane, close: (using) => plane.close(using) };\n },\n };\n}\n\nexport type { Rewound, Scoops } from './service.ts';\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -28,7 +28,7 @@ function answered(channel, state, signal, timeoutMs) {
|
|
|
28
28
|
};
|
|
29
29
|
const timer = setTimeout(() => {
|
|
30
30
|
done();
|
|
31
|
-
reject(new Error("
|
|
31
|
+
reject(new Error("The sign-in window gave no answer. Try again."));
|
|
32
32
|
}, timeoutMs);
|
|
33
33
|
signal.addEventListener("abort", stop, { once: true });
|
|
34
34
|
channel.onmessage = ({ data }) => {
|
|
@@ -47,11 +47,11 @@ function mcpLogin(host = browser) {
|
|
|
47
47
|
try {
|
|
48
48
|
const started = await signIn();
|
|
49
49
|
options.signal.throwIfAborted();
|
|
50
|
-
if (!started?.url) throw new Error("
|
|
50
|
+
if (!started?.url) throw new Error("This MCP server has no sign-in.");
|
|
51
51
|
if (!safeAuthorizationUrl(started.url))
|
|
52
|
-
throw new Error("
|
|
52
|
+
throw new Error("The authorization server gave a sign-in address that isn't https.");
|
|
53
53
|
if (!popup)
|
|
54
|
-
throw new Error("
|
|
54
|
+
throw new Error("The browser blocked the sign-in window. Allow pop-ups for SLICC.");
|
|
55
55
|
state = started.state;
|
|
56
56
|
host.remember?.(state, providerId.replace(/^mcp:/, ""));
|
|
57
57
|
popup.location.href = started.url;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/spectrum/mcp-login.ts"],
|
|
4
|
-
"sourcesContent": ["import type { McpSignIn, SignIn } from '../services.ts';\n\nexport const MCP_CHANNEL = 'slicc-mcp-oauth';\nexport const MCP_NAME_KEY = 'slicc-mcp-oauth:';\nexport const SIGN_IN_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface Popup {\n location: { href: string };\n close(): void;\n}\n\nexport interface Channel {\n onmessage: ((event: { data: unknown }) => void) | null;\n close(): void;\n}\n\nexport interface McpLoginHost {\n open(): Popup | null;\n channel(): Channel;\n remember?(state: string, name: string | null): void;\n timeoutMs?: number;\n}\n\nconst browser: McpLoginHost = {\n open: () =>\n globalThis.open?.('about:blank', 'slicc-mcp-oauth', 'popup,width=520,height=720') ?? null,\n channel: () => new BroadcastChannel(MCP_CHANNEL) as unknown as Channel,\n remember: (state, name) => {\n const key = `${MCP_NAME_KEY}${state}`;\n if (name === null) globalThis.localStorage?.removeItem(key);\n else globalThis.localStorage?.setItem(key, name);\n },\n};\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function safeAuthorizationUrl(value: string): boolean {\n if (!URL.canParse(value)) return false;\n const url = new URL(value);\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nfunction answered(channel: Channel, state: string, signal: AbortSignal, timeoutMs: number) {\n return new Promise<string>((resolve, reject) => {\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', stop);\n };\n const stop = () => {\n done();\n reject(signal.reason);\n };\n const timer = setTimeout(() => {\n done();\n reject(new Error('
|
|
5
|
-
"mappings": "AAEO,MAAM,cAAc;AACpB,MAAM,eAAe;AACrB,MAAM,qBAAqB,IAAI,KAAK;AAmB3C,MAAM,UAAwB;AAAA,EAC5B,MAAM,MACJ,WAAW,OAAO,eAAe,mBAAmB,4BAA4B,KAAK;AAAA,EACvF,SAAS,MAAM,IAAI,iBAAiB,WAAW;AAAA,EAC/C,UAAU,CAAC,OAAO,SAAS;AACzB,UAAM,MAAM,GAAG,YAAY,GAAG,KAAK;AACnC,QAAI,SAAS,KAAM,YAAW,cAAc,WAAW,GAAG;AAAA,QACrD,YAAW,cAAc,QAAQ,KAAK,IAAI;AAAA,EACjD;AACF;AAEA,MAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAErD,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,CAAC,IAAI,SAAS,KAAK,EAAG,QAAO;AACjC,QAAM,MAAM,IAAI,IAAI,KAAK;AACzB,SAAO,IAAI,aAAa,YAAa,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ;AAC5F;AAEA,SAAS,SAAS,SAAkB,OAAe,QAAqB,WAAmB;AACzF,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,UAAM,OAAO,MAAM;AACjB,mBAAa,KAAK;AAClB,aAAO,oBAAoB,SAAS,IAAI;AAAA,IAC1C;AACA,UAAM,OAAO,MAAM;AACjB,WAAK;AACL,aAAO,OAAO,MAAM;AAAA,IACtB;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK;AACL,aAAO,IAAI,MAAM,
|
|
4
|
+
"sourcesContent": ["import type { McpSignIn, SignIn } from '../services.ts';\n\nexport const MCP_CHANNEL = 'slicc-mcp-oauth';\nexport const MCP_NAME_KEY = 'slicc-mcp-oauth:';\nexport const SIGN_IN_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface Popup {\n location: { href: string };\n close(): void;\n}\n\nexport interface Channel {\n onmessage: ((event: { data: unknown }) => void) | null;\n close(): void;\n}\n\nexport interface McpLoginHost {\n open(): Popup | null;\n channel(): Channel;\n remember?(state: string, name: string | null): void;\n timeoutMs?: number;\n}\n\nconst browser: McpLoginHost = {\n open: () =>\n globalThis.open?.('about:blank', 'slicc-mcp-oauth', 'popup,width=520,height=720') ?? null,\n channel: () => new BroadcastChannel(MCP_CHANNEL) as unknown as Channel,\n remember: (state, name) => {\n const key = `${MCP_NAME_KEY}${state}`;\n if (name === null) globalThis.localStorage?.removeItem(key);\n else globalThis.localStorage?.setItem(key, name);\n },\n};\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function safeAuthorizationUrl(value: string): boolean {\n if (!URL.canParse(value)) return false;\n const url = new URL(value);\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nfunction answered(channel: Channel, state: string, signal: AbortSignal, timeoutMs: number) {\n return new Promise<string>((resolve, reject) => {\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', stop);\n };\n const stop = () => {\n done();\n reject(signal.reason);\n };\n const timer = setTimeout(() => {\n done();\n reject(new Error('The sign-in window gave no answer. Try again.'));\n }, timeoutMs);\n signal.addEventListener('abort', stop, { once: true });\n channel.onmessage = ({ data }) => {\n const answer = data as { state?: unknown } | null;\n if (answer?.state !== state) return;\n done();\n resolve(JSON.stringify(answer));\n };\n });\n}\n\nexport function mcpLogin(host: McpLoginHost = browser) {\n return async (\n providerId: string,\n signIn: () => Promise<SignIn | McpSignIn | null>,\n options: { signal: AbortSignal }\n ): Promise<string> => {\n const popup = host.open();\n const channel = host.channel();\n let state: string | undefined;\n try {\n const started = (await signIn()) as McpSignIn | null;\n options.signal.throwIfAborted();\n if (!started?.url) throw new Error('This MCP server has no sign-in.');\n if (!safeAuthorizationUrl(started.url))\n throw new Error(\"The authorization server gave a sign-in address that isn't https.\");\n if (!popup)\n throw new Error('The browser blocked the sign-in window. Allow pop-ups for SLICC.');\n state = started.state;\n host.remember?.(state, providerId.replace(/^mcp:/, ''));\n popup.location.href = started.url;\n return await answered(channel, state, options.signal, host.timeoutMs ?? SIGN_IN_TIMEOUT_MS);\n } finally {\n if (state) host.remember?.(state, null);\n channel.close();\n popup?.close();\n }\n };\n}\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,cAAc;AACpB,MAAM,eAAe;AACrB,MAAM,qBAAqB,IAAI,KAAK;AAmB3C,MAAM,UAAwB;AAAA,EAC5B,MAAM,MACJ,WAAW,OAAO,eAAe,mBAAmB,4BAA4B,KAAK;AAAA,EACvF,SAAS,MAAM,IAAI,iBAAiB,WAAW;AAAA,EAC/C,UAAU,CAAC,OAAO,SAAS;AACzB,UAAM,MAAM,GAAG,YAAY,GAAG,KAAK;AACnC,QAAI,SAAS,KAAM,YAAW,cAAc,WAAW,GAAG;AAAA,QACrD,YAAW,cAAc,QAAQ,KAAK,IAAI;AAAA,EACjD;AACF;AAEA,MAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAErD,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,CAAC,IAAI,SAAS,KAAK,EAAG,QAAO;AACjC,QAAM,MAAM,IAAI,IAAI,KAAK;AACzB,SAAO,IAAI,aAAa,YAAa,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ;AAC5F;AAEA,SAAS,SAAS,SAAkB,OAAe,QAAqB,WAAmB;AACzF,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,UAAM,OAAO,MAAM;AACjB,mBAAa,KAAK;AAClB,aAAO,oBAAoB,SAAS,IAAI;AAAA,IAC1C;AACA,UAAM,OAAO,MAAM;AACjB,WAAK;AACL,aAAO,OAAO,MAAM;AAAA,IACtB;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK;AACL,aAAO,IAAI,MAAM,+CAA+C,CAAC;AAAA,IACnE,GAAG,SAAS;AACZ,WAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACrD,YAAQ,YAAY,CAAC,EAAE,KAAK,MAAM;AAChC,YAAM,SAAS;AACf,UAAI,QAAQ,UAAU,MAAO;AAC7B,WAAK;AACL,cAAQ,KAAK,UAAU,MAAM,CAAC;AAAA,IAChC;AAAA,EACF,CAAC;AACH;AAEO,SAAS,SAAS,OAAqB,SAAS;AACrD,SAAO,OACL,YACA,QACA,YACoB;AACpB,UAAM,QAAQ,KAAK,KAAK;AACxB,UAAM,UAAU,KAAK,QAAQ;AAC7B,QAAI;AACJ,QAAI;AACF,YAAM,UAAW,MAAM,OAAO;AAC9B,cAAQ,OAAO,eAAe;AAC9B,UAAI,CAAC,SAAS,IAAK,OAAM,IAAI,MAAM,iCAAiC;AACpE,UAAI,CAAC,qBAAqB,QAAQ,GAAG;AACnC,cAAM,IAAI,MAAM,mEAAmE;AACrF,UAAI,CAAC;AACH,cAAM,IAAI,MAAM,kEAAkE;AACpF,cAAQ,QAAQ;AAChB,WAAK,WAAW,OAAO,WAAW,QAAQ,SAAS,EAAE,CAAC;AACtD,YAAM,SAAS,OAAO,QAAQ;AAC9B,aAAO,MAAM,SAAS,SAAS,OAAO,QAAQ,QAAQ,KAAK,aAAa,kBAAkB;AAAA,IAC5F,UAAE;AACA,UAAI,MAAO,MAAK,WAAW,OAAO,IAAI;AACtC,cAAQ,MAAM;AACd,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,6 +8,7 @@ type Tokens = Awaited<ReturnType<Provider['tokens']>>;
|
|
|
8
8
|
type Client = Awaited<ReturnType<Provider['clientInformation']>>;
|
|
9
9
|
type Discovery = NonNullable<McpOAuthState['discovery']>;
|
|
10
10
|
export declare const CLIENT_NAME = "SLICC";
|
|
11
|
+
export declare function sentence(text: string): string;
|
|
11
12
|
export declare function secureUrl(url: URL): boolean;
|
|
12
13
|
export declare function signInRequired(cause?: unknown): McpOAuthAuthorizationRequiredError;
|
|
13
14
|
export declare function authProvider(provider: StoredOAuth, server: OAuthServer): AuthProvider;
|
|
@@ -43,7 +43,11 @@ export interface ScoopsSetup {
|
|
|
43
43
|
}
|
|
44
44
|
export declare const COMMANDS: string[];
|
|
45
45
|
export declare function pnpmHomeOf(env: ExecutionEnv, context: Context): Promise<string>;
|
|
46
|
-
export
|
|
46
|
+
export interface Installed {
|
|
47
|
+
done: boolean;
|
|
48
|
+
skipped: string[];
|
|
49
|
+
}
|
|
50
|
+
export declare function installCommands(env: ExecutionEnv, script: string, binDir: string, context: Context, warn?: (message: string) => void): Promise<Installed>;
|
|
47
51
|
export declare function retire(env: ExecutionEnv, binDir: string, context: Context): Promise<void>;
|
|
48
52
|
export declare function setupScoops(registry: Registry, licks: Licks, fileTools?: readonly ToolRegistration[]): ScoopsSetup;
|
|
49
53
|
export type { Rewound, Scoops } from './service.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-agent",
|
|
3
|
-
"version": "3.36.
|
|
3
|
+
"version": "3.36.2",
|
|
4
4
|
"description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@ai-ecoverse/slicc-kernel": "1.47.1",
|
|
65
65
|
"@ai-ecoverse/slicc-shared-web": "1.12.1",
|
|
66
|
-
"@ai-ecoverse/slicc-spectrum": "1.
|
|
66
|
+
"@ai-ecoverse/slicc-spectrum": "1.54.0",
|
|
67
67
|
"@ai-ecoverse/wasm-bash": "5.3.0-17",
|
|
68
68
|
"@ai-ecoverse/wasm-coreutils": "9.12.0-5",
|
|
69
69
|
"@ai-ecoverse/wasm-git": "2.55.0-15",
|