var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { try { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; } catch (e) { throw mod = 0, e; } }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // node_modules/partial-json/dist/options.js var require_options = __commonJS({ "node_modules/partial-json/dist/options.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Allow = exports.ALL = exports.COLLECTION = exports.ATOM = exports.SPECIAL = exports.INF = exports._INFINITY = exports.INFINITY = exports.NAN = exports.BOOL = exports.NULL = exports.OBJ = exports.ARR = exports.NUM = exports.STR = void 0; exports.STR = 1; exports.NUM = 2; exports.ARR = 4; exports.OBJ = 8; exports.NULL = 16; exports.BOOL = 32; exports.NAN = 64; exports.INFINITY = 128; exports._INFINITY = 256; exports.INF = exports.INFINITY | exports._INFINITY; exports.SPECIAL = exports.NULL | exports.BOOL | exports.INF | exports.NAN; exports.ATOM = exports.STR | exports.NUM | exports.SPECIAL; exports.COLLECTION = exports.ARR | exports.OBJ; exports.ALL = exports.ATOM | exports.COLLECTION; exports.Allow = { STR: exports.STR, NUM: exports.NUM, ARR: exports.ARR, OBJ: exports.OBJ, NULL: exports.NULL, BOOL: exports.BOOL, NAN: exports.NAN, INFINITY: exports.INFINITY, _INFINITY: exports._INFINITY, INF: exports.INF, SPECIAL: exports.SPECIAL, ATOM: exports.ATOM, COLLECTION: exports.COLLECTION, ALL: exports.ALL }; exports.default = exports.Allow; } }); // node_modules/partial-json/dist/index.js var require_dist = __commonJS({ "node_modules/partial-json/dist/index.js"(exports) { "use strict"; var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) { if (k2 === void 0) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === void 0) k2 = k; o[k2] = m[k]; })); var __exportStar = exports && exports.__exportStar || function(m, exports2) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Allow = exports.MalformedJSON = exports.PartialJSON = exports.parseJSON = exports.parse = void 0; var options_1 = require_options(); Object.defineProperty(exports, "Allow", { enumerable: true, get: function() { return options_1.Allow; } }); __exportStar(require_options(), exports); var PartialJSON = class extends Error { }; exports.PartialJSON = PartialJSON; var MalformedJSON = class extends Error { }; exports.MalformedJSON = MalformedJSON; function parseJSON(jsonString, allowPartial = options_1.Allow.ALL) { if (typeof jsonString !== "string") { throw new TypeError(`expecting str, got ${typeof jsonString}`); } if (!jsonString.trim()) { throw new Error(`${jsonString} is empty`); } return _parseJSON(jsonString.trim(), allowPartial); } exports.parseJSON = parseJSON; var _parseJSON = (jsonString, allow) => { const length = jsonString.length; let index = 0; const markPartialJSON = (msg) => { throw new PartialJSON(`${msg} at position ${index}`); }; const throwMalformedError = (msg) => { throw new MalformedJSON(`${msg} at position ${index}`); }; const parseAny = () => { skipBlank(); if (index >= length) markPartialJSON("Unexpected end of input"); if (jsonString[index] === '"') return parseStr(); if (jsonString[index] === "{") return parseObj(); if (jsonString[index] === "[") return parseArr(); if (jsonString.substring(index, index + 4) === "null" || options_1.Allow.NULL & allow && length - index < 4 && "null".startsWith(jsonString.substring(index))) { index += 4; return null; } if (jsonString.substring(index, index + 4) === "true" || options_1.Allow.BOOL & allow && length - index < 4 && "true".startsWith(jsonString.substring(index))) { index += 4; return true; } if (jsonString.substring(index, index + 5) === "false" || options_1.Allow.BOOL & allow && length - index < 5 && "false".startsWith(jsonString.substring(index))) { index += 5; return false; } if (jsonString.substring(index, index + 8) === "Infinity" || options_1.Allow.INFINITY & allow && length - index < 8 && "Infinity".startsWith(jsonString.substring(index))) { index += 8; return Infinity; } if (jsonString.substring(index, index + 9) === "-Infinity" || options_1.Allow._INFINITY & allow && 1 < length - index && length - index < 9 && "-Infinity".startsWith(jsonString.substring(index))) { index += 9; return -Infinity; } if (jsonString.substring(index, index + 3) === "NaN" || options_1.Allow.NAN & allow && length - index < 3 && "NaN".startsWith(jsonString.substring(index))) { index += 3; return NaN; } return parseNum(); }; const parseStr = () => { const start = index; let escape = false; index++; while (index < length && (jsonString[index] !== '"' || escape && jsonString[index - 1] === "\\")) { escape = jsonString[index] === "\\" ? !escape : false; index++; } if (jsonString.charAt(index) == '"') { try { return JSON.parse(jsonString.substring(start, ++index - Number(escape))); } catch (e) { throwMalformedError(String(e)); } } else if (options_1.Allow.STR & allow) { try { return JSON.parse(jsonString.substring(start, index - Number(escape)) + '"'); } catch (e) { return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf("\\")) + '"'); } } markPartialJSON("Unterminated string literal"); }; const parseObj = () => { index++; skipBlank(); const obj2 = {}; try { while (jsonString[index] !== "}") { skipBlank(); if (index >= length && options_1.Allow.OBJ & allow) return obj2; const key = parseStr(); skipBlank(); index++; try { const value2 = parseAny(); obj2[key] = value2; } catch (e) { if (options_1.Allow.OBJ & allow) return obj2; else throw e; } skipBlank(); if (jsonString[index] === ",") index++; } } catch (e) { if (options_1.Allow.OBJ & allow) return obj2; else markPartialJSON("Expected '}' at end of object"); } index++; return obj2; }; const parseArr = () => { index++; const arr2 = []; try { while (jsonString[index] !== "]") { arr2.push(parseAny()); skipBlank(); if (jsonString[index] === ",") { index++; } } } catch (e) { if (options_1.Allow.ARR & allow) { return arr2; } markPartialJSON("Expected ']' at end of array"); } index++; return arr2; }; const parseNum = () => { if (index === 0) { if (jsonString === "-") throwMalformedError("Not sure what '-' is"); try { return JSON.parse(jsonString); } catch (e) { if (options_1.Allow.NUM & allow) try { return JSON.parse(jsonString.substring(0, jsonString.lastIndexOf("e"))); } catch (e2) { } throwMalformedError(String(e)); } } const start = index; if (jsonString[index] === "-") index++; while (jsonString[index] && ",]}".indexOf(jsonString[index]) === -1) index++; if (index == length && !(options_1.Allow.NUM & allow)) markPartialJSON("Unterminated number literal"); try { return JSON.parse(jsonString.substring(start, index)); } catch (e) { if (jsonString.substring(start, index) === "-") markPartialJSON("Not sure what '-' is"); try { return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf("e"))); } catch (e2) { throwMalformedError(String(e2)); } } }; const skipBlank = () => { while (index < length && " \n\r ".includes(jsonString[index])) { index++; } }; return parseAny(); }; var parse = parseJSON; exports.parse = parse; } }); // node_modules/@earendil-works/chord/dist/context/index.js var context_exports = {}; __export(context_exports, { BACKGROUND_CONTEXT: () => BACKGROUND_CONTEXT, TODO_CONTEXT: () => TODO_CONTEXT, awaitWithContext: () => awaitWithContext, createContextKey: () => createContextKey, traceIn: () => traceIn, withAbortSignal: () => withAbortSignal, withCancel: () => withCancel, withContextValue: () => withContextValue, withTelemetry: () => withTelemetry, withoutAbortSignal: () => withoutAbortSignal }); // node_modules/@earendil-works/pi-telemetry/dist/metrics.js function createTypedMetrics(metrics, schemas) { const definitions = /* @__PURE__ */ new Map(); for (const schema of schemas) { for (const [name, definition] of Object.entries(schema.metrics ?? {})) definitions.set(name, definition); } return { counter: (name) => metrics.counter(name, definitions.get(name)), upDownCounter: (name) => metrics.upDownCounter(name, definitions.get(name)), histogram: (name) => metrics.histogram(name, definitions.get(name)) }; } // node_modules/@earendil-works/pi-telemetry/dist/noop.js async function startNoopSpan(_options, callback) { return callback(noopTelemetrySpan); } var noopCounter = Object.freeze({ add: () => { } }); var noopUpDownCounter = Object.freeze({ add: () => { } }); var noopHistogram = Object.freeze({ record: () => { } }); var NOOP_TELEMETRY_METRICS = Object.freeze({ counter: () => noopCounter, upDownCounter: () => noopUpDownCounter, histogram: () => noopHistogram }); var NOOP_TELEMETRY_PROPAGATION = Object.freeze({ inject: () => void 0, extract: () => NOOP_TELEMETRY_CONTEXT }); var noopTelemetrySpan = { metrics: NOOP_TELEMETRY_METRICS, propagation: NOOP_TELEMETRY_PROPAGATION, startSpan: startNoopSpan, addLink: () => { }, addEvent: () => { }, setAttributes: () => { }, setStatus: () => { } }; Object.freeze(noopTelemetrySpan); var NOOP_TELEMETRY_CONTEXT = noopTelemetrySpan; // node_modules/@earendil-works/pi-telemetry/dist/index.js function defineTelemetrySchema(schema) { return schema; } function bindTypedSpanStarter(telemetryContext) { const startSpan = (name, attributes, callback) => telemetryContext.startSpan({ name, attributes }, (span) => callback(span, bindTypedSpanStarter(span))); return startSpan; } function createTypedSpanStarter(telemetryContext, _schemas) { return bindTypedSpanStarter(telemetryContext); } // node_modules/@earendil-works/chord/dist/context/index.js var ABORT_SIGNAL_CONTEXT_KEY = Object.freeze({ token: /* @__PURE__ */ Symbol("chord.abortSignal") }); var TELEMETRY_CONTEXT_KEY = Object.freeze({ token: /* @__PURE__ */ Symbol("chord.telemetry") }); var BaseContext = class { get abortSignal() { return this.value(ABORT_SIGNAL_CONTEXT_KEY); } get telemetry() { return this.value(TELEMETRY_CONTEXT_KEY) ?? NOOP_TELEMETRY_CONTEXT; } }; var EmptyContext = class extends BaseContext { #name; constructor(name) { super(); this.#name = name; } value(_key) { return void 0; } toString() { return this.#name; } }; var ContextValue = class extends BaseContext { #parent; #key; #value; constructor(parent, key, value2) { super(); this.#parent = parent; this.#key = key; this.#value = value2; } value(key) { if (key.token === this.#key.token) return this.#value; return this.#parent.value(key); } toString() { return `${this.#parent}.WithValue(${this.#key.token.description ?? "anonymous"})`; } }; var BACKGROUND_CONTEXT = new EmptyContext("[Context BACKGROUND_CONTEXT]"); var TODO_CONTEXT = new EmptyContext("[Context TODO_CONTEXT]"); function createContextKey(description) { return Object.freeze({ token: Symbol(description) }); } function withContextValue(key, value2, parent) { return new ContextValue(parent, key, value2); } function withTelemetry(telemetry, context) { return withContextValue(TELEMETRY_CONTEXT_KEY, telemetry, context); } function traceIn(options, fn, context) { return context.telemetry.startSpan(options, (span) => fn(withTelemetry(span, context))); } function withAbortSignal(signal, context) { const parentSignal = context.abortSignal; const combined = parentSignal === void 0 ? signal : AbortSignal.any([parentSignal, signal]); return withContextValue(ABORT_SIGNAL_CONTEXT_KEY, combined, context); } function withoutAbortSignal(context) { return withContextValue(ABORT_SIGNAL_CONTEXT_KEY, void 0, context); } function withCancel(context) { const controller = new AbortController(); return { context: withAbortSignal(controller.signal, context), cancel: (reason) => controller.abort(reason) }; } function awaitWithContext(promise, context) { const signal = context.abortSignal; if (signal === void 0) return promise; if (signal.aborted) return Promise.reject(abortError(signal)); return new Promise((resolve2, reject2) => { const onAbort = () => reject2(abortError(signal)); signal.addEventListener("abort", onAbort, { once: true }); void promise.then((value2) => { signal.removeEventListener("abort", onAbort); resolve2(value2); }, (error) => { signal.removeEventListener("abort", onAbort); reject2(error); }); }); } function abortError(signal) { const reason = signal.reason; return reason instanceof Error ? reason : new DOMException("The operation was aborted", "AbortError"); } // node_modules/@earendil-works/pi-ai/dist/models.js var models_exports = {}; __export(models_exports, { AI_TELEMETRY_SCHEMA: () => AI_TELEMETRY_SCHEMA, ModelsError: () => ModelsError, calculateCost: () => calculateCost, clampThinkingLevel: () => clampThinkingLevel, createModels: () => createModels, createProvider: () => createProvider, getModelType: () => getModelType, getSupportedThinkingLevels: () => getSupportedThinkingLevels, hasApi: () => hasApi, isModelType: () => isModelType, modelsAreEqual: () => modelsAreEqual }); // node_modules/@earendil-works/pi-ai/dist/utils/event-stream.js var FifoQueue = class { incoming = []; outgoing = []; get length() { return this.incoming.length + this.outgoing.length; } enqueue(value2) { this.incoming.push(value2); } dequeue() { if (this.outgoing.length === 0) { while (this.incoming.length > 0) { this.outgoing.push(this.incoming.pop()); } } return this.outgoing.pop(); } }; var EventStream = class { queue = new FifoQueue(); waiting = new FifoQueue(); done = false; finalResultPromise; resolveFinalResult; isComplete; extractResult; constructor(isComplete, extractResult) { this.isComplete = isComplete; this.extractResult = extractResult; this.finalResultPromise = new Promise((resolve2) => { this.resolveFinalResult = resolve2; }); } push(event) { if (this.done) return; if (this.isComplete(event)) { this.done = true; this.resolveFinalResult(this.extractResult(event)); } const waiter = this.waiting.dequeue(); if (waiter) { waiter({ value: event, done: false }); } else { this.queue.enqueue(event); } } end(result) { this.done = true; if (result !== void 0) { this.resolveFinalResult(result); } while (this.waiting.length > 0) { const waiter = this.waiting.dequeue(); waiter({ value: void 0, done: true }); } } async *[Symbol.asyncIterator]() { while (true) { if (this.queue.length > 0) { yield this.queue.dequeue(); } else if (this.done) { return; } else { const result = await new Promise((resolve2) => this.waiting.enqueue(resolve2)); if (result.done) return; yield result.value; } } } result() { return this.finalResultPromise; } }; var AssistantMessageEventStream = class extends EventStream { #startedAt = Date.now(); #startedAtMonotonic = performance.now(); constructor() { super((event) => event.type === "done" || event.type === "error", (event) => { if (event.type === "done") { return event.message; } else if (event.type === "error") { return event.error; } throw new Error("Unexpected event type for final result"); }); } push(event) { if (event.type === "done") this.#time(event.message); else if (event.type === "error") this.#time(event.error); super.push(event); } end(result) { if (result !== void 0) this.#time(result); super.end(result); } #time(message) { if (this.done || message.durationMs !== void 0 || message.timestamp < this.#startedAt) return; message.durationMs = Math.max(0, Math.round(performance.now() - this.#startedAtMonotonic)); } }; function createAssistantMessageEventStream() { return new AssistantMessageEventStream(); } // node_modules/@earendil-works/pi-ai/dist/api/lazy.js function createSetupErrorMessage(model2, error, timestamp) { return { role: "assistant", content: [], api: model2.api, provider: model2.provider, model: model2.id, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "error", errorMessage: error instanceof Error ? error.message : String(error), timestamp }; } function hasResult(source) { return typeof source.result === "function"; } async function forwardStream(target, source, observer, observeResult) { let result; for await (const event of source) { target.push(event); observer?.onEvent?.(event); if (event.type === "done") result = event.message; else if (event.type === "error") result = event.error; } if (hasResult(source)) result = await source.result(); target.end(result); observeResult(result); } function lazyStream(model2, setup, observer) { const startedAt = Date.now(); const outer = new AssistantMessageEventStream(); let observedResult = false; const observeResult = (result) => { if (observedResult) return; observedResult = true; observer?.onResult?.(result); }; setup().then((inner) => forwardStream(outer, inner, observer, observeResult)).catch((error) => { const message = createSetupErrorMessage(model2, error, startedAt); const event = { type: "error", reason: "error", error: message }; outer.push(event); observer?.onEvent?.(event); outer.end(message); observeResult(message); }); return outer; } function lazyApi(load, capabilities) { const api = { stream: (model2, context, options) => lazyStream(model2, async () => (await load()).stream(model2, context, options)), streamSimple: (model2, context, options) => lazyStream(model2, async () => (await load()).streamSimple(model2, context, options)) }; if (capabilities?.fetchDeferred) { api.fetchDeferred = (model2, handle, options) => lazyStream(model2, async () => { const implementation = await load(); if (!implementation.fetchDeferred) throw new Error("API does not support deferred responses"); return implementation.fetchDeferred(model2, handle, options); }); } if (capabilities?.cancelDeferred) { api.cancelDeferred = async (model2, handle, options) => { const implementation = await load(); if (!implementation.cancelDeferred) throw new Error("API cannot cancel deferred responses"); await implementation.cancelDeferred(model2, handle, options); }; } return api; } // node_modules/@earendil-works/pi-ai/dist/auth/context.js var __rewriteRelativeImportExtension = function(path, preserveJsx) { if (typeof path === "string" && /^\.\.?\//.test(path)) { return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js"; }); } return path; }; var importNodeModule = (specifier) => import(__rewriteRelativeImportExtension(specifier)); function getProcessEnv() { const proc = globalThis.process; return proc?.env; } function defaultProviderAuthContext() { return { async env(name) { const value2 = getProcessEnv()?.[name]; return typeof value2 === "string" && value2.trim().length > 0 ? value2 : void 0; }, async fileExists(path) { try { const fs2 = await importNodeModule("node:fs/promises"); let resolved = path; if (resolved.startsWith("~")) { const os = await importNodeModule("node:os"); resolved = os.homedir() + resolved.slice(1); } await fs2.access(resolved); return true; } catch { return false; } } }; } // node_modules/@earendil-works/pi-ai/dist/utils/abort.js function abortReason(signal) { if (signal.reason !== void 0) return signal.reason; const error = new Error("The operation was aborted"); error.name = "AbortError"; return error; } function operationSignal(signal) { return signal ?? new AbortController().signal; } function raceWithAbortSignal(operation, signal) { if (signal.aborted) { void operation.catch(() => { }); return Promise.reject(abortReason(signal)); } return new Promise((resolve2, reject2) => { let settled = false; const cleanup = () => signal.removeEventListener("abort", onAbort); const onAbort = () => { if (settled) return; settled = true; cleanup(); reject2(abortReason(signal)); }; signal.addEventListener("abort", onAbort, { once: true }); void operation.then((value2) => { if (settled) return; settled = true; cleanup(); resolve2(value2); }, (error) => { if (settled) return; settled = true; cleanup(); reject2(error); }); if (signal.aborted) onAbort(); }); } // node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js var InMemoryCredentialStore = class { credentials = /* @__PURE__ */ new Map(); chains = /* @__PURE__ */ new Map(); /** Serialize tasks per provider id without releasing the chain before active work settles. */ enqueue(providerId, task, options) { const signal = operationSignal(options?.signal); const previous = this.chains.get(providerId) ?? Promise.resolve(); const queued = (async () => { await previous.catch(() => { }); signal.throwIfAborted(); return task(); })(); const tail = queued.catch(() => { }); this.chains.set(providerId, tail); void tail.then(() => { if (this.chains.get(providerId) === tail) this.chains.delete(providerId); }); return raceWithAbortSignal(queued, signal); } async read(providerId, options) { options?.signal?.throwIfAborted(); return this.credentials.get(providerId); } async list(options) { options?.signal?.throwIfAborted(); return [...this.credentials].map(([providerId, credential]) => ({ providerId, type: credential.type })); } modify(providerId, fn, options) { return this.enqueue(providerId, async () => { const current = this.credentials.get(providerId); const next = await fn(current); options?.signal?.throwIfAborted(); if (next !== void 0) this.credentials.set(providerId, next); return next ?? current; }, options); } delete(providerId, options) { return this.enqueue(providerId, async () => { this.credentials.delete(providerId); }, options); } }; // node_modules/@earendil-works/pi-ai/dist/utils/diagnostics.js function formatThrownValue(value2) { if (value2 instanceof Error) return value2.message || value2.name; if (typeof value2 === "string") return value2; return String(value2); } function extractDiagnosticError(error) { if (!(error instanceof Error)) return { name: "ThrownValue", message: formatThrownValue(error) }; const code = error.code; return { name: error.name || void 0, message: error.message || error.name, stack: error.stack, code: typeof code === "string" || typeof code === "number" ? code : void 0 }; } function createAssistantMessageDiagnostic(type, error, details) { return { type, timestamp: Date.now(), error: extractDiagnosticError(error), details }; } function appendAssistantMessageDiagnostic(message, diagnostic) { message.diagnostics = [...message.diagnostics ?? [], diagnostic]; } // node_modules/@earendil-works/pi-ai/dist/utils/models-error.js var ModelsError = class extends Error { code; constructor(code, message, options) { super(withCauseDetail(message, options?.cause), options); this.name = "ModelsError"; this.code = code; } }; function withCauseDetail(message, cause) { if (cause === void 0 || cause === null) return message; const detail = formatThrownValue(cause).trim(); if (!detail || message.includes(detail)) return message; return `${message}: ${detail}`; } // node_modules/@earendil-works/pi-ai/dist/auth/resolve.js function resolveProviderAuth(provider2, credentials, authContext, overrides) { const signal = operationSignal(overrides?.signal); return raceWithAbortSignal(resolveProviderAuthWithSignal(provider2, credentials, authContext, overrides, signal), signal); } async function resolveProviderAuthWithSignal(provider2, credentials, authContext, overrides, signal) { signal.throwIfAborted(); const requestAuthContext = overrides?.env ? overlayEnvAuthContext(authContext, overrides.env) : authContext; if (overrides?.apiKey !== void 0 && provider2.auth.apiKey) { return resolveApiKey(requestAuthContext, provider2.auth.apiKey, provider2.id, { type: "api_key", key: overrides.apiKey, env: overrides.env }, signal); } const stored = await readCredential(credentials, provider2.id, signal); if (stored) { if (stored.type === "oauth" && provider2.auth.oauth) { return resolveStoredOAuth(credentials, provider2.id, provider2.auth.oauth, stored, signal, overrides?.minOAuthValidityMs); } if (stored.type === "api_key" && provider2.auth.apiKey) { const credential = overrides?.env ? { ...stored, env: { ...stored.env, ...overrides.env } } : stored; return resolveApiKey(requestAuthContext, provider2.auth.apiKey, provider2.id, credential, signal); } return void 0; } return provider2.auth.apiKey ? resolveApiKey(requestAuthContext, provider2.auth.apiKey, provider2.id, void 0, signal) : void 0; } function overlayEnvAuthContext(base, env) { return { env: async (name) => env[name] || await base.env(name), fileExists: (path) => base.fileExists(path) }; } var DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 5 * 60 * 1e3; var DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15e3; async function refreshStoredOAuthCredential(credentials, providerId, oauth, needsRefresh, signal) { let post; const lockWait = new AbortController(); const cancelLockWait = () => lockWait.abort(signal.reason); signal.addEventListener("abort", cancelLockWait, { once: true }); if (signal.aborted) cancelLockWait(); try { post = await credentials.modify(providerId, async (current) => { signal.removeEventListener("abort", cancelLockWait); signal.throwIfAborted(); if (current?.type !== "oauth") return void 0; if (!needsRefresh(current)) return void 0; try { return await oauth.refresh(current, AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS)); } catch (error) { throw new ModelsError("oauth", `OAuth refresh failed for ${providerId}`, { cause: error }); } }, { signal: lockWait.signal }); } catch (error) { if (error instanceof ModelsError) throw error; signal.throwIfAborted(); throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error }); } finally { signal.removeEventListener("abort", cancelLockWait); } return post?.type === "oauth" ? post : void 0; } async function resolveStoredOAuth(credentials, providerId, oauth, stored, signal, minOAuthValidityMs) { const minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0); const expiresSoon = (credential2) => Date.now() + minimumValidityMs >= credential2.expires; let credential = stored; if (expiresSoon(credential)) { const post = await refreshStoredOAuthCredential(credentials, providerId, oauth, expiresSoon, signal); if (!post) return void 0; credential = post; if (minOAuthValidityMs !== void 0 && expiresSoon(credential)) { throw new ModelsError("oauth", `OAuth refresh returned a token that expires too soon for ${providerId}`); } } try { return { auth: await oauth.toAuth(credential), source: "OAuth" }; } catch (error) { throw new ModelsError("oauth", `OAuth auth derivation failed for ${providerId}`, { cause: error }); } } async function resolveApiKey(authContext, apiKey, providerId, credential, signal) { try { return await apiKey.resolve({ ctx: authContext, credential, signal }); } catch (error) { throw new ModelsError("auth", `API key auth failed for provider ${providerId}`, { cause: error }); } } async function readCredential(credentials, providerId, signal) { try { return await credentials.read(providerId, { signal }); } catch (error) { throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error }); } } // node_modules/@earendil-works/pi-ai/dist/models-store.js var InMemoryModelsStore = class { entries = /* @__PURE__ */ new Map(); async read(providerId, options) { options?.signal?.throwIfAborted(); const entry2 = this.entries.get(providerId); return entry2 ? structuredClone(entry2) : void 0; } async write(providerId, entry2, options) { options?.signal?.throwIfAborted(); this.entries.set(providerId, structuredClone(entry2)); } async delete(providerId, options) { options?.signal?.throwIfAborted(); this.entries.delete(providerId); } }; // node_modules/@earendil-works/pi-ai/dist/utils/text.js function contentText(content, separator = "\n") { if (typeof content === "string") return content; return content.filter((block) => block.type === "text").map((block) => block.text).join(separator); } function getSystemMessageText(message) { const parts = [contentText(message.content)]; for (const text of Object.values(message.sections ?? {})) { if (text !== null) parts.push(text); } return parts.filter((part) => part.length > 0).join("\n\n"); } function renderSystemMessageUpdate(message) { const parts = []; const text = contentText(message.content); if (text.length > 0) parts.push(text); for (const [name, value2] of Object.entries(message.sections ?? {})) { parts.push(value2 === null ? `Removed system prompt section "${name}".` : `Updated system prompt section "${name}": ${value2}`); } return parts.join("\n\n"); } function ownedSlice(text, start, end = text.length) { return `${text.slice(start, end)} `.slice(0, -1); } // node_modules/@earendil-works/pi-ai/dist/telemetry.js var provider = { type: "string", description: "Provider identifier.", required: true }; var model = { type: "string", description: "Requested model identifier.", required: true }; var stop = { type: "string", description: "Normalized stop reason.", values: ["pending", "stop", "length", "toolUse", "error", "aborted", "deferred"] }; var request = { description: "One logical model request, including authentication and provider setup.", parents: { kind: "any" }, startAttributes: { provider, api: { type: "string", description: "API identifier.", required: true }, model, reasoning: { type: "string", description: "Scalar requested reasoning level.", required: false } }, endAttributes: { stop, "usage.input": { type: "number", description: "Uncached input tokens." }, "usage.output": { type: "number", description: "Output tokens." }, "usage.cache_read": { type: "number", description: "Cache-read tokens." }, "usage.cache_write": { type: "number", description: "Cache-write tokens." }, "cost.total": { type: "number", description: "Total request cost in USD." }, "response.id": { type: "string", description: "Provider response identifier.", cardinality: "high" } }, events: { "pi.ai.first_chunk": { description: "First normalized stream event delivered.", attributes: {} }, "pi.ai.block": { description: "A completed content block; content is never recorded.", attributes: { type: { type: "string", description: "Block kind.", values: ["text", "thinking", "tool_call"], required: true }, index: { type: "number", description: "Content block index.", required: true }, chars: { type: "number", description: "UTF-16 character count.", required: true }, tool: { type: "string", description: "Tool name, for tool-call blocks only.", required: false } } } }, status: { default: "ok", errorWhen: "Stop reason is error: provider_error with at most 500 error-message characters. Aborted stays ok." } }; var AI_TELEMETRY_SCHEMA = defineTelemetrySchema({ version: 1, spans: { "pi.ai.request": request, "pi.ai.poll": { ...request, description: "One deferred-response poll, not the deferred request's lifetime." } }, metrics: { "pi.ai.request.duration": { kind: "histogram", description: "Logical request or poll duration.", unit: "ms", attributes: { provider, model, stop: { ...stop, required: true } } }, "pi.ai.first_chunk.duration": { kind: "histogram", description: "Time to the first normalized stream event.", unit: "ms", attributes: { provider, model } }, "pi.ai.tokens": { kind: "counter", description: "Reported model token usage; pending and deferred polls are excluded.", unit: "token", attributes: { provider, model, kind: { type: "string", description: "Token usage category.", values: ["input", "output", "cache_read", "cache_write"], required: true } } }, "pi.ai.cost": { kind: "counter", description: "Reported model cost; pending and deferred polls are excluded.", unit: "USD", attributes: { provider, model } } } }); function createInstruments(adapter) { const metrics = createTypedMetrics(adapter, [AI_TELEMETRY_SCHEMA]); return { requestDuration: metrics.histogram("pi.ai.request.duration"), firstChunkDuration: metrics.histogram("pi.ai.first_chunk.duration"), tokens: metrics.counter("pi.ai.tokens"), cost: metrics.counter("pi.ai.cost") }; } var instrumentCache = /* @__PURE__ */ new WeakMap(); function observeModelRequest(requestedModel, options, poll) { const started = performance.now(); const attributes = { provider: requestedModel.provider, model: requestedModel.id }; const adapter = options?.telemetry?.metrics ?? NOOP_TELEMETRY_METRICS; let instruments = instrumentCache.get(adapter); if (!instruments) { instruments = createInstruments(adapter); instrumentCache.set(adapter, instruments); } const { requestDuration, firstChunkDuration, tokens, cost } = instruments; let span; let finish2; let first = true; const toolChars = /* @__PURE__ */ new Map(); const finished = new Promise((resolve2) => { finish2 = resolve2; }); const run2 = (activeSpan) => { span = activeSpan; return finished; }; const startAttributes = { ...attributes, api: requestedModel.api, ...typeof options?.reasoning === "string" ? { reasoning: options.reasoning } : {} }; const start = createTypedSpanStarter(options?.telemetry ?? NOOP_TELEMETRY_CONTEXT, [AI_TELEMETRY_SCHEMA]); if (poll) void start("pi.ai.poll", startAttributes, run2); else void start("pi.ai.request", startAttributes, run2); return { onEvent(event) { if (first) { first = false; span.addEvent("pi.ai.first_chunk"); firstChunkDuration.record(performance.now() - started, attributes, span); } if (event.type === "toolcall_delta") { toolChars.set(event.contentIndex, (toolChars.get(event.contentIndex) ?? 0) + event.delta.length); } else if (event.type === "text_end" || event.type === "thinking_end") { span.addEvent("pi.ai.block", { type: event.type === "text_end" ? "text" : "thinking", index: event.contentIndex, chars: event.content.length }); } else if (event.type === "toolcall_end") { const chars = toolChars.get(event.contentIndex) ?? 0; toolChars.delete(event.contentIndex); span.addEvent("pi.ai.block", { type: "tool_call", index: event.contentIndex, chars, tool: event.toolCall.name }); } }, onResult(message) { if (message) { span.setAttributes({ stop: message.stopReason, "usage.input": message.usage.input, "usage.output": message.usage.output, "usage.cache_read": message.usage.cacheRead, "usage.cache_write": message.usage.cacheWrite, "cost.total": message.usage.cost.total, "response.id": message.responseId }); if (message.stopReason === "error") { span.setStatus({ status: "error", error: { name: "provider_error", message: ownedSlice(message.errorMessage ?? "", 0, 500) } }); } requestDuration.record(performance.now() - started, { ...attributes, stop: message.stopReason }, span); if (!poll || message.stopReason !== "pending" && message.stopReason !== "deferred") { tokens.add(message.usage.input, { ...attributes, kind: "input" }); tokens.add(message.usage.output, { ...attributes, kind: "output" }); tokens.add(message.usage.cacheRead, { ...attributes, kind: "cache_read" }); tokens.add(message.usage.cacheWrite, { ...attributes, kind: "cache_write" }); cost.add(message.usage.cost.total, attributes); } } finish2(); } }; } // node_modules/@earendil-works/pi-ai/dist/utils/version.js var PI_AI_VERSION = "1.5.0"; // node_modules/@earendil-works/pi-ai/dist/utils/identity.js var PI_ATTRIBUTION = { name: "pi", url: "https://pi.dev", categories: ["cli-agent"] }; var NVIDIA_NIM_HOST = "integrate.api.nvidia.com"; var OPENCODE_HOST = "opencode.ai"; function mergeClientIdentity(...sources) { let merged; for (const source of sources) { if (!source) continue; merged = { ...merged }; if (source.userAgent !== void 0) merged.userAgent = source.userAgent; if (source.attribution !== void 0) merged.attribution = source.attribution; if (source.version !== void 0) merged.version = source.version; } return merged; } function getClientVersion(identity) { return identity?.version ?? PI_AI_VERSION; } function runtimeDescription() { if (typeof process === "undefined") return void 0; const versions = process.versions; if (versions?.bun) return `bun/${versions.bun}`; if (versions?.node) return `node/${process.version}`; return void 0; } function formatUserAgent(identity) { const mode = identity?.userAgent ?? "detailed"; if (typeof mode === "object") return mode.custom; if (mode === "short") return "pi"; const product = `pi/${getClientVersion(identity)}`; const runtime = runtimeDescription(); return runtime ? `${product} (${process.platform}; ${runtime}; ${process.arch})` : `${product} (browser)`; } function matchesHost(baseUrl, expectedHost) { try { return new URL(baseUrl).hostname === expectedHost; } catch { return false; } } function isOpenRouter(target) { return target.provider === "openrouter" || target.baseUrl.includes("openrouter.ai"); } function isNvidiaNim(target) { return target.provider === "nvidia" || matchesHost(target.baseUrl, NVIDIA_NIM_HOST); } function isOpenCode(target) { return target.provider === "opencode" || target.provider === "opencode-go" || matchesHost(target.baseUrl, OPENCODE_HOST); } function getAttributionHeaders(target, identity) { const attribution = identity?.attribution; if (!attribution) return {}; const { name, url, categories } = attribution; if (isOpenRouter(target)) { return { "HTTP-Referer": url, "X-OpenRouter-Title": name, ...categories === void 0 || categories.length === 0 ? {} : { "X-OpenRouter-Categories": categories.join(",") } }; } if (isNvidiaNim(target)) return { "X-BILLING-INVOKE-ORIGIN": name.charAt(0).toUpperCase() + name.slice(1) }; if (isOpenCode(target)) return { "x-opencode-client": name }; return {}; } function getOpenCodeSessionHeaders(target, sessionId) { return sessionId && isOpenCode(target) ? { "x-opencode-session": sessionId } : void 0; } function getIdentityHeaders(target, identity) { return { "User-Agent": formatUserAgent(identity), ...getAttributionHeaders(target, identity) }; } function isMutableModels(models2) { return "setProvider" in models2 && "deleteProvider" in models2 && "clearProviders" in models2; } function withClientIdentity(models2, identity) { const withIdentity = (options) => ({ ...options, identity: mergeClientIdentity(identity, options?.identity) }); const wrapped = { getProviders: () => models2.getProviders(), getProvider: (id) => models2.getProvider(id), getModels: (provider2) => models2.getModels(provider2), getModel: (provider2, id) => models2.getModel(provider2, id), getModelsOfType: (type, provider2) => models2.getModelsOfType(type, provider2), getModelOfType: (type, provider2, id) => models2.getModelOfType(type, provider2, id), getAllModels: (provider2) => models2.getAllModels(provider2), refresh: (options) => models2.refresh(options), checkAuth: (providerId, options) => models2.checkAuth(providerId, options), getAvailable: (providerId, options) => models2.getAvailable(providerId, options), getAvailableOfType: (type, providerId, options) => models2.getAvailableOfType(type, providerId, options), getAllAvailable: (providerId, options) => models2.getAllAvailable(providerId, options), getAuth: models2.getAuth.bind(models2), login: (providerId, type, interaction, options) => models2.login(providerId, type, interaction, options), logout: (providerId, options) => models2.logout(providerId, options), stream: (model2, context, options) => models2.stream(model2, context, withIdentity(options)), complete: (model2, context, options) => models2.complete(model2, context, withIdentity(options)), streamSimple: (model2, context, options) => models2.streamSimple(model2, context, withIdentity(options)), completeSimple: (model2, context, options) => models2.completeSimple(model2, context, withIdentity(options)), streamDeferred: (model2, handle, options) => models2.streamDeferred(model2, handle, withIdentity(options)), fetchDeferred: (model2, handle, options) => models2.fetchDeferred(model2, handle, withIdentity(options)), cancelDeferred: (model2, handle, options) => models2.cancelDeferred(model2, handle, withIdentity(options)), generateImages: (model2, context, options) => models2.generateImages(model2, context, withIdentity(options)), classify: (model2, context, options) => models2.classify(model2, context, withIdentity(options)) }; if (!isMutableModels(models2)) return wrapped; const mutable = { ...wrapped, setProvider: (provider2) => models2.setProvider(provider2), deleteProvider: (id) => models2.deleteProvider(id), clearProviders: () => models2.clearProviders() }; return mutable; } // node_modules/@earendil-works/pi-ai/dist/utils/model-operations.js function getModelType(model2) { return model2.type ?? "chat"; } function isModelType(model2, type) { return getModelType(model2) === type; } function assertChatModel(model2) { if (!isModelType(model2, "chat")) { throw new ModelsError("provider", `Model ${model2.provider}/${model2.id} is not a chat model`); } } function assertImageModel(model2) { if (!isModelType(model2, "image")) { throw new ModelsError("provider", `Model ${model2.provider}/${model2.id} is not an image model`); } } function assertClassifierModel(model2) { if (!isModelType(model2, "classifier")) { throw new ModelsError("provider", `Model ${model2.provider}/${model2.id} is not a classifier model`); } } function assertClassifierInputSupported(model2, context) { if (context.images?.length && !model2.input.includes("image")) { throw new ModelsError("provider", `Model ${model2.provider}/${model2.id} does not accept image input`); } } function imageErrorResult(model2, error, aborted = false) { return { api: model2.api, provider: model2.provider, model: model2.id, output: [], stopReason: aborted ? "aborted" : "error", errorMessage: error instanceof Error ? error.message : String(error), timestamp: Date.now() }; } function classifierErrorResult(model2, error, aborted = false) { return { api: model2.api, provider: model2.provider, model: model2.id, answers: {}, stopReason: aborted ? "aborted" : "error", errorMessage: error instanceof Error ? error.message : String(error), timestamp: Date.now() }; } // node_modules/@earendil-works/pi-ai/dist/utils/transcript.js function createInitialSystemMessage(systemPrompt, tools) { const hasSystemPrompt = systemPrompt !== void 0 && systemPrompt.length > 0; const hasTools = tools !== void 0 && tools.length > 0; if (!hasSystemPrompt && !hasTools) return void 0; return { role: "system", content: systemPrompt ?? "", ...hasTools ? { toolsAdded: tools } : {}, timestamp: 0 }; } function normalizeContext(context) { const initialMessage = createInitialSystemMessage(context.systemPrompt, context.tools); const messages = initialMessage ? [initialMessage, ...context.messages] : context.messages; return { messages }; } function isSystemMessage(message) { return message.role === "system"; } function getInitialSystemMessage(messages) { const first = messages[0]; return first && isSystemMessage(first) ? first : void 0; } function withoutInitialSystemMessage(messages) { return getInitialSystemMessage(messages) ? messages.slice(1) : messages; } function getCurrentTools(messages) { const tools = /* @__PURE__ */ new Map(); for (const message of messages) { if (!isSystemMessage(message)) continue; for (const tool of message.toolsRemoved ?? []) tools.delete(tool.name); for (const tool of message.toolsAdded ?? []) tools.set(tool.name, tool); } return [...tools.values()]; } function getCurrentSystemMessage(messages) { const content = []; const sections = /* @__PURE__ */ new Map(); let timestamp; for (const message of messages) { if (!isSystemMessage(message)) continue; timestamp ??= message.timestamp; const text = contentText(message.content); if (text.length > 0) content.push(text); for (const [name, value2] of Object.entries(message.sections ?? {})) { if (value2 === null) sections.delete(name); else sections.set(name, value2); } } const tools = getCurrentTools(messages); if (timestamp === void 0 && tools.length === 0) return void 0; return { role: "system", content: content.join("\n\n"), ...sections.size > 0 ? { sections: Object.fromEntries(sections) } : {}, ...tools.length > 0 ? { toolsAdded: tools } : {}, timestamp: timestamp ?? 0 }; } function getCurrentSystemPrompt(messages) { const message = getCurrentSystemMessage(messages); return message ? getSystemMessageText(message) : ""; } function collapseSystemMessages(context) { const head = getCurrentSystemMessage(context.messages); const messages = context.messages.filter((message) => message.role !== "system"); return { messages: head ? [head, ...messages] : messages }; } function resolveTranscript(context, supportsMidConvoSystemMessages) { return supportsMidConvoSystemMessages ? context : collapseSystemMessages(context); } function toToolDeclaration(tool) { return { name: tool.name, description: tool.description, parameters: JSON.parse(JSON.stringify(tool.parameters)), ...tool.constrainedSampling === void 0 ? {} : { constrainedSampling: tool.constrainedSampling } }; } function declarationsEqual(left, right) { return JSON.stringify(toToolDeclaration(left)) === JSON.stringify(toToolDeclaration(right)); } function getToolStateChanges(previous, current) { const previousTools = new Map(previous.map((tool) => [tool.name, tool])); const currentTools = new Map(current.map((tool) => [tool.name, tool])); return { toolsAdded: current.filter((tool) => { const previousTool = previousTools.get(tool.name); return previousTool === void 0 || !declarationsEqual(previousTool, tool); }).map(toToolDeclaration), toolsRemoved: previous.filter((tool) => { const currentTool = currentTools.get(tool.name); return currentTool === void 0 || !declarationsEqual(tool, currentTool); }).map((tool) => ({ name: tool.name })) }; } function getDeclaredTools(messages) { const definitions = /* @__PURE__ */ new Map(); for (const message of messages) { if (!isSystemMessage(message)) continue; for (const tool of message.toolsAdded ?? []) definitions.set(tool.name, tool); } return [...definitions.values()]; } function hasToolRedefinitions(messages) { const declared = /* @__PURE__ */ new Map(); for (const message of messages) { if (!isSystemMessage(message)) continue; for (const tool of message.toolsAdded ?? []) { const previous = declared.get(tool.name); if (previous !== void 0 && !declarationsEqual(previous, tool)) return true; declared.set(tool.name, tool); } } return false; } function hasNonAdditiveToolChanges(messages) { return findLastNonAdditiveToolChange(messages.filter(isSystemMessage)) >= 0; } function findLastNonAdditiveToolChange(systemMessages) { const declared = /* @__PURE__ */ new Set(); let last = -1; for (let index = 0; index < systemMessages.length; index++) { const message = systemMessages[index]; if ((message.toolsRemoved?.length ?? 0) > 0) last = index; for (const tool of message.toolsAdded ?? []) { if (declared.has(tool.name)) last = index; declared.add(tool.name); } } return last; } function resolveTranscriptTools(messages, support) { const systemMessages = messages.filter(isSystemMessage); if (support === "none") { return { requestTools: getCurrentTools(systemMessages), foldedSystemMessages: systemMessages.length }; } if (support === "changes") return { requestTools: [], foldedSystemMessages: 0 }; const folded = Math.max(getInitialSystemMessage(messages) ? 1 : 0, findLastNonAdditiveToolChange(systemMessages) + 1); return { requestTools: getCurrentTools(systemMessages.slice(0, folded)), foldedSystemMessages: folded }; } // node_modules/@earendil-works/pi-ai/dist/models.js var KNOWN_MODEL_TYPES = { chat: true, image: true, classifier: true }; function hasKnownModelType(model2) { return Object.hasOwn(KNOWN_MODEL_TYPES, getModelType(model2)); } function withKnownModelTypes(entry2) { return { ...entry2, models: entry2.models.filter(hasKnownModelType) }; } function mergeHeaders(base, override) { if (!base && !override) return void 0; const merged = { ...base }; for (const [name, value2] of Object.entries(override ?? {})) { const lowerName = name.toLowerCase(); for (const existingName of Object.keys(merged)) { if (existingName.toLowerCase() === lowerName) delete merged[existingName]; } merged[name] = value2; } return merged; } var ModelsImpl = class { providers = /* @__PURE__ */ new Map(); credentials; modelsStore; authContext; identity; refreshGenerations = /* @__PURE__ */ new Map(); refreshControllers = /* @__PURE__ */ new Map(); publicationChains = /* @__PURE__ */ new Map(); constructor(options) { this.credentials = options?.credentials ?? new InMemoryCredentialStore(); this.modelsStore = options?.modelsStore ?? new InMemoryModelsStore(); this.authContext = options?.authContext ?? defaultProviderAuthContext(); this.identity = options?.identity; } setProvider(provider2) { this.supersedeProviderRefresh(provider2.id); this.providers.set(provider2.id, provider2); } deleteProvider(id) { this.supersedeProviderRefresh(id); this.providers.delete(id); } clearProviders() { for (const id of /* @__PURE__ */ new Set([...this.providers.keys(), ...this.refreshControllers.keys()])) { this.supersedeProviderRefresh(id); } this.providers.clear(); } getProviders() { return Array.from(this.providers.values()); } getProvider(id) { return this.providers.get(id); } getModels(provider2) { if (provider2 !== void 0) { const entry2 = this.providers.get(provider2); if (!entry2) return []; try { return entry2.getModels(); } catch { return []; } } const models2 = []; for (const entry2 of this.providers.values()) { try { models2.push(...entry2.getModels()); } catch { } } return models2; } getAllModels(provider2) { if (provider2 !== void 0) { const entry2 = this.providers.get(provider2); if (!entry2) return []; try { return entry2.getAllModels?.() ?? entry2.getModels(); } catch { return []; } } const models2 = []; for (const entry2 of this.providers.values()) { try { models2.push(...entry2.getAllModels?.() ?? entry2.getModels()); } catch { } } return models2; } getModelsOfType(type, provider2) { return this.getAllModels(provider2).filter((model2) => isModelType(model2, type)); } getModel(provider2, id) { return this.getModels(provider2).find((model2) => model2.id === id); } getModelOfType(type, provider2, id) { return this.getModelsOfType(type, provider2).find((model2) => model2.id === id); } supersedeProviderRefresh(providerId) { const generation = (this.refreshGenerations.get(providerId) ?? 0) + 1; this.refreshGenerations.set(providerId, generation); const previous = this.refreshControllers.get(providerId); if (previous) { this.refreshControllers.delete(providerId); previous.abort(); } return generation; } beginProviderRefresh(providerId) { const generation = this.supersedeProviderRefresh(providerId); const controller = new AbortController(); this.refreshControllers.set(providerId, controller); return { generation, controller }; } publishProviderModels(providerId, generation, signal, publication) { const previous = this.publicationChains.get(providerId) ?? Promise.resolve(); const queued = (async () => { await previous.catch(() => { }); if (signal.aborted || this.refreshGenerations.get(providerId) !== generation) return false; if (publication.persist === null) { await this.modelsStore.delete(providerId, { signal }); } else if (publication.persist !== void 0) { await this.modelsStore.write(providerId, structuredClone(publication.persist), { signal }); } if (signal.aborted || this.refreshGenerations.get(providerId) !== generation) return false; publication.update?.(); return true; })(); const tail = queued.catch(() => { }); this.publicationChains.set(providerId, tail); void tail.then(() => { if (this.publicationChains.get(providerId) === tail) this.publicationChains.delete(providerId); }); return raceWithAbortSignal(queued, signal); } async runProviderRefreshPhase(provider2, credential, allowNetwork, force, generation, signal) { const stored = await this.modelsStore.read(provider2.id, { signal }); await provider2.refreshModels({ credential, stored: stored ? withKnownModelTypes(structuredClone(stored)) : void 0, publish: (publication) => this.publishProviderModels(provider2.id, generation, signal, publication), allowNetwork, force: allowNetwork ? force : void 0, signal, baseUrl: provider2.baseUrl }); } async refresh(options = {}) { const allowNetwork = options.allowNetwork ?? true; const callerSignal = operationSignal(options.signal); const errors = /* @__PURE__ */ new Map(); if (callerSignal.aborted) return { aborted: true, errors }; const selected = options.providers ? new Set(options.providers) : void 0; const refreshable = Array.from(this.providers.values()).filter((provider2) => provider2.refreshModels !== void 0 && (!selected || selected.has(provider2.id))); const refresh = Promise.all(refreshable.map(async (provider2) => { const { generation, controller } = this.beginProviderRefresh(provider2.id); const signal = AbortSignal.any([callerSignal, controller.signal]); const operation = (async () => { let storedCredential; let credentialError; try { storedCredential = await this.readCredential(provider2.id, signal); } catch (error) { credentialError = error; } await this.runProviderRefreshPhase(provider2, storedCredential, false, void 0, generation, signal); if (credentialError !== void 0) throw credentialError; if (!allowNetwork || signal.aborted) return; const credential = await this.resolveRefreshCredential(provider2, storedCredential, signal); if (!credential) return; await this.runProviderRefreshPhase(provider2, credential, true, options.force, generation, signal); })(); try { await raceWithAbortSignal(operation, signal); } catch (error) { if (!signal.aborted) { errors.set(provider2.id, error instanceof Error ? error : new ModelsError("model_source", `Model refresh failed for ${provider2.id}`, { cause: error })); } } finally { if (this.refreshControllers.get(provider2.id) === controller) { this.refreshControllers.delete(provider2.id); } } })); try { await raceWithAbortSignal(refresh, callerSignal); } catch (error) { if (!callerSignal.aborted) throw error; } return { aborted: callerSignal.aborted, errors: new Map(errors) }; } async resolveRefreshCredential(provider2, stored, signal) { if (stored?.type === "oauth") { const oauth = provider2.auth.oauth; if (!oauth) return void 0; if (Date.now() < stored.expires) return stored; if (signal.aborted) return void 0; return refreshStoredOAuthCredential(this.credentials, provider2.id, oauth, (current) => Date.now() >= current.expires, signal); } const apiKey = provider2.auth.apiKey; if (!apiKey) return void 0; const credential = stored?.type === "api_key" ? stored : void 0; const result = await apiKey.resolve({ ctx: this.authContext, credential, signal }); if (!result) return void 0; return { type: "api_key", key: result.auth.apiKey, env: result.env }; } async readCredential(providerId, signal) { try { return await this.credentials.read(providerId, { signal }); } catch (error) { throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error }); } } async checkProviderAuth(provider2, credential, signal) { if (credential?.type === "oauth") { return provider2.auth.oauth ? { source: "OAuth", type: "oauth" } : void 0; } const apiKey = provider2.auth.apiKey; if (!apiKey) return void 0; if (apiKey.check) { try { return await apiKey.check({ ctx: this.authContext, credential: credential?.type === "api_key" ? credential : void 0, signal }); } catch (error) { throw new ModelsError("auth", `API key auth check failed for provider ${provider2.id}`, { cause: error }); } } const resolution = await resolveProviderAuth(provider2, this.credentials, this.authContext, { signal }); return resolution ? { source: resolution.source, type: "api_key" } : void 0; } checkAuth(providerId, options) { const signal = operationSignal(options?.signal); const check2 = (async () => { signal.throwIfAborted(); const provider2 = this.providers.get(providerId); if (!provider2) return void 0; return this.checkProviderAuth(provider2, await this.readCredential(providerId, signal), signal); })(); return raceWithAbortSignal(check2, signal); } async getAuthenticatedProviders(providerId, signal) { signal.throwIfAborted(); const providers = providerId ? [this.providers.get(providerId)].filter((entry2) => entry2 !== void 0) : this.getProviders(); const checks = await Promise.all(providers.map(async (provider2) => { const credential = await this.readCredential(provider2.id, signal); return { provider: provider2, credential, auth: await this.checkProviderAuth(provider2, credential, signal) }; })); return checks.filter((entry2) => entry2.auth !== void 0); } getAvailable(providerId, options) { const signal = operationSignal(options?.signal); const available = (async () => { const providers = await this.getAuthenticatedProviders(providerId, signal); return providers.flatMap(({ provider: provider2, credential }) => { const models2 = provider2.getModels(); return provider2.filterModels?.(models2, credential) ?? models2; }); })(); return raceWithAbortSignal(available, signal); } async getAvailableOfType(type, providerId, options) { return (await this.getAllAvailable(providerId, options)).filter((model2) => isModelType(model2, type)); } getAllAvailable(providerId, options) { const signal = operationSignal(options?.signal); const available = (async () => { const providers = await this.getAuthenticatedProviders(providerId, signal); return providers.flatMap(({ provider: provider2, credential }) => { const models2 = provider2.getAllModels?.() ?? provider2.getModels(); if (provider2.filterAllModels) return provider2.filterAllModels(models2, credential); if (!provider2.filterModels) return models2; const availableChatIds = new Set(provider2.filterModels(provider2.getModels(), credential).map((model2) => model2.id)); return models2.filter((model2) => !isModelType(model2, "chat") || availableChatIds.has(model2.id)); }); })(); return raceWithAbortSignal(available, signal); } async getAuth(providerOrModel, overrides) { const signal = operationSignal(overrides?.signal); const providerId = typeof providerOrModel === "string" ? providerOrModel : providerOrModel.provider; const provider2 = this.providers.get(providerId); if (!provider2) return void 0; const result = await resolveProviderAuth(provider2, this.credentials, this.authContext, { ...overrides, signal }); if (!result || typeof providerOrModel === "string" || !providerOrModel.headers) return result; return { ...result, auth: { ...result.auth, headers: mergeHeaders(result.auth.headers, providerOrModel.headers) } }; } async login(providerId, type, interaction, options) { const signal = operationSignal(interaction.signal); signal.throwIfAborted(); const provider2 = this.providers.get(providerId); if (!provider2) throw new ModelsError("provider", `Unknown provider: ${providerId}`); const method2 = type === "oauth" ? provider2.auth.oauth : provider2.auth.apiKey; if (!method2?.login) { throw new ModelsError("auth", `${provider2.name} does not support ${type} login`); } const loginOperation = method2.login({ ...interaction, signal }, options); const credential = await raceWithAbortSignal(loginOperation, signal); let mutationStarted = false; let markMutationStarted; const started = new Promise((resolve2) => { markMutationStarted = resolve2; }); const mutation = this.credentials.modify(providerId, async () => { mutationStarted = true; markMutationStarted?.(); return credential; }, { signal }); void mutation.catch(() => { }); try { await new Promise((resolve2, reject2) => { const onAbort = () => { if (!mutationStarted) reject2(signal.reason); }; signal.addEventListener("abort", onAbort, { once: true }); void Promise.race([started, mutation]).then(() => { signal.removeEventListener("abort", onAbort); resolve2(); }, (error) => { signal.removeEventListener("abort", onAbort); reject2(error); }); if (signal.aborted) onAbort(); }); await mutation; } catch (error) { signal.throwIfAborted(); throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error }); } return credential; } async logout(providerId, options) { const signal = operationSignal(options?.signal); signal.throwIfAborted(); try { await this.credentials.delete(providerId, { signal }); } catch (error) { signal.throwIfAborted(); throw new ModelsError("auth", `Credential store delete failed for ${providerId}`, { cause: error }); } } requireProvider(model2) { const provider2 = this.providers.get(model2.provider); if (!provider2) { throw new ModelsError("provider", `Unknown provider: ${model2.provider}`); } return provider2; } requireChatProvider(model2) { assertChatModel(model2); return this.requireProvider(model2); } async applyAuth(model2, options) { this.requireProvider(model2); const resolution = await this.getAuth(model2, { apiKey: options?.apiKey, env: options?.env, signal: options?.signal }); if (!resolution) { throw new ModelsError("auth", `Provider is not configured: ${model2.provider}`); } const auth = resolution.auth; const apiKey = options?.apiKey ?? auth.apiKey; const requestModel = auth.baseUrl ? { ...model2, baseUrl: auth.baseUrl } : model2; const sessionId = options && "sessionId" in options && typeof options.sessionId === "string" ? options.sessionId : void 0; let headers = mergeHeaders(mergeHeaders(getOpenCodeSessionHeaders(requestModel, sessionId), auth.headers), options?.headers); if (options?.transformHeaders) headers = await options.transformHeaders(headers ?? {}); const env = resolution.env || options?.env ? { ...resolution.env ?? {}, ...options?.env ?? {} } : void 0; const identity = mergeClientIdentity(this.identity, options?.identity); const { transformHeaders: _transformHeaders, ...providerOptions } = options ?? {}; const requestOptions = { ...providerOptions, apiKey, headers, env, ...identity === void 0 ? {} : { identity } }; return { requestModel, requestOptions }; } stream(model2, context, options) { const transcript = normalizeContext(context); return lazyStream(model2, async () => { const provider2 = this.requireChatProvider(model2); const { requestModel, requestOptions } = await this.applyAuth(model2, options); return provider2.stream(requestModel, transcript, requestOptions); }, observeModelRequest(model2, options, false)); } async complete(model2, context, options) { return this.stream(model2, context, options).result(); } streamSimple(model2, context, options) { const transcript = normalizeContext(context); return lazyStream(model2, async () => { const provider2 = this.requireChatProvider(model2); const { requestModel, requestOptions } = await this.applyAuth(model2, options); return provider2.streamSimple(requestModel, transcript, requestOptions); }, observeModelRequest(model2, options, false)); } async completeSimple(model2, context, options) { return this.streamSimple(model2, context, options).result(); } streamDeferred(model2, handle, options) { return lazyStream(model2, async () => { const provider2 = this.requireChatProvider(model2); if (!provider2.fetchDeferred) { throw new ModelsError("provider", `Provider ${model2.provider} does not support deferred responses`); } const { requestModel, requestOptions } = await this.applyAuth(model2, options); return provider2.fetchDeferred(requestModel, handle, requestOptions); }, observeModelRequest(model2, options, true)); } async fetchDeferred(model2, handle, options) { return this.streamDeferred(model2, handle, options).result(); } async cancelDeferred(model2, handle, options) { const provider2 = this.requireChatProvider(model2); if (!provider2.cancelDeferred) { throw new ModelsError("provider", `Provider ${model2.provider} does not support deferred responses`); } const { requestModel, requestOptions } = await this.applyAuth(model2, options); await provider2.cancelDeferred(requestModel, handle, requestOptions); } async generateImages(model2, context, options) { try { assertImageModel(model2); const provider2 = this.requireProvider(model2); if (!provider2.generateImages) { throw new ModelsError("provider", `Provider ${model2.provider} does not support image generation`); } const { requestModel, requestOptions } = await this.applyAuth(model2, options); return await provider2.generateImages(requestModel, context, requestOptions); } catch (error) { return imageErrorResult(model2, error, options?.signal?.aborted); } } async classify(model2, context, options) { try { assertClassifierModel(model2); assertClassifierInputSupported(model2, context); const provider2 = this.requireProvider(model2); if (!provider2.classify) { throw new ModelsError("provider", `Provider ${model2.provider} does not support classification`); } const { requestModel, requestOptions } = await this.applyAuth(model2, options); return await provider2.classify(requestModel, context, requestOptions); } catch (error) { return classifierErrorResult(model2, error, options?.signal?.aborted); } } }; function createModels(options) { return new ModelsImpl(options); } function createProvider(input) { const single = input.api && typeof input.api.stream === "function" ? input.api : void 0; const byApi = single || !input.api ? void 0 : input.api; const images = input.images; const classifiers = input.classifiers; const streams = single ? [single] : Object.values(byApi ?? {}).filter((entry2) => entry2 !== void 0); const imageImplementations = Object.values(images ?? {}).filter((entry2) => entry2 !== void 0); const classifierImplementations = Object.values(classifiers ?? {}).filter((entry2) => entry2 !== void 0); if (streams.length === 0 && imageImplementations.length === 0 && classifierImplementations.length === 0) { throw new Error(`Provider ${input.id}: at least one of "api", "images", or "classifiers" is required.`); } const baselineModels = input.models; let dynamicModels = []; const fetchModels = input.fetchModels; const currentModels2 = () => { const merged = [...baselineModels]; for (const model2 of dynamicModels) { const index = merged.findIndex((entry2) => getModelType(entry2) === getModelType(model2) && entry2.id === model2.id); if (index >= 0) merged[index] = model2; else merged.push(model2); } return merged; }; const apiFor = (model2) => single ?? byApi?.[model2.api]; const dispatch = (model2, run2) => { const streams2 = apiFor(model2); if (!streams2) { return lazyStream(model2, async () => { throw new ModelsError("stream", `Provider ${input.id} has no API implementation for "${model2.api}"`); }); } return run2(streams2); }; const provider2 = { id: input.id, name: input.name ?? input.id, baseUrl: input.baseUrl, headers: input.headers, auth: input.auth, getModels: () => currentModels2().filter((model2) => isModelType(model2, "chat")), getAllModels: currentModels2, refreshModels: fetchModels ? async (context) => { if (context.stored) { const restored = context.stored.models.filter((model2) => model2.provider === input.id).map((model2) => model2); if (!await context.publish({ update: () => { dynamicModels = restored; } })) { return; } } if (!context.allowNetwork || context.signal.aborted) return; const fetched = await fetchModels(context); if (context.signal.aborted) return; const refreshed = fetched.filter(hasKnownModelType); await context.publish({ persist: { models: refreshed, checkedAt: Date.now() }, update: () => { dynamicModels = refreshed; } }); } : void 0, filterModels: input.filterModels, filterAllModels: input.filterAllModels, stream: (model2, context, options) => dispatch(model2, (streams2) => streams2.stream(model2, context, options)), streamSimple: (model2, context, options) => dispatch(model2, (streams2) => streams2.streamSimple(model2, context, options)) }; if (streams.some((entry2) => entry2.fetchDeferred !== void 0)) { provider2.fetchDeferred = (model2, handle, options) => lazyStream(model2, async () => { const implementation = apiFor(model2); if (!implementation?.fetchDeferred) { throw new ModelsError("provider", `Provider ${input.id} does not support deferred responses for "${model2.api}"`); } return implementation.fetchDeferred(model2, handle, options); }); } if (streams.some((entry2) => entry2.cancelDeferred !== void 0)) { provider2.cancelDeferred = async (model2, handle, options) => { const implementation = apiFor(model2); if (!implementation?.cancelDeferred) { throw new ModelsError("provider", `Provider ${input.id} cannot cancel deferred responses for "${model2.api}"`); } await implementation.cancelDeferred(model2, handle, options); }; } if (images && imageImplementations.length > 0) { provider2.generateImages = async (model2, context, options) => { const implementation = images[model2.api]; if (!implementation) { return imageErrorResult(model2, new ModelsError("provider", `Provider ${input.id} has no image generation implementation for "${model2.api}"`)); } return implementation.generateImages(model2, context, options); }; } if (classifiers && classifierImplementations.length > 0) { provider2.classify = async (model2, context, options) => { const implementation = classifiers[model2.api]; if (!implementation) { return classifierErrorResult(model2, new ModelsError("provider", `Provider ${input.id} has no classifier implementation for "${model2.api}"`)); } return implementation.classify(model2, context, options); }; } return provider2; } function hasApi(model2, api) { return isModelType(model2, "chat") && model2.api === api; } function calculateCost(model2, usage) { const inputTokens = usage.input + usage.cacheRead + usage.cacheWrite; let rates = model2.cost; let matchedThreshold = -1; for (const tier of model2.cost.tiers ?? []) { if (inputTokens > tier.inputTokensAbove && tier.inputTokensAbove > matchedThreshold) { rates = tier; matchedThreshold = tier.inputTokensAbove; } } const longWrite = usage.cacheWrite1h ?? 0; const shortWrite = usage.cacheWrite - longWrite; usage.cost.input = rates.input / 1e6 * usage.input; usage.cost.output = rates.output / 1e6 * usage.output; usage.cost.cacheRead = rates.cacheRead / 1e6 * usage.cacheRead; usage.cost.cacheWrite = (rates.cacheWrite * shortWrite + rates.input * 2 * longWrite) / 1e6; usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite; usage.cost.totalFromProvider = false; return usage.cost; } var EXTENDED_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"]; function getSupportedThinkingLevels(model2) { if (!model2.reasoning) return ["off"]; return EXTENDED_THINKING_LEVELS.filter((level) => { const mapped = model2.thinkingLevelMap?.[level]; if (mapped === null) return false; if (level === "xhigh" || level === "max") return mapped !== void 0; return true; }); } function clampThinkingLevel(model2, level) { const availableLevels = getSupportedThinkingLevels(model2); if (availableLevels.includes(level)) return level; const requestedIndex = EXTENDED_THINKING_LEVELS.indexOf(level); if (requestedIndex === -1) return availableLevels[0] ?? "off"; for (let i = requestedIndex; i < EXTENDED_THINKING_LEVELS.length; i++) { const candidate = EXTENDED_THINKING_LEVELS[i]; if (availableLevels.includes(candidate)) return candidate; } for (let i = requestedIndex - 1; i >= 0; i--) { const candidate = EXTENDED_THINKING_LEVELS[i]; if (availableLevels.includes(candidate)) return candidate; } return availableLevels[0] ?? "off"; } function modelsAreEqual(a, b) { if (!a || !b) return false; return getModelType(a) === getModelType(b) && a.id === b.id && a.provider === b.provider; } // node_modules/@earendil-works/pi-ai/dist/utils/json-parse.js var import_partial_json = __toESM(require_dist(), 1); var VALID_JSON_ESCAPES = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t"]); function isControlCharacter(char) { const codePoint = char.codePointAt(0); return codePoint !== void 0 && codePoint >= 0 && codePoint <= 31; } function escapeControlCharacter(char) { switch (char) { case "\b": return "\\b"; case "\f": return "\\f"; case "\n": return "\\n"; case "\r": return "\\r"; case " ": return "\\t"; default: return `\\u${char.codePointAt(0)?.toString(16).padStart(4, "0") ?? "0000"}`; } } function repairJson(json) { let repaired = ""; let inString = false; for (let index = 0; index < json.length; index++) { const char = json[index]; if (!inString) { repaired += char; if (char === '"') { inString = true; } continue; } if (char === '"') { repaired += char; inString = false; continue; } if (char === "\\") { const nextChar = json[index + 1]; if (nextChar === void 0) { repaired += "\\\\"; continue; } if (nextChar === "u") { const unicodeDigits = json.slice(index + 2, index + 6); if (/^[0-9a-fA-F]{4}$/.test(unicodeDigits)) { repaired += `\\u${unicodeDigits}`; index += 5; continue; } } if (VALID_JSON_ESCAPES.has(nextChar)) { repaired += `\\${nextChar}`; index += 1; continue; } repaired += "\\\\"; continue; } repaired += isControlCharacter(char) ? escapeControlCharacter(char) : char; } return repaired; } function parseJsonWithRepair(json) { try { return JSON.parse(json); } catch (error) { const repairedJson = repairJson(json); if (repairedJson !== json) { return JSON.parse(repairedJson); } throw error; } } function parseToolCallArguments(json) { if (!json?.trim()) return { arguments: {} }; try { const parsed = parseJsonWithRepair(json); if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { throw new Error("Tool arguments must be a JSON object"); } return { arguments: parsed }; } catch (error) { return { arguments: {}, argumentsError: `${error instanceof Error ? error.message : String(error)} Received arguments: ${json}` }; } } function parseStreamingJson(partialJson) { if (!partialJson || partialJson.trim() === "") { return {}; } try { return parseJsonWithRepair(partialJson); } catch { try { const result = (0, import_partial_json.parse)(partialJson); return result ?? {}; } catch { try { const result = (0, import_partial_json.parse)(repairJson(partialJson)); return result ?? {}; } catch { return {}; } } } } // node_modules/@earendil-works/pi-ai/dist/utils/streaming-json.js var MAX_REPORTED_DEPTH = 32; var ROOT_SLOT = { kind: "root" }; var CREDIT_PER_APPEND = 256; var CREDIT_PER_CHARACTER = 8; var MAX_PENDING_SCALAR_LENGTH = 64; var NUMBER = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/; var NUMBER_WITHOUT_EXPONENT_DIGITS = /^(-?(?:0|[1-9]\d*)(?:\.\d+)?)[eE][+-]?$/; var LITERALS = [ ["true", true], ["false", false], ["null", null] ]; var QUOTE = 34; var BACKSLASH = 92; var StreamingJsonParser = class { #root = void 0; #done = false; #stack = []; #string = void 0; #scalar = ""; #scalarSlot = void 0; /** Sum of `size + 1` over the open path: what a fresh snapshot copies. */ #pathCost = 0; #credit = 0; #dirty = false; #snapshot = void 0; /** Changes of the piece being read. */ #changes = []; /** * Read the next piece of text; returns the changes it made, in order. Costs time linear in `text.length`. */ append(text) { this.#credit += CREDIT_PER_APPEND + CREDIT_PER_CHARACTER * text.length; this.#changes = []; if (this.#string !== void 0) this.#string.change = void 0; let index = 0; while (index < text.length && !this.#done) { const open = this.#string; if (open !== void 0) index = this.#readString(open, text, index); else if (this.#scalar !== "") index = this.#readScalar(text, index); else this.#structural(text[index++]); } return this.#changes; } /** * The value of the text read so far: `undefined` before a value starts, otherwise a plain JSON value that never * changes afterwards. Returns the previous snapshot while the text read since did not change the value, or while a * fresh copy of the open path is not yet paid for (see the module comment). */ snapshot() { if (!this.#dirty) return this.#snapshot; const pending = this.#pendingScalar(); const cost = this.#pathCost + (pending === void 0 ? 0 : 1); if (cost > this.#credit) return this.#snapshot; this.#credit -= cost; this.#dirty = false; this.#snapshot = this.#copyOpenPath(pending); return this.#snapshot; } /** Read string characters from `start` until the string ends or the text does; returns the next index. */ #readString(open, text, start) { let runStart = start; for (let index = start; index < text.length; index++) { const code = text.charCodeAt(index); if (open.escape !== "") { if (this.#continueEscape(open, text[index])) { runStart = index + 1; continue; } runStart = index; } if (code === QUOTE) { if (index > runStart) this.#appendString(open, text.slice(runStart, index)); this.#endString(open); return index + 1; } if (code === BACKSLASH) { if (index > runStart) this.#appendString(open, text.slice(runStart, index)); open.escape = "\\"; runStart = index + 1; } } if (text.length > runStart) this.#appendString(open, text.slice(runStart)); return text.length; } /** Read number or literal characters from `start` up to the next delimiter; returns the delimiter's index. */ #readScalar(text, start) { let end = start; while (end < text.length && !isDelimiter(text[end])) end++; if (end > start) { this.#scalar += text.slice(start, end); if (this.#scalarSlot !== void 0) this.#dirty = true; } if (end < text.length) this.#endScalar(); return end; } /** Handle one character outside strings. */ #structural(char) { switch (char) { case " ": case " ": case "\n": case "\r": return; case '"': this.#startString(); return; case "{": case "[": this.#openContainer(char === "{" ? {} : []); return; case "}": case "]": this.#closeContainer(); return; case ":": { const frame = this.#stack.at(-1); if (frame !== void 0 && !Array.isArray(frame.container) && frame.phase === "colon") frame.phase = "value"; return; } case ",": { const frame = this.#stack.at(-1); if (frame !== void 0) frame.phase = Array.isArray(frame.container) ? "value" : "key"; return; } default: this.#scalar = char; this.#scalarSlot = this.#valueSlot(); if (this.#scalarSlot !== void 0) this.#dirty = true; } } /** Where a value starting now goes, or `undefined` when this position takes no value. */ #valueSlot() { const frame = this.#stack.at(-1); if (frame === void 0) return this.#root === void 0 ? ROOT_SLOT : void 0; if (Array.isArray(frame.container)) return { kind: "element", index: frame.container.length }; if ((frame.phase === "value" || frame.phase === "colon") && frame.key !== void 0) { return { kind: "member", key: frame.key }; } return void 0; } #startString() { const frame = this.#stack.at(-1); if (frame !== void 0 && !Array.isArray(frame.container) && (frame.phase === "key" || frame.phase === "comma")) { this.#string = { target: "key", slot: void 0, text: "", escape: "", path: void 0, change: void 0 }; return; } const slot = this.#valueSlot(); const path = this.#reportedPath(slot); this.#string = { target: "value", slot, text: "", escape: "", path, change: void 0 }; this.#place(slot, ""); if (path !== void 0) this.#string.change = this.#report({ kind: "set", path, value: "" }); } /** The path of a value at `slot` in the innermost open container, when its changes are reported. */ #reportedPath(slot) { if (slot === void 0) return void 0; if (slot.kind === "root") return []; const frame = this.#stack.at(-1); if (!frame.reportsInside) return void 0; return [...frame.path, slot.kind === "member" ? slot.key : slot.index]; } #report(change) { this.#changes.push(change); return change; } /** Continue an escape with `char`; returns false when `char` is not part of it and must be read normally. */ #continueEscape(open, char) { if (open.escape === "\\") { const decoded = SIMPLE_ESCAPES[char]; if (decoded !== void 0) { open.escape = ""; this.#appendString(open, decoded); return true; } if (char === "u") { open.escape = "\\u"; return true; } open.escape = ""; this.#appendString(open, "\\"); return false; } if (isHexDigit(char)) { open.escape += char; if (open.escape.length < 6) return true; const decoded = String.fromCharCode(Number.parseInt(open.escape.slice(2), 16)); open.escape = ""; this.#appendString(open, decoded); return true; } const literal = open.escape; open.escape = ""; this.#appendString(open, literal); return false; } #appendString(open, text) { open.text += text; if (open.target !== "value") return; this.#place(open.slot, open.text); if (open.path === void 0) return; const change = open.change; if (change !== void 0 && this.#changes.at(-1) === change) { if (change.kind === "set") change.value = change.value + text; else change.text += text; return; } open.change = this.#report({ kind: "append", path: open.path, text }); } #endString(open) { this.#string = void 0; const frame = this.#stack.at(-1); if (open.target === "key") { if (frame === void 0) return; frame.key = open.text; frame.phase = "colon"; return; } this.#valueEnded(frame); } #endScalar() { const token = this.#scalar; const slot = this.#scalarSlot; this.#scalar = ""; this.#scalarSlot = void 0; const value2 = scalarValue(token); if (value2 !== void 0) { this.#place(slot, value2); const path = this.#reportedPath(slot); if (path !== void 0) this.#report({ kind: "set", path, value: value2 }); } this.#valueEnded(this.#stack.at(-1)); } /** The value of the unterminated scalar at the end of the text, if it shows. */ #pendingScalar() { const slot = this.#scalarSlot; const token = this.#scalar; if (slot === void 0 || token === "" || token.length > MAX_PENDING_SCALAR_LENGTH) return void 0; const value2 = pendingScalarValue(token); return value2 === void 0 ? void 0 : { slot, value: value2 }; } #openContainer(container) { const slot = this.#valueSlot(); const path = this.#reportedPath(slot); this.#place(slot, container); this.#stack.push({ container, slot, path, reportsInside: path !== void 0 && path.length < MAX_REPORTED_DEPTH, size: 0, key: void 0, phase: Array.isArray(container) ? "value" : "key" }); this.#pathCost += 1; this.#dirty = true; if (path !== void 0) this.#report({ kind: "set", path, value: Array.isArray(container) ? [] : {} }); } #closeContainer() { const frame = this.#stack.pop(); if (frame === void 0) return; this.#pathCost -= frame.size + 1; this.#dirty = true; if (frame.path !== void 0 && !frame.reportsInside) { this.#report({ kind: "set", path: frame.path, value: frame.container }); } this.#valueEnded(this.#stack.at(-1)); } /** A value in `frame`, or the root value when there is no frame, has ended. */ #valueEnded(frame) { if (frame === void 0) { if (this.#root !== void 0) this.#done = true; return; } frame.phase = "comma"; } /** Set the value at `slot` in the open path; nothing for a value in an invalid position. */ #place(slot, value2) { if (slot === void 0) return; this.#dirty = true; if (slot.kind === "root") { this.#root = value2; return; } const frame = this.#stack.at(-1); if (slot.kind === "element") { const elements = frame.container; if (slot.index === elements.length) { frame.size++; this.#pathCost++; } elements[slot.index] = value2; return; } const members = frame.container; if (!Object.hasOwn(members, slot.key)) { frame.size++; this.#pathCost++; } setMember(members, slot.key, value2); } /** A fresh snapshot: copies of the open containers, innermost first, each holding the copy of its open child. */ #copyOpenPath(pending) { if (this.#stack.length === 0) return pending?.slot.kind === "root" ? pending.value : this.#root; let child; let childSlot; for (let depth = this.#stack.length - 1; depth >= 0; depth--) { const frame = this.#stack[depth]; const copy2 = Array.isArray(frame.container) ? frame.container.slice() : { ...frame.container }; if (depth === this.#stack.length - 1 && pending !== void 0) setSlot(copy2, pending.slot, pending.value); if (child !== void 0 && childSlot !== void 0) setSlot(copy2, childSlot, child); child = copy2; childSlot = frame.slot; } return childSlot?.kind === "root" ? child : this.#root; } }; var ToolCallArgumentsStream = class { #text = ""; #parser = new StreamingJsonParser(); #snapshot = void 0; #arguments = {}; /** The raw argument text received so far. */ get text() { return this.#text; } /** Append a piece of argument text; returns the partial arguments, a new object whenever they changed. */ append(delta) { this.#text += delta; this.#parser.append(delta); return this.#partial(); } /** * Take the provider's complete argument text. When it continues the text so far, returns the missing piece as * `delta`; otherwise starts over with `text`. Either way returns the partial arguments of `text`. */ replace(text) { if (text.startsWith(this.#text)) { const delta = text.slice(this.#text.length); return { delta, arguments: delta === "" ? this.#partial() : this.append(delta) }; } this.#text = ""; this.#parser = new StreamingJsonParser(); return { delta: void 0, arguments: this.append(text) }; } /** The final arguments: one full parse of the complete text, which reports malformed arguments. */ finish() { return parseToolCallArguments(this.#text); } #partial() { const snapshot = this.#parser.snapshot(); if (snapshot === this.#snapshot) return this.#arguments; this.#snapshot = snapshot; this.#arguments = typeof snapshot === "object" && snapshot !== null && !Array.isArray(snapshot) ? snapshot : {}; return this.#arguments; } }; var SIMPLE_ESCAPES = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: " " }; function isDelimiter(char) { return char === " " || char === " " || char === "\n" || char === "\r" || char === "," || char === ":" || char === "{" || char === "}" || char === "[" || char === "]" || char === '"'; } function isHexDigit(char) { return char >= "0" && char <= "9" || char >= "a" && char <= "f" || char >= "A" && char <= "F"; } function scalarValue(token) { for (const [literal, value2] of LITERALS) if (token === literal) return value2; return NUMBER.test(token) ? Number(token) : void 0; } function pendingScalarValue(token) { for (const [literal, value2] of LITERALS) if (literal.startsWith(token)) return value2; if (NUMBER.test(token)) return Number(token); const mantissa = NUMBER_WITHOUT_EXPONENT_DIGITS.exec(token); return mantissa === null ? void 0 : Number(mantissa[1]); } function setSlot(container, slot, value2) { if (slot.kind === "element") container[slot.index] = value2; else if (slot.kind === "member") setMember(container, slot.key, value2); } function setMember(members, key, value2) { if (key === "__proto__") { Object.defineProperty(members, key, { value: value2, writable: true, enumerable: true, configurable: true }); } else { members[key] = value2; } } // node_modules/@earendil-works/pi-ai/dist/providers/faux.js var DEFAULT_API = "faux"; var DEFAULT_PROVIDER = "faux"; var DEFAULT_MODEL_ID = "faux-1"; var DEFAULT_MODEL_NAME = "Faux Model"; var DEFAULT_BASE_URL = "http://localhost:0"; var DEFAULT_MIN_TOKEN_SIZE = 3; var DEFAULT_MAX_TOKEN_SIZE = 5; var DEFAULT_USAGE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }; function fauxText(text) { return { type: "text", text }; } function fauxThinking(thinking) { return { type: "thinking", thinking }; } function fauxToolCall(name, arguments_, options = {}) { return { type: "toolCall", id: options.id ?? randomId("tool"), name, arguments: arguments_ }; } function normalizeFauxAssistantContent(content) { if (typeof content === "string") { return [fauxText(content)]; } return Array.isArray(content) ? content : [content]; } function fauxAssistantMessage(content, options = {}) { return { role: "assistant", content: normalizeFauxAssistantContent(content), api: DEFAULT_API, provider: DEFAULT_PROVIDER, model: DEFAULT_MODEL_ID, usage: DEFAULT_USAGE, stopReason: options.stopReason ?? "stop", ...options.deferred === void 0 ? {} : { deferred: options.deferred }, ...options.errorMessage === void 0 ? {} : { errorMessage: options.errorMessage }, ...options.responseId === void 0 ? {} : { responseId: options.responseId }, timestamp: options.timestamp ?? Date.now() }; } function estimateTokens(text) { return Math.ceil(text.length / 4); } function randomId(prefix) { return `${prefix}:${Date.now()}:${Math.random().toString(36).slice(2)}`; } function contentToText(content) { if (typeof content === "string") { return content; } return content.map((block) => { if (block.type === "text") { return block.text; } return `[image:${block.mimeType}:${block.data.length}]`; }).join("\n"); } function assistantContentToText(content) { return content.map((block) => { if (block.type === "text") { return block.text; } if (block.type === "thinking") { return block.thinking; } return `${block.name}:${JSON.stringify(block.arguments)}`; }).join("\n"); } function toolResultToText(message) { return [message.toolName, ...message.content.map((block) => contentToText([block]))].join("\n"); } function messageToText(message) { if (message.role === "system") { return [ getSystemMessageText(message), ...message.toolsRemoved?.map((tool) => `tool-:${JSON.stringify(tool)}`) ?? [], ...message.toolsAdded?.map((tool) => `tool+:${JSON.stringify(tool)}`) ?? [] ].filter((part) => part.length > 0).join("\n"); } if (message.role === "user") { return contentToText(message.content); } if (message.role === "assistant") { return assistantContentToText(message.content); } return toolResultToText(message); } function joinedLength(messages, count = messages.length) { let length = count > 0 ? (count - 1) * 2 : 0; for (let index = 0; index < count; index++) length += messages[index].length; return length; } function commonPromptPrefixLength(previous, current) { let index = 0; while (index < previous.length && index < current.length && previous[index] === current[index]) index++; const rest = (messages) => index === messages.length ? "" : (index > 0 ? "\n\n" : "") + messages.slice(index).join("\n\n"); return joinedLength(previous, index) + commonPrefixLength(rest(previous), rest(current)); } function commonPrefixLength(a, b) { const length = Math.min(a.length, b.length); let index = 0; while (index < length && a[index] === b[index]) { index++; } return index; } function withUsageEstimate(message, context, options, promptCache) { const prompt = context.messages.map((message2) => `${message2.role}:${messageToText(message2)}`); const promptLength = joinedLength(prompt); const promptTokens = Math.ceil(promptLength / 4); const outputTokens = estimateTokens(assistantContentToText(message.content)); let input = promptTokens; let cacheRead = 0; let cacheWrite = 0; const sessionId = options?.sessionId; if (sessionId && options?.cacheRetention !== "none") { const previousPrompt = promptCache.get(sessionId); if (previousPrompt) { const cachedChars = commonPromptPrefixLength(previousPrompt, prompt); cacheRead = Math.ceil(cachedChars / 4); cacheWrite = Math.max(0, promptTokens - cacheRead); } else { cacheWrite = promptTokens; } input = 0; promptCache.set(sessionId, prompt); } return { ...message, usage: { input, output: outputTokens, cacheRead, cacheWrite, totalTokens: input + outputTokens + cacheRead + cacheWrite, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } } }; } function splitStringByTokenSize(text, minTokenSize, maxTokenSize) { const chunks = []; let index = 0; while (index < text.length) { const tokenSize = minTokenSize + Math.floor(Math.random() * (maxTokenSize - minTokenSize + 1)); const charSize = Math.max(1, tokenSize * 4); chunks.push(text.slice(index, index + charSize)); index += charSize; } return chunks.length > 0 ? chunks : [""]; } function cloneMessage(message, api, provider2, modelId) { const cloned = structuredClone(message); return { ...cloned, api, provider: provider2, model: modelId, timestamp: cloned.timestamp ?? Date.now(), usage: cloned.usage ?? DEFAULT_USAGE }; } function createDeferredMessage(model2, handle) { return { role: "assistant", content: [], api: model2.api, provider: model2.provider, model: model2.id, usage: DEFAULT_USAGE, stopReason: "deferred", deferred: handle, timestamp: Date.now() }; } function createErrorMessage(error, api, provider2, modelId) { return { role: "assistant", content: [], api, provider: provider2, model: modelId, usage: DEFAULT_USAGE, stopReason: "error", errorMessage: error instanceof Error ? error.message : String(error), timestamp: Date.now() }; } function createAbortedMessage(partial) { return { ...partial, stopReason: "aborted", errorMessage: "Request was aborted", timestamp: Date.now() }; } function scheduleChunk(chunk, tokensPerSecond) { if (!tokensPerSecond || tokensPerSecond <= 0) { return new Promise((resolve2) => queueMicrotask(resolve2)); } const delayMs = estimateTokens(chunk) / tokensPerSecond * 1e3; return new Promise((resolve2) => setTimeout(resolve2, delayMs)); } async function streamWithDeltas(stream, message, minTokenSize, maxTokenSize, tokensPerSecond, signal) { const partial = { ...message, content: [], stopReason: "pending" }; if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } stream.push({ type: "start", partial: { ...partial } }); for (let index = 0; index < message.content.length; index++) { if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } const block = message.content[index]; if (block.type === "thinking") { partial.content = [...partial.content, { type: "thinking", thinking: "" }]; stream.push({ type: "thinking_start", contentIndex: index, partial: { ...partial } }); for (const chunk of splitStringByTokenSize(block.thinking, minTokenSize, maxTokenSize)) { await scheduleChunk(chunk, tokensPerSecond); if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } partial.content[index].thinking += chunk; stream.push({ type: "thinking_delta", contentIndex: index, delta: chunk, partial: { ...partial } }); } stream.push({ type: "thinking_end", contentIndex: index, content: block.thinking, partial: { ...partial } }); continue; } if (block.type === "text") { partial.content = [...partial.content, { type: "text", text: "" }]; stream.push({ type: "text_start", contentIndex: index, partial: { ...partial } }); for (const chunk of splitStringByTokenSize(block.text, minTokenSize, maxTokenSize)) { await scheduleChunk(chunk, tokensPerSecond); if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } partial.content[index].text += chunk; stream.push({ type: "text_delta", contentIndex: index, delta: chunk, partial: { ...partial } }); } stream.push({ type: "text_end", contentIndex: index, content: block.text, partial: { ...partial } }); continue; } partial.content = [...partial.content, { type: "toolCall", id: block.id, name: block.name, arguments: {} }]; stream.push({ type: "toolcall_start", contentIndex: index, partial: { ...partial } }); const toolArguments = new ToolCallArgumentsStream(); for (const chunk of splitStringByTokenSize(JSON.stringify(block.arguments), minTokenSize, maxTokenSize)) { await scheduleChunk(chunk, tokensPerSecond); if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } partial.content[index].arguments = toolArguments.append(chunk); stream.push({ type: "toolcall_delta", contentIndex: index, delta: chunk, partial: { ...partial } }); } partial.content[index].arguments = block.arguments; stream.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: { ...partial } }); } if (message.stopReason === "pending") { throw new Error("Faux response ended without a stop reason"); } if (message.stopReason === "error" || message.stopReason === "aborted") { stream.push({ type: "error", reason: message.stopReason, error: message }); stream.end(message); return; } stream.push({ type: "done", reason: message.stopReason, message }); stream.end(message); } function createFauxCore(options) { const api = options.api ?? randomId(DEFAULT_API); const provider2 = options.provider ?? DEFAULT_PROVIDER; const minTokenSize = Math.max(1, Math.min(options.tokenSize?.min ?? DEFAULT_MIN_TOKEN_SIZE, options.tokenSize?.max ?? DEFAULT_MAX_TOKEN_SIZE)); const maxTokenSize = Math.max(minTokenSize, options.tokenSize?.max ?? DEFAULT_MAX_TOKEN_SIZE); let pendingResponses = []; const tokensPerSecond = options.tokensPerSecond; const state = { callCount: 0, deferredFetchCount: 0, cancelledDeferred: [] }; const promptCache = /* @__PURE__ */ new Map(); const deferredResponses = /* @__PURE__ */ new Map(); const modelDefinitions = options.models?.length ? options.models : [ { id: DEFAULT_MODEL_ID, name: DEFAULT_MODEL_NAME, reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128e3, maxTokens: 16384 } ]; const models2 = modelDefinitions.map((definition) => ({ id: definition.id, name: definition.name ?? definition.id, api, provider: provider2, baseUrl: DEFAULT_BASE_URL, reasoning: definition.reasoning ?? false, input: definition.input ?? ["text", "image"], inputLimits: definition.inputLimits, cost: definition.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: definition.contextWindow ?? 128e3, maxTokens: definition.maxTokens ?? 16384 })); const resolveResponse = async (step, context, streamOptions, requestModel) => { const resolved = typeof step === "function" ? await step(context, streamOptions, state, requestModel) : step; return withUsageEstimate(cloneMessage(resolved, api, provider2, requestModel.id), context, streamOptions, promptCache); }; const stream = (requestModel, context, streamOptions) => { const outer = createAssistantMessageEventStream(); const step = pendingResponses.shift(); state.callCount++; queueMicrotask(async () => { try { await streamOptions?.onResponse?.({ status: 200, headers: {} }, requestModel); if (!step) { let message2 = createErrorMessage(new Error("No more faux responses queued"), api, provider2, requestModel.id); message2 = withUsageEstimate(message2, context, streamOptions, promptCache); outer.push({ type: "error", reason: "error", error: message2 }); outer.end(message2); return; } if (streamOptions?.deferred) { const handle = { provider: requestModel.provider, modelId: requestModel.id, api: requestModel.api, id: randomId("deferred"), ...options.deferred?.pollAfterMs !== void 0 ? { pollAfterMs: options.deferred.pollAfterMs } : {} }; deferredResponses.set(handle.id, { handle, step, context, options: streamOptions, model: requestModel, pendingFetches: Math.max(0, Math.floor(options.deferred?.pendingFetches ?? 0)), cancelled: false }); await streamWithDeltas(outer, createDeferredMessage(requestModel, handle), minTokenSize, maxTokenSize, tokensPerSecond, streamOptions.signal); return; } const message = await resolveResponse(step, context, streamOptions, requestModel); await streamWithDeltas(outer, message, minTokenSize, maxTokenSize, tokensPerSecond, streamOptions?.signal); } catch (error) { const message = createErrorMessage(error, api, provider2, requestModel.id); outer.push({ type: "error", reason: "error", error: message }); outer.end(message); } }); return outer; }; const streamSimple = (streamModel, context, streamOptions) => stream(streamModel, context, streamOptions); const fetchDeferred = (requestModel, handle, fetchOptions) => { const outer = createAssistantMessageEventStream(); state.deferredFetchCount++; queueMicrotask(async () => { try { await fetchOptions?.onResponse?.({ status: 200, headers: {} }, requestModel); const entry2 = deferredResponses.get(handle.id); if (!entry2 || entry2.handle.provider !== handle.provider || entry2.handle.modelId !== handle.modelId || entry2.handle.api !== handle.api) { throw new Error(`Unknown faux deferred response: ${handle.id}`); } if (entry2.cancelled) throw new Error(`Faux deferred response was cancelled: ${handle.id}`); if (entry2.pendingFetches > 0) { entry2.pendingFetches--; await streamWithDeltas(outer, createDeferredMessage(requestModel, entry2.handle), minTokenSize, maxTokenSize, tokensPerSecond, fetchOptions?.signal); return; } if (!entry2.final) { const { deferred: _deferred, signal: _submissionSignal, onResponse: _submissionOnResponse, ...submissionOptions } = entry2.options ?? {}; try { entry2.final = await resolveResponse(entry2.step, entry2.context, submissionOptions, entry2.model); } catch (error) { entry2.final = createErrorMessage(error, api, provider2, entry2.model.id); } } await streamWithDeltas(outer, entry2.final, minTokenSize, maxTokenSize, tokensPerSecond, fetchOptions?.signal); } catch (error) { const message = createErrorMessage(error, api, provider2, requestModel.id); outer.push({ type: "error", reason: "error", error: message }); outer.end(message); } }); return outer; }; const cancelDeferred2 = async (requestModel, handle, cancelOptions) => { state.cancelledDeferred.push(structuredClone(handle)); const entry2 = deferredResponses.get(handle.id); if (entry2) entry2.cancelled = true; await cancelOptions?.onResponse?.({ status: 200, headers: {} }, requestModel); }; function getModel(requestedModelId) { if (!requestedModelId) { return models2[0]; } return models2.find((candidate) => candidate.id === requestedModelId); } return { api, provider: provider2, models: models2, stream, streamSimple, fetchDeferred, cancelDeferred: cancelDeferred2, getModel, state, setResponses(responses) { pendingResponses = [...responses]; }, appendResponses(responses) { pendingResponses.push(...responses); }, getPendingResponseCount() { return pendingResponses.length; } }; } function fauxProvider(options = {}) { const core = createFauxCore(options); const provider2 = createProvider({ id: core.provider, auth: { apiKey: { name: "Faux", resolve: async () => ({ auth: {} }) } }, models: core.models, api: { stream: core.stream, streamSimple: core.streamSimple, fetchDeferred: core.fetchDeferred, cancelDeferred: core.cancelDeferred } }); return { provider: provider2, api: core.api, models: core.models, getModel: core.getModel, state: core.state, setResponses: core.setResponses, appendResponses: core.appendResponses, getPendingResponseCount: core.getPendingResponseCount }; } // node_modules/@earendil-works/pi-durable/dist/index.js var dist_exports3 = {}; __export(dist_exports3, { AgentDoc: () => AgentDoc, AssistantEntry: () => AssistantEntry, CompactionEntry: () => CompactionEntry, CompactionTask: () => CompactionTask, ConversationBusy: () => ConversationBusy, ConversationIdle: () => ConversationIdle, ConversationTranscript: () => ConversationTranscript, DEFAULT_COMPACTION_POLICY: () => DEFAULT_COMPACTION_POLICY, DEFAULT_HARNESS_IDENTITY: () => DEFAULT_HARNESS_IDENTITY, DEFAULT_PROGRESS_POLICY: () => DEFAULT_PROGRESS_POLICY, DEFAULT_RESTART_LIMIT: () => DEFAULT_RESTART_LIMIT, DEFAULT_RETRY_POLICY: () => DEFAULT_RETRY_POLICY, DURABLE_TELEMETRY_SCHEMA: () => DURABLE_TELEMETRY_SCHEMA, GenerationTask: () => GenerationTask, Harness: () => Harness, InboxDoc: () => InboxDoc, LiveDoc: () => LiveDoc, MemoryStorage: () => MemoryStorage, NestedCallDoc: () => NestedCallDoc, ProviderDoc: () => ProviderDoc, ROOT_CONVERSATION_ID: () => ROOT_CONVERSATION_ID, ReadAfterWrite: () => ReadAfterWrite, ReadOnlySession: () => ReadOnlySession, ResetEntry: () => ResetEntry, SessionFailed: () => SessionFailed, StorageRequestError: () => StorageRequestError, SystemEntry: () => SystemEntry, ToolResultEntry: () => ToolResultEntry, ToolTask: () => ToolTask, UsageDoc: () => UsageDoc, UserEntry: () => UserEntry, configure: () => configure, createRegistry: () => createRegistry, createSession: () => createSession, defineDoc: () => defineDoc, defineDocFamily: () => defineDocFamily, defineEntry: () => defineEntry, defineExtension: () => defineExtension, defineTask: () => defineTask, defineTool: () => defineTool, hook: () => hook, registryReader: () => registryReader, runEndTask: () => runEndTask, section: () => section, wrapSection: () => wrapSection, wrapTool: () => wrapTool }); // node_modules/@earendil-works/chord/dist/index.js var dist_exports2 = {}; __export(dist_exports2, { ChordError: () => ChordError, NativeWatch: () => NativeWatch, PACK_THRESHOLD: () => PACK_THRESHOLD, PROTOCOL_VERSION: () => PROTOCOL_VERSION, Watch: () => Watch, acceptWebSockets: () => acceptWebSockets, bind: () => bind, copyJson: () => copyJson, coverage: () => coverage, createHost: () => createHost, defineFacet: () => defineFacet, defineRegistry: () => defineRegistry, forbidden: () => forbidden, hostFamily: () => hostFamily, hostNamed: () => hostNamed, inflateMessage: () => inflateMessage, invalid: () => invalid, ipcLink: () => ipcLink, isChordError: () => isChordError, isJsonValue: () => isJsonValue, local: () => local, memoryLinkPair: () => memoryLinkPair, method: () => method, opaque: () => opaque, remote: () => remote, replicatedState: () => replicatedState, webSocketLink: () => webSocketLink }); // node_modules/@earendil-works/pi-schema/dist/index.js var dist_exports = {}; __export(dist_exports, { CoercingValidator: () => CoercingValidator, Optional: () => Optional, Validator: () => Validator, arr: () => arr, bool: () => bool, bytes: () => bytes, compile: () => compile, int: () => int, lit: () => lit, map: () => map, nul: () => nul, nullable: () => nullable, num: () => num, obj: () => obj, oneOf: () => oneOf, opt: () => opt, str: () => str, tuple: () => tuple, union: () => union, unknown: () => unknown }); // node_modules/@earendil-works/pi-schema/dist/builders.js function str(options = {}) { return { type: "string", ...options }; } function num(options = {}) { return { type: "number", ...options }; } function int(options = {}) { return { type: "integer", ...options }; } function bool(options = {}) { return { type: "boolean", ...options }; } function nul(options = {}) { return { type: "null", ...options }; } function unknown(options = {}) { return { ...options }; } function lit(value2, options = {}) { return { const: value2, ...options }; } function oneOf(values, options = {}) { return { enum: values, ...options }; } function union(schemas, options = {}) { return { anyOf: schemas, ...options }; } function nullable(schema, options = {}) { return { anyOf: [schema, { type: "null" }], ...options }; } function arr(items, options = {}) { return { type: "array", items, ...options }; } function tuple(items, options = {}) { return { type: "array", prefixItems: items, items: false, minItems: items.length, ...options }; } function map(value2, options = {}) { return { type: "object", additionalProperties: value2, ...options }; } function bytes(options = {}) { return { type: "bytes", ...options }; } var Optional = class { schema; constructor(schema) { this.schema = schema; } }; function opt(schema) { return new Optional(schema); } function obj(properties, options = {}) { const { open, ...annotations } = options; const props = {}; const required = []; for (const [name, property] of Object.entries(properties)) { if (property instanceof Optional) props[name] = property.schema; else { props[name] = property; required.push(name); } } return { type: "object", properties: props, ...required.length > 0 ? { required } : {}, ...open === true ? {} : { additionalProperties: false }, ...annotations }; } // node_modules/@earendil-works/pi-schema/dist/resolver.js var DEFAULT_BASE = "https://json-schema.invalid/root.json"; var Resolver = class { draft; rootBase; /** Schema object to the base URI it was indexed under. */ baseOf = /* @__PURE__ */ new Map(); /** URI (with a `#name` fragment) of every `$dynamicAnchor`, to its schema. */ dynamicAnchors = /* @__PURE__ */ new Map(); root; options; registry = /* @__PURE__ */ new Map(); rejectUnresolved; /** The references that named nothing and resolved to `false` (with `unresolvedRefs: "reject"`). */ unresolved = /* @__PURE__ */ new Set(); resources = /* @__PURE__ */ new Map(); anchors = /* @__PURE__ */ new Map(); indexed = false; constructor(root2, options) { this.draft = options.draft === "7" ? "7" : "2020-12"; this.root = root2; this.options = options; this.rejectUnresolved = options.unresolvedRefs === "reject"; this.rootBase = isSchemaObject(root2) ? this.baseFor(root2, DEFAULT_BASE) : DEFAULT_BASE; } /** * The base URI of `node`, whose parent has `base`: its `$id` changes it, unless draft 7 makes the `$id` inert. Once * the schema is indexed, the indexed base (a resolved reference target already has its own). */ baseFor(node, base) { const indexed = this.baseOf.get(node); if (indexed !== void 0) return indexed; const id = node.$id; if (typeof id !== "string") return base; if (this.draft === "7" && (typeof node.$ref === "string" || id.startsWith("#"))) return base; return documentOf(resolveUri(id, base)); } /** Indexes the root schema and the registered documents' URIs (once). */ ensureIndexed() { if (this.indexed) return; this.indexed = true; for (const [uri, schema] of Object.entries(this.options.schemas ?? {})) { this.registry.set(documentOf(resolveUri(uri, DEFAULT_BASE)), schema); } this.resources.set(DEFAULT_BASE, this.root); this.index(this.root, DEFAULT_BASE); } /** * The schema a reference names, with the base URI it compiles under. Throws if it names nothing, or, with * `unresolvedRefs: "reject"`, resolves to `false`, the schema no value passes. */ resolve(ref, base) { this.ensureIndexed(); if (!this.rejectUnresolved) return this.find(ref, base); try { return this.find(ref, base); } catch { this.unresolved.add(resolveUri(ref, base)); return { node: false, base }; } } /** Whether `ref`, from `base`, named nothing and resolved to `false`. */ isUnresolved(ref, base) { return this.unresolved.has(resolveUri(ref, base)); } find(ref, base) { const uri = resolveUri(ref, base); const doc = documentOf(uri); const fragment = uri.length > doc.length ? decodeURIComponent(uri.slice(doc.length + 1)) : ""; const root2 = this.resources.has(doc) ? this.resources.get(doc) : this.load(doc); if (root2 === void 0) throw new Error(`Unresolved $ref ${ref} (no schema registered for ${doc})`); if (fragment === "") return { node: root2, base: doc }; if (fragment.startsWith("/")) return this.pointer(root2, doc, fragment); const target = this.anchors.get(`${doc}#${fragment}`); if (target === void 0) throw new Error(`Unresolved $ref ${ref} (no anchor ${fragment} in ${doc})`); return { node: target, base: this.baseOf.get(target) ?? doc }; } /** Whether `node` is the root of the schema resource at `base`. */ isResource(node, base) { this.ensureIndexed(); return this.resources.get(base) === node; } // Walks only keywords that hold subschemas: an `$id` inside `enum`, `const` or an unknown keyword names nothing. index(node, base) { if (!isSchemaObject(node) || this.baseOf.has(node)) return; let b = base; const refOnly = this.draft === "7" && typeof node.$ref === "string"; if (!refOnly && typeof node.$id === "string") { if (this.draft === "7" && node.$id.startsWith("#")) this.anchors.set(documentOf(b) + node.$id, node); else { const uri = resolveUri(node.$id, b); b = documentOf(uri); this.resources.set(b, node); if (uri.length > b.length + 1) this.anchors.set(uri, node); } } this.baseOf.set(node, b); if (refOnly) return; if (this.draft !== "7") { if (typeof node.$anchor === "string") this.anchors.set(`${b}#${node.$anchor}`, node); if (typeof node.$dynamicAnchor === "string") { this.anchors.set(`${b}#${node.$dynamicAnchor}`, node); this.dynamicAnchors.set(`${b}#${node.$dynamicAnchor}`, node); } } for (const key of SCHEMA_KEYWORDS) this.index(node[key], b); for (const key of MAP_KEYWORDS) { const map2 = node[key]; if (isSchemaObject(map2)) for (const k of Object.keys(map2)) this.index(map2[k], b); } for (const key of ARRAY_KEYWORDS) { const list = node[key]; if (Array.isArray(list)) for (const s of list) this.index(s, b); } } load(doc) { const schema = this.registry.get(doc); if (schema === void 0) return void 0; this.resources.set(doc, schema); this.index(schema, doc); return schema; } pointer(root2, base, fragment) { let node = root2; let b = base; for (const raw of fragment.split("/").slice(1)) { const key = raw.replace(/~1/g, "/").replace(/~0/g, "~"); node = typeof node === "object" && node !== null && Object.hasOwn(node, key) ? node[key] : void 0; if (node === void 0) throw new Error(`Unresolved $ref #${fragment} in ${base}`); if (typeof node === "object" && node !== null) b = this.baseOf.get(node) ?? b; } return { node, base: b }; } }; var SCHEMA_KEYWORDS = [ "additionalProperties", "propertyNames", "contains", "not", "if", "then", "else", "unevaluatedProperties", "unevaluatedItems", "additionalItems", "contentSchema", "items" ]; var MAP_KEYWORDS = ["properties", "patternProperties", "$defs", "definitions", "dependentSchemas", "dependencies"]; var ARRAY_KEYWORDS = ["allOf", "anyOf", "oneOf", "prefixItems", "items"]; function isSchemaObject(value2) { return typeof value2 === "object" && value2 !== null && !Array.isArray(value2); } function resolveUri(ref, base) { try { return new URL(ref, base).href; } catch { return ref.startsWith("#") ? documentOf(base) + ref : ref; } } function documentOf(uri) { const hash = uri.indexOf("#"); return hash < 0 ? uri : uri.slice(0, hash); } // node_modules/@earendil-works/pi-schema/dist/shared.js function sharedRegex(source) { const hit = regexes.get(source)?.deref(); if (hit !== void 0) return hit; let re; try { re = new RegExp(source, "u"); } catch { re = new RegExp(source); } regexes.set(source, new WeakRef(re)); regexCleanup.register(re, source); return re; } var regexes = /* @__PURE__ */ new Map(); var regexCleanup = new FinalizationRegistry((source) => { if (regexes.get(source)?.deref() === void 0) regexes.delete(source); }); // node_modules/@earendil-works/pi-schema/dist/coerce.js var Coercer = class { c; schema; /** * The (schema node, value) pairs pass 2 is converting through a re-entry point, as a stack. Conversion can enter * a node again with the same value only through a `$ref` or by wrapping a single value into an array (also for * schemas that cycle through shared JS objects): a recursive schema that meets a value no branch accepts would * convert it without end. A pair already on the stack (primitives by value, objects by reference) leaves the value * unchanged. Only those two points push, so schemas without references or wraps pay nothing. */ entered = []; /** How often a re-entry was cut short in this coerce() call; results computed across one are not memoized. */ cuts = 0; /** * Results of `$ref` targets by (node, value) within one coerce() call: a recursive union whose branches all * convert the same children (and all fail) would convert them once per branch, exponentially in the depth. */ memo = void 0; constructor(compiler2, schema) { this.c = compiler2; this.schema = schema; } /** `value` coerced: a copy along the changed paths only, or `value` itself when nothing changes. */ coerce(value2) { const base = this.c.r.rootBase; const withoutNulls = this.dropOptionalNulls(value2, this.schema, base); return this.coerceTypes(this.convertOnly(withoutNulls), this.schema, base); } /** Pass 2 alone, as TypeBox's `Value.Convert` (the TypeBox conformance tests run it). */ convertOnly(value2) { try { return this.convert(this.schema, this.c.r.rootBase, value2); } finally { this.memo = void 0; this.cuts = 0; } } /** `node` converted for `value` at a re-entry point: unchanged when that pair is already being converted. */ reenter(node, base, value2, memoize) { const entered = this.entered; for (let i = 0; i < entered.length; i += 2) { if (entered[i] === node && Object.is(entered[i + 1], value2)) { this.cuts++; return value2; } } const memo = memoize ? this.memo?.get(node) : void 0; if (memo?.has(value2)) return memo.get(value2); const cuts = this.cuts; entered.push(node, value2); let result; try { result = this.convert(node, base, value2); } finally { entered.length -= 2; } if (memoize && this.cuts === cuts) { this.memo ??= /* @__PURE__ */ new Map(); let values = this.memo.get(node); if (values === void 0) { values = /* @__PURE__ */ new Map(); this.memo.set(node, values); } values.set(value2, result); } return result; } /** Whether `value` passes the schema `node`; undefined when `node` cannot be compiled. */ passes(node, base, value2) { try { return this.c.check(node, this.baseOf(node, base))(value2); } catch { return void 0; } } baseOf(node, base) { return isSchemaObject(node) ? this.c.r.baseOf.get(node) ?? base : base; } // --------------------------------------------------------------------------------------------------------------- // Pass 1: optional nulls // --------------------------------------------------------------------------------------------------------------- dropOptionalNulls(value2, schema, base) { if (!isSchemaObject(schema)) return value2; const b = this.baseOf(schema, base); if (Array.isArray(value2)) { return mapItems(value2, (item, i) => { const itemSchema = itemSchemaAt(schema, i); return itemSchema === void 0 ? item : this.dropOptionalNulls(item, itemSchema, b); }); } if (!isPlainObject(value2)) return value2; const properties = isSchemaObject(schema.properties) ? schema.properties : {}; const patterns = isSchemaObject(schema.patternProperties) ? Object.entries(schema.patternProperties).map(([pattern, item]) => [sharedRegex(pattern), item]) : []; const additional = isSchemaObject(schema.additionalProperties) ? schema.additionalProperties : void 0; if (patterns.length === 0 && additional === void 0 && !isSchemaObject(schema.properties)) return value2; const required = new Set(Array.isArray(schema.required) ? schema.required : []); let out; const set = (key, next) => { if (next === (out ?? value2)[key]) return; out ??= { ...value2 }; out[key] = next; }; for (const [key, property] of Object.entries(properties)) { if (!Object.hasOwn(value2, key)) continue; const isRef = isSchemaObject(property) && typeof property.$ref === "string"; if (value2[key] === null && !required.has(key) && !isRef && this.passes(property, b, null) === false) { out ??= { ...value2 }; delete out[key]; continue; } set(key, this.dropOptionalNulls(value2[key], property, b)); } if (patterns.length > 0 || additional !== void 0) { for (const key of Object.keys(value2)) { let matched = Object.hasOwn(properties, key); for (const [pattern, item] of patterns) { if (!pattern.test(key)) continue; matched = true; set(key, this.dropOptionalNulls((out ?? value2)[key], item, b)); } if (!matched && additional !== void 0) set(key, this.dropOptionalNulls(value2[key], additional, b)); } } return out ?? value2; } // --------------------------------------------------------------------------------------------------------------- // Pass 2: TypeBox's conversion rules // --------------------------------------------------------------------------------------------------------------- convert(node, base, value2) { if (!isSchemaObject(node)) return value2; const b = this.baseOf(node, base); if (typeof node.$ref === "string") { let target; try { target = this.c.r.resolve(node.$ref, b); } catch { return value2; } return isSchemaObject(target.node) ? this.reenter(target.node, target.base, value2, true) : value2; } return this.convertNode(node, b, value2); } /** One schema node's conversion: its first keyword among `const`, `enum`, the unions, `allOf` and `type`. */ convertNode(node, b, value2) { if (Object.hasOwn(node, "const")) return convertLiteral(node.const, value2); if (Array.isArray(node.enum)) return this.convertEnum(node, node.enum, b, value2); if (Array.isArray(node.anyOf)) return this.convertUnion(node, node.anyOf, b, value2); if (Array.isArray(node.oneOf)) return this.convertUnion(node, node.oneOf, b, value2); if (Array.isArray(node.allOf)) { let next = value2; for (const member of node.allOf) next = this.convert(member, b, next); return this.convertMembers(node, b, next); } switch (node.type) { case "string": return orValue(tryString(value2), value2); case "number": return orValue(tryNumber(value2), value2); case "integer": { const result = tryNumber(value2); return result === void 0 ? value2 : Math.trunc(result.value); } case "boolean": return orValue(tryBoolean(value2), value2); case "null": return orValue(tryNull(value2), value2); case "array": return this.convertArray(node, b, value2); case "object": return this.convertObject(node, b, value2); case void 0: if (node.properties !== void 0 || node.patternProperties !== void 0) { return this.convertObject(node, b, value2); } if (node.items !== void 0 || node.prefixItems !== void 0) return this.convertArray(node, b, value2); return value2; default: return value2; } } /** An enum is a union of its values: a member stays, else the first value the input converts to. */ convertEnum(node, values, base, value2) { if (values.some((member) => member === value2)) return value2; for (const member of values) { const candidate = convertLiteral(member, value2); if (candidate !== value2 && this.passes(node, base, candidate) === true) return candidate; } return value2; } /** * A value one branch accepts stays; else the first branch's conversion that the whole union accepts. Branches are * converted in order and only until one is accepted, so a later recursive branch is not entered needlessly. A node * that also declares members (plain JSON Schema; TypeBox's never do) converts them into each candidate before it is * checked, so a branch is not discarded for a sibling property that is converted after it. */ convertUnion(node, branches, base, value2) { if (branches.some((branch) => this.passes(branch, base, value2) === true)) return this.convertMembers(node, base, value2); for (const branch of branches) { const candidate = this.convertMembers(node, base, this.convert(branch, base, value2)); if (this.passes(node, base, candidate) === true) return candidate; } return this.convertMembers(node, base, value2); } /** The object members of a union or intersection node, when it declares any. */ convertMembers(node, base, value2) { const declares = node.properties !== void 0 || node.patternProperties !== void 0 || node.additionalProperties !== void 0; return declares ? this.convertObject(node, base, value2) : value2; } convertArray(node, base, value2) { const prefix = Array.isArray(node.items) ? node.items : Array.isArray(node.prefixItems) ? node.prefixItems : void 0; if (prefix !== void 0) { if (!Array.isArray(value2)) return value2; const rest = Array.isArray(node.items) ? void 0 : node.items; return mapItems(value2, (item2, i) => { const schema = i < prefix.length ? prefix[i] : rest; return schema === void 0 ? item2 : this.convert(schema, base, item2); }); } if (Array.isArray(value2)) { return node.items === void 0 ? value2 : mapItems(value2, (item2) => this.convert(node.items, base, item2)); } const item = node.items; return [isSchemaObject(item) ? this.reenter(item, base, value2, false) : value2]; } convertObject(node, base, value2) { if (!isPlainObject(value2)) return value2; const properties = isSchemaObject(node.properties) ? node.properties : {}; const patterns = isSchemaObject(node.patternProperties) ? Object.entries(node.patternProperties).map(([pattern, schema]) => [sharedRegex(pattern), schema]) : []; const additional = isSchemaObject(node.additionalProperties) ? node.additionalProperties : void 0; let out; for (const key of Object.keys(value2)) { if (value2[key] === void 0) continue; let next = value2[key]; let matched = Object.hasOwn(properties, key); if (matched) next = this.convert(properties[key], base, next); for (const [pattern, schema] of patterns) { if (!pattern.test(key)) continue; matched = true; next = this.convert(schema, base, next); } if (!matched && additional !== void 0) next = this.convert(additional, base, next); if (next !== value2[key]) { out ??= { ...value2 }; out[key] = next; } } return out ?? value2; } // --------------------------------------------------------------------------------------------------------------- // Pass 3: primitive coercion by type // --------------------------------------------------------------------------------------------------------------- coerceTypes(value2, schema, base) { if (!isSchemaObject(schema)) return value2; const b = this.baseOf(schema, base); let next = value2; if (Array.isArray(schema.allOf)) for (const member of schema.allOf) next = this.coerceTypes(next, member, b); if (Array.isArray(schema.anyOf)) next = this.coerceUnion(next, schema.anyOf, b); if (Array.isArray(schema.oneOf)) next = this.coerceUnion(next, schema.oneOf, b); const types = typesOf(schema); const matchesUnionMember = types.length > 1 && types.some((type) => matchesType(next, type)); if (types.length > 0 && !matchesUnionMember) { for (const type of types) { const candidate = coercePrimitive(next, type); if (candidate !== next) { next = candidate; break; } } } if (types.includes("object") && isPlainObject(next)) next = this.coerceProperties(next, schema, b); if (types.includes("array") && Array.isArray(next)) next = this.coerceItems(next, schema, b); return next; } coerceUnion(value2, branches, base) { for (const branch of branches) if (this.passes(branch, base, value2) === true) return value2; for (const branch of branches) { const coerced = this.coerceTypes(value2, branch, base); if (this.passes(branch, base, coerced) === true) return coerced; } return value2; } coerceProperties(value2, schema, base) { const properties = isSchemaObject(schema.properties) ? schema.properties : {}; const patterns = isSchemaObject(schema.patternProperties) ? Object.entries(schema.patternProperties).map(([pattern, item]) => [sharedRegex(pattern), item]) : []; const additional = isSchemaObject(schema.additionalProperties) ? schema.additionalProperties : void 0; let out; const set = (key, next) => { if (next === (out ?? value2)[key]) return; out ??= { ...value2 }; out[key] = next; }; for (const [key, property] of Object.entries(properties)) { if (key in value2) set(key, this.coerceTypes(value2[key], property, base)); } for (const key of Object.keys(value2)) { let matched = Object.hasOwn(properties, key); for (const [pattern, item] of patterns) { if (!pattern.test(key)) continue; matched = true; set(key, this.coerceTypes((out ?? value2)[key], item, base)); } if (!matched && additional !== void 0) set(key, this.coerceTypes(value2[key], additional, base)); } return out ?? value2; } coerceItems(value2, schema, base) { return mapItems(value2, (item, i) => { const itemSchema = itemSchemaAt(schema, i); return isSchemaObject(itemSchema) ? this.coerceTypes(item, itemSchema, base) : item; }); } }; function itemSchemaAt(schema, index) { if (Array.isArray(schema.items)) return schema.items[index]; if (Array.isArray(schema.prefixItems) && index < schema.prefixItems.length) return schema.prefixItems[index]; return schema.items; } function mapItems(items, map2) { let out; for (let i = 0; i < items.length; i++) { const next = map2(items[i], i); if (next === items[i]) continue; out ??= items.slice(); out[i] = next; } return out ?? items; } function orValue(result, value2) { return result === void 0 ? value2 : result.value; } function convertLiteral(literal, value2) { if (literal === value2) return value2; const result = typeof literal === "string" ? tryString(value2) : typeof literal === "number" ? tryNumber(value2) : typeof literal === "boolean" ? tryBoolean(value2) : void 0; return result !== void 0 && result.value === literal ? result.value : value2; } function tryString(value2) { if (typeof value2 === "bigint" || typeof value2 === "boolean" || isFiniteNumber(value2)) return { value: String(value2) }; if (value2 === null) return { value: "null" }; if (typeof value2 === "string") return { value: value2 }; if (value2 === void 0) return { value: "" }; return void 0; } var MAX_BIGINT = BigInt(Number.MAX_SAFE_INTEGER); var MIN_BIGINT = BigInt(Number.MIN_SAFE_INTEGER); function tryNumber(value2) { if (typeof value2 === "bigint") return value2 <= MAX_BIGINT && value2 >= MIN_BIGINT ? { value: Number(value2) } : void 0; if (typeof value2 === "boolean") return { value: value2 ? 1 : 0 }; if (isFiniteNumber(value2)) return { value: value2 }; if (value2 === null || value2 === void 0) return { value: 0 }; if (typeof value2 !== "string") return void 0; const coerced = +value2; if (Number.isFinite(coerced)) return { value: coerced }; const lowercase = value2.toLowerCase(); if (lowercase === "false") return { value: 0 }; if (lowercase === "true") return { value: 1 }; const big = bigIntOfString(value2); return big !== void 0 && big <= MAX_BIGINT && big >= MIN_BIGINT ? { value: Number(big) } : void 0; } function bigIntOfString(value2) { if (/^-?(0|[1-9]\d*)n$/.test(value2)) return BigInt(value2.slice(0, -1)); if (/^-?(0|[1-9]\d*)\.\d+$/.test(value2)) return BigInt(value2.split(".")[0]); if (/^-?(0|[1-9]\d*)$/.test(value2)) return BigInt(value2); return void 0; } function tryBoolean(value2) { if (typeof value2 === "bigint") return value2 === 0n ? { value: false } : value2 === 1n ? { value: true } : void 0; if (typeof value2 === "boolean") return { value: value2 }; if (isFiniteNumber(value2)) return value2 === 0 ? { value: false } : value2 === 1 ? { value: true } : void 0; if (value2 === null || value2 === void 0) return { value: false }; if (typeof value2 !== "string") return void 0; const lowercase = value2.toLowerCase(); if (lowercase === "false" || value2 === "0") return { value: false }; if (lowercase === "true" || value2 === "1") return { value: true }; return void 0; } function tryNull(value2) { if (typeof value2 === "bigint") return value2 === 0n ? { value: null } : void 0; if (typeof value2 === "boolean") return value2 === false ? { value: null } : void 0; if (isFiniteNumber(value2)) return value2 === 0 ? { value: null } : void 0; if (value2 === null || value2 === void 0) return { value: null }; if (typeof value2 !== "string") return void 0; const lowercase = value2.toLowerCase(); return lowercase === "undefined" || lowercase === "null" || value2 === "" || value2 === "0" ? { value: null } : void 0; } function isFiniteNumber(value2) { return typeof value2 === "number" && Number.isFinite(value2); } function typesOf(schema) { if (typeof schema.type === "string") return [schema.type]; if (Array.isArray(schema.type)) return schema.type.filter((type) => typeof type === "string"); return []; } function matchesType(value2, type) { switch (type) { case "number": return typeof value2 === "number"; case "integer": return typeof value2 === "number" && Number.isInteger(value2); case "boolean": return typeof value2 === "boolean"; case "string": return typeof value2 === "string"; case "null": return value2 === null; case "array": return Array.isArray(value2); case "object": return isPlainObject(value2); default: return false; } } function coercePrimitive(value2, type) { switch (type) { case "number": case "integer": { if (value2 === null) return 0; if (typeof value2 === "string" && value2.trim() !== "") { const parsed = Number(value2); if (type === "number" ? Number.isFinite(parsed) : Number.isInteger(parsed)) return parsed; } if (typeof value2 === "boolean") return value2 ? 1 : 0; return value2; } case "boolean": if (value2 === null) return false; if (value2 === "true" || value2 === 1) return true; if (value2 === "false" || value2 === 0) return false; return value2; case "string": if (value2 === null) return ""; if (typeof value2 === "number" || typeof value2 === "boolean") return String(value2); return value2; case "null": return value2 === "" || value2 === 0 || value2 === false ? null : value2; default: return value2; } } function isPlainObject(value2) { return typeof value2 === "object" && value2 !== null && !Array.isArray(value2); } // node_modules/@earendil-works/pi-schema/dist/checks.js var ALWAYS = () => true; var NEVER = () => false; var IS_STRING = (v) => typeof v === "string"; var IS_NUMBER = (v) => typeof v === "number"; var IS_INTEGER = (v) => Number.isInteger(v); var IS_BOOLEAN = (v) => typeof v === "boolean"; var IS_NULL = (v) => v === null; var IS_ARRAY = (v) => Array.isArray(v); var IS_BYTES = (v) => v instanceof Uint8Array; var IS_OBJECT = (v) => isObject(v); function isObject(v) { return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Uint8Array); } var TYPE_CHECKS = /* @__PURE__ */ new Map([ ["string", IS_STRING], ["number", IS_NUMBER], ["integer", IS_INTEGER], ["boolean", IS_BOOLEAN], ["null", IS_NULL], ["array", IS_ARRAY], ["object", IS_OBJECT], ["bytes", IS_BYTES] ]); var LEAF_CODES = /* @__PURE__ */ new Map([ [IS_STRING, 1], [IS_NUMBER, 2], [IS_INTEGER, 3], [IS_BOOLEAN, 4], [IS_NULL, 5], [ALWAYS, 6] ]); function leafOk(code, x) { switch (code) { case 1: return typeof x === "string"; case 2: return typeof x === "number"; case 3: return Number.isInteger(x); case 4: return typeof x === "boolean"; case 5: return x === null; default: return true; } } var GUARD_NONE = 0; var GUARD_STRICT = 1; var GUARD_LOOSE = 2; var CODE = 7; var REQUIRED = 8; var INHERITED = 16; function and(parts) { switch (parts.length) { case 0: return ALWAYS; case 1: return parts[0]; case 2: { const [a, b] = parts; return (v) => a(v) && b(v); } case 3: { const [a, b, c] = parts; return (v) => a(v) && b(v) && c(v); } default: return (v) => { for (let i = 0; i < parts.length; i++) if (!parts[i](v)) return false; return true; }; } } function indirect(slot) { return (v) => slot.check(v); } function scopedCheck(scope, uri, check2) { return (v) => { scope.push(uri); const ok = check2(v); scope.pop(); return ok; }; } function typeCheck(types) { const checks = types.map((t) => (typeof t === "string" ? TYPE_CHECKS.get(t) : void 0) ?? NEVER); if (checks.length === 1) return checks[0]; return (v) => { for (let i = 0; i < checks.length; i++) if (checks[i](v)) return true; return false; }; } function bytesCheck(min, max) { return (v) => v instanceof Uint8Array && v.length >= min && v.length <= max; } function codePoints(s) { let n = 0; for (let i = 0; i < s.length; i++, n++) { const c = s.charCodeAt(i); if (c >= 55296 && c <= 56319 && i + 1 < s.length) { const next = s.charCodeAt(i + 1); if (next >= 56320 && next <= 57343) i++; } } return n; } function lengthOk(s, min, max) { const units = s.length; if (units < min) return false; if (units < 2 * min && codePoints(s) < min) return false; return units <= max || codePoints(s) <= max; } function stringCheck(strict, min, max, re, format) { if (re !== void 0 && min === 0 && max === Number.POSITIVE_INFINITY && format === void 0) { return strict ? (v) => typeof v === "string" && re.test(v) : (v) => typeof v !== "string" || re.test(v); } if (re === void 0 && format === void 0) { return strict ? (v) => typeof v === "string" && lengthOk(v, min, max) : (v) => typeof v !== "string" || lengthOk(v, min, max); } return (v) => { if (typeof v !== "string") return !strict; return lengthOk(v, min, max) && (re === void 0 || re.test(v)) && (format === void 0 || format(v)); }; } function numberCheck(mode, min, max, xmin, xmax, multipleOf) { return (v) => { if (typeof v !== "number") return mode === 0; if (mode === 2 && !Number.isInteger(v)) return false; return v >= min && v <= max && v > xmin && v < xmax && (multipleOf === void 0 || isMultiple(v, multipleOf)); }; } function isMultiple(v, d) { const q = v / d; if (Number.isInteger(q)) return true; if (!Number.isFinite(q)) return v % d === 0; const [vc, ve] = decimal(v); const [dc, de] = decimal(d); return ve >= de ? vc * 10n ** BigInt(ve - de) % dc === 0n : vc % (dc * 10n ** BigInt(de - ve)) === 0n; } function decimal(x) { const [mantissa, exponent = "0"] = Math.abs(x).toString().split("e"); const point = mantissa.indexOf("."); const fraction = point < 0 ? 0 : mantissa.length - point - 1; return [BigInt(mantissa.replace(".", "")), Number(exponent) - fraction]; } function guardObject(inner, strict) { return strict ? (v) => isObject(v) && inner(v) : (v) => !isObject(v) || inner(v); } function guardArray(inner, strict) { return strict ? (v) => Array.isArray(v) && inner(v) : (v) => !Array.isArray(v) || inner(v); } function declaredObject(entries2, requiredCount, closed, guard) { const n = entries2.length; return (v) => { if (guard !== GUARD_NONE && !isObject(v)) return guard === GUARD_LOOSE; const o = v; let required = 0; for (const k in o) { let i = 0; while (i < n && entries2[i] !== k) i += 3; if (i === n) { if (closed) return false; continue; } const x = o[k]; if (x === void 0) continue; const f = entries2[i + 2]; const c = f & CODE; if (c !== 0 ? !leafOk(c, x) : !entries2[i + 1](x)) return false; if (f & REQUIRED) required++; } return required === requiredCount; }; } function openObject(entries2, keys, extraRequired, patterns, additional, guard) { const known = patterns.length > 0 || additional !== void 0 ? new Set(keys) : void 0; return (v) => { if (guard !== GUARD_NONE && !isObject(v)) return guard === GUARD_LOOSE; const o = v; for (let i = 0; i < entries2.length; i += 3) { const k = entries2[i]; const x = o[k]; const f = entries2[i + 2]; if (x === void 0 || f & INHERITED && !Object.hasOwn(o, k)) { if (f & REQUIRED) return false; continue; } const c = f & CODE; if (c !== 0 ? !leafOk(c, x) : !entries2[i + 1](x)) return false; } for (let i = 0; i < extraRequired.length; i++) if (!Object.hasOwn(o, extraRequired[i])) return false; if (known === void 0) return true; for (const k of Object.keys(o)) { let matched = known.has(k); for (let i = 0; i < patterns.length; i++) { if (patterns[i][0].test(k)) { matched = true; if (!patterns[i][1](o[k])) return false; } } if (!matched && additional !== void 0 && !additional(o[k])) return false; } return true; }; } function propertyNamesCheck(check2) { return (v) => { for (const k in v) if (!check2(k)) return false; return true; }; } function propertyCountCheck(min, max) { return (v) => { let n = 0; for (const _ in v) n++; return n >= min && n <= max; }; } function dependentRequiredCheck(entries2) { return (v) => { const o = v; for (const [k, list] of entries2) if (Object.hasOwn(o, k)) { for (const r of list) if (!Object.hasOwn(o, r)) return false; } return true; }; } function dependentSchemasCheck(entries2) { return (v) => { for (const [k, check2] of entries2) if (Object.hasOwn(v, k) && !check2(v)) return false; return true; }; } function listCheck(guard, min, max, prefix, rest) { const n = prefix.length; const code = rest === void 0 ? 0 : LEAF_CODES.get(rest) ?? 0; return (v) => { if (!Array.isArray(v)) return guard === GUARD_LOOSE; const length = v.length; if (length < min || length > max) return false; const m = length < n ? length : n; for (let i = 0; i < m; i++) if (!prefix[i](v[i])) return false; if (rest === void 0) return true; if (code !== 0) { for (let i = n; i < length; i++) if (!leafOk(code, v[i])) return false; return true; } for (let i = n; i < length; i++) if (!rest(v[i])) return false; return true; }; } function itemsCheck(prefix, rest) { return listCheck(GUARD_NONE, 0, Number.POSITIVE_INFINITY, prefix, rest); } function lengthCheck(min, max) { return (v) => v.length >= min && v.length <= max; } function containsCheck(check2, min, max) { return (v) => { const list = v; let n = 0; for (let i = 0; i < list.length; i++) if (check2(list[i]) && ++n > max) return false; return n >= min; }; } function uniqueCheck(v) { const list = v; for (let i = 1; i < list.length; i++) for (let j = 0; j < i; j++) if (deepEqual(list[i], list[j])) return false; return true; } function enumCheck(values) { const primitives = values.filter((x) => typeof x !== "object" || x === null); if (primitives.length === values.length && primitives.length <= 8) { return (v) => { for (let i = 0; i < primitives.length; i++) if (primitives[i] === v) return true; return false; }; } const set = new Set(primitives); const objects = values.filter((x) => typeof x === "object" && x !== null); return (v) => set.has(v) || typeof v === "object" && objects.some((o) => deepEqual(v, o)); } function constCheck(value2) { if (typeof value2 !== "object" || value2 === null) return (v) => v === value2; return (v) => deepEqual(v, value2); } function anyCheck(checks) { return (v) => { for (let i = 0; i < checks.length; i++) if (checks[i](v)) return true; return false; }; } function oneCheck(checks) { return (v) => { let n = 0; for (let i = 0; i < checks.length; i++) if (checks[i](v) && ++n > 1) return false; return n === 1; }; } function notCheck(check2) { return (v) => !check2(v); } function ifCheck(cond, then, otherwise) { return (v) => cond(v) ? then(v) : otherwise(v); } function taggedCheck(key, tags, checks) { return (v) => { if (!isObject(v)) return false; const tag = v[key]; for (let i = 0; i < tags.length; i++) if (tags[i] === tag) return checks[i](v); return false; }; } function deepEqual(a, b) { if (a === b) return true; if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false; const array = Array.isArray(a); if (array !== Array.isArray(b)) return false; if (array) { const x2 = a; const y2 = b; if (x2.length !== y2.length) return false; for (let i = 0; i < x2.length; i++) if (!deepEqual(x2[i], y2[i])) return false; return true; } const x = a; const y = b; const keys = Object.keys(x); if (keys.length !== Object.keys(y).length) return false; for (const k of keys) if (!Object.hasOwn(y, k) || !deepEqual(x[k], y[k])) return false; return true; } // node_modules/@earendil-works/pi-schema/dist/annotations.js function newAnnotations() { return { props: null, allProps: false, items: 0, allItems: false, indexes: null }; } function merge(a, b) { if (b.props !== null) for (const k of b.props) addProp(a, k); a.allProps ||= b.allProps; if (b.items > a.items) a.items = b.items; a.allItems ||= b.allItems; if (b.indexes !== null) for (const i of b.indexes) addIndex(a, i); } function addProp(a, key) { if (a.props === null) a.props = /* @__PURE__ */ new Set(); a.props.add(key); } function addIndex(a, index) { if (a.indexes === null) a.indexes = /* @__PURE__ */ new Set(); a.indexes.add(index); } var ANNOTATING_TRUE = () => true; var ANNOTATING_FALSE = () => false; function indirectAnnotating(slot) { return (v, a) => slot.check(v, a); } function ignoreAnnotations(check2) { return (v) => check2(v); } function withFreshAnnotations(check2) { return (v) => check2(v, newAnnotations()); } function scopedAnnotating(scope, uri, check2) { return (v, a) => { scope.push(uri); const ok = check2(v, a); scope.pop(); return ok; }; } function isolateAnnotations(check2) { return (v, a) => { const b = newAnnotations(); if (!check2(v, b)) return false; merge(a, b); return true; }; } function annotateAll(plain, parts) { return (v, a) => { if (!plain(v)) return false; for (let i = 0; i < parts.length; i++) if (!parts[i](v, a)) return false; return true; }; } function annotateObject(props, patterns, additional) { const declared = new Set(props.map(([k]) => k)); return (v, a) => { if (!isObject(v)) return true; const seen = []; for (const [k, check2] of props) { if (!Object.hasOwn(v, k)) continue; if (!check2(v[k])) return false; seen.push(k); } for (const k of Object.keys(v)) { let matched = declared.has(k); for (const [re, check2] of patterns) { if (re.test(k)) { if (!check2(v[k])) return false; matched = true; seen.push(k); } } if (!matched && additional !== void 0) { if (!additional(v[k])) return false; seen.push(k); } } for (const k of seen) addProp(a, k); return true; }; } function annotateArray(prefix, rest, contains, minContains, maxContains) { return (v, a) => { if (!Array.isArray(v)) return true; const m = Math.min(v.length, prefix.length); for (let i = 0; i < m; i++) if (!prefix[i](v[i])) return false; if (rest !== void 0) { for (let i = prefix.length; i < v.length; i++) if (!rest(v[i])) return false; a.allItems = true; } if (m > a.items) a.items = m; if (contains !== void 0) { let n = 0; for (let i = 0; i < v.length; i++) { if (contains(v[i])) { n++; addIndex(a, i); } } if (n < minContains || n > maxContains) return false; } return true; }; } function annotateAny(branches, exactlyOne) { return (v, a) => { let n = 0; for (const branch of branches) { const b = newAnnotations(); if (branch(v, b)) { n++; merge(a, b); } } return exactlyOne ? n === 1 : n > 0; }; } function annotateIf(cond, then, otherwise) { return (v, a) => { const b = newAnnotations(); if (cond(v, b)) { merge(a, b); return then === void 0 || then(v, a); } return otherwise === void 0 || otherwise(v, a); }; } function annotateDependent(key, check2) { return (v, a) => !isObject(v) || !Object.hasOwn(v, key) || check2(v, a); } function unevaluatedProperties(check2) { return (v, a) => { if (!isObject(v) || a.allProps) return true; for (const k of Object.keys(v)) if (!(a.props?.has(k) ?? false) && !check2(v[k])) return false; a.allProps = true; return true; }; } function unevaluatedItems(check2) { return (v, a) => { if (!Array.isArray(v) || a.allItems) return true; for (let i = a.items; i < v.length; i++) if (!(a.indexes?.has(i) ?? false) && !check2(v[i])) return false; a.allItems = true; return true; }; } // node_modules/@earendil-works/pi-schema/dist/compiler.js var RESTART = /* @__PURE__ */ Symbol("restart"); var Compiler = class { r; draft; formats; /** Whether checks track the dynamic scope (the schema has a bookended `$dynamicRef`). */ dynamic; schema; scope; cache = /* @__PURE__ */ new Map(); annotatingCache = /* @__PURE__ */ new Map(); constructor(schema, options, dynamic) { this.schema = schema; this.r = new Resolver(schema, options); this.draft = this.r.draft; this.formats = options.formats; this.dynamic = dynamic; this.scope = []; } /** The check of the root schema. */ root() { return this.check(this.schema, this.r.rootBase); } /** The check of a schema (cached by schema object; a recursive reference goes through an indirection). */ check(node, base) { if (node === true) return ALWAYS; if (node === false) return NEVER; if (!isSchemaObject(node)) throw new Error(`A schema must be an object or a boolean, got ${JSON.stringify(node)}`); const cached = this.cache.get(node); if (cached !== void 0) return cached; const bare = this.bareType(node); if (bare !== void 0) return bare; const slot = { check: void 0 }; this.cache.set(node, indirect(slot)); const check2 = this.build(node, this.r.baseFor(node, base)); slot.check = check2; this.cache.set(node, check2); return check2; } build(node, base) { if (this.draft === "7" && typeof node.$ref === "string") return this.ref(node.$ref, base); if (this.draft !== "7" && (node.unevaluatedProperties !== void 0 || node.unevaluatedItems !== void 0)) { return withFreshAnnotations(this.annotating(node, base)); } const check2 = this.plain(node, base); return this.dynamic && this.r.isResource(node, base) ? scopedCheck(this.scope, base, check2) : check2; } /** * The module-level check of a schema that is only a `type` (with annotations), `ALWAYS` for one with no assertion * at all; undefined for every other schema. These are the most common subschemas and cost nothing to share. */ bareType(node) { let type; for (const k in node) { if (k === "type") type = node.type; else if (!NEUTRAL_KEYWORDS.has(k)) return void 0; } if (type === void 0) return ALWAYS; return typeof type === "string" ? TYPE_CHECKS.get(type) : void 0; } /** Every keyword of `node`, not tracking annotations. */ plain(node, base) { const parts = []; const types = node.type === void 0 ? void 0 : Array.isArray(node.type) ? node.type : [node.type]; const single = types?.length === 1 ? types[0] : void 0; let typeInGroup = false; if (single === "bytes") { parts.push(bytesCheck(number(node.minLength) ?? 0, number(node.maxLength) ?? Number.POSITIVE_INFINITY)); typeInGroup = true; } else { const string = this.stringGroup(node, single === "string"); if (string !== void 0) parts.push(string); const numeric = single === "number" || single === "integer"; const number2 = this.numberGroup(node, single === "number" ? 1 : single === "integer" ? 2 : 0); if (number2 !== void 0) parts.push(number2); const object = this.objectGroup(node, base, single === "object"); if (object !== void 0) parts.push(object); const array = this.arrayGroup(node, base, single === "array"); if (array !== void 0) parts.push(array); typeInGroup = single === "string" && string !== void 0 || numeric && number2 !== void 0 || single === "object" && object !== void 0 || single === "array" && array !== void 0; } if (types !== void 0 && !typeInGroup) parts.unshift(typeCheck(types)); this.inPlace(node, base, parts); return and(parts); } // The keywords that apply to the value itself, whatever its type. inPlace(node, base, parts) { if (Array.isArray(node.enum)) parts.push(enumCheck(node.enum)); if (Object.hasOwn(node, "const")) parts.push(constCheck(node.const)); if (Array.isArray(node.allOf)) parts.push(and(node.allOf.map((s) => this.check(s, base)))); if (Array.isArray(node.anyOf)) parts.push(this.union(node.anyOf, base, false)); if (Array.isArray(node.oneOf)) parts.push(this.union(node.oneOf, base, true)); if (node.not !== void 0) parts.push(notCheck(this.check(node.not, base))); if (node.if !== void 0 && (node.then !== void 0 || node.else !== void 0)) { const then = node.then === void 0 ? ALWAYS : this.check(node.then, base); const otherwise = node.else === void 0 ? ALWAYS : this.check(node.else, base); parts.push(ifCheck(this.check(node.if, base), then, otherwise)); } if (typeof node.$ref === "string") parts.push(this.ref(node.$ref, base)); if (this.draft !== "7" && typeof node.$dynamicRef === "string") { parts.push(this.dynamicRef(node.$dynamicRef, base)); } } union(branches, base, exactlyOne) { const checks = () => branches.map((s) => this.check(s, base)); return constUnion(branches, exactlyOne) ?? this.tagged(branches, base) ?? (exactlyOne ? oneCheck(checks()) : anyCheck(checks())); } ref(ref, base) { const target = this.r.resolve(ref, base); const check2 = this.check(target.node, target.base); return this.dynamic && target.base !== base ? scopedCheck(this.scope, target.base, check2) : check2; } dynamicRef(ref, base) { const resolved = this.bookended(ref, base); if (resolved === void 0) return this.ref(ref, base); const { initial, name } = resolved; const fallback = this.check(initial.node, initial.base); const lookup = this.dynamicLookup(name, (node, b) => this.check(node, b)); const scope = this.scope; return (v) => { for (let i = 0; i < scope.length; i++) { const check2 = lookup(scope[i]); if (check2 !== null) return check2(v); } return fallback(v); }; } /** The initial target and anchor name of a `$dynamicRef` that resolves dynamically, or undefined for a plain ref. */ bookended(ref, base) { const initial = this.r.resolve(ref, base); const uri = resolveUri(ref, base); const name = uri.slice(documentOf(uri).length + 1); const anchored = isSchemaObject(initial.node) && initial.node.$dynamicAnchor === name; if (name === "" || name.startsWith("/") || !anchored) return void 0; if (!this.dynamic) throw RESTART; return { initial, name }; } // Resolved at run time against the dynamic scope, so a schema with a bookended $dynamicRef keeps its compiler. dynamicLookup(name, compile2) { const found = /* @__PURE__ */ new Map(); return (uri) => { const key = `${uri}#${name}`; let check2 = found.get(key); if (check2 === void 0) { const target = this.r.dynamicAnchors.get(key); check2 = target === void 0 ? null : compile2(target, this.r.baseOf.get(target) ?? uri); found.set(key, check2); } return check2; }; } /** anyOf/oneOf whose branches are all objects with a distinct required const on one key: the tag picks the branch. */ tagged(branches, base) { if (branches.length < 2) return void 0; const resolved = []; for (const branch of branches) { if (!isSchemaObject(branch)) return void 0; const ref = typeof branch.$ref === "string" && Object.keys(branch).length === 1 ? branch.$ref : void 0; const node = ref === void 0 ? branch : this.r.resolve(ref, this.r.baseFor(branch, base)).node; if (!isSchemaObject(node)) return void 0; resolved.push(node); } const first = resolved[0].properties; if (!isSchemaObject(first)) return void 0; for (const key of Object.keys(first)) { const tags = resolved.map((s) => tagOf(s, key)); if (tags.some((t) => t === void 0) || new Set(tags).size !== tags.length) continue; return taggedCheck(key, tags, branches.map((s) => this.check(s, base))); } return void 0; } stringGroup(node, strict) { const min = number(node.minLength) ?? 0; const max = number(node.maxLength) ?? Number.POSITIVE_INFINITY; const re = typeof node.pattern === "string" ? sharedRegex(node.pattern) : void 0; const format = this.formats !== void 0 && typeof node.format === "string" && Object.hasOwn(this.formats, node.format) ? this.formats[node.format] : void 0; if (min === 0 && max === Number.POSITIVE_INFINITY && re === void 0 && format === void 0) return void 0; return stringCheck(strict, min, max, re, format); } numberGroup(node, mode) { const min = number(node.minimum); const max = number(node.maximum); const xmin = number(node.exclusiveMinimum); const xmax = number(node.exclusiveMaximum); const multipleOf = number(node.multipleOf); if ([min, max, xmin, xmax, multipleOf].every((x) => x === void 0)) return void 0; const inf = Number.POSITIVE_INFINITY; return numberCheck(mode, min ?? -inf, max ?? inf, xmin ?? -inf, xmax ?? inf, multipleOf); } objectGroup(node, base, strict) { const onlyCore = OBJECT_EXTRAS.every((k) => node[k] === void 0); const core = this.objectCore(node, base, onlyCore ? strict ? GUARD_STRICT : GUARD_LOOSE : GUARD_NONE); if (core !== void 0 && onlyCore) return core; const parts = core === void 0 ? [] : [core]; if (node.propertyNames !== void 0) parts.push(propertyNamesCheck(this.check(node.propertyNames, base))); const minProperties = number(node.minProperties); const maxProperties = number(node.maxProperties); if (minProperties !== void 0 || maxProperties !== void 0) { parts.push(propertyCountCheck(minProperties ?? 0, maxProperties ?? Number.POSITIVE_INFINITY)); } const dependentRequired = []; const dependentSchemas = []; if (this.draft !== "7") { for (const [k, list] of entries(node.dependentRequired)) if (Array.isArray(list)) dependentRequired.push([k, list]); for (const [k, s] of entries(node.dependentSchemas)) dependentSchemas.push([k, this.check(s, base)]); } for (const [k, d] of entries(node.dependencies)) { if (Array.isArray(d)) dependentRequired.push([k, d]); else dependentSchemas.push([k, this.check(d, base)]); } if (dependentRequired.length > 0) parts.push(dependentRequiredCheck(dependentRequired)); if (dependentSchemas.length > 0) parts.push(dependentSchemasCheck(dependentSchemas)); if (parts.length === 0) return strict ? IS_OBJECT : void 0; return guardObject(and(parts), strict); } objectCore(node, base, guard) { const properties = isSchemaObject(node.properties) ? node.properties : {}; const required = Array.isArray(node.required) ? node.required.filter((k) => typeof k === "string") : []; const patterns = isSchemaObject(node.patternProperties) ? node.patternProperties : {}; const additional = node.additionalProperties; const keys = Object.keys(properties); const patternKeys = Object.keys(patterns); if (keys.length + required.length + patternKeys.length === 0 && (additional === void 0 || additional === true)) { return void 0; } const entries2 = []; for (const k of keys) { const check2 = this.check(properties[k], base); const flags = (LEAF_CODES.get(check2) ?? 0) | (required.includes(k) ? REQUIRED : 0) | (k in Object.prototype ? INHERITED : 0); entries2.push(k, check2, flags); } const extraRequired = required.filter((k) => !Object.hasOwn(properties, k)); const pats = patternKeys.map((p) => [sharedRegex(p), this.check(patterns[p], base)]); const extra = additional === void 0 || additional === true ? void 0 : this.check(additional, base); if (pats.length === 0 && extraRequired.length === 0 && (extra === void 0 || additional === false)) { const requiredCount = keys.filter((k) => required.includes(k)).length; return declaredObject(entries2, requiredCount, additional === false, guard); } return openObject(entries2, keys, extraRequired, pats, extra, guard); } arrayGroup(node, base, strict) { let prefix = []; let rest; if (Array.isArray(node.items)) { prefix = node.items; rest = node.additionalItems; } else { if (this.draft !== "7" && Array.isArray(node.prefixItems)) prefix = node.prefixItems; rest = node.items; } const prefixChecks = prefix.map((s) => this.check(s, base)); const restCheck = rest === void 0 || rest === true ? void 0 : this.check(rest, base); const minItems = number(node.minItems); const maxItems = number(node.maxItems); if (node.contains === void 0 && node.uniqueItems !== true && (prefixChecks.length > 0 || restCheck !== void 0)) { const guard = strict ? GUARD_STRICT : GUARD_LOOSE; return listCheck(guard, minItems ?? 0, maxItems ?? Number.POSITIVE_INFINITY, prefixChecks, restCheck); } const parts = []; if (prefixChecks.length > 0 || restCheck !== void 0) parts.push(itemsCheck(prefixChecks, restCheck)); if (minItems !== void 0 || maxItems !== void 0) { parts.push(lengthCheck(minItems ?? 0, maxItems ?? Number.POSITIVE_INFINITY)); } if (node.contains !== void 0) { const min = this.draft === "7" ? 1 : number(node.minContains) ?? 1; const max = this.draft === "7" ? Number.POSITIVE_INFINITY : number(node.maxContains) ?? Number.POSITIVE_INFINITY; parts.push(containsCheck(this.check(node.contains, base), min, max)); } if (node.uniqueItems === true) parts.push(uniqueCheck); if (parts.length === 0) return strict ? IS_ARRAY : void 0; return guardArray(and(parts), strict); } /** The annotating check of a schema evaluated in place (only below `unevaluatedProperties`/`unevaluatedItems`). */ annotating(node, base) { if (node === true) return ANNOTATING_TRUE; if (node === false) return ANNOTATING_FALSE; if (!isSchemaObject(node)) throw new Error(`A schema must be an object or a boolean, got ${JSON.stringify(node)}`); const cached = this.annotatingCache.get(node); if (cached !== void 0) return cached; const slot = { check: void 0 }; this.annotatingCache.set(node, indirectAnnotating(slot)); const check2 = this.buildAnnotating(node, this.r.baseFor(node, base)); slot.check = check2; this.annotatingCache.set(node, check2); return check2; } buildAnnotating(node, base) { const rest = {}; let annotates = false; for (const k of Object.keys(node)) { if (ANNOTATING.has(k)) annotates = true; else rest[k] = node[k]; } const plain = this.plain(rest, base); const parts = []; if (annotates) this.annotatingParts(node, base, parts); const all = parts.length === 0 ? ignoreAnnotations(plain) : annotateAll(plain, parts); const own = node.unevaluatedProperties !== void 0 || node.unevaluatedItems !== void 0 ? isolateAnnotations(all) : all; return this.dynamic && this.r.isResource(node, base) ? scopedAnnotating(this.scope, base, own) : own; } annotatingParts(node, base, parts) { if (node.properties !== void 0 || node.patternProperties !== void 0 || node.additionalProperties !== void 0) { const props = entries(node.properties).map(([k, s]) => [k, this.check(s, base)]); const pats = entries(node.patternProperties).map(([p, s]) => [sharedRegex(p), this.check(s, base)]); const extra = node.additionalProperties === void 0 ? void 0 : this.check(node.additionalProperties, base); parts.push(annotateObject(props, pats, extra)); } if (node.prefixItems !== void 0 || node.items !== void 0 || node.contains !== void 0) { const prefix = Array.isArray(node.prefixItems) ? node.prefixItems : Array.isArray(node.items) ? node.items : []; const rest = Array.isArray(node.items) ? node.additionalItems : node.items; parts.push(annotateArray(prefix.map((s) => this.check(s, base)), rest === void 0 ? void 0 : this.check(rest, base), node.contains === void 0 ? void 0 : this.check(node.contains, base), number(node.minContains) ?? 1, number(node.maxContains) ?? Number.POSITIVE_INFINITY)); } if (Array.isArray(node.allOf)) for (const s of node.allOf) parts.push(this.annotating(s, base)); if (Array.isArray(node.anyOf)) parts.push(annotateAny(node.anyOf.map((s) => this.annotating(s, base)), false)); if (Array.isArray(node.oneOf)) parts.push(annotateAny(node.oneOf.map((s) => this.annotating(s, base)), true)); if (node.if !== void 0) { const then = node.then === void 0 ? void 0 : this.annotating(node.then, base); const otherwise = node.else === void 0 ? void 0 : this.annotating(node.else, base); parts.push(annotateIf(this.annotating(node.if, base), then, otherwise)); } if (typeof node.$ref === "string") parts.push(this.annotatingRef(node.$ref, base)); if (typeof node.$dynamicRef === "string") parts.push(this.annotatingDynamicRef(node.$dynamicRef, base)); for (const [k, s] of entries(node.dependentSchemas)) parts.push(annotateDependent(k, this.annotating(s, base))); if (node.unevaluatedProperties !== void 0) { parts.push(unevaluatedProperties(this.check(node.unevaluatedProperties, base))); } if (node.unevaluatedItems !== void 0) parts.push(unevaluatedItems(this.check(node.unevaluatedItems, base))); } annotatingRef(ref, base) { const target = this.r.resolve(ref, base); const check2 = this.annotating(target.node, target.base); return this.dynamic && target.base !== base ? scopedAnnotating(this.scope, target.base, check2) : check2; } annotatingDynamicRef(ref, base) { const resolved = this.bookended(ref, base); if (resolved === void 0) return this.annotatingRef(ref, base); const { initial, name } = resolved; const fallback = this.annotating(initial.node, initial.base); const lookup = this.dynamicLookup(name, (node, b) => this.annotating(node, b)); const scope = this.scope; return (v, a) => { for (let i = 0; i < scope.length; i++) { const check2 = lookup(scope[i]); if (check2 !== null) return check2(v, a); } return fallback(v, a); }; } }; var NEUTRAL_KEYWORDS = /* @__PURE__ */ new Set([ "$schema", "$comment", "title", "description", "default", "examples", "deprecated", "readOnly", "writeOnly", "$defs", "definitions", "contentEncoding", "contentMediaType", "minContains", "maxContains", "$vocabulary" ]); var OBJECT_EXTRAS = [ "propertyNames", "minProperties", "maxProperties", "dependentRequired", "dependentSchemas", "dependencies" ]; var ANNOTATING = /* @__PURE__ */ new Set([ "properties", "patternProperties", "additionalProperties", "prefixItems", "items", "additionalItems", "contains", "minContains", "maxContains", "allOf", "anyOf", "oneOf", "if", "then", "else", "$ref", "$dynamicRef", "dependentSchemas", "unevaluatedProperties", "unevaluatedItems" ]); function constUnion(branches, exactlyOne) { const values = []; for (const branch of branches) { if (!isSchemaObject(branch) || !Object.hasOwn(branch, "const")) return void 0; for (const k of Object.keys(branch)) if (k !== "const" && !NEUTRAL_KEYWORDS.has(k)) return void 0; if (typeof branch.const === "object" && branch.const !== null) return void 0; if (exactlyOne && values.includes(branch.const)) return void 0; values.push(branch.const); } return enumCheck(values); } function tagOf(branch, key) { const properties = branch.properties; if (branch.type !== "object" || !Array.isArray(branch.required) || !branch.required.includes(key)) return void 0; const tag = isSchemaObject(properties) ? properties[key] : void 0; if (!isSchemaObject(tag) || !Object.hasOwn(tag, "const")) return void 0; return typeof tag.const === "object" && tag.const !== null ? void 0 : tag.const; } function number(value2) { return typeof value2 === "number" ? value2 : void 0; } function entries(value2) { return isSchemaObject(value2) ? Object.entries(value2) : []; } // node_modules/@earendil-works/pi-schema/dist/explain.js var Explainer = class { c; schema; /** * The errors of `$ref` targets for object values, within one errors() call: a recursive union explains each branch * to pick the one to blame, and every branch would explain the same children again, exponentially in the depth. */ memo = void 0; constructor(compiler2, schema) { this.c = compiler2; this.schema = schema; compiler2.root(); } /** The errors of `value`, at least one (`value` failed the check). */ errors(value2) { const out = []; try { this.walk(this.schema, this.c.r.rootBase, value2, "", out); } finally { this.memo = void 0; } if (out.length === 0) out.push({ path: "", keyword: "schema", message: "invalid" }); return out; } ok(node, base, v) { return this.c.check(node, base)(v); } // For $dynamicRef schemas this check runs outside the original dynamic scope, so their errors are approximate. walk(node, base, v, path, out) { if (node === true) return; if (node === false) { out.push({ path, keyword: "false", message: "no value is allowed here" }); return; } if (!isSchemaObject(node)) return; const b = this.c.r.baseFor(node, base); if (this.ok(node, b, v)) return; const start = out.length; const push = (keyword, message, at = path) => { out.push({ path: at, keyword, message }); }; if (this.c.draft === "7" && typeof node.$ref === "string") { this.walkRef(node.$ref, b, v, path, out); return; } if (node.type !== void 0) { const types = Array.isArray(node.type) ? node.type : [node.type]; const checks = types.map((t) => (typeof t === "string" ? TYPE_CHECKS.get(t) : void 0) ?? NEVER); if (!checks.some((check2) => check2(v))) { push("type", `expected ${types.join(" or ")}, got ${typeName(v)}`); return; } } if (Array.isArray(node.enum) && !enumCheck(node.enum)(v)) push("enum", `expected one of ${listValues(node.enum)}`); if (Object.hasOwn(node, "const") && !constCheck(node.const)(v)) push("const", `expected ${JSON.stringify(node.const)}`); if (typeof v === "string" && node.type !== "bytes") this.walkString(node, v, push); if (v instanceof Uint8Array && node.type === "bytes") this.walkBytes(node, v, push); if (typeof v === "number") this.walkNumber(node, v, push); if (isObject(v)) this.walkObject(node, b, v, path, out, push); if (Array.isArray(v)) this.walkArray(node, b, v, path, out, push); if (Array.isArray(node.allOf)) for (const s of node.allOf) this.walk(s, b, v, path, out); if (Array.isArray(node.anyOf)) this.walkUnion(node.anyOf, b, v, path, out, false); if (Array.isArray(node.oneOf)) this.walkUnion(node.oneOf, b, v, path, out, true); if (node.not !== void 0 && this.ok(node.not, b, v)) push("not", "must not match the schema in not"); if (node.if !== void 0) { const branch = this.ok(node.if, b, v) ? node.then : node.else; if (branch !== void 0) this.walk(branch, b, v, path, out); } if (typeof node.$ref === "string") this.walkRef(node.$ref, b, v, path, out); if (out.length === start && (node.unevaluatedProperties !== void 0 || node.unevaluatedItems !== void 0)) { this.walkUnevaluated(node, b, v, path, out); } if (out.length === start && typeof node.$dynamicRef === "string") { push("$dynamicRef", "does not match the dynamically referenced schema"); } } walkRef(ref, base, v, path, out) { const target = this.c.r.resolve(ref, base); if (target.node === false && this.c.r.isUnresolved(ref, base)) { out.push({ path, keyword: "$ref", message: `unresolved reference ${ref}` }); return; } if (!isSchemaObject(target.node) || typeof v !== "object" || v === null) { this.walk(target.node, target.base, v, path, out); return; } this.memo ??= /* @__PURE__ */ new Map(); let values = this.memo.get(target.node); const known = values?.get(v); if (known !== void 0 && known.path === path) { out.push(...known.errors); return; } const errors = []; this.walk(target.node, target.base, v, path, errors); if (values === void 0) { values = /* @__PURE__ */ new Map(); this.memo.set(target.node, values); } values.set(v, { path, errors }); out.push(...errors); } walkString(node, v, push) { const n = codePoints(v); if (typeof node.minLength === "number" && n < node.minLength) { push("minLength", `expected at least ${node.minLength} characters`); } if (typeof node.maxLength === "number" && n > node.maxLength) { push("maxLength", `expected at most ${node.maxLength} characters`); } if (typeof node.pattern === "string" && !sharedRegex(node.pattern).test(v)) { push("pattern", `expected to match ${node.pattern}`); } const formats2 = this.c.formats; const format = formats2 !== void 0 && typeof node.format === "string" && Object.hasOwn(formats2, node.format) ? formats2[node.format] : void 0; if (format !== void 0 && !format(v)) push("format", `expected format ${node.format}`); } walkBytes(node, v, push) { if (typeof node.minLength === "number" && v.length < node.minLength) { push("minLength", `expected at least ${node.minLength} bytes`); } if (typeof node.maxLength === "number" && v.length > node.maxLength) { push("maxLength", `expected at most ${node.maxLength} bytes`); } } walkNumber(node, v, push) { if (typeof node.minimum === "number" && v < node.minimum) push("minimum", `expected >= ${node.minimum}`); if (typeof node.maximum === "number" && v > node.maximum) push("maximum", `expected <= ${node.maximum}`); if (typeof node.exclusiveMinimum === "number" && v <= node.exclusiveMinimum) { push("exclusiveMinimum", `expected > ${node.exclusiveMinimum}`); } if (typeof node.exclusiveMaximum === "number" && v >= node.exclusiveMaximum) { push("exclusiveMaximum", `expected < ${node.exclusiveMaximum}`); } if (typeof node.multipleOf === "number" && !isMultiple(v, node.multipleOf)) { push("multipleOf", `expected a multiple of ${node.multipleOf}`); } } walkObject(node, b, v, path, out, push) { const properties = isSchemaObject(node.properties) ? node.properties : {}; const required = Array.isArray(node.required) ? node.required : []; for (const k of required) { if (typeof k === "string" && (!Object.hasOwn(v, k) || v[k] === void 0)) push("required", "missing", pointer(path, k)); } for (const k of Object.keys(properties)) { if (Object.hasOwn(v, k) && v[k] !== void 0) this.walk(properties[k], b, v[k], pointer(path, k), out); } const patterns = isSchemaObject(node.patternProperties) ? Object.entries(node.patternProperties) : []; for (const k of Object.keys(v)) { let matched = Object.hasOwn(properties, k); for (const [p, s] of patterns) { if (sharedRegex(p).test(k)) { matched = true; this.walk(s, b, v[k], pointer(path, k), out); } } if (!matched && node.additionalProperties !== void 0) { if (node.additionalProperties === false) push("additionalProperties", "unknown field", pointer(path, k)); else this.walk(node.additionalProperties, b, v[k], pointer(path, k), out); } if (node.propertyNames !== void 0 && !this.ok(node.propertyNames, b, k)) { push("propertyNames", "invalid property name", pointer(path, k)); } } const count = Object.keys(v).length; if (typeof node.minProperties === "number" && count < node.minProperties) { push("minProperties", `expected at least ${node.minProperties} properties`); } if (typeof node.maxProperties === "number" && count > node.maxProperties) { push("maxProperties", `expected at most ${node.maxProperties} properties`); } const dependencies = { ...this.c.draft === "7" || !isSchemaObject(node.dependentRequired) ? {} : node.dependentRequired, ...isSchemaObject(node.dependencies) ? node.dependencies : {} }; for (const [k, d] of Object.entries(dependencies)) { if (!Object.hasOwn(v, k)) continue; if (!Array.isArray(d)) this.walk(d, b, v, path, out); else for (const r of d) if (!Object.hasOwn(v, r)) push("dependentRequired", `required when ${k} is present`, pointer(path, r)); } if (this.c.draft !== "7" && isSchemaObject(node.dependentSchemas)) { for (const [k, s] of Object.entries(node.dependentSchemas)) if (Object.hasOwn(v, k)) this.walk(s, b, v, path, out); } } walkArray(node, b, v, path, out, push) { let prefix = []; let rest; if (Array.isArray(node.items)) { prefix = node.items; rest = node.additionalItems; } else { if (this.c.draft !== "7" && Array.isArray(node.prefixItems)) prefix = node.prefixItems; rest = node.items; } for (let i = 0; i < v.length; i++) { const s = i < prefix.length ? prefix[i] : rest; if (s !== void 0) this.walk(s, b, v[i], `${path}/${i}`, out); } if (typeof node.minItems === "number" && v.length < node.minItems) { push("minItems", `expected at least ${node.minItems} items`); } if (typeof node.maxItems === "number" && v.length > node.maxItems) { push("maxItems", `expected at most ${node.maxItems} items`); } if (node.uniqueItems === true && !uniqueCheck(v)) push("uniqueItems", "expected unique items"); if (node.contains !== void 0) { const draft7 = this.c.draft === "7"; const min = draft7 || typeof node.minContains !== "number" ? 1 : node.minContains; const max = draft7 || typeof node.maxContains !== "number" ? Number.POSITIVE_INFINITY : node.maxContains; if (!containsCheck(this.c.check(node.contains, b), min, max)(v)) { push("contains", containsMessage(min, max)); } } } walkUnion(branches, b, v, path, out, exactlyOne) { const matches = branches.filter((s) => this.ok(s, b, v)).length; if (matches === 1 || !exactlyOne && matches > 0) return; const keyword = exactlyOne ? "oneOf" : "anyOf"; if (matches > 1) { out.push({ path, keyword, message: "matches more than one alternative" }); return; } const tag = this.tagOf(branches, b); if (tag !== void 0 && isObject(v)) { const i = tag.values.indexOf(v[tag.key]); if (i < 0) out.push({ path: pointer(path, tag.key), keyword, message: `expected one of ${listValues(tag.values)}` }); else this.walk(branches[i], b, v, path, out); return; } let best; for (const s of branches) { const errors = []; this.walk(s, b, v, path, errors); if (errors.length === 0) continue; const declared = isObject(v) ? this.declaredKeys(s, b, v) : 0; const better = best === void 0 || declared > best.declared || declared === best.declared && errors[0].path.length > best.errors[0].path.length; if (better) best = { errors, declared }; } out.push(...best?.errors ?? [{ path, keyword, message: "matches no alternative" }]); } /** How many of the keys of `v` the branch `s` (through a `$ref`) declares in `properties`. */ declaredKeys(s, b, v) { const node = isSchemaObject(s) && typeof s.$ref === "string" ? this.c.r.resolve(s.$ref, b).node : s; const properties = isSchemaObject(node) && isSchemaObject(node.properties) ? node.properties : {}; return Object.keys(v).filter((k) => Object.hasOwn(properties, k)).length; } tagOf(branches, b) { const resolved = branches.map((s) => isSchemaObject(s) && typeof s.$ref === "string" ? this.c.r.resolve(s.$ref, b).node : s); const first = resolved[0]; if (!isSchemaObject(first) || !isSchemaObject(first.properties)) return void 0; for (const key of Object.keys(first.properties)) { const values = resolved.map((s) => { const tag = isSchemaObject(s) && isSchemaObject(s.properties) ? s.properties[key] : void 0; return isSchemaObject(tag) ? tag.const : void 0; }); const primitive = values.every((x) => x !== void 0 && (typeof x !== "object" || x === null)); if (primitive && new Set(values).size === values.length) return { key, values }; } return void 0; } walkUnevaluated(node, b, v, path, out) { const { unevaluatedProperties: unevaluatedProperties2, unevaluatedItems: unevaluatedItems2, ...rest } = node; const a = newAnnotations(); this.c.annotating(rest, b)(v, a); if (isObject(v) && unevaluatedProperties2 !== void 0 && !a.allProps) { for (const k of Object.keys(v)) { if (a.props?.has(k)) continue; if (unevaluatedProperties2 === false) { out.push({ path: pointer(path, k), keyword: "unevaluatedProperties", message: "unknown field" }); } else this.walk(unevaluatedProperties2, b, v[k], pointer(path, k), out); } } if (Array.isArray(v) && unevaluatedItems2 !== void 0 && !a.allItems) { for (let i = a.items; i < v.length; i++) { if (a.indexes?.has(i)) continue; if (unevaluatedItems2 === false) { out.push({ path: `${path}/${i}`, keyword: "unevaluatedItems", message: "unexpected item" }); } else this.walk(unevaluatedItems2, b, v[i], `${path}/${i}`, out); } } } }; var LISTED_VALUES = 20; function listValues(values) { if (values.length <= LISTED_VALUES) return JSON.stringify(values); const listed = JSON.stringify(values.slice(0, LISTED_VALUES)); return `${listed.slice(0, -1)}, ... and ${values.length - LISTED_VALUES} more]`; } function containsMessage(min, max) { if (max === Number.POSITIVE_INFINITY) return `expected at least ${min} matching ${min === 1 ? "item" : "items"}`; if (min === 0) return `expected at most ${max} matching ${max === 1 ? "item" : "items"}`; return `expected between ${min} and ${max} matching items`; } function pointer(path, key) { return `${path}/${String(key).replace(/~/g, "~0").replace(/\//g, "~1")}`; } function typeName(v) { if (v === null) return "null"; if (Array.isArray(v)) return "array"; if (v instanceof Uint8Array) return "bytes"; if (typeof v === "number" && Number.isInteger(v)) return "integer"; return typeof v; } // node_modules/@earendil-works/pi-schema/dist/validator.js function compile(schema, options = NO_OPTIONS) { const check2 = compileCheck(schema, options); return options.coerce === true ? new CoercingValidator(schema, options, check2) : new Validator(schema, options, check2); } var NO_OPTIONS = Object.freeze({}); var Validator = class { /** Whether `value` is valid. A plain function: it may be passed around without its validator. */ check; schema; options; explainer = void 0; constructor(schema, options, check2) { this.check = check2; this.schema = schema; this.options = options; } /** * Why `value` is invalid: one error per failing keyword, `[]` when it is valid. Built on the first failure and * kept, so a validator whose values always pass never pays for it. */ errors(value2) { if (this.check(value2)) return []; this.explainer ??= new Explainer(compiler(this.schema, this.options), this.schema); return this.explainer.errors(value2); } }; var CoercingValidator = class extends Validator { coercer = void 0; /** * `value` coerced towards the schema: numeric strings to numbers, `"true"` to `true`, a single value to a one-item * array, an optional `null` dropped, the first union branch the value converts to (the README lists every rule). * Only the changed paths are copied; `value` comes back itself when nothing changes, and is never modified. Values * that cannot be coerced stay as they are. The rules are built on the first call. */ coerce(value2) { this.coercer ??= new Coercer(compiler(this.schema, this.options), this.schema); return this.coercer.coerce(value2); } /** Coerces `value`, then checks it: the coerced value when it is valid, else its errors. */ parse(value2) { const coerced = this.coerce(value2); return this.check(coerced) ? { ok: true, value: coerced } : { ok: false, errors: this.errors(coerced) }; } }; function compileCheck(schema, options) { return compiler(schema, options).root(); } function compiler(schema, options) { const plain = new Compiler(schema, options, false); try { plain.root(); return plain; } catch (error) { if (error !== RESTART) throw error; return new Compiler(schema, options, true); } } // node_modules/@earendil-works/chord/dist/errors.js var CHORD_ERROR_CODES = [ "aborted", "contract_mismatch", "forbidden", "gone", "invalid", "not_in_variant", "not_ready", "released", "superseded", "unavailable", "upgrade_conflict", "upgrade_required" ]; var RETRYABLE = /* @__PURE__ */ new Set(["superseded", "upgrade_conflict"]); var ChordError = class extends Error { code; reason; retryable; details; constructor(code, message, options) { super(`${code}${options?.reason ? `/${options.reason}` : ""}: ${message}`, { cause: options?.cause }); this.name = "ChordError"; this.code = code; this.reason = options?.reason; this.retryable = RETRYABLE.has(code); this.details = options?.details; } }; var isChordError = (error, code) => error instanceof ChordError && (code === void 0 || error.code === code); var forbidden = (message = "forbidden") => new ChordError("forbidden", message); var invalid = (reason, message, details) => new ChordError("invalid", message, { reason, details }); var asChordError = (error, context) => error instanceof ChordError ? error : new ChordError("unavailable", `${context}: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); async function runAll(steps, message) { const errors = []; for (const step of steps) { try { await step(); } catch (error) { errors.push(error); } } if (errors.length === 1) throw errors[0]; if (errors.length > 1) throw new AggregateError(errors, message); } // node_modules/@earendil-works/chord/dist/schema.js var asSchema = (schema) => schema !== null && typeof schema === "object" ? schema : schema === false ? { not: {} } : {}; function canonicalJson(value2) { if (value2 === null || typeof value2 !== "object") return JSON.stringify(value2) ?? "null"; if (Array.isArray(value2)) return `[${value2.map((item) => canonicalJson(item)).join(",")}]`; const keys = Object.keys(value2).filter((key) => value2[key] !== void 0).sort(); return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(value2[key])}`).join(",")}}`; } function hash64(text) { let a = 2166136261; let b = 16777619 ^ 1540483477; for (let index = 0; index < text.length; index++) { const code = text.charCodeAt(index); a = Math.imul(a ^ code, 16777619); b = Math.imul(b ^ code, 1540483477) ^ b >>> 13; } return (a >>> 0).toString(16).padStart(8, "0") + (b >>> 0).toString(16).padStart(8, "0"); } function deepEqual2(a, b) { return a === b || canonicalJson(a) === canonicalJson(b); } var validators = /* @__PURE__ */ new WeakMap(); function validatorOf(schema) { if (typeof schema === "boolean") return compile(schema); let validator = validators.get(schema); if (validator === void 0) { validator = compile(schema); validators.set(schema, validator); } return validator; } function isValid(schema, value2) { return validatorOf(schema).check(value2); } function assertValid(reason, what, schema, value2) { const validator = validatorOf(schema); if (validator.check(value2)) return; const errors = validator.errors(value2).slice(0, 3); const summary = errors.map((error) => `${error.path || "/"} ${error.message}`).join("; "); throw invalid(reason, `${what}: ${summary}`, errors); } function normalize(schema, value2, what) { const normalized = cleanFrom(asSchema(schema), withDefaults(asSchema(schema), cloneData(value2 ?? {}))); assertValid("input", what, schema, normalized); return normalized; } function cleaned(schema, value2) { return cleanFrom(asSchema(schema), cloneData(value2)); } function cloneData(value2) { if (Array.isArray(value2)) return value2.map(cloneData); if (value2 instanceof Uint8Array) return value2.slice(); if (value2 === null || typeof value2 !== "object") return value2; const prototype = Object.getPrototypeOf(value2); if (prototype !== Object.prototype && prototype !== null) return value2; const copy2 = {}; for (const [key, item] of Object.entries(value2)) copy2[key] = cloneData(item); return copy2; } var isPlainObject2 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2) && !(value2 instanceof Uint8Array); var isSchemaObject2 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2); function mergedAllOf(schema) { if (!Array.isArray(schema.allOf) || !schema.allOf.every((branch) => isSchemaObject2(branch.properties))) return void 0; const properties = {}; const required = /* @__PURE__ */ new Set(); for (const branch of schema.allOf) { Object.assign(properties, branch.properties); for (const key of branch.required ?? []) required.add(key); } return { type: "object", properties, required: [...required], ...schema.unevaluatedProperties === void 0 ? {} : { additionalProperties: schema.unevaluatedProperties } }; } function recordOf(schema) { if (!isSchemaObject2(schema.patternProperties) || isSchemaObject2(schema.properties)) return void 0; const [entry2] = Object.entries(schema.patternProperties); return entry2 === void 0 ? void 0 : [new RegExp(entry2[0], "u"), entry2[1]]; } var keepsUnknown = (additional, value2) => additional === true || isSchemaObject2(additional) && isValid(additional, value2); function cleanFrom(schema, value2) { const merged = mergedAllOf(schema); if (merged !== void 0) return cleanFrom(merged, value2); if (Array.isArray(schema.anyOf)) { for (const branch of schema.anyOf) { const candidate = cleanFrom(asSchema(branch), cloneData(value2)); if (isValid(asSchema(branch), candidate)) return candidate; } return value2; } if (Array.isArray(value2)) { const prefix = Array.isArray(schema.items) ? schema.items : Array.isArray(schema.prefixItems) ? schema.prefixItems : void 0; if (prefix !== void 0) { const length = Math.min(value2.length, prefix.length); for (let index = 0; index < length; index++) value2[index] = cleanFrom(asSchema(prefix[index]), value2[index]); return value2.length > length ? value2.slice(0, length) : value2; } if (isSchemaObject2(schema.items)) return value2.map((item) => cleanFrom(schema.items, item)); return value2; } if (!isPlainObject2(value2)) return value2; const record2 = recordOf(schema); const properties = isSchemaObject2(schema.properties) ? schema.properties : void 0; if (properties === void 0 && record2 === void 0 && !isSchemaObject2(schema.additionalProperties)) return value2; for (const key of Object.keys(value2)) { const known = properties !== void 0 && Object.hasOwn(properties, key) ? properties[key] : void 0; const matched = known ?? (record2?.[0].test(key) ? record2[1] : void 0); if (matched !== void 0) value2[key] = cleanFrom(asSchema(matched), value2[key]); else if (keepsUnknown(schema.additionalProperties, value2[key])) value2[key] = cleanFrom(asSchema(schema.additionalProperties), value2[key]); else delete value2[key]; } return value2; } function withDefaults(schema, value2) { const defaulted = value2 === void 0 && "default" in schema ? cloneData(schema.default) : value2; const merged = mergedAllOf(schema); if (merged !== void 0) return withDefaults(merged, defaulted); if (Array.isArray(schema.anyOf)) { for (const branch of schema.anyOf) { const candidate = withDefaults(asSchema(branch), cloneData(defaulted)); if (isValid(asSchema(branch), candidate)) return candidate; } return defaulted; } if (Array.isArray(defaulted)) { const prefix = Array.isArray(schema.items) ? schema.items : Array.isArray(schema.prefixItems) ? schema.prefixItems : void 0; if (prefix !== void 0) { for (let index = 0; index < prefix.length; index++) { const item = withDefaults(asSchema(prefix[index]), defaulted[index]); if (index < defaulted.length || item !== void 0) defaulted[index] = item; } return defaulted; } if (isSchemaObject2(schema.items)) return defaulted.map((item) => withDefaults(schema.items, item)); return defaulted; } if (!isPlainObject2(defaulted)) return defaulted; const properties = isSchemaObject2(schema.properties) ? schema.properties : void 0; const record2 = recordOf(schema); if (properties !== void 0) { const required = schema.required ?? []; for (const [key, property] of Object.entries(properties)) { const item = withDefaults(asSchema(property), defaulted[key]); if (item === void 0 && (!required.includes(key) || !("default" in asSchema(property)))) continue; defaulted[key] = item; } } else if (record2 !== void 0 && isSchemaObject2(record2[1]) && "default" in record2[1]) { for (const key of Object.keys(defaulted)) { if (record2[0].test(key)) defaulted[key] = withDefaults(record2[1], defaulted[key]); } } if (!isSchemaObject2(schema.additionalProperties)) return defaulted; for (const key of Object.keys(defaulted)) { const known = properties !== void 0 && Object.hasOwn(properties, key); if (!known && !record2?.[0].test(key)) defaulted[key] = withDefaults(schema.additionalProperties, defaulted[key]); } return defaulted; } function canonicalIdentity(schema, value2) { return canonicalJson(stripDefaults(asSchema(schema), value2)); } function stripDefaults(schema, value2) { if (Array.isArray(value2)) { const items = schema.items; if (Array.isArray(items)) return value2.map((item, index) => stripDefaults(asSchema(items[index]), item)); return value2.map((item) => stripDefaults(asSchema(items), item)); } if (value2 === null || typeof value2 !== "object") return value2; const properties = asSchema(schema.properties); const result = {}; for (const [key, item] of Object.entries(value2)) { const property = asSchema(properties[key]); if ("default" in property && deepEqual2(property.default, item)) continue; result[key] = stripDefaults(property, item); } return result; } var CONSTRAINTS = [ "minLength", "maxLength", "pattern", "format", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf", "minItems", "maxItems", "uniqueItems", "minProperties", "maxProperties" ]; var typesOf2 = (schema) => typeof schema.type === "string" ? [schema.type] : Array.isArray(schema.type) ? schema.type : void 0; var KNOWN = /* @__PURE__ */ new Set([ ...CONSTRAINTS, "type", "properties", "required", "additionalProperties", "patternProperties", "items", "additionalItems", "const", "enum", "anyOf", "parameters", "returnType" ]); var ANNOTATIONS = /* @__PURE__ */ new Set([ "title", "description", "default", "examples", "$id", "$schema", "$comment", "readOnly", "writeOnly", "deprecated" ]); var withoutAnnotations = (schema) => Object.fromEntries(Object.entries(schema).filter(([key]) => !ANNOTATIONS.has(key))); function fits(subSchema, supSchema) { const sub = withoutAnnotations(asSchema(subSchema)); const sup = withoutAnnotations(asSchema(supSchema)); if (Object.keys(sup).length === 0) return true; if (deepEqual2(sub, sup)) return true; if (Object.keys(sup).some((key) => !KNOWN.has(key))) return false; if (sup.additionalProperties !== void 0 && typeof sup.additionalProperties !== "boolean") return false; if (Array.isArray(sub.anyOf)) return sub.anyOf.every((branch) => fits(branch, sup)); if (Array.isArray(sup.anyOf)) return sup.anyOf.some((branch) => fits(sub, branch)); if ("const" in sup) return "const" in sub && deepEqual2(sub.const, sup.const); if (Array.isArray(sup.enum)) { const values = "const" in sub ? [sub.const] : Array.isArray(sub.enum) ? sub.enum : void 0; return values?.every((item) => sup.enum.some((allowed) => deepEqual2(allowed, item))) ?? false; } for (const key of CONSTRAINTS) if (key in sup && !deepEqual2(sub[key], sup[key])) return false; const supTypes = typesOf2(sup); const subTypes = typesOf2(sub); if (supTypes === void 0 || subTypes === void 0) return false; if (!subTypes.every((type) => supTypes.includes(type) || type === "integer" && supTypes.includes("number"))) { return false; } if ("const" in sub || Array.isArray(sub.enum)) return true; if (subTypes.includes("object") && !objectFits(sub, sup)) return false; if (subTypes.includes("array") && !arrayFits(sub, sup)) return false; if (subTypes.includes("function")) return functionFits(sub, sup); return true; } function objectFits(sub, sup) { const subProperties = asSchema(sub.properties); const supProperties = asSchema(sup.properties); const subRequired = new Set(sub.required ?? []); for (const key of sup.required ?? []) if (!subRequired.has(key)) return false; for (const [key, property] of Object.entries(supProperties)) { if (key in subProperties) { if (!fits(subProperties[key], property)) return false; } else if (typeof sub.additionalProperties === "object" && !fits(sub.additionalProperties, property)) { return false; } } if (sup.additionalProperties === false) { if (sub.additionalProperties !== false) return false; if (Object.keys(subProperties).some((key) => !(key in supProperties))) return false; } const supPatterns = asSchema(sup.patternProperties); if (Object.keys(supPatterns).length > 0) { const subPatterns = asSchema(sub.patternProperties); if (sub.additionalProperties !== false && Object.keys(subPatterns).length === 0) return false; for (const [pattern, property] of Object.entries(supPatterns)) { const matcher = new RegExp(pattern, "u"); for (const [key, subProperty] of Object.entries(subProperties)) { if (!(key in supProperties) && matcher.test(key) && !fits(subProperty, property)) return false; } if (Object.keys(subPatterns).length > 0 && (!(pattern in subPatterns) || !fits(subPatterns[pattern], property))) { return false; } } for (const pattern of Object.keys(subPatterns)) if (!(pattern in supPatterns)) return false; } return true; } function arrayFits(sub, sup) { if (Array.isArray(sup.items)) { if (!Array.isArray(sub.items) || sub.items.length !== sup.items.length) return false; return sub.items.every((item, index) => fits(item, sup.items[index])); } if (Array.isArray(sub.items)) return sub.items.every((item) => fits(item, sup.items)); return fits(sub.items, sup.items); } function functionFits(sub, sup) { const subParameters = sub.parameters ?? []; const supParameters = sup.parameters ?? []; if (subParameters.length > supParameters.length) return false; if (!subParameters.every((parameter, index) => fits(supParameters[index], parameter))) return false; return fits(sub.returnType, sup.returnType); } // node_modules/@earendil-works/chord/dist/contract.js var MEMBER = /* @__PURE__ */ Symbol("chord.member"); function method(args, result) { return { [MEMBER]: "method", args, result }; } var opaque = (id) => { if (!/^[^@\s]+@\d+$/.test(id)) throw new TypeError(`opaque id must be name@version: ${id}`); return { [MEMBER]: "opaque", id }; }; function hostNamed(name) { if (typeof name !== "string" || name === "") throw new TypeError("hostNamed needs a host name"); return { name }; } function hostFamily(prefix, key) { if (typeof prefix !== "string" || prefix === "") throw new TypeError("hostFamily needs a host-name prefix"); if (typeof key !== "string" || key === "") throw new TypeError("hostFamily needs an input field name"); return { prefix, key }; } function resolveHost(typeName2, placement, input) { if ("name" in placement) return placement.name; const value2 = input !== null && typeof input === "object" ? input[placement.key] : void 0; if (typeof value2 !== "string" || value2 === "") throw invalid("placement", `${typeName2}: input field ${placement.key} names no host`); return placement.prefix + value2; } var placedOn = (placement, hostName) => "name" in placement ? placement.name === hostName : hostName.startsWith(placement.prefix) && hostName.length > placement.prefix.length; var placementText = (placement) => "name" in placement ? placement.name : `${placement.prefix}<${placement.key}>`; var SchemaObject = obj({}, { open: true }); var MemberDescriptionSchema = union([ // Absent `args`: the method takes none. Absent `result`: it returns nothing. obj({ kind: lit("method"), args: opt(SchemaObject), result: opt(SchemaObject) }, { open: true }), obj({ kind: lit("state"), schema: SchemaObject }, { open: true }), obj({ kind: lit("opaque"), id: str() }, { open: true }) ]); var LOCAL_VARIANT = ""; var PlacementSchema = union([ obj({ name: str({ minLength: 1 }) }), obj({ prefix: str({ minLength: 1 }), key: str({ minLength: 1 }) }) ]); var DescriptionSchema = obj({ name: str({ minLength: 1 }), scope: union([lit("remote"), lit("local")]), version: int(), input: SchemaObject, /** Remote types only (required there): where their objects live. */ host: opt(PlacementSchema), variants: map(map(MemberDescriptionSchema)) }, { open: true }); var RESERVED_MEMBERS = /* @__PURE__ */ new Set([ "variant", "status", "ready", "shape", "error", "onShape", "onStatus", "release", "then", "contract" ]); function describeMember(name, member, scope) { if (RESERVED_MEMBERS.has(name)) throw new TypeError(`member name is reserved: ${name}`); const brand = member[MEMBER]; if (brand === "method") { const spec = member; return { kind: "method", ...spec.args === void 0 ? {} : { args: spec.args }, ...spec.result === void 0 ? {} : { result: spec.result } }; } if (brand === "opaque") { if (scope === "remote") throw new TypeError(`remote contracts cannot expose opaque members: ${name}`); return { kind: "opaque", id: member.id }; } return { kind: "state", schema: member }; } function finish(contract) { return { ...contract, fingerprint: hash64(canonicalJson(contract.description)) }; } function checkHeader(name, version) { if (!name) throw new TypeError("contract name is empty"); if (!Number.isInteger(version) || version < 1) throw new TypeError(`contract version must be a positive integer`); } function remote(name, def) { checkHeader(name, def.version); const placement = def.host; if (placement === void 0 || !isValid(PlacementSchema, placement)) throw new TypeError(`remote contract ${name} needs \`host\`: hostNamed(\u2026) or hostFamily(\u2026)`); const host2 = "name" in placement ? { name: placement.name } : { prefix: placement.prefix, key: placement.key }; const variants = {}; if (Object.keys(def.variants).length === 0) throw new TypeError(`remote contract ${name} has no variants`); for (const [kind, variant] of Object.entries(def.variants)) { const members = {}; for (const [key, schema] of Object.entries(variant.state ?? {})) members[key] = describeMember(key, schema, "remote"); for (const [key, spec] of Object.entries(variant.methods ?? {})) { if (key in members) throw new TypeError(`member ${key} is both state and method in ${name}.${kind}`); members[key] = describeMember(key, spec, "remote"); if (members[key].kind !== "method") throw new TypeError(`methods.${key} is not a method()`); } variants[kind] = members; } const description = { name, scope: "remote", version: def.version, input: def.input, host: host2, variants }; return finish({ scope: "remote", name, version: def.version, input: def.input, def, description }); } function local(name, def) { checkHeader(name, def.version); const members = {}; for (const [key, member] of Object.entries(def.members ?? {})) members[key] = describeMember(key, member, "local"); const description = { name, scope: "local", version: def.version, input: def.input, variants: { [LOCAL_VARIANT]: members } }; return finish({ scope: "local", name, version: def.version, input: def.input, def, description }); } function contractFromDescription(description) { const { name, scope, version, input, host: host2 } = description; if (typeof name !== "string" || scope !== "remote" && scope !== "local" || !Number.isInteger(version) || scope === "remote" !== (host2 !== void 0)) { throw new ChordError("invalid", "malformed contract token", { reason: "token" }); } const base = { name, version, input, description, fingerprint: hash64(canonicalJson(description)) }; return scope === "remote" ? { ...base, scope, def: { version, input, host: host2, variants: {} } } : { ...base, scope, def: { version, input } }; } function coverage(provider2, consumer) { if (provider2.name !== consumer.name || provider2.scope !== consumer.scope) return "different type"; if (!samePlacement(provider2, consumer)) return "host"; if (!fits(consumer.input, provider2.input)) return "input"; for (const [kind, consumerMembers] of Object.entries(consumer.variants)) { const providerMembers = provider2.variants[kind]; if (providerMembers === void 0) return `variant ${kind} missing`; for (const [name, wanted] of Object.entries(consumerMembers)) { const offered = providerMembers[name]; if (offered === void 0 || offered.kind !== wanted.kind) return `${kind}.${name} missing or of another kind`; if (offered.kind === "method" && wanted.kind === "method") { if (wanted.args === void 0 !== (offered.args === void 0)) return `${kind}.${name} arguments`; if (wanted.args !== void 0 && offered.args !== void 0 && !fits(wanted.args, offered.args)) return `${kind}.${name} arguments`; if (wanted.result !== void 0 && (offered.result === void 0 || !fits(offered.result, wanted.result))) return `${kind}.${name} result`; } else if (offered.kind === "state" && wanted.kind === "state") { if (!fits(offered.schema, wanted.schema)) return `${kind}.${name} state`; } else if (offered.kind === "opaque" && wanted.kind === "opaque" && offered.id !== wanted.id) { return `${kind}.${name} opaque ${offered.id} != ${wanted.id}`; } } } return void 0; } var samePlacement = (a, b) => canonicalJson(a.host ?? null) === canonicalJson(b.host ?? null); var placementOf = (description) => description.host === void 0 ? "(local)" : placementText(description.host); function bind(provider2, accepts, consumer) { if (provider2.fingerprint === consumer.fingerprint) return void 0; if (!samePlacement(provider2.description, consumer.description)) { throw new ChordError("contract_mismatch", `${provider2.name} lives on ${placementOf(provider2.description)}, the consumer expects ${placementOf(consumer.description)}`); } if (provider2.version === consumer.version) { const reason = coverage(provider2.description, consumer.description); if (reason === void 0) return void 0; throw new ChordError("contract_mismatch", `${provider2.name}@${provider2.version} does not cover the consumer's description (${reason}); forgotten version bump?`); } const adapter = accepts?.[consumer.version]; if (adapter === void 0) { throw new ChordError("contract_mismatch", `${provider2.name}: provider version ${provider2.version}, consumer version ${consumer.version}, no accepts adapter`); } return adapter; } function takesNoArgs(contract, name) { for (const members of Object.values(contract.description.variants)) { const member = members[name]; if (member?.kind === "method") return member.args === void 0; } return false; } // node_modules/@earendil-works/chord/dist/delta/diff.js var DEFAULT_OVERLAP_SCAN = 65536; var MAX_DELTA_OPERATIONS = 4096; var overflowedBatches = /* @__PURE__ */ new WeakSet(); var emitOperation = (operations, operation) => { if (overflowedBatches.has(operations)) return; if (operations.length >= MAX_DELTA_OPERATIONS) { overflowedBatches.add(operations); return; } operations.push(operation); }; var isContainer = (value2) => value2 !== null && typeof value2 === "object"; var emitSet = (path, value2, operations) => { if (path.length === 0) emitOperation(operations, ["r", value2]); else emitOperation(operations, ["s", path, value2]); }; var equalJson = (left, right) => { if (left === right) return true; if (!isContainer(left) || !isContainer(right) || Array.isArray(left) !== Array.isArray(right)) return false; if (Array.isArray(left)) { const other2 = right; if (left.length !== other2.length) return false; for (let index = 0; index < left.length; index++) { if (!equalJson(left[index], other2[index])) return false; } return true; } const other = right; const keys = Object.keys(left); if (keys.length !== Object.keys(other).length) return false; for (const key of keys) { if (!Object.hasOwn(other, key) || !equalJson(left[key], other[key])) return false; } return true; }; var permutation = (before, after) => { if (before.length !== after.length) return void 0; const positions = /* @__PURE__ */ new Map(); for (let index = 0; index < before.length; index++) { const value2 = before[index]; const entry2 = positions.get(value2); if (entry2 === void 0) positions.set(value2, { indices: [index], used: 0 }); else entry2.indices.push(index); } const result = new Array(after.length); for (let index = 0; index < after.length; index++) { const entry2 = positions.get(after[index]); if (entry2 === void 0 || entry2.used === entry2.indices.length) return void 0; result[index] = entry2.indices[entry2.used++]; } return result; }; var emitString = (before, after, path, operations) => { if (before === after) return; if (after.length > before.length && after.slice(0, before.length) === before) { emitOperation(operations, ["a", path, ownedSlice2(after, before.length)]); return; } const shared = overlap(before, after, DEFAULT_OVERLAP_SCAN); if (shared === 0) { emitOperation(operations, ["s", path, after]); return; } emitOperation(operations, ["t", path, before.length - shared]); if (after.length > shared) emitOperation(operations, ["a", path, ownedSlice2(after, shared)]); }; var MAX_IDENTITY_CANDIDATES = 2e5; var MAX_SEMANTIC_CELLS = 65536; var sameValue = (left, right) => left === right || equalJson(left, right); var lcsMatches = (before, after, equal, maxCells) => { if (before.length === 0 || after.length === 0) return []; if (before.length * after.length > maxCells) return void 0; const width = after.length + 1; const lengths = new Uint32Array((before.length + 1) * width); for (let left2 = before.length - 1; left2 >= 0; left2--) { for (let right2 = after.length - 1; right2 >= 0; right2--) { const at = left2 * width + right2; lengths[at] = equal(before[left2], after[right2]) ? lengths[(left2 + 1) * width + right2 + 1] + 1 : Math.max(lengths[(left2 + 1) * width + right2], lengths[left2 * width + right2 + 1]); } } const matches = []; let left = 0; let right = 0; while (left < before.length && right < after.length) { if (equal(before[left], after[right]) && lengths[left * width + right] === lengths[(left + 1) * width + right + 1] + 1) { matches.push([left++, right++]); } else if (lengths[(left + 1) * width + right] >= lengths[left * width + right + 1]) left += 1; else right += 1; } return matches; }; var semanticallyAligned = (left, right) => { if (sameValue(left, right)) return true; if (!isContainer(left) || !isContainer(right) || Array.isArray(left) !== Array.isArray(right)) return false; if (Array.isArray(left) && Array.isArray(right)) { if (left.length !== right.length) return false; return left.some((value2, index) => isContainer(value2) && value2 === right[index]); } const leftObject = left; const rightObject = right; for (const key of Object.keys(leftObject)) { const value2 = leftObject[key]; if (isContainer(value2) && Object.hasOwn(rightObject, key) && value2 === rightObject[key]) return true; } return false; }; var lowerBound = (values, value2) => { let low = 0; let high = values.length; while (low < high) { const middle = low + high >>> 1; if (values[middle] < value2) low = middle + 1; else high = middle; } return low; }; var identitySubsequence = (before, beforeStart, beforeEnd, after, afterStart, afterEnd) => { const beforeCount = beforeEnd - beforeStart; const afterCount = afterEnd - afterStart; const matches = []; if (afterCount < beforeCount) { let beforeIndex = beforeStart; for (let afterIndex = afterStart; afterIndex < afterEnd; afterIndex++) { while (beforeIndex < beforeEnd && before[beforeIndex] !== after[afterIndex]) beforeIndex += 1; if (beforeIndex === beforeEnd) return void 0; matches.push([beforeIndex++, afterIndex]); } return matches; } if (beforeCount < afterCount) { let afterIndex = afterStart; for (let beforeIndex = beforeStart; beforeIndex < beforeEnd; beforeIndex++) { while (afterIndex < afterEnd && before[beforeIndex] !== after[afterIndex]) afterIndex += 1; if (afterIndex === afterEnd) return void 0; matches.push([beforeIndex, afterIndex++]); } return matches; } return void 0; }; var greedyIdentityAnchors = (positions, after, afterStart, afterEnd) => { const matches = []; let previous = -1; for (let afterIndex = afterStart; afterIndex < afterEnd; afterIndex++) { const candidates = positions.get(after[afterIndex]); if (candidates === void 0) continue; const at = lowerBound(candidates, previous + 1); const beforeIndex = candidates[at]; if (beforeIndex === void 0) continue; matches.push([beforeIndex, afterIndex]); previous = beforeIndex; } return matches; }; var identityAnchors = (before, beforeStart, beforeEnd, after, afterStart, afterEnd) => { const positions = /* @__PURE__ */ new Map(); for (let index = beforeStart; index < beforeEnd; index++) { const value2 = before[index]; const existing = positions.get(value2); if (existing === void 0) positions.set(value2, [index]); else existing.push(index); } let candidateCount = 0; for (let index = afterStart; index < afterEnd; index++) { candidateCount += positions.get(after[index])?.length ?? 0; if (candidateCount > MAX_IDENTITY_CANDIDATES) { return greedyIdentityAnchors(positions, after, afterStart, afterEnd); } } if (candidateCount === 0) return []; const candidates = []; const tails = []; const tailValues = []; for (let afterIndex = afterStart; afterIndex < afterEnd; afterIndex++) { const beforePositions = positions.get(after[afterIndex]); if (beforePositions === void 0) continue; for (let index = beforePositions.length - 1; index >= 0; index--) { const beforeIndex = beforePositions[index]; const at = lowerBound(tailValues, beforeIndex); const candidateIndex2 = candidates.length; candidates.push({ before: beforeIndex, after: afterIndex, previous: at === 0 ? -1 : tails[at - 1] }); tails[at] = candidateIndex2; tailValues[at] = beforeIndex; } } const matches = []; let candidateIndex = tails[tails.length - 1] ?? -1; while (candidateIndex >= 0) { const candidate = candidates[candidateIndex]; matches.push([candidate.before, candidate.after]); candidateIndex = candidate.previous; } matches.reverse(); return matches; }; var processArrayMatches = (before, after, path, operations, beforeStart, beforeEnd, afterStart, afterEnd, outputStart, matches) => { let beforeAt = beforeStart; let afterAt = afterStart; let outputAt = outputStart; for (const [beforeMatch, afterMatch] of matches) { if (overflowedBatches.has(operations)) return; diffArrayRegion(before, after, path, operations, beforeAt, beforeMatch, afterAt, afterMatch, outputAt); outputAt += afterMatch - afterAt; if (!sameValue(before[beforeMatch], after[afterMatch])) { diffValue(before[beforeMatch], after[afterMatch], [...path, outputAt], operations); } outputAt += 1; beforeAt = beforeMatch + 1; afterAt = afterMatch + 1; } if (!overflowedBatches.has(operations)) { diffArrayRegion(before, after, path, operations, beforeAt, beforeEnd, afterAt, afterEnd, outputAt); } }; function diffArrayRegion(before, after, path, operations, beforeStart, beforeEnd, afterStart, afterEnd, outputStart) { if (overflowedBatches.has(operations)) return; while (beforeStart < beforeEnd && afterStart < afterEnd && sameValue(before[beforeStart], after[afterStart])) { beforeStart += 1; afterStart += 1; outputStart += 1; } while (beforeStart < beforeEnd && afterStart < afterEnd && sameValue(before[beforeEnd - 1], after[afterEnd - 1])) { beforeEnd -= 1; afterEnd -= 1; } const beforeCount = beforeEnd - beforeStart; const afterCount = afterEnd - afterStart; if (beforeCount === 0 && afterCount === 0) return; if (beforeCount === 0 || afterCount === 0) { emitOperation(operations, ["p", path, outputStart, beforeCount, after.slice(afterStart, afterEnd)]); return; } if (beforeCount === afterCount) { const positional = []; for (let offset = 0; offset < beforeCount; offset++) { if (sameValue(before[beforeStart + offset], after[afterStart + offset])) { positional.push([beforeStart + offset, afterStart + offset]); } } if (positional.length > 0) { processArrayMatches(before, after, path, operations, beforeStart, beforeEnd, afterStart, afterEnd, outputStart, positional); return; } } const subsequence = identitySubsequence(before, beforeStart, beforeEnd, after, afterStart, afterEnd); if (subsequence !== void 0 && subsequence.length > 0) { processArrayMatches(before, after, path, operations, beforeStart, beforeEnd, afterStart, afterEnd, outputStart, subsequence); return; } const identity = identityAnchors(before, beforeStart, beforeEnd, after, afterStart, afterEnd); if (identity.length > 0) { processArrayMatches(before, after, path, operations, beforeStart, beforeEnd, afterStart, afterEnd, outputStart, identity); return; } const semantic = lcsMatches(before.slice(beforeStart, beforeEnd), after.slice(afterStart, afterEnd), semanticallyAligned, MAX_SEMANTIC_CELLS); if (semantic !== void 0 && semantic.length > 0) { const absolute = semantic.map(([beforeIndex, afterIndex]) => [beforeStart + beforeIndex, afterStart + afterIndex]); processArrayMatches(before, after, path, operations, beforeStart, beforeEnd, afterStart, afterEnd, outputStart, absolute); return; } if (beforeCount === 1 && afterCount === 1) { diffValue(before[beforeStart], after[afterStart], [...path, outputStart], operations); return; } emitOperation(operations, ["p", path, outputStart, beforeCount, after.slice(afterStart, afterEnd)]); } var diffArray = (before, after, path, operations) => { if (before === after || equalJson(before, after)) return; if (before.length === after.length && before.length > 1 && !sameValue(before[0], after[0]) && !sameValue(before[before.length - 1], after[after.length - 1])) { const order = permutation(before, after); if (order !== void 0) { emitOperation(operations, ["m", path, order]); return; } } diffArrayRegion(before, after, path, operations, 0, before.length, 0, after.length, 0); }; var diffObject = (before, after, path, operations) => { const beforeKeys = Object.keys(before); const afterKeys = Object.keys(after); if ([...beforeKeys, ...afterKeys].some((key) => RESERVED_SEGMENTS.has(key))) { if (!equalJson(before, after)) emitSet(path, after, operations); return; } for (const key of afterKeys) { if (overflowedBatches.has(operations)) return; if (Object.hasOwn(before, key)) diffValue(before[key], after[key], [...path, key], operations); else emitSet([...path, key], after[key], operations); } for (const key of beforeKeys) { if (overflowedBatches.has(operations)) return; if (!Object.hasOwn(after, key)) emitOperation(operations, ["d", [...path, key]]); } }; var diffValue = (before, after, path, operations) => { if (before === after || overflowedBatches.has(operations)) return; if (typeof before === "string" && typeof after === "string" && path.length > 0) { emitString(before, after, path, operations); return; } if (Array.isArray(before) && Array.isArray(after)) { diffArray(before, after, path, operations); return; } if (isContainer(before) && isContainer(after) && !Array.isArray(before) && !Array.isArray(after)) { diffObject(before, after, path, operations); return; } emitSet(path, after, operations); }; var jsonCost = (value2) => { if (value2 === null) return 4; if (typeof value2 === "string") return value2.length + 2; if (typeof value2 === "number") return String(value2).length; if (typeof value2 === "boolean") return value2 ? 4 : 5; if (Array.isArray(value2)) { let cost2 = 2; for (let index2 = 0; index2 < value2.length; index2++) cost2 += jsonCost(value2[index2]) + (index2 === 0 ? 0 : 1); return cost2; } let cost = 2; let index = 0; for (const key of Object.keys(value2)) { cost += key.length + 3 + jsonCost(value2[key]) + (index++ === 0 ? 0 : 1); } return cost; }; var pathCost = (path) => { let cost = 2; for (let index = 0; index < path.length; index++) { const segment = path[index]; cost += (typeof segment === "string" ? segment.length + 2 : String(segment).length) + (index === 0 ? 0 : 1); } return cost; }; var operationCost = (operation) => { switch (operation[0]) { case "r": return 6 + jsonCost(operation[1]); case "s": return 7 + pathCost(operation[1]) + jsonCost(operation[2]); case "d": return 6 + pathCost(operation[1]); case "a": return 7 + pathCost(operation[1]) + operation[2].length + 2; case "t": return 7 + pathCost(operation[1]) + String(operation[2]).length; case "p": return 10 + pathCost(operation[1]) + String(operation[2]).length + String(operation[3]).length + jsonCost(operation[4]); case "m": return 7 + pathCost(operation[1]) + jsonCost(operation[2]); } }; function diffRevisions(before, after) { const operations = []; diffValue(before, after, [], operations); if (overflowedBatches.has(operations)) return [["r", after]]; if (operations.length === 0 || operations[0]?.[0] === "r") return operations; let deltaCost = 2; for (const operation of operations) deltaCost += operationCost(operation) + 1; if (deltaCost < 65536) return operations; const snapshotCost = jsonCost(after) + 6; return deltaCost >= snapshotCost ? [["r", after]] : operations; } // node_modules/@earendil-works/chord/dist/json.js var DATA_DESCRIPTOR = { value: void 0, writable: true, enumerable: true, configurable: true }; function copyJson(value2, options) { return copy(value2, void 0, options?.omitUndefinedProperties === true); } function copy(value2, ancestors, omitUndefinedProperties) { if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2; if (typeof value2 === "number") { if (Number.isFinite(value2)) return value2; throw new TypeError("Value contains a non-finite number and is not strict JSON"); } if (typeof value2 !== "object") throw new TypeError(`Value contains a non-JSON ${typeof value2}; expected strict JSON`); const active = ancestors ?? /* @__PURE__ */ new Set(); if (active.has(value2)) throw new TypeError("Value contains cycles and is not strict JSON"); active.add(value2); try { if (Array.isArray(value2)) { if (Object.getPrototypeOf(value2) !== Array.prototype || Reflect.ownKeys(value2).length !== value2.length + 1) { throw new TypeError("Value must contain strict JSON dense plain arrays"); } const result2 = new Array(value2.length); for (let index = 0; index < value2.length; index++) { const descriptor = Object.getOwnPropertyDescriptor(value2, index); if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) { throw new TypeError("Value must contain strict JSON enumerable indexed data properties"); } defineData(result2, String(index), copy(descriptor.value, active, omitUndefinedProperties)); } return result2; } const prototype = Object.getPrototypeOf(value2); if (prototype !== Object.prototype && prototype !== null) { throw new TypeError("Value must contain strict JSON plain objects or arrays"); } const result = Object.create(prototype); for (const key of Reflect.ownKeys(value2)) { if (typeof key === "symbol") throw new TypeError("Value contains a symbol key and is not strict JSON"); const descriptor = Object.getOwnPropertyDescriptor(value2, key); if (!descriptor.enumerable || !("value" in descriptor)) { throw new TypeError("Value must contain strict JSON enumerable data properties"); } if (descriptor.value === void 0 && omitUndefinedProperties) continue; defineData(result, key, copy(descriptor.value, active, omitUndefinedProperties)); } return result; } finally { active.delete(value2); } } function defineData(target, key, value2) { DATA_DESCRIPTOR.value = value2; Object.defineProperty(target, key, DATA_DESCRIPTOR); DATA_DESCRIPTOR.value = void 0; } function isJsonValue(value2) { return check(value2, /* @__PURE__ */ new Set()); } function check(value2, ancestors) { if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return true; if (typeof value2 === "number") return Number.isFinite(value2); if (Array.isArray(value2)) { if (Object.getPrototypeOf(value2) !== Array.prototype) return false; const keys = Reflect.ownKeys(value2); if (keys.length !== value2.length + 1 || keys.some((key) => typeof key !== "string")) return false; if (ancestors.has(value2)) return false; ancestors.add(value2); try { for (let index = 0; index < value2.length; index++) { const descriptor = Object.getOwnPropertyDescriptor(value2, String(index)); if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor) || !check(descriptor.value, ancestors)) { return false; } } return true; } finally { ancestors.delete(value2); } } if (typeof value2 !== "object" || value2 === null) return false; const prototype = Object.getPrototypeOf(value2); if (prototype !== Object.prototype && prototype !== null) return false; if (Reflect.ownKeys(value2).some((key) => typeof key !== "string")) return false; if (ancestors.has(value2)) return false; ancestors.add(value2); try { for (const descriptor of Object.values(Object.getOwnPropertyDescriptors(value2))) { if (!descriptor.enumerable || !("value" in descriptor) || !check(descriptor.value, ancestors)) { return false; } } return true; } finally { ancestors.delete(value2); } } // node_modules/@earendil-works/chord/dist/delta/apply-immutable-trusted.js var DATA_DESCRIPTOR2 = { value: void 0, writable: true, enumerable: true, configurable: true }; function applyImmutableTrusted(target, operations) { let root2 = target; const owned = /* @__PURE__ */ new WeakSet(); for (const operation of operations) { if (operation[0] === "r") { root2 = operation[1]; continue; } if (operation[0] === "p" || operation[0] === "m") { const copied2 = copyPath(root2, operation[1], operation[1].length, owned); root2 = copied2.root; if (!Array.isArray(copied2.target)) throw new TypeError("Trusted array operation target is not an array"); if (operation[0] === "p") spliceTrusted(copied2.target, operation[2], operation[3], operation[4]); else permuteTrusted(copied2.target, operation[2]); continue; } const path = operation[1]; const copied = copyPath(root2, path, path.length - 1, owned); root2 = copied.root; const key = path[path.length - 1]; switch (operation[0]) { case "s": defineData2(copied.target, key, operation[2]); break; case "d": if (Array.isArray(copied.target)) { if (typeof key !== "number") throw new TypeError("Trusted array deletion key is not numeric"); spliceTrusted(copied.target, key, 1, []); } else Reflect.deleteProperty(copied.target, key); break; case "a": { const current = read(copied.target, key); if (typeof current !== "string") throw new TypeError("Trusted append target is not a string"); defineData2(copied.target, key, `${current}${operation[2]}`); break; } case "t": { const current = read(copied.target, key); if (typeof current !== "string") throw new TypeError("Trusted truncate target is not a string"); defineData2(copied.target, key, current.slice(operation[2])); break; } } } return root2; } function copyPath(root2, path, length, owned) { if (!isContainer2(root2)) throw new TypeError("Trusted operation root is not a container"); let copiedRoot = root2; if (!owned.has(root2)) { copiedRoot = shallowCopy(root2); owned.add(copiedRoot); } let destination = copiedRoot; for (let index = 0; index < length; index++) { const segment = path[index]; const child = read(destination, segment); if (!isContainer2(child)) throw new TypeError("Trusted operation path is not a container"); if (owned.has(child)) { destination = child; continue; } const copy2 = shallowCopy(child); defineData2(destination, segment, copy2); owned.add(copy2); destination = copy2; } return { root: copiedRoot, target: destination }; } function shallowCopy(value2) { if (Array.isArray(value2)) return Array.from(value2); return Object.getPrototypeOf(value2) === null ? Object.assign(/* @__PURE__ */ Object.create(null), value2) : { ...value2 }; } function read(target, key) { return target[key]; } function defineData2(target, key, value2) { DATA_DESCRIPTOR2.value = value2; Object.defineProperty(target, key, DATA_DESCRIPTOR2); DATA_DESCRIPTOR2.value = void 0; } function spliceTrusted(target, start, remove, items) { const oldLength = target.length; const delta = items.length - remove; if (delta > 0) { target.length = oldLength + delta; target.copyWithin(start + items.length, start + remove, oldLength); } else if (delta < 0) { target.copyWithin(start + items.length, start + remove, oldLength); target.length = oldLength + delta; } for (let index = 0; index < items.length; index++) defineData2(target, start + index, items[index]); } function permuteTrusted(target, permutation2) { if (target.length !== permutation2.length) throw new TypeError("Trusted permutation length mismatch"); const previous = target.slice(); for (let index = 0; index < permutation2.length; index++) target[index] = previous[permutation2[index]]; } function isContainer2(value2) { return value2 !== null && typeof value2 === "object"; } // node_modules/@earendil-works/chord/dist/delta/tracker.js var NODE = /* @__PURE__ */ Symbol("chord.delta.overlay.node"); var PREPARED = /* @__PURE__ */ new WeakMap(); var RELEASED = {}; var ARRAY_MUTATORS = /* @__PURE__ */ new Set([ "push", "pop", "shift", "unshift", "splice", "reverse", "sort", "fill", "copyWithin" ]); var MAX_DELTA_OPERATIONS2 = 4096; var MAX_SIMPLE_OBJECT_NODES = 128; var DATA_DESCRIPTOR3 = { value: void 0, writable: true, enumerable: true, configurable: true }; var PreparedImpl = class { #context; value; base; ops; constructor(context, value2, operations) { this.#context = context; this.value = value2; this.base = context.baseValue; this.ops = operations; PREPARED.set(this, context); } get baseRevision() { return this.#context.baseRevision; } abort() { abortContext(this.#context); } }; var ChangeImpl = class { #context; #preparedStatus; #settled = false; constructor(context) { this.#context = context; } get state() { const state = this.#context?.root?.proxy; if (state === void 0) throw new TypeError("Cannot use a settled overlay"); return state; } prepare() { if (this.#settled) throw new Error("Change has already been settled"); const context = this.#context; assertWritable(context); context.status.value = "prepared"; try { if (!context.replacement) context.ops = context.dirty.length === 0 ? [] : emitOperations(context); const prepared = materializePrepared(context); this.#preparedStatus = context.status; this.#settled = true; return prepared; } catch (error) { context.status.value = "aborted"; clearContext(context); this.#settled = true; throw error; } finally { this.#context = void 0; } } abort() { if (this.#settled) { if (this.#preparedStatus?.value === "prepared") this.#preparedStatus.value = "aborted"; this.#preparedStatus = void 0; return; } this.#settled = true; abortContext(this.#context); this.#context = void 0; } }; var TrackerImpl = class { #owner = {}; #open; #competitors; #selfRef; #value; #revision = 0; #pruneBudget = 256; constructor(initial) { this.#value = initial; this.#selfRef = new WeakRef(this); } get value() { return this.#value; } get revision() { return this.#revision; } beginChange() { const context = createContext(this.#selfRef, this.#owner, this.#revision, this.#value, false, this.#value); if (this.#open !== void 0) this.#demote(this.#open); this.#open = context; return new ChangeImpl(context); } prepareReplace(value2) { const context = createContext(this.#selfRef, this.#owner, this.#revision, value2, true, this.#value); context.status.value = "prepared"; try { return materializePrepared(context); } catch (error) { context.status.value = "aborted"; clearContext(context); throw error; } } adopt(prepared) { const context = PREPARED.get(prepared); if (context?.owner !== this.#owner) throw new Error("Prepared change belongs to a different tracker"); if (context.status.value === "consumed") throw new Error("Prepared change has already been used"); if (context.status.value === "aborted") throw new Error("Prepared change has been aborted"); if (context.status.value === "stale") throw new Error("Prepared change is stale"); if (context.status.value !== "prepared") throw new Error("Prepared change is not ready"); if (context.baseRevision !== this.#revision) { context.status.value = "stale"; clearContext(context); throw new Error("Prepared change is stale"); } if (this.#value !== prepared.base) { context.status.value = "stale"; throw new Error("Prepared change is stale"); } this.#value = prepared.value; context.status.value = "consumed"; this.#revision += 1; this.#invalidate(context); } releaseContext(context) { if (this.#open === context) this.#open = void 0; else if (context.registryRef !== void 0) this.#competitors?.delete(context.registryRef); context.registryRef = void 0; } #demote(context) { const reference = new WeakRef(context); context.registryRef = reference; this.#competitors ??= /* @__PURE__ */ new Set(); this.#competitors.add(reference); this.#pruneBudget -= 1; if (this.#pruneBudget === 0) { for (const entry2 of this.#competitors) { if (entry2.deref() === void 0) this.#competitors.delete(entry2); } this.#pruneBudget = Math.max(256, this.#competitors.size); } } #invalidate(winner) { const open = this.#open; const competitors = this.#competitors; this.#open = void 0; this.#competitors = void 0; this.#pruneBudget = 256; if (open !== void 0) staleCompetitor(open, winner); if (competitors === void 0) return; for (const reference of competitors) { const context = reference.deref(); if (context !== void 0) staleCompetitor(context, winner); } } replacementBase(context) { return context.baseRevision === this.#revision ? this.#value : void 0; } }; function staleCompetitor(context, winner) { if (context === winner) return; if (context.status.value === "open" || context.status.value === "prepared") context.status.value = "stale"; context.registryRef = void 0; releaseOverlayReferences(context); } function track(initial) { return new TrackerImpl(initial); } function appendText(draft, key, text) { const node = draft[NODE]; if (node === void 0) throw new TypeError("appendText() needs a draft"); assertWritable(node.context); const name = String(key); const current = Array.isArray(nodeBase(node)) || !objectHas(node, name) ? void 0 : objectValue(node, name); if (typeof current !== "string") throw new TypeError(`appendText() needs a string at ${JSON.stringify(name)}`); if (text.length === 0) return; const owned = ownedSlice2(text, 0); const appended = node.appends?.get(name) ?? (hasObjectWrite(node, name) ? void 0 : ""); setObjectWrite(node, name, current + owned); markDirty(node); if (appended !== void 0) { node.appends ??= /* @__PURE__ */ new Map(); node.appends.set(name, appended + owned); } } function materializePrepared(context) { const operations = ensureOperations(context); const base = context.baseValue; const value2 = operations.length === 0 ? base : context.simpleObjectMaterialization ? cloneNode(context.root) : materializeOperations(base, operations); const prepared = new PreparedImpl(context, value2, operations); if (context.nodes.length > 4096) releaseOverlayReferences(context); else clearContext(context); return prepared; } function materializeOperations(base, operations) { for (const operation of operations) { if (operation[0] === "p" || operation[0] === "m") return applyImmutable(base, operations); } return applyImmutableTrusted(base, operations); } function createContext(tracker, owner, baseRevision, root2, replacement, base) { const context = { owner, tracker, baseRevision, status: { value: "open" }, root: void 0, bases: [], stored: void 0, dirty: [], nodes: [], rawNodes: void 0, ops: void 0, replacement, replacementNoop: false, baseValue: base, overlayReleased: false, simpleObjectMaterialization: false, registryRef: void 0 }; context.root = createNode(context, root2, void 0); return context; } var sharedObjectHandler = { deleteProperty(target, property) { return deleteProperty(nodeForTarget(target), property); }, defineProperty(target) { assertWritable(nodeForTarget(target).context); throw new TypeError("Defining overlay properties is not supported"); }, get(target, property) { if (property === "then") { const node2 = target[NODE]; if (node2 === void 0 || isSettledContext(node2.context)) return void 0; } const node = nodeForTarget(target); if (property === NODE) return node; return getProperty(node, property); }, getOwnPropertyDescriptor(target, property) { return getDescriptor(nodeForTarget(target), property); }, getPrototypeOf(target) { const node = nodeForTarget(target); assertReadable(node.context); return Object.getPrototypeOf(nodeBase(node)); }, has(target, property) { return hasProperty(nodeForTarget(target), property); }, isExtensible(target) { assertReadable(nodeForTarget(target).context); return true; }, ownKeys(target) { return ownKeys(nodeForTarget(target)); }, preventExtensions(target) { assertWritable(nodeForTarget(target).context); throw new TypeError("Overlays cannot be made non-extensible"); }, set(target, property, value2) { return setProperty(nodeForTarget(target), property, value2); }, setPrototypeOf(target) { assertWritable(nodeForTarget(target).context); throw new TypeError("Changing an overlay prototype is not supported"); } }; var sharedArrayHandler = sharedObjectHandler; function createNode(context, base, parent, parentKind = 0, parentKey = "", parentSource = void 0, parentPlacement = false) { const existing = context.rawNodes?.get(base); if (existing !== void 0) return existing; const target = Array.isArray(base) ? [] : {}; const baseIndex = context.bases.length; const parentIndex = parent?.baseIndex ?? -1; context.bases.push(base); const node = { context, baseIndex, parentIndex, parentKind, parentKey, parentPlacement, target, proxy: target }; if (parentSource !== void 0) node.parentSource = parentSource; target[NODE] = node; node.proxy = new Proxy(target, Array.isArray(base) ? sharedArrayHandler : sharedObjectHandler); if (parent !== void 0) { context.rawNodes ??= /* @__PURE__ */ new WeakMap(); context.rawNodes.set(base, node); } context.nodes.push(node); return node; } function nodeBase(node) { return node.context.bases[node.baseIndex]; } function nodeParent(node) { return node.parentIndex < 0 ? void 0 : node.context.nodes[node.parentIndex]; } function storeValue(context, value2) { if (!isContainer3(value2)) return value2; context.stored ??= []; const index = context.stored.length; context.stored.push(value2); return { index }; } function storedValue(context, reference) { return typeof reference === "object" && reference !== null ? context.stored[reference.index] : reference; } function replaceStoredValue(context, reference, value2) { if (isContainer3(value2)) { if (typeof reference === "object" && reference !== null) { context.stored[reference.index] = value2; return reference; } return storeValue(context, value2); } if (typeof reference === "object" && reference !== null) context.stored[reference.index] = RELEASED; return value2; } function releaseStoredValue(context, reference) { if (typeof reference === "object" && reference !== null) context.stored[reference.index] = RELEASED; } function nodeForTarget(target) { const node = target[NODE]; if (node === void 0) throw new TypeError("Cannot use a settled overlay"); return node; } function isSettledContext(context) { return context.overlayReleased || context.status.value === "consumed" || context.status.value === "aborted" || context.status.value === "stale"; } function assertReadable(context) { if (isSettledContext(context)) throw new TypeError("Cannot use a settled overlay"); } function assertWritable(context) { assertReadable(context); if (context.status.value !== "open") throw new TypeError("Prepared overlays are read-only"); } function getProperty(node, property) { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { const overlay = arrayOverlay(node); if (property === "length") return arrayLength(overlay); if (ARRAY_MUTATORS.has(property)) return arrayMutators[property]; const index = arrayIndex(property); if (index !== void 0) return getArrayIndex(node, index); return Reflect.get(Array.prototype, property, node.proxy); } if (typeof property === "symbol") return Reflect.get(nodeBase(node), property, node.proxy); const key = String(property); if (!objectHas(node, key)) { if (isObjectDeleted(node, key) || Object.hasOwn(nodeBase(node), key)) return void 0; return Reflect.get(nodeBase(node), property, node.proxy); } const value2 = objectValue(node, key); if (!isContainer3(value2)) return value2; return createNode(node.context, value2, node, 0, key, void 0, hasObjectWrite(node, key)).proxy; } function getArrayIndex(node, index) { const overlay = arrayOverlay(node); if (index >= arrayLength(overlay)) return void 0; const piece = locatePiece(overlay, index); const sourceIndex = piece.start + piece.step * overlay.locatedOffset; const value2 = entryValueAt(node, piece, sourceIndex); if (!isContainer3(value2)) return value2; return createNode(node.context, value2, node, piece.kind === "base" ? 1 : 2, sourceIndex, piece.kind === "insert" ? piece.source : void 0, piece.kind === "insert" || hasEntryOverrideAt(overlay, piece, sourceIndex)).proxy; } function setProperty(node, property, supplied) { assertWritable(node.context); if (typeof property === "symbol") throw new TypeError("Symbol writes are not supported"); if (Array.isArray(nodeBase(node))) { if (property === "length") { setArrayLength(node, toArrayLength(supplied)); return true; } const index = arrayIndex(property); if (index === void 0) throw new TypeError("Only array indices and length can be written"); if (index > arrayLength(arrayOverlay(node))) throw new TypeError("Overlay arrays cannot contain holes"); setArrayIndex(node, index, clonePlacement(supplied)); return true; } const key = String(property); if (supplied === void 0) return deleteProperty(node, key); const stored = clonePlacement(supplied); const current = objectValue(node, key); const wasDeleted = isObjectDeleted(node, key); if (!wasDeleted && !isContainer3(stored) && current === stored) return true; node.appends?.delete(key); setObjectWrite(node, key, stored); if (wasDeleted && Object.hasOwn(nodeBase(node), key)) { if (node.readded === void 0) node.readded = /* @__PURE__ */ new Set(); node.readded.add(key); } deleteObjectDeletion(node, key); markDirty(node); return true; } function deleteProperty(node, property) { assertWritable(node.context); if (typeof property === "symbol") throw new TypeError("Symbol writes are not supported"); if (Array.isArray(nodeBase(node))) throw new TypeError("Overlay arrays cannot contain holes"); const key = String(property); if (!objectHas(node, key)) return true; node.appends?.delete(key); deleteObjectWrite(node, key); node.readded?.delete(key); setObjectDeletion(node, key); markDirty(node); return true; } function hasProperty(node, property) { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { if (property === "length") return true; const index = arrayIndex(property); if (index !== void 0) return index < arrayLength(arrayOverlay(node)); return property in Array.prototype; } return typeof property === "symbol" ? property in nodeBase(node) : objectHas(node, String(property)); } function ownKeys(node) { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { const length = arrayLength(arrayOverlay(node)); const keys2 = new Array(length + 1); for (let index = 0; index < length; index++) keys2[index] = String(index); keys2[length] = "length"; return keys2; } let existingKeysOnly = node.deleteKey === void 0 && node.deletes === void 0 && node.readded === void 0 && (node.writeKey === void 0 || Object.hasOwn(nodeBase(node), node.writeKey)); if (existingKeysOnly && node.writes !== void 0) { for (const key of node.writes.keys()) { if (!Object.hasOwn(nodeBase(node), key)) { existingKeysOnly = false; break; } } } if (existingKeysOnly) return Object.keys(nodeBase(node)); const keys = Object.keys(nodeBase(node)).filter((key) => !isObjectDeleted(node, key) && !node.readded?.has(key)); const seen = new Set(keys); if (node.writeKey !== void 0 && !seen.has(node.writeKey)) { keys.push(node.writeKey); seen.add(node.writeKey); } if (node.writes !== void 0) { for (const key of node.writes.keys()) { if (seen.has(key)) continue; keys.push(key); seen.add(key); } } const indices = []; const strings = []; for (const key of keys) { const index = arrayIndex(key); if (index === void 0) strings.push(key); else indices.push(index); } indices.sort((left, right) => left - right); return [...indices.map(String), ...strings]; } function getDescriptor(node, property) { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { if (property === "length") { const length = arrayLength(arrayOverlay(node)); node.target.length = length; return Reflect.getOwnPropertyDescriptor(node.target, "length"); } const index = arrayIndex(property); if (index === void 0 || index >= arrayLength(arrayOverlay(node))) return void 0; } else { if (typeof property === "symbol" || !objectHas(node, String(property))) return void 0; } return { configurable: true, enumerable: true, writable: node.context.status.value === "open", value: getProperty(node, property) }; } function hasObjectWrite(node, key) { return node.writeKey === key || (node.writes?.has(key) ?? false); } function setObjectWrite(node, key, value2) { if (node.writes !== void 0) { const reference = node.writes.get(key); node.writes.set(key, reference === void 0 ? storeValue(node.context, value2) : replaceStoredValue(node.context, reference, value2)); return; } if (node.writeKey === void 0 || node.writeKey === key) { node.writeKey = key; node.writeValue = node.writeValue === void 0 ? storeValue(node.context, value2) : replaceStoredValue(node.context, node.writeValue, value2); return; } node.writes = /* @__PURE__ */ new Map(); node.writes.set(node.writeKey, node.writeValue); node.writes.set(key, storeValue(node.context, value2)); node.writeKey = void 0; node.writeValue = void 0; } function deleteObjectWrite(node, key) { if (node.writeKey === key) { node.writeKey = void 0; releaseStoredValue(node.context, node.writeValue); node.writeValue = void 0; } else { const reference = node.writes?.get(key); if (reference !== void 0) releaseStoredValue(node.context, reference); node.writes?.delete(key); } } function isObjectDeleted(node, key) { return node.deleteKey === key || (node.deletes?.has(key) ?? false); } function setObjectDeletion(node, key) { if (node.deletes !== void 0) { node.deletes.add(key); return; } if (node.deleteKey === void 0 || node.deleteKey === key) { node.deleteKey = key; return; } node.deletes = /* @__PURE__ */ new Set(); node.deletes.add(node.deleteKey); node.deletes.add(key); node.deleteKey = void 0; } function deleteObjectDeletion(node, key) { if (node.deleteKey === key) node.deleteKey = void 0; else node.deletes?.delete(key); } function objectHas(node, key) { if (isObjectDeleted(node, key)) return false; return hasObjectWrite(node, key) || Object.hasOwn(nodeBase(node), key); } function objectValue(node, key) { if (node.writeKey === key) return storedValue(node.context, node.writeValue); const reference = node.writes?.get(key); if (reference !== void 0) return storedValue(node.context, reference); return nodeBase(node)[key]; } function markDirty(node) { if (node.dirty) return; node.dirty = true; node.context.dirty.push(node); for (let parent = nodeParent(node); parent !== void 0; parent = nodeParent(parent)) parent.subtreeDirty = true; } function arrayOverlay(node) { if (node.array !== void 0) return node.array; const base = nodeBase(node); const overlay = { root: void 0, pieces: void 0, baseOverrides: void 0, insertOverrides: void 0, structural: false, generation: 0, plan: void 0, seed: 2654435769, locatedOffset: 0, baseLocations: void 0, insertLocations: void 0 }; if (base.length > 0) overlay.root = createPieceNode(overlay, { kind: "base", start: 0, length: base.length, step: 1 }); node.array = overlay; return overlay; } function baseOverridesForWrite(overlay) { if (overlay.baseOverrides === void 0) overlay.baseOverrides = /* @__PURE__ */ new Map(); return overlay.baseOverrides; } function insertOverridesForWrite(overlay) { if (overlay.insertOverrides === void 0) overlay.insertOverrides = /* @__PURE__ */ new Map(); return overlay.insertOverrides; } function nextPiecePriority(overlay) { let value2 = overlay.seed; value2 ^= value2 << 13; value2 ^= value2 >>> 17; value2 ^= value2 << 5; overlay.seed = value2 >>> 0; return overlay.seed; } function treeElements(node) { return node?.elements ?? 0; } function updatePieceNode(node) { node.elements = treeElements(node.left) + node.piece.length + treeElements(node.right); } function createPieceNode(overlay, piece) { return { piece, left: void 0, right: void 0, priority: nextPiecePriority(overlay), elements: piece.length }; } function mergePieceTrees(left, right) { if (left === void 0) return right; if (right === void 0) return left; if (left.priority >= right.priority) { left.right = mergePieceTrees(left.right, right); updatePieceNode(left); return left; } right.left = mergePieceTrees(left, right.left); updatePieceNode(right); return right; } function splitPieceTree(overlay, root2, index) { if (root2 === void 0) return [void 0, void 0]; const leftLength = treeElements(root2.left); if (index < leftLength) { const [left, right] = splitPieceTree(overlay, root2.left, index); root2.left = right; updatePieceNode(root2); return [left, root2]; } const pieceEnd = leftLength + root2.piece.length; if (index > pieceEnd) { const [left, right] = splitPieceTree(overlay, root2.right, index - pieceEnd); root2.right = left; updatePieceNode(root2); return [root2, right]; } if (index === leftLength) { const left = root2.left; root2.left = void 0; updatePieceNode(root2); return [left, root2]; } if (index === pieceEnd) { const right = root2.right; root2.right = void 0; updatePieceNode(root2); return [root2, right]; } const offset = index - leftLength; const first = { ...root2.piece, length: offset }; const second = { ...root2.piece, start: root2.piece.start + root2.piece.step * offset, length: root2.piece.length - offset }; return [ mergePieceTrees(root2.left, createPieceNode(overlay, first)), mergePieceTrees(createPieceNode(overlay, second), root2.right) ]; } function leftmostPieceNode(node) { while (node.left !== void 0) node = node.left; return node; } function rightmostPieceNode(node) { while (node.right !== void 0) node = node.right; return node; } function mergeablePieces(left, right) { if (left.kind !== right.kind) return false; if (left.kind === "insert" && left.source !== right.source) return false; if (left.length === 1 && right.length === 1) return Math.abs(right.start - left.start) === 1; return left.step === right.step && left.start + left.step * left.length === right.start; } function joinNormalized(overlay, left, right) { if (left === void 0) return right; if (right === void 0) return left; const leftPiece = rightmostPieceNode(left).piece; const rightPiece = leftmostPieceNode(right).piece; if (!mergeablePieces(leftPiece, rightPiece)) return mergePieceTrees(left, right); const [leftRest] = splitPieceTree(overlay, left, treeElements(left) - leftPiece.length); const [, rightRest] = splitPieceTree(overlay, right, rightPiece.length); const step = leftPiece.length === 1 ? rightPiece.start - leftPiece.start : leftPiece.step; const combined = leftPiece.kind === "base" ? { kind: "base", start: leftPiece.start, length: leftPiece.length + rightPiece.length, step } : { kind: "insert", source: leftPiece.source, start: leftPiece.start, length: leftPiece.length + rightPiece.length, step }; return joinNormalized(overlay, joinNormalized(overlay, leftRest, createPieceNode(overlay, combined)), rightRest); } function flattenPieceTree(node, output) { if (node === void 0) return; flattenPieceTree(node.left, output); output.push(node.piece); flattenPieceTree(node.right, output); } function piecesOf(overlay) { if (overlay.pieces === void 0) { overlay.pieces = []; flattenPieceTree(overlay.root, overlay.pieces); } return overlay.pieces; } function treeFromPieces(overlay, pieces) { mergePieces(pieces); let root2; for (const piece of pieces) root2 = mergePieceTrees(root2, createPieceNode(overlay, piece)); return root2; } function replaceAllPieces(overlay, pieces) { overlay.root = treeFromPieces(overlay, pieces); overlay.pieces = void 0; overlay.baseLocations = void 0; overlay.insertLocations = void 0; } function arrayLength(overlay) { return treeElements(overlay.root); } function locatePiece(overlay, index) { let node = overlay.root; while (node !== void 0) { const leftLength = treeElements(node.left); if (index < leftLength) node = node.left; else if (index >= leftLength + node.piece.length) { index -= leftLength + node.piece.length; node = node.right; } else { overlay.locatedOffset = index - leftLength; return node.piece; } } throw new RangeError("Array overlay index is out of range"); } function arrayIndex(property) { if (typeof property !== "string" || property.length === 0 || property.length > 10) return void 0; if (property === "0") return 0; const first = property.charCodeAt(0); if (first < 49 || first > 57) return void 0; let index = first - 48; for (let offset = 1; offset < property.length; offset++) { const digit = property.charCodeAt(offset) - 48; if (digit < 0 || digit > 9) return void 0; index = index * 10 + digit; if (index >= 4294967295) return void 0; } return index; } function entryValueAt(node, piece, sourceIndex) { const overlay = arrayOverlay(node); if (piece.kind === "base") { const valueIndex = overlay.baseOverrides?.get(sourceIndex); return valueIndex === void 0 ? nodeBase(node)[sourceIndex] : storedValue(node.context, valueIndex); } const overrides = overlay.insertOverrides?.get(piece.source); const reference = overrides?.has(sourceIndex) ? overrides.get(sourceIndex) : piece.source.refs[sourceIndex]; return storedValue(node.context, reference); } function hasEntryOverrideAt(overlay, piece, sourceIndex) { return piece.kind === "base" ? overlay.baseOverrides?.has(sourceIndex) ?? false : overlay.insertOverrides?.get(piece.source)?.has(sourceIndex) ?? false; } function extendRightmostPiece(node, amount) { if (node.right !== void 0) extendRightmostPiece(node.right, amount); else node.piece.length += amount; updatePieceNode(node); } function invalidatePieceCaches(overlay) { overlay.pieces = void 0; overlay.baseLocations = void 0; overlay.insertLocations = void 0; overlay.plan = void 0; } function replacePieceRange(node, index, remove, inserted) { if (remove === 0 && inserted.length === 0) return; const overlay = arrayOverlay(node); if (remove === 0 && index === arrayLength(overlay) && inserted.length === 1 && overlay.root !== void 0) { const addition = inserted[0]; const tail = rightmostPieceNode(overlay.root).piece; if (addition.kind === "insert" && tail.kind === "insert" && tail.step === 1 && tail.start + tail.length === tail.source.refs.length) { for (let offset = 0; offset < addition.length; offset++) { tail.source.refs.push(addition.source.refs[addition.start + offset]); } extendRightmostPiece(overlay.root, addition.length); invalidatePieceCaches(overlay); overlay.structural = true; overlay.generation += 1; markDirty(node); return; } } const [left, rest] = splitPieceTree(overlay, overlay.root, index); const [, right] = splitPieceTree(overlay, rest, remove); const middle = treeFromPieces(overlay, inserted); overlay.root = joinNormalized(overlay, joinNormalized(overlay, left, middle), right); invalidatePieceCaches(overlay); overlay.structural = true; overlay.generation += 1; markDirty(node); } function mergePieces(pieces) { for (let index = 1; index < pieces.length; ) { const left = pieces[index - 1]; const right = pieces[index]; const sameSource = left.kind === right.kind && (left.kind === "base" || left.source === right.source); if (sameSource && left.length === 1 && right.length === 1 && Math.abs(right.start - left.start) === 1) { left.step = right.start - left.start; left.length = 2; pieces.splice(index, 1); } else if (sameSource && left.step === right.step && left.start + left.step * left.length === right.start) { left.length += right.length; pieces.splice(index, 1); } else index += 1; } } function insertPiece(node, items, start = 0) { if (items.length === start) return []; const refs = []; for (let index = start; index < items.length; index++) refs.push(storeValue(node.context, items[index])); return [{ kind: "insert", source: { refs }, start: 0, length: refs.length, step: 1 }]; } function insertPlacementPiece(node, values, start = 0) { if (values.length === start) return []; const storedLength = node.context.stored?.length ?? 0; try { for (let index = start; index < values.length; index++) values[index] = storeValue(node.context, clonePlacement(values[index])); } catch (error) { if (node.context.stored !== void 0) node.context.stored.length = storedLength; throw error; } return [ { kind: "insert", source: { refs: values }, start, length: values.length - start, step: 1 } ]; } function setArrayIndex(node, index, stored) { const overlay = arrayOverlay(node); const length = arrayLength(overlay); if (index === length) { replacePieceRange(node, length, 0, insertPiece(node, [stored])); return; } const piece = locatePiece(overlay, index); const sourceIndex = piece.start + piece.step * overlay.locatedOffset; const current = entryValueAt(node, piece, sourceIndex); if (!isContainer3(stored) && current === stored) return; if (piece.kind === "base") { const existingIndex = overlay.baseOverrides?.get(sourceIndex); if (!isContainer3(stored) && stored === nodeBase(node)[sourceIndex]) { if (existingIndex !== void 0) releaseStoredValue(node.context, existingIndex); overlay.baseOverrides?.delete(sourceIndex); } else if (existingIndex === void 0) { baseOverridesForWrite(overlay).set(sourceIndex, storeValue(node.context, stored)); } else overlay.baseOverrides.set(sourceIndex, replaceStoredValue(node.context, existingIndex, stored)); } else { let overrides = overlay.insertOverrides?.get(piece.source); const existingIndex = overrides?.get(sourceIndex); if (!isContainer3(stored) && stored === storedValue(node.context, piece.source.refs[sourceIndex])) { if (existingIndex !== void 0) releaseStoredValue(node.context, existingIndex); overrides?.delete(sourceIndex); if (overrides?.size === 0) overlay.insertOverrides?.delete(piece.source); } else { if (overrides === void 0) { overrides = /* @__PURE__ */ new Map(); insertOverridesForWrite(overlay).set(piece.source, overrides); } if (existingIndex === void 0) overrides.set(sourceIndex, storeValue(node.context, stored)); else overrides.set(sourceIndex, replaceStoredValue(node.context, existingIndex, stored)); } } markDirty(node); } function setArrayLength(node, next) { const current = arrayLength(arrayOverlay(node)); if (next === current) return; if (next < current) replacePieceRange(node, next, current - next, []); else replacePieceRange(node, current, 0, insertPiece(node, Array.from({ length: next - current }, () => null))); } function toArrayLength(value2) { const number2 = Number(value2); if (!Number.isInteger(number2) || number2 < 0 || number2 >= 4294967296) throw new RangeError("Invalid array length"); return number2; } function toIntegerOrInfinity(value2) { const number2 = Number(value2); if (Number.isNaN(number2) || number2 === 0) return 0; return Number.isFinite(number2) ? Math.trunc(number2) : number2; } function clampIndex(value2, length) { if (value2 === Number.NEGATIVE_INFINITY) return 0; if (value2 < 0) return Math.max(length + value2, 0); return Math.min(value2, length); } function mutatorNode(receiver) { if (!isContainer3(receiver)) return void 0; const node = Reflect.get(receiver, NODE); if (node === void 0) return void 0; if (!Array.isArray(nodeBase(node))) throw new TypeError("Array mutator called on incompatible receiver"); assertWritable(node.context); return node; } var arrayMutators = { push(...items) { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.push, this, items); const length = arrayLength(arrayOverlay(node)); replacePieceRange(node, length, 0, insertPlacementPiece(node, items)); return length + items.length; }, pop() { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.pop, this, []); const length = arrayLength(arrayOverlay(node)); if (length === 0) return void 0; const value2 = getArrayIndex(node, length - 1); replacePieceRange(node, length - 1, 1, []); return value2; }, shift() { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.shift, this, []); const length = arrayLength(arrayOverlay(node)); if (length === 0) return void 0; const value2 = getArrayIndex(node, 0); replacePieceRange(node, 0, 1, []); return value2; }, unshift(...items) { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.unshift, this, items); replacePieceRange(node, 0, 0, insertPlacementPiece(node, items)); return arrayLength(arrayOverlay(node)); }, splice(...args) { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.splice, this, args); const length = arrayLength(arrayOverlay(node)); const start = args.length === 0 ? 0 : clampIndex(toIntegerOrInfinity(args[0]), length); const remove = args.length === 0 ? 0 : args.length === 1 ? length - start : Math.min(Math.max(toIntegerOrInfinity(args[1]), 0), length - start); const removed = Array.from({ length: remove }, (_, offset) => getArrayIndex(node, start + offset)); const itemStart = Math.min(2, args.length); const itemCount = Math.max(args.length - 2, 0); replacePieceRange(node, start, remove, insertPlacementPiece(node, args, itemStart)); setArrayLength(node, length - remove + itemCount); return removed; }, reverse() { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.reverse, this, []); const overlay = arrayOverlay(node); if (arrayLength(overlay) < 2) return node.proxy; const pieces = [...piecesOf(overlay)].reverse(); for (const piece of pieces) { piece.start += piece.step * (piece.length - 1); piece.step = piece.step === 1 ? -1 : 1; } replaceAllPieces(overlay, pieces); overlay.structural = true; overlay.generation += 1; overlay.plan = void 0; markDirty(node); return node.proxy; }, sort(comparator) { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.sort, this, [comparator]); if (comparator !== void 0 && typeof comparator !== "function") throw new TypeError("Comparator must be a function"); const overlay = arrayOverlay(node); const insertedSources = []; const insertedIndices = []; const insertedValues = []; const baseValues = new Array(nodeBase(node).length); const order = []; for (const piece of piecesOf(overlay)) { for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; if (piece.kind === "base") { order.push(sourceIndex); baseValues[sourceIndex] = publicSortValue(node, sourceIndex, insertedSources, insertedIndices); } else { insertedSources.push(piece.source); insertedIndices.push(sourceIndex); const token = -insertedSources.length; order.push(token); insertedValues.push(publicSortValue(node, token, insertedSources, insertedIndices)); } } } const baseSnapshot = /* @__PURE__ */ new Map(); for (const [index, valueIndex] of overlay.baseOverrides ?? []) baseSnapshot.set(index, storedValue(node.context, valueIndex)); const insertSnapshots = /* @__PURE__ */ new Map(); for (const [source, overrides] of overlay.insertOverrides ?? []) { const snapshot = /* @__PURE__ */ new Map(); for (const [index, valueIndex] of overrides) snapshot.set(index, storedValue(node.context, valueIndex)); insertSnapshots.set(source, snapshot); } const generation = overlay.generation; order.sort((left, right) => { const leftValue = left < 0 ? insertedValues[-left - 1] : baseValues[left]; const rightValue = right < 0 ? insertedValues[-right - 1] : baseValues[right]; if (typeof comparator === "function") return Number(comparator(leftValue, rightValue)); const a = String(leftValue); const b = String(rightValue); return a < b ? -1 : a > b ? 1 : 0; }); if (baseSnapshot.size > 0 || insertSnapshots.size > 0 || (overlay.baseOverrides?.size ?? 0) > 0 || (overlay.insertOverrides?.size ?? 0) > 0) { for (const token of order) { restoreSortOverride(node, token, insertedSources, insertedIndices, baseSnapshot, insertSnapshots); } } const comparatorWasStructural = overlay.generation !== generation; const currentLength = arrayLength(overlay); const samePrefix = currentLength >= order.length && order.every((token, index) => sameSortTokenAt(overlay, index, token, insertedSources, insertedIndices)); if (!samePrefix) { replacePieceRange(node, 0, Math.min(order.length, currentLength), piecesFromSortOrder(order, insertedSources, insertedIndices)); } if (comparatorWasStructural) deduplicateArrayEntries(node); return node.proxy; }, fill(supplied, startArg, endArg) { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.fill, this, [supplied, startArg, endArg]); const length = arrayLength(arrayOverlay(node)); const start = startArg === void 0 ? 0 : clampIndex(toIntegerOrInfinity(startArg), length); const end = endArg === void 0 ? length : clampIndex(toIntegerOrInfinity(endArg), length); if (end <= start) return node.proxy; const items = Array.from({ length: end - start }, () => clonePlacement(supplied)); replacePieceRange(node, start, end - start, insertPiece(node, items)); return node.proxy; }, copyWithin(targetArg, startArg, endArg) { const node = mutatorNode(this); if (node === void 0) return Reflect.apply(Array.prototype.copyWithin, this, [targetArg, startArg, endArg]); const length = arrayLength(arrayOverlay(node)); const target = clampIndex(toIntegerOrInfinity(targetArg), length); const start = clampIndex(toIntegerOrInfinity(startArg), length); const end = endArg === void 0 ? length : clampIndex(toIntegerOrInfinity(endArg), length); const count = Math.min(Math.max(end - start, 0), length - target); const values = Array.from({ length: count }, (_, offset) => clonePlacement(getArrayIndex(node, start + offset))); replacePieceRange(node, target, count, insertPiece(node, values)); return node.proxy; } }; function sortTokenSource(token, sources) { return token < 0 ? sources[-token - 1] : void 0; } function sortTokenIndex(token, indices) { return token < 0 ? indices[-token - 1] : token; } function publicSortValue(node, token, sources, indices) { const overlay = arrayOverlay(node); const source = sortTokenSource(token, sources); const sourceIndex = sortTokenIndex(token, indices); const overrideIndex = source === void 0 ? overlay.baseOverrides?.get(sourceIndex) : overlay.insertOverrides?.get(source)?.get(sourceIndex); const value2 = overrideIndex !== void 0 ? storedValue(node.context, overrideIndex) : source === void 0 ? nodeBase(node)[sourceIndex] : storedValue(node.context, source.refs[sourceIndex]); if (!isContainer3(value2)) return value2; return createNode(node.context, value2, node, source === void 0 ? 1 : 2, sourceIndex, source, source !== void 0 || (overlay.baseOverrides?.has(sourceIndex) ?? false)).proxy; } function restoreSortOverride(node, token, sources, indices, baseSnapshot, insertSnapshots) { const overlay = arrayOverlay(node); const source = sortTokenSource(token, sources); const sourceIndex = sortTokenIndex(token, indices); if (source === void 0) { const existingIndex2 = overlay.baseOverrides?.get(sourceIndex); const snapshotValue2 = baseSnapshot.get(sourceIndex); if (snapshotValue2 !== void 0) { if (existingIndex2 === void 0) baseOverridesForWrite(overlay).set(sourceIndex, storeValue(node.context, snapshotValue2)); else overlay.baseOverrides.set(sourceIndex, replaceStoredValue(node.context, existingIndex2, snapshotValue2)); } else { if (existingIndex2 !== void 0) releaseStoredValue(node.context, existingIndex2); overlay.baseOverrides?.delete(sourceIndex); } return; } let overrides = overlay.insertOverrides?.get(source); const existingIndex = overrides?.get(sourceIndex); const snapshotValue = insertSnapshots.get(source)?.get(sourceIndex); if (snapshotValue !== void 0) { if (overrides === void 0) { overrides = /* @__PURE__ */ new Map(); insertOverridesForWrite(overlay).set(source, overrides); } if (existingIndex === void 0) overrides.set(sourceIndex, storeValue(node.context, snapshotValue)); else overrides.set(sourceIndex, replaceStoredValue(node.context, existingIndex, snapshotValue)); } else { if (existingIndex !== void 0) releaseStoredValue(node.context, existingIndex); overrides?.delete(sourceIndex); if (overrides?.size === 0) overlay.insertOverrides?.delete(source); } } function sameSortTokenAt(overlay, logicalIndex, token, sources, indices) { const piece = locatePiece(overlay, logicalIndex); const sourceIndex = piece.start + piece.step * overlay.locatedOffset; const tokenSource = sortTokenSource(token, sources); return sourceIndex === sortTokenIndex(token, indices) && (piece.kind === "base" && tokenSource === void 0 || piece.kind === "insert" && piece.source === tokenSource); } function deduplicateArrayEntries(node) { const overlay = arrayOverlay(node); const seenBase = /* @__PURE__ */ new Set(); const seenInsert = /* @__PURE__ */ new Map(); const next = []; let duplicated = false; for (const piece of piecesOf(overlay)) { for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; let seen; if (piece.kind === "base") { seen = seenBase.has(sourceIndex); seenBase.add(sourceIndex); } else { let indices = seenInsert.get(piece.source); if (indices === void 0) { indices = /* @__PURE__ */ new Set(); seenInsert.set(piece.source, indices); } seen = indices.has(sourceIndex); indices.add(sourceIndex); } if (seen) { duplicated = true; appendMergedPiece(next, insertPiece(node, [clonePlacementStored(entryValueAt(node, piece, sourceIndex), node.context)])[0]); } else appendMergedPiece(next, singletonPiece(piece, sourceIndex)); } } if (!duplicated) return; replaceAllPieces(overlay, next); overlay.structural = true; overlay.generation += 1; overlay.plan = void 0; markDirty(node); } function piecesFromSortOrder(order, sources, indices) { const pieces = []; for (const token of order) { const source = token < 0 ? sources[-token - 1] : void 0; const sourceIndex = token < 0 ? indices[-token - 1] : token; const previous = pieces.at(-1); const sameSource = previous !== void 0 && (source === void 0 && previous.kind === "base" || source !== void 0 && previous.kind === "insert" && previous.source === source); if (previous !== void 0 && sameSource) { if (previous.length === 1) { const step = sourceIndex - previous.start; if (step === 1 || step === -1) { previous.step = step; previous.length = 2; continue; } } else if (previous.start + previous.step * previous.length === sourceIndex) { previous.length += 1; continue; } } pieces.push(source === void 0 ? { kind: "base", start: sourceIndex, length: 1, step: 1 } : { kind: "insert", source, start: sourceIndex, length: 1, step: 1 }); } return pieces; } function appendMergedPiece(pieces, piece) { const previous = pieces.at(-1); const sameSource = previous !== void 0 && previous.kind === piece.kind && (previous.kind === "base" || previous.source === piece.source); if (previous !== void 0 && sameSource && previous.length === 1 && piece.length === 1) { const step = piece.start - previous.start; if (step === 1 || step === -1) { previous.step = step; previous.length = 2; return; } } if (previous !== void 0 && sameSource && previous.step === piece.step && previous.start + previous.step * previous.length === piece.start) { previous.length += piece.length; } else pieces.push(piece); } function singletonPiece(piece, sourceIndex) { return piece.kind === "base" ? { kind: "base", start: sourceIndex, length: 1, step: 1 } : { kind: "insert", source: piece.source, start: sourceIndex, length: 1, step: 1 }; } function clonePlacement(value2) { const proxyNode = isContainer3(value2) ? Reflect.get(value2, NODE) : void 0; return proxyNode === void 0 ? copyJson(value2) : clonePlacementNode(proxyNode); } function cloneNode(node) { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { const overlay = arrayOverlay(node); const result2 = []; for (const piece of piecesOf(overlay)) { for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; result2.push(cloneStored(entryValueAt(node, piece, sourceIndex), node.context)); } } return result2; } const result = Object.create(Object.getPrototypeOf(nodeBase(node))); for (const key of ownKeys(node)) defineData3(result, key, cloneStored(objectValue(node, key), node.context)); return result; } function cloneStored(value2, context) { if (!isContainer3(value2)) return value2; const node = context.rawNodes?.get(value2); return node === void 0 || !node.dirty && !node.subtreeDirty ? value2 : cloneNode(node); } function clonePlacementNode(node) { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { const overlay = arrayOverlay(node); const result2 = []; for (const piece of piecesOf(overlay)) { for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; result2.push(clonePlacementStored(entryValueAt(node, piece, sourceIndex), node.context)); } } return result2; } const result = Object.create(Object.getPrototypeOf(nodeBase(node))); for (const key of ownKeys(node)) defineData3(result, key, clonePlacementStored(objectValue(node, key), node.context)); return result; } function clonePlacementStored(value2, context) { if (!isContainer3(value2)) return copyJson(value2); const node = context.rawNodes?.get(value2); return node === void 0 ? copyJson(value2) : clonePlacementNode(node); } function defineData3(target, key, value2) { DATA_DESCRIPTOR3.value = value2; Object.defineProperty(target, key, DATA_DESCRIPTOR3); DATA_DESCRIPTOR3.value = void 0; } function emitOperations(context) { const simple = emitSimpleObjectOperations(context); if (simple !== void 0) return simple; const operations = []; const forcedFolds = /* @__PURE__ */ new Map(); const emissionPaths = /* @__PURE__ */ new Map(); const denseIndices = /* @__PURE__ */ new Map(); for (const node of context.dirty) { recordDenseArrayPosition(node, denseIndices); if (Array.isArray(nodeBase(node))) { const overlay = arrayOverlay(node); if (!overlay.structural && (overlay.baseOverrides?.size ?? 0) > 0) { let candidates = denseIndices.get(node); if (candidates === void 0) { candidates = { indices: [], bits: void 0, length: nodeBase(node).length }; denseIndices.set(node, candidates); } for (const index of overlay.baseOverrides.keys()) addDenseCandidate(candidates, index); } } } const denseRegions = /* @__PURE__ */ new Map(); for (const [array, candidates] of denseIndices) { const path = resolvePath(array); if (path === void 0 || path.some((segment) => typeof segment === "string" && RESERVED_SEGMENTS.has(segment))) { continue; } const regions = buildDenseRegions(candidates); if (regions.length > 0) denseRegions.set(array, regions); } for (const node of context.dirty) { const path = resolvePath(node); if (path === void 0) continue; if (hasCoveringDenseRegion(node, path, denseRegions)) continue; emissionPaths.set(node, path); if (!Array.isArray(nodeBase(node)) && hasReservedMutation(node)) forcedFolds.set(node, path); const reservedAt = path.findIndex((segment) => typeof segment === "string" && RESERVED_SEGMENTS.has(segment)); if (reservedAt >= 0) { let ancestor = node; for (let depth = path.length; depth > reservedAt; depth--) ancestor = nodeParent(ancestor); forcedFolds.set(ancestor, path.slice(0, reservedAt)); } } for (const [node, path] of forcedFolds) emissionPaths.set(node, path); for (const node of denseRegions.keys()) { const path = resolvePath(node); if (path !== void 0 && !hasCoveringDenseRegion(node, path, denseRegions)) emissionPaths.set(node, path); } let maxDepth = 0; for (const path of emissionPaths.values()) maxDepth = Math.max(maxDepth, path.length); const buckets = Array.from({ length: maxDepth + 1 }, () => []); for (const [node, path] of emissionPaths) buckets[path.length].push(node); const folded = /* @__PURE__ */ new Set(); for (const bucket of buckets) { for (const node of bucket) { const path = emissionPaths.get(node); if (hasPlacementAncestor(node) || hasFoldedAncestor(node, folded)) continue; if (forcedFolds.has(node)) { emitSet2(operations, path, cloneNode(node)); folded.add(node); continue; } if (Array.isArray(nodeBase(node))) emitArrayOperations(node, path, operations, denseRegions.get(node)); else emitObjectOperations(node, path, operations); if (operations.length > MAX_DELTA_OPERATIONS2) { locatedDenseArray = void 0; return [["r", cloneNode(context.root)]]; } } } locatedDenseArray = void 0; return operations; } function emitSimpleObjectOperations(context) { const nodes = []; for (const node of context.dirty) { if (Array.isArray(nodeBase(node)) || hasReservedMutation(node) || hasPlacementAncestor(node)) return void 0; for (let parent = nodeParent(node); parent !== void 0; parent = nodeParent(parent)) { if (Array.isArray(nodeBase(parent))) return void 0; } const path = resolvePath(node); if (path === void 0) continue; for (const segment of path) { if (typeof segment === "string" && RESERVED_SEGMENTS.has(segment)) return void 0; } nodes.push(node); if (nodes.length > MAX_SIMPLE_OBJECT_NODES) return void 0; } for (let index = 1; index < nodes.length; index++) { const node = nodes[index]; const depth = node.preparedPath.length; let at = index; while (at > 0 && nodes[at - 1].preparedPath.length > depth) { nodes[at] = nodes[at - 1]; at -= 1; } nodes[at] = node; } const operations = []; let canMaterializeDirectly = true; for (const node of nodes) { if (emitObjectOperations(node, node.preparedPath, operations)) canMaterializeDirectly = false; if (operations.length > MAX_DELTA_OPERATIONS2) return [["r", cloneNode(context.root)]]; } context.simpleObjectMaterialization = canMaterializeDirectly; return operations; } var locatedDenseArray; var locatedDenseIndex = 0; function locateDenseArrayPosition(node) { let child = node; for (let parent = nodeParent(child); parent !== void 0; parent = nodeParent(child)) { if (Array.isArray(nodeBase(parent))) { const overlay = arrayOverlay(parent); if (child.parentKind !== 1 || overlay.structural) return false; const sourceIndex = child.parentKey; const valueIndex = overlay.baseOverrides?.get(sourceIndex); const current = valueIndex === void 0 ? nodeBase(parent)[sourceIndex] : storedValue(parent.context, valueIndex); if (current !== nodeBase(child)) return false; locatedDenseArray = parent; locatedDenseIndex = sourceIndex; return true; } child = parent; } return false; } function hasCoveringDenseRegion(node, path, denseRegions) { for (let parent = nodeParent(node); parent !== void 0; parent = nodeParent(parent)) { const regions = denseRegions.get(parent); if (regions === void 0) continue; const parentPath = resolvePath(parent); if (parentPath === void 0 || path.length <= parentPath.length) continue; let matches = true; for (let index2 = 0; index2 < parentPath.length; index2++) { if (path[index2] !== parentPath[index2]) { matches = false; break; } } if (!matches) continue; const index = path[parentPath.length]; if (typeof index === "number" && regionContaining(regions, index)) return true; } return false; } function addDenseCandidate(candidates, index) { if (candidates.bits !== void 0) { candidates.bits[index] = 1; return; } candidates.indices.push(index); if (candidates.indices.length < 256) return; candidates.bits = new Uint8Array(candidates.length); for (const existing of candidates.indices) candidates.bits[existing] = 1; candidates.indices.length = 0; } function recordDenseArrayPosition(node, groups) { if (!locateDenseArrayPosition(node)) return; let candidates = groups.get(locatedDenseArray); if (candidates === void 0) { candidates = { indices: [], bits: void 0, length: nodeBase(locatedDenseArray).length }; groups.set(locatedDenseArray, candidates); } addDenseCandidate(candidates, locatedDenseIndex); } function buildDenseRegions(candidates) { if (candidates.bits === void 0) return []; const regions = []; const bits = candidates.bits; for (let at = 0; at < bits.length; ) { while (at < bits.length && bits[at] === 0) at += 1; if (at === bits.length) break; const start = at; let end = at; let count = 0; let gap = 0; while (at < bits.length) { if (bits[at] !== 0) { count += 1; end = at; gap = 0; } else if (++gap > 1) break; at += 1; } const length = end - start + 1; if (count >= 256 && count * 2 >= length) regions.push({ start, length }); } return regions; } function regionContaining(regions, index) { for (const region of regions) if (index >= region.start && index < region.start + region.length) return true; return false; } function hasReservedMutation(node) { if (node.writeKey !== void 0 && RESERVED_SEGMENTS.has(node.writeKey)) return true; if (node.deleteKey !== void 0 && RESERVED_SEGMENTS.has(node.deleteKey)) return true; if (node.writes !== void 0) { for (const key of node.writes.keys()) if (RESERVED_SEGMENTS.has(key)) return true; } if (node.deletes !== void 0) { for (const key of node.deletes) if (RESERVED_SEGMENTS.has(key)) return true; } return false; } function hasFoldedAncestor(node, folded) { for (let parent = nodeParent(node); parent !== void 0; parent = nodeParent(parent)) if (folded.has(parent)) return true; return false; } function hasPlacementAncestor(node) { for (let current = node; current !== void 0; current = nodeParent(current)) { if (nodeParent(current) !== void 0 && current.parentPlacement) return true; } return false; } function emitObjectWrite(node, path, operations, key, value2) { const nextPath = [...path, key]; const appended = node.appends?.get(key); if (appended !== void 0) { operations.push(["a", nextPath, appended]); return false; } const before = node.readded?.has(key) || !Object.hasOwn(nodeBase(node), key) ? void 0 : nodeBase(node)[key]; const after = cloneStored(value2, node.context); const emitted = emitChangedValue(operations, nextPath, before, after); return isContainer3(before) && isContainer3(after) && !emitted; } function emitObjectOperations(node, path, operations) { if (node.readded !== void 0) { for (const key of node.readded) { if (Object.hasOwn(nodeBase(node), key)) operations.push(["d", [...path, key]]); } } let normalizedContainerWrite = false; if (node.writeKey !== void 0) normalizedContainerWrite = emitObjectWrite(node, path, operations, node.writeKey, objectValue(node, node.writeKey)); if (node.writes !== void 0) { for (const [key, valueIndex] of node.writes) { if (operations.length > MAX_DELTA_OPERATIONS2) return normalizedContainerWrite; if (emitObjectWrite(node, path, operations, key, storedValue(node.context, valueIndex))) normalizedContainerWrite = true; } } if (node.deleteKey !== void 0 && Object.hasOwn(nodeBase(node), node.deleteKey)) { operations.push(["d", [...path, node.deleteKey]]); } if (node.deletes !== void 0) { for (const key of node.deletes) { if (operations.length > MAX_DELTA_OPERATIONS2) return normalizedContainerWrite; if (Object.hasOwn(nodeBase(node), key)) operations.push(["d", [...path, key]]); } } return normalizedContainerWrite; } function buildArrayPlan(node) { const overlay = arrayOverlay(node); if (overlay.plan !== void 0) return overlay.plan; const base = nodeBase(node); const pieces = piecesOf(overlay); overlay.plan = orderedArrayPlan(base.length, pieces) ?? elementArrayPlan(base.length, pieces); return overlay.plan; } function orderedArrayPlan(baseLength, pieces) { const ranges = []; for (const piece of pieces) { if (piece.kind !== "base" || piece.length === 0) continue; if (piece.step !== 1 && piece.length > 1) return void 0; if (ranges.length > 0 && piece.start <= ranges[ranges.length - 1]) return void 0; ranges.push(piece.start, piece.start + piece.length - 1); } const removeRuns = []; let end = baseLength; for (let index = ranges.length - 2; index >= -2; index -= 2) { const start = index >= 0 ? ranges[index + 1] + 1 : 0; if (end > start) removeRuns.push(start, end - start); if (index >= 0) end = ranges[index]; } return { removeRuns, permutation: void 0, insertRuns: insertRunsOf(pieces) }; } function elementArrayPlan(baseLength, pieces) { const retained = new Uint8Array(baseLength); const targetBase = []; for (const piece of pieces) { if (piece.kind !== "base") continue; for (let offset = 0; offset < piece.length; offset++) { const index = piece.start + piece.step * offset; retained[index] = 1; targetBase.push(index); } } const removeRuns = []; for (let end = baseLength; end > 0; ) { if (retained[end - 1] !== 0) { end -= 1; continue; } let start = end - 1; while (start > 0 && retained[start - 1] === 0) start -= 1; removeRuns.push(start, end - start); end = start; } const retainedBase = []; for (let index = 0; index < retained.length; index++) if (retained[index] !== 0) retainedBase.push(index); let permutation2; if (targetBase.some((value2, index) => value2 !== retainedBase[index])) { const positions = new Map(retainedBase.map((value2, index) => [value2, index])); permutation2 = targetBase.map((value2) => positions.get(value2)); } return { removeRuns, permutation: permutation2, insertRuns: insertRunsOf(pieces) }; } function insertRunsOf(pieces) { const insertRuns = []; let logicalIndex = 0; for (let pieceIndex = 0; pieceIndex < pieces.length; ) { const piece = pieces[pieceIndex]; if (piece.kind === "base") { logicalIndex += piece.length; pieceIndex += 1; continue; } const startPiece = pieceIndex; while (pieceIndex < pieces.length && pieces[pieceIndex].kind === "insert") { logicalIndex += pieces[pieceIndex].length; pieceIndex += 1; } insertRuns.push(logicalIndex - rangeLength(pieces, startPiece, pieceIndex), startPiece, pieceIndex); } return insertRuns; } function rangeLength(pieces, start, end) { let length = 0; for (let index = start; index < end; index++) length += pieces[index].length; return length; } function emitArrayOperations(node, path, operations, denseRegions) { const overlay = arrayOverlay(node); const base = nodeBase(node); if (denseRegions !== void 0) { for (const region of denseRegions) { if (operations.length > MAX_DELTA_OPERATIONS2) return; operations.push(["p", path, region.start, region.length, cloneArrayRegion(node, region)]); } } if (overlay.structural) { const plan = buildArrayPlan(node); for (let index = 0; index < plan.removeRuns.length; index += 2) { if (operations.length > MAX_DELTA_OPERATIONS2) return; operations.push(["p", path, plan.removeRuns[index], plan.removeRuns[index + 1], []]); } if (plan.permutation !== void 0) operations.push(["m", path, plan.permutation]); const pieces = piecesOf(overlay); for (let run2 = 0; run2 < plan.insertRuns.length; run2 += 3) { if (operations.length > MAX_DELTA_OPERATIONS2) return; const logicalIndex = plan.insertRuns[run2]; const items = []; for (let pieceIndex = plan.insertRuns[run2 + 1]; pieceIndex < plan.insertRuns[run2 + 2]; pieceIndex++) { const piece = pieces[pieceIndex]; for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; items.push(cloneStored(entryValueAt(node, piece, sourceIndex), node.context)); } } operations.push(["p", path, logicalIndex, 0, items]); } } for (const [baseIndex, valueIndex] of overlay.baseOverrides ?? []) { if (operations.length > MAX_DELTA_OPERATIONS2) return; const index = findEntryIndex(overlay, 1, baseIndex, void 0); if (index === void 0 || denseRegions !== void 0 && regionContaining(denseRegions, index)) continue; emitChangedValue(operations, [...path, index], base[baseIndex], cloneStored(storedValue(node.context, valueIndex), node.context)); } } function cloneArrayRegion(node, region) { const overlay = arrayOverlay(node); const result = []; for (let index = region.start; index < region.start + region.length; index++) { const piece = locatePiece(overlay, index); const sourceIndex = piece.start + piece.step * overlay.locatedOffset; result.push(cloneStored(entryValueAt(node, piece, sourceIndex), node.context)); } return result; } function emitChangedValue(operations, path, before, after) { if (!isContainer3(after) && before === after) return false; if (isContainer3(before) && isContainer3(after) && equalTrustedJson(before, after)) return false; if (typeof before === "string" && typeof after === "string") { if (after.length > before.length && after.slice(0, before.length) === before) { operations.push(["a", path, ownedSlice2(after, before.length)]); return true; } const shared = overlap(before, after, 65536); if (shared > 0) { operations.push(["t", path, before.length - shared]); if (after.length > shared) operations.push(["a", path, ownedSlice2(after, shared)]); return true; } } operations.push(["s", path, after]); return true; } function emitSet2(operations, path, value2) { if (path.length === 0) operations.push(["r", value2]); else operations.push(["s", path, value2]); } function resolvePath(node) { if (node.preparedPath !== void 0) return node.preparedPath; const parent = nodeParent(node); if (parent === void 0) { node.preparedPath = []; return node.preparedPath; } const parentPath = resolvePath(parent); if (parentPath === void 0) return void 0; if (node.parentKind === 0) { const key = node.parentKey; if (!objectHas(parent, key) || objectValue(parent, key) !== nodeBase(node)) return void 0; node.preparedPath = [...parentPath, key]; return node.preparedPath; } const overlay = arrayOverlay(parent); const index = findEntryIndex(overlay, node.parentKind, node.parentKey, node.parentSource); if (index === void 0) return void 0; const piece = locatePiece(overlay, index); if (entryValueAt(parent, piece, node.parentKey) !== nodeBase(node)) return void 0; node.preparedPath = [...parentPath, index]; return node.preparedPath; } function ensurePieceLocations(overlay) { if (overlay.baseLocations !== void 0) return; overlay.baseLocations = []; overlay.insertLocations = /* @__PURE__ */ new Map(); let logicalStart = 0; for (const piece of piecesOf(overlay)) { const last = piece.start + piece.step * (piece.length - 1); const location = { piece, logicalStart, minimum: Math.min(piece.start, last), maximum: Math.max(piece.start, last) }; if (piece.kind === "base") overlay.baseLocations.push(location); else { let locations = overlay.insertLocations.get(piece.source); if (locations === void 0) { locations = []; overlay.insertLocations.set(piece.source, locations); } locations.push(location); } logicalStart += piece.length; } const byMinimum = (left, right) => left.minimum - right.minimum; overlay.baseLocations.sort(byMinimum); for (const locations of overlay.insertLocations.values()) locations.sort(byMinimum); } function findEntryIndex(overlay, kind, sourceIndex, source) { if (kind === 1 && !overlay.structural) return sourceIndex; ensurePieceLocations(overlay); const locations = kind === 1 ? overlay.baseLocations : overlay.insertLocations.get(source); if (locations === void 0) return void 0; let low = 0; let high = locations.length; while (low < high) { const middle = low + high >>> 1; if (locations[middle].minimum <= sourceIndex) low = middle + 1; else high = middle; } const location = locations[low - 1]; if (location === void 0 || sourceIndex > location.maximum) return void 0; const offset = (sourceIndex - location.piece.start) / location.piece.step; return offset >= 0 && offset < location.piece.length ? location.logicalStart + offset : void 0; } function ensureOperations(context) { if (context.ops !== void 0) return context.ops; assertReadable(context); if (context.replacement) { const base = context.tracker.deref()?.replacementBase(context); context.replacementNoop = base !== void 0 && equalTrustedJson(base, nodeBase(context.root)); context.ops = context.replacementNoop ? [] : [["r", nodeBase(context.root)]]; } else context.ops = emitOperations(context); return context.ops; } function equalTrustedJson(left, right) { if (left === right) return true; if (!isContainer3(left) || !isContainer3(right) || Array.isArray(left) !== Array.isArray(right)) return false; if (Array.isArray(left)) { const other2 = right; if (left.length !== other2.length) return false; for (let index = 0; index < left.length; index++) if (!equalTrustedJson(left[index], other2[index])) return false; return true; } const other = right; const keys = Object.keys(left); if (keys.length !== Object.keys(other).length) return false; for (const key of keys) if (!Object.hasOwn(other, key) || !equalTrustedJson(left[key], other[key])) return false; return true; } function abortContext(context) { if (context.status.value === "aborted" || context.status.value === "consumed" || context.status.value === "stale") return; context.status.value = "aborted"; clearContext(context); } function releaseOverlayReferences(context) { context.tracker.deref()?.releaseContext(context); if (locatedDenseArray?.context === context) locatedDenseArray = void 0; context.overlayReleased = true; context.dirty.length = 0; context.nodes.length = 0; context.rawNodes = void 0; context.ops = void 0; context.root = void 0; context.bases = void 0; context.stored = void 0; context.baseValue = void 0; } function clearContext(context) { context.tracker.deref()?.releaseContext(context); if (locatedDenseArray?.context === context) locatedDenseArray = void 0; for (const node of context.nodes) { if (Array.isArray(node.target)) node.target.length = 0; Reflect.deleteProperty(node.target, NODE); node.writes?.clear(); node.deletes?.clear(); node.readded?.clear(); if (node.array?.pieces !== void 0) node.array.pieces.length = 0; if (node.array !== void 0) { node.array.root = void 0; node.array.baseLocations = void 0; node.array.insertLocations = void 0; } node.array?.baseOverrides?.clear(); node.array?.insertOverrides?.clear(); node.parentSource = void 0; node.target = RELEASED; node.proxy = RELEASED; node.writeKey = void 0; node.writeValue = void 0; node.writes = void 0; node.deleteKey = void 0; node.deletes = void 0; node.readded = void 0; node.array = void 0; node.subtreeDirty = void 0; node.preparedPath = void 0; } context.dirty.length = 0; context.nodes.length = 0; context.rawNodes = void 0; context.ops = void 0; context.root = void 0; context.bases = void 0; context.stored = void 0; context.baseValue = void 0; context.overlayReleased = true; } function isContainer3(value2) { return value2 !== null && typeof value2 === "object"; } // node_modules/@earendil-works/chord/dist/delta/pending.js var MAX_PENDING_OPS = 1e4; var EMPTY = { kind: "empty" }; var REPLACE = { kind: "replace" }; var NO_OPS = { kind: "ops", ops: [] }; var REPLACE_TAKE = { kind: "replace" }; var PendingOps = class { #state = EMPTY; /** Queue the ops of one commit. */ push(ops3) { if (ops3.length === 0) return; const state = this.#state; if (ops3[0][0] === "r") { this.#state = { kind: "batch", ops: ops3 }; return; } switch (state.kind) { case "empty": this.#state = { kind: "batch", ops: ops3 }; return; case "batch": { const folding = { kind: "folding", ops: [], leaves: /* @__PURE__ */ new Map() }; this.#state = folding; if (this.#fold(state.ops, folding)) this.#fold(ops3, folding); return; } case "folding": this.#fold(ops3, state); return; case "replace": return; } } /** * Pending ops after folding; 1 once overflowed (the replacement the owner sends). One batch is counted as received, * so a single commit larger than `MAX_PENDING_OPS` counts in full until a second one arrives and overflows. */ get size() { const state = this.#state; if (state.kind === "empty") return 0; return state.kind === "replace" ? 1 : state.ops.length; } /** Hand over everything pending and start empty. */ take() { const state = this.#state; this.#state = EMPTY; switch (state.kind) { case "empty": return NO_OPS; case "batch": case "folding": return { kind: "ops", ops: state.ops }; case "replace": return REPLACE_TAKE; } } /** Fold `ops` into `folding`; false once the queue overflowed into `replace`. */ #fold(ops3, folding) { for (const op2 of ops3) { switch (op2[0]) { case "r": folding.ops.length = 0; folding.leaves.clear(); break; case "a": if (mergeLeaf(op2, folding)) continue; break; case "s": if (isPrimitive(op2[2])) { if (mergeLeaf(op2, folding)) continue; } else if (folding.leaves.size > 0) folding.leaves.clear(); break; default: if (folding.leaves.size > 0) folding.leaves.clear(); } folding.ops.push(op2); if (folding.ops.length > MAX_PENDING_OPS) { this.#state = REPLACE; return false; } } return true; } }; var isPrimitive = (value2) => value2 === null || typeof value2 !== "object"; function mergeLeaf(op2, folding) { const key = pathKey(op2[1]); const at = folding.leaves.get(key); if (at !== void 0) { const merged = merge2(folding.ops[at], op2); if (merged !== void 0) { folding.ops[at] = merged; return true; } } folding.leaves.set(key, folding.ops.length); return false; } function pathKey(path) { return JSON.stringify(path); } function merge2(previous, next) { if (next[0] === "s") return next; if (previous[0] === "a") return ["a", previous[1], previous[2] + next[2]]; if (typeof previous[2] === "string") return ["s", previous[1], previous[2] + next[2]]; return void 0; } // node_modules/@earendil-works/chord/dist/delta/index.js var isObj = (value2) => value2 !== null && typeof value2 === "object"; function overlap(a, b, scan2, probe2 = 64, maxCandidates = 8) { if (a.length === 0 || b.length === 0 || scan2 === 0) return 0; const tail = a.length > scan2 ? a.slice(a.length - scan2) : a; for (const h of [Math.min(probe2, b.length), 1]) { const head = b.slice(0, h); let tried = 0; for (let k = tail.indexOf(head); k !== -1; k = tail.indexOf(head, k + 1)) { if (++tried > maxCandidates) break; const n = tail.length - k; if (n <= b.length && tail.slice(k) === b.slice(0, n)) return n; } if (h === 1) break; } return 0; } function ownedSlice2(text, start, end = text.length) { return `${text.slice(start, end)} `.slice(0, -1); } var RESERVED_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]); var UnsafePathError = class extends Error { // Not a parameter property: Node's --experimental-strip-types rejects those, // and these files are meant to run under it directly. segment; constructor(segment) { super(`unsafe path segment: ${String(segment)}`); this.segment = segment; this.name = "UnsafePathError"; } }; function assertValidOp(op2) { if (!Array.isArray(op2) || op2.length === 0) throw new TypeError("op is not a tuple"); switch (op2[0]) { case "r": if (op2.length !== 2) throw new TypeError("r arity"); return; case "s": if (op2.length !== 3) throw new TypeError("s arity"); assertPathArg(op2[1], true); return; case "d": if (op2.length !== 2) throw new TypeError("d arity"); assertPathArg(op2[1], true); return; case "a": if (op2.length !== 3 || typeof op2[2] !== "string") throw new TypeError("a shape"); assertPathArg(op2[1], true); return; case "t": if (op2.length !== 3 || !Number.isInteger(op2[2]) || op2[2] < 0) throw new TypeError("t shape"); assertPathArg(op2[1], true); return; case "p": { if (op2.length !== 5) throw new TypeError("p arity"); assertPathArg(op2[1]); if (!Number.isInteger(op2[2]) || op2[2] < 0) throw new TypeError("p index"); if (!Number.isInteger(op2[3]) || op2[3] < 0) throw new TypeError("p remove"); if (!Array.isArray(op2[4])) throw new TypeError("p items"); return; } case "m": if (op2.length !== 3) throw new TypeError("m arity"); assertPathArg(op2[1]); assertPermutation(op2[2]); return; // Silently skipping an unknown verb is how a newer producer's op vanishes. default: throw new TypeError(`unknown op verb: ${String(op2[0])}`); } } function assertPathArg(p, nonEmpty = false) { if (!Array.isArray(p)) throw new TypeError("path is not an array"); if (nonEmpty && p.length === 0) throw new TypeError("path is empty"); assertSafePath(p); } function assertPermutation(value2) { if (!Array.isArray(value2)) throw new TypeError("m permutation is not an array"); const seen = new Uint8Array(value2.length); for (const index of value2) { if (!Number.isInteger(index) || index < 0 || index >= value2.length || seen[index] !== 0) { throw new TypeError("m permutation is not a bijection"); } seen[index] = 1; } } function assertValidWireOp(op2) { if (!Array.isArray(op2) || op2.length === 0) throw new TypeError("op is not a tuple"); const [verb] = op2; const okRef = (r) => { if (typeof r === "number") { if (!Number.isInteger(r) || r < 0) throw new TypeError("bad path id"); return; } if (!Array.isArray(r)) throw new TypeError("path is not an array"); assertSafePath(r); }; switch (verb) { case "r": if (op2.length !== 2) throw new TypeError("r arity"); return; case "s": if (op2.length === 3) okRef(op2[1]); else if (op2.length !== 2) throw new TypeError("s arity"); return; case "d": if (op2.length === 2) okRef(op2[1]); else if (op2.length !== 1) throw new TypeError("d arity"); return; case "a": if (op2.length === 3) { okRef(op2[1]); if (typeof op2[2] !== "string") throw new TypeError("a value"); } else if (op2.length === 2) { if (typeof op2[1] !== "string") throw new TypeError("a value"); } else throw new TypeError("a arity"); return; case "t": if (op2.length === 3) { okRef(op2[1]); if (!Number.isInteger(op2[2]) || op2[2] < 0) throw new TypeError("t count"); } else if (op2.length === 2) { if (!Number.isInteger(op2[1]) || op2[1] < 0) throw new TypeError("t count"); } else throw new TypeError("t arity"); return; case "p": { const [i, r, items] = op2.length === 5 ? [op2[2], op2[3], op2[4]] : op2.length === 4 ? [op2[1], op2[2], op2[3]] : []; if (items === void 0) throw new TypeError("p arity"); if (op2.length === 5) okRef(op2[1]); if (!Number.isInteger(i) || i < 0) throw new TypeError("p index"); if (!Number.isInteger(r) || r < 0) throw new TypeError("p remove"); if (!Array.isArray(items)) throw new TypeError("p items"); return; } case "m": if (op2.length === 3) okRef(op2[1]); else if (op2.length !== 2) throw new TypeError("m arity"); assertPermutation(op2[op2.length - 1]); return; case "#": { if (op2.length !== 3 || !Number.isInteger(op2[1]) || op2[1] < 0 || !Array.isArray(op2[2])) { throw new TypeError("# shape"); } assertSafePath(op2[2]); return; } // Silently skipping an unknown verb is how a newer producer's op vanishes. default: throw new TypeError(`unknown op verb: ${String(verb)}`); } } function assertSafePath(path) { for (const seg of path) { if (typeof seg === "string") { if (RESERVED_SEGMENTS.has(seg)) throw new UnsafePathError(seg); } else if (!Number.isInteger(seg) || seg < 0) { throw new UnsafePathError(seg); } } } function assertIndexInRange(parent, index) { if (index > parent.length) throw new UnsafePathError(index); } var PathError = class extends Error { path; constructor(path) { super(`unresolvable path: ${JSON.stringify(path)}`); this.path = path; this.name = "PathError"; } }; function apply(target, ops3) { return applyOps(target, ops3); } function applyOps(target, ops3) { let root2 = target; for (const op2 of ops3) { assertValidOp(op2); if (op2[0] === "r") { root2 = op2[1]; continue; } const path = op2[1]; assertSafePath(path); if (op2[0] === "p") { const target_ = path.length === 0 ? root2 : resolve(root2, path); if (!Array.isArray(target_)) throw new PathError(path); target_.splice(op2[2], op2[3]); const chunkSize = 1e4; for (let offset = 0; offset < op2[4].length; offset += chunkSize) { target_.splice(op2[2] + offset, 0, ...op2[4].slice(offset, offset + chunkSize)); } continue; } if (op2[0] === "m") { const target_ = path.length === 0 ? root2 : resolve(root2, path); if (!Array.isArray(target_) || target_.length !== op2[2].length) throw new PathError(path); const previous = target_.slice(); for (let index = 0; index < op2[2].length; index++) target_[index] = previous[op2[2][index]]; continue; } const parent = resolve(root2, path.slice(0, -1)); const key = path[path.length - 1]; if (Array.isArray(parent)) { if (typeof key !== "number") throw new UnsafePathError(key); assertIndexInRange(parent, key); } const write = (value2) => { Object.defineProperty(parent, key, { value: value2, writable: true, enumerable: true, configurable: true }); }; const read2 = () => Object.hasOwn(parent, key) ? parent[key] : void 0; switch (op2[0]) { case "s": write(op2[2]); break; case "d": if (Array.isArray(parent)) { if (typeof key !== "number" || key >= parent.length) throw new PathError(path); parent.splice(key, 1); } else delete parent[key]; break; case "a": { const current = read2(); if (typeof current !== "string") throw new PathError(path); write(`${current}${op2[2]}`); break; } case "t": { const current = read2(); if (typeof current !== "string") throw new PathError(path); write(current.slice(op2[2])); break; } } } return root2; } function applyImmutable(target, ops3) { return applyImmutableBatches(target, [ops3]); } function applyImmutableBatches(target, batches) { let root2 = target; const owned = /* @__PURE__ */ new WeakSet(); for (const ops3 of batches) { for (const op2 of ops3) { assertValidOp(op2); if (op2[0] === "r") { root2 = op2[1]; continue; } if (op2[0] === "p") { const appended = appendCopy(root2, op2, owned); if (appended !== void 0) { root2 = appended; continue; } } root2 = copyContainers(root2, op2[0] === "p" || op2[0] === "m" ? op2[1] : op2[1].slice(0, -1), owned); root2 = applyOps(root2, [op2]); } } return root2; } function appendCopy(root2, op2, owned) { const [, path, index, deleteCount, items] = op2; if (deleteCount !== 0) return void 0; let target = root2; for (const segment of path) { const valid = typeof segment === "string" ? !RESERVED_SEGMENTS.has(segment) && isObj(target) && !Array.isArray(target) : Number.isInteger(segment) && segment >= 0 && Array.isArray(target); if (!valid || !Object.hasOwn(target, segment)) return void 0; target = target[segment]; } if (!Array.isArray(target) || owned.has(target) || index !== target.length) return void 0; const appended = target.concat(items); owned.add(appended); if (path.length === 0) return appended; const parentPath = path.slice(0, -1); const copiedRoot = copyContainers(root2, parentPath, owned); Object.defineProperty(resolve(copiedRoot, parentPath), path[path.length - 1], { value: appended, writable: true, enumerable: true, configurable: true }); return copiedRoot; } function copyContainers(root2, path, owned) { const copy2 = (value2) => { if (Array.isArray(value2)) return value2.slice(); if (!isObj(value2)) throw new PathError(path); return Object.getPrototypeOf(value2) === null ? Object.assign(/* @__PURE__ */ Object.create(null), value2) : { ...value2 }; }; if (!isObj(root2)) throw new PathError(path); let copiedRoot; if (owned.has(root2)) copiedRoot = root2; else { copiedRoot = copy2(root2); owned.add(copiedRoot); } let destination = copiedRoot; for (const segment of path) { if (!Object.hasOwn(destination, segment)) throw new PathError(path); if (Array.isArray(destination) && typeof segment !== "number") throw new UnsafePathError(segment); const child = destination[segment]; if (!isObj(child)) throw new PathError(path); if (owned.has(child)) { destination = child; continue; } const copiedChild = copy2(child); Object.defineProperty(destination, segment, { value: copiedChild, writable: true, enumerable: true, configurable: true }); owned.add(copiedChild); destination = copiedChild; } return copiedRoot; } function resolveValue(root2, path) { let node = root2; for (const seg of path) { if (!isObj(node)) throw new PathError(path); if (Array.isArray(node) && typeof seg !== "number") throw new UnsafePathError(seg); if (!Object.hasOwn(node, seg)) throw new PathError(path); node = node[seg]; } return node; } function resolve(root2, path) { const node = resolveValue(root2, path); if (!isObj(node)) throw new PathError(path); return node; } var pathKey2 = (path) => JSON.stringify(path); function encoder() { const seen = /* @__PURE__ */ new Set(); const ids = /* @__PURE__ */ new Map(); let nextId = 0; let previous; return { encode(ops3) { previous = void 0; const out = []; for (const op2 of ops3) { if (op2[0] === "r") { out.push(op2); seen.clear(); ids.clear(); nextId = 0; previous = void 0; continue; } const path = op2[1]; const key = pathKey2(path); if (key === previous) { switch (op2[0]) { case "s": out.push(["s", op2[2]]); break; case "d": out.push(["d"]); break; case "a": out.push(["a", op2[2]]); break; case "t": out.push(["t", op2[2]]); break; case "p": out.push(["p", op2[2], op2[3], op2[4]]); break; case "m": out.push(["m", op2[2]]); break; } continue; } let ref = path; const existing = ids.get(key); if (existing !== void 0) { ref = existing; } else if (seen.has(key)) { const id = nextId++; ids.set(key, id); out.push(["#", id, path]); ref = id; } else { seen.add(key); } switch (op2[0]) { case "s": out.push(["s", ref, op2[2]]); break; case "d": out.push(["d", ref]); break; case "a": out.push(["a", ref, op2[2]]); break; case "t": out.push(["t", ref, op2[2]]); break; case "p": out.push(["p", ref, op2[2], op2[3], op2[4]]); break; case "m": out.push(["m", ref, op2[2]]); break; } previous = key; } return out; } }; } function decoder() { const paths = /* @__PURE__ */ new Map(); return { decode(wire) { let previous; const out = []; for (const op2 of wire) { assertValidWireOp(op2); if (op2[0] === "#") { assertSafePath(op2[2]); paths.set(op2[1], op2[2]); continue; } if (op2[0] === "r") { out.push(op2); paths.clear(); previous = void 0; continue; } const short = op2[0] === "d" && op2.length === 1 || op2[0] !== "d" && op2[0] !== "p" && op2.length === 2 || op2[0] === "p" && op2.length === 4; let path; if (short) { if (previous === void 0) throw new PathError([]); path = previous; } else { const ref = op2[1]; if (typeof ref === "number") { const resolved = paths.get(ref); if (resolved === void 0) throw new PathError(ref); path = resolved; } else { path = ref; } previous = path; } if (op2[0] !== "p" && op2[0] !== "m" && path.length === 0) throw new PathError(path); switch (op2[0]) { case "s": out.push(["s", path, short ? op2[1] : op2[2]]); break; case "d": out.push(["d", path]); break; case "a": out.push(["a", path, short ? op2[1] : op2[2]]); break; case "t": out.push(["t", path, short ? op2[1] : op2[2]]); break; case "p": { const [i, r, items] = short ? [op2[1], op2[2], op2[3]] : [op2[2], op2[3], op2[4]]; out.push(["p", path, i, r, items]); break; } case "m": out.push(["m", path, short ? op2[1] : op2[2]]); break; } } return out; } }; } // node_modules/@earendil-works/chord/dist/publish.js var queue = []; var held = 0; var draining = false; function enqueue(task) { queue.push(task); drain(); } function holdPublications(fn) { held++; try { return fn(); } finally { held--; drain(); } } function drain() { if (draining || held > 0) return; draining = true; try { for (let task = queue.shift(); task !== void 0; task = queue.shift()) task(); } finally { draining = false; } } // node_modules/@earendil-works/chord/dist/cell.js var sequence = 0; var Fanout = class { #values = /* @__PURE__ */ new Set(); #events = /* @__PURE__ */ new Set(); #report; constructor(report2) { this.#report = report2; } subscribe(listener, current) { this.#values.add(listener); this.#call(listener, current); return () => this.#values.delete(listener); } events(listener, current) { this.#events.add(listener); this.#call(listener, { type: "snap", value: current }); return () => this.#events.delete(listener); } /** * Queues delivery to the listeners subscribed now. A listener that subscribes later already got this revision in * its snapshot; one that unsubscribes before delivery is skipped. */ emit(event) { const events = [...this.#events]; const values = [...this.#values]; enqueue(() => { for (const listener of events) if (this.#events.has(listener)) this.#call(listener, event); for (const listener of values) if (this.#values.has(listener)) this.#call(listener, event.value); }); } #call(listener, value2) { try { listener(value2); } catch (error) { this.#report(error); } } }; var HYDRATION = /* @__PURE__ */ Symbol("chord.hydration"); function hydrationOf(value2) { if (value2 === null || typeof value2 !== "object") return void 0; return value2[HYDRATION]; } var unhydrated = (sources) => [...sources].map(hydrationOf).filter((hydration) => hydration?.hydrated === false); var Source = class { key; seq = ++sequence; dependents = /* @__PURE__ */ new Set(); /** Schemas of remote state members currently exposing this source (checked per write in dev mode). */ schemas = []; options; fanout; constructor(key, options) { this.key = key; this.options = options; this.fanout = new Fanout(options.report); } subscribe(listener) { return this.fanout.subscribe(listener, this.value); } events(listener) { return this.fanout.events(listener, this.value); } /** Per-write validation, dev mode only. */ validate(value2) { if (!this.options.dev) return; for (const schema of this.ownSchemas()) assertValid("state", `state ${this.key}`, schema, value2); for (const schema of this.schemas) assertValid("state", `state ${this.key}`, schema, value2); } ownSchemas() { return []; } }; function unbox(ops3, value2) { const result = []; for (const op2 of ops3) { if (op2[0] === "r" || op2[1].length === 0) return [["r", value2]]; const path = op2[1].slice(1); switch (op2[0]) { case "p": result.push(["p", path, op2[2], op2[3], op2[4]]); break; case "m": result.push(["m", path, op2[2]]); break; default: if (path.length === 0) return [["r", value2]]; if (op2[0] === "d") result.push(["d", path]); else result.push([op2[0], path, op2[2]]); } } return result; } var Cell = class extends Source { /** Replaced at a reload commit that changes the schema (state-version bump). */ schema; #tracker; #guard; /** False until `initialize` for a cell declared with `init`: reads and writes throw `not_ready`. */ #initialized; /** * `initial` is always validated against `schema`; ownership moves to the cell. Without `initial`, the cell has no * value until `initialize` (a cell declared with `init`, filled before its object goes live). */ constructor(key, schema, initial, guard, options) { super(key, options); if (initial !== void 0) assertValid("state", `initial value of state ${key}`, schema, initial.value); this.schema = schema; this.#tracker = track({ v: initial?.value ?? null }); this.#guard = guard; this.#initialized = initial !== void 0; } /** Sets the first value of a cell declared with `init` (validated; ownership moves to the cell). Publishes nothing. */ initialize(value2) { if (this.#initialized) throw new Error(`state ${this.key} is already initialized`); assertValid("state", `initial value of state ${this.key}`, this.schema, value2); this.#tracker = track({ v: value2 }); this.#initialized = true; } ownSchemas() { return [this.schema]; } get value() { if (!this.#initialized) throw notReady(`state ${this.key} (its init has not run)`); return this.#tracker.value.v; } change(ctx, fn) { this.#guard(ctx); if (!this.#initialized) throw notReady(`state ${this.key} (its init has not run)`); const change = this.#tracker.beginChange(); let prepared; try { const draft = change.state; const result = fn(draft.v); if (result instanceof Promise) throw new TypeError("cell.change callbacks must be synchronous"); if (result !== void 0) { if (result !== draft.v) assertValid("state", `state ${this.key}`, this.schema, result); draft.v = result; } prepared = change.prepare(); } catch (error) { change.abort(); throw error; } this.#publish(prepared, "ops"); } /** * Replaces the whole value. Ownership of `value` moves to the cell: never mutate it afterwards. * * Subscribers get a structural diff (`diffRevisions`) when `diff` is true, or by default when the new value shares * containers with the old one (a new immutable revision of the same state, e.g. mirrored from another store); * otherwise a single `r` op. The resulting value is the same either way; only the ops differ. */ replace(ctx, value2, options = {}) { this.#guard(ctx); if (!this.#initialized) throw notReady(`state ${this.key} (its init has not run)`); const before = this.value; const diff = options.diff ?? sharesStructure(before, value2); const ops3 = diff ? diffRevisions(before, value2) : void 0; this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops3); } /** * Installs a migrated value and its schema (reload commit). The caller validated the value and the derivations * over it against every schema that will apply, so nothing is checked here. Subscribers receive a reset. */ reset(value2, schema) { this.schema = schema; this.#publish(this.#tracker.prepareReplace({ v: value2 }), "reset", false); } /** Adopts this revision and every derived one, then queues all notifications (cell first, then derivations). */ #publish(prepared, type, validate2 = true, ops3) { if (prepared.ops.length === 0 && type === "ops") { this.#tracker.adopt(prepared); return; } const value2 = prepared.value.v; let candidates; try { if (validate2) this.validate(value2); candidates = computeDependents(this, value2, validate2); } catch (error) { prepared.abort(); throw error; } holdPublications(() => { this.#tracker.adopt(prepared); const published = ops3 !== void 0 && ops3.length > 0 ? ops3 : unbox(prepared.ops, value2); this.fanout.emit(type === "reset" ? { type, value: value2 } : { type, ops: published, value: value2 }); for (const [derived, next] of candidates) derived.install(next); }); } }; function sharesStructure(a, b) { if (a === null || b === null || typeof a !== "object" || typeof b !== "object" || a === b) return false; if (Array.isArray(a) !== Array.isArray(b)) return false; for (const [key, child] of Object.entries(b)) { if (child !== null && typeof child === "object" && a[key] === child) return true; } return false; } function computeDependents(changed, value2, validate2 = true) { const pending = /* @__PURE__ */ new Set(); const visit = (source) => { for (const derived of source.dependents) { if (pending.has(derived)) continue; pending.add(derived); visit(derived); } }; visit(changed); const values = /* @__PURE__ */ new Map([[changed, value2]]); const result = /* @__PURE__ */ new Map(); for (const derived of [...pending].sort((a, b) => a.seq - b.seq)) { const next = derived.compute((source) => values.has(source) ? values.get(source) : source.value); if (validate2) derived.validate(next); values.set(derived, next); result.set(derived, next); } return result; } var isReadable = (value2) => value2 !== null && typeof value2 === "object" && typeof value2.subscribe === "function" && "value" in value2; var Derived = class extends Source { sources; #fn; #value; #external = []; constructor(key, sources, fn, options) { super(key, options); this.sources = sources.map((source) => { if (!isReadable(source)) throw new TypeError(`derive ${key}: sources must be readables`); return source; }); this.#fn = fn; this.#value = void 0; } get value() { return this.#value; } /** Hydrated once every source is (a derivation over a remote member waits for its snapshot). */ get [HYDRATION]() { const pending = unhydrated(this.sources); if (pending.length === 0) return void 0; return { hydrated: false, whenHydrated: () => Promise.all(pending.map((hydration) => hydration.whenHydrated())).then(() => { }) }; } /** * Computes the first value (or takes it from `precomputed`, a reload's validated dry run) and starts following * the sources (at install, never inside a factory). */ attach(precomputed) { this.#value = precomputed?.has(this) ? precomputed.get(this) : this.compute((source) => source.value); for (const source of this.sources) { if (source instanceof Source) source.dependents.add(this); else this.#external.push(source.subscribe(() => this.refresh())); } } detach() { for (const source of this.sources) if (source instanceof Source) source.dependents.delete(this); for (const unsubscribe of this.#external.splice(0)) unsubscribe(); } compute(read2) { const result = this.#fn(...this.sources.map(read2)); if (result instanceof Promise) throw new TypeError(`derive ${this.key}: fn must be synchronous`); return result; } /** Recomputes and republishes, including dependents. Errors are reported (the source write already happened). */ refresh() { try { const next = this.compute((source) => source.value); const dependents = computeDependents(this, next); holdPublications(() => { this.install(next); for (const [derived, value2] of dependents) derived.install(value2); }); } catch (error) { this.options.report(error); } } install(next) { const before = this.#value; this.#value = next; const ops3 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next); if (ops3.length > 0) this.fanout.emit({ type: "ops", ops: ops3, value: next }); } }; var isSource = (value2) => value2 instanceof Source; var notReady = (what) => new ChordError("not_ready", `${what} is not ready`); // node_modules/@earendil-works/chord/dist/object.js var noop = () => { }; var makeService = (contract, factory, options, facet) => { checkLifetime(contract, options); return { contract, factory, options, facet }; }; var hostLifetime = (service2) => service2.options.lifetime === "host"; function checkLifetime(contract, options) { const lifetime = options.lifetime ?? "referenced"; if (lifetime !== "referenced" && lifetime !== "host") throw new TypeError(`${contract.name}: unknown lifetime ${String(lifetime)}`); if (lifetime !== "host") return; if (contract.scope !== "local") throw new TypeError(`${contract.name}: lifetime "host" needs a local type (remote types are referenced)`); if (options.linger !== void 0) throw new TypeError(`${contract.name}: an object of lifetime "host" does not linger (it lives as long as the host)`); try { normalize(contract.input, {}, `${contract.name} input`); } catch { throw new TypeError(`${contract.name}: lifetime "host" needs an input that accepts {} (the object's one address)`); } } var ExposedImpl = class { resource; constructor(resource) { this.resource = resource; } }; var ResourceImpl = class { key; /** The declaration of the factory run whose code is current: a kept resource adopts the new one at commit. */ spec; deps; /** `unavailable`: a close/reopen failed during a reload rollback; the value is gone for good. */ state = "closed"; #value; constructor(key, spec, deps) { this.key = key; this.spec = spec; this.deps = deps; } get value() { if (this.state === "unavailable") throw new ChordError("unavailable", `resource ${this.key} could not be restored`); if (this.state !== "open") throw notReady(`resource ${this.key}`); return this.#value; } get exclusive() { return this.spec.exclusive === true; } /** * Opens the resource. An open that completes after `signal` aborted (rollback, abandonment) closes the value it * produced at once: nobody owns it any more. */ async open(signal, order) { if (this.state !== "closed") return; this.state = "opening"; let value2; try { value2 = await this.spec.open({ signal }); } catch (error) { this.state = "closed"; throw error; } if (signal.aborted) { this.state = "closed"; await this.spec.close?.(value2); throw new ChordError("aborted", `resource ${this.key} opened after abort`); } this.#value = value2; this.state = "open"; order.push(this); } async close(order) { if (this.state !== "open") return; this.state = "closed"; const index = order.indexOf(this); if (index !== -1) order.splice(index, 1); const value2 = this.#value; this.#value = void 0; await this.spec.close?.(value2); } markUnavailable() { this.state = "unavailable"; this.#value = void 0; } /** * A reload kept this resource (same version, deps and exclusivity): its later `close` is the new declaration's, and * the old declaration (with the old code it closes over) is no longer referenced. */ adopt(spec) { this.spec = spec; } }; var ResourceFacade = class { #resource; #host; constructor(resource, host2) { this.#resource = resource; this.#host = host2; facades.set(this, resource); } get key() { return this.#resource.key; } get value() { if (this.#host.inFactory > 0) throw notReady(`resource ${this.key} (inside a factory)`); return this.#resource.value; } }; var facades = /* @__PURE__ */ new WeakMap(); var resourceOf = (value2) => value2 instanceof ResourceImpl ? value2 : value2 !== null && typeof value2 === "object" ? facades.get(value2) : void 0; var Decls = class { cells = /* @__PURE__ */ new Map(); cellSchemas = /* @__PURE__ */ new Map(); cellVersions = /* @__PURE__ */ new Map(); cellMigrations = /* @__PURE__ */ new Map(); refs = /* @__PURE__ */ new Map(); resources = /* @__PURE__ */ new Map(); /** Resources this run kept from the previous one, with this run's declarations (adopted at commit). */ adopted = /* @__PURE__ */ new Map(); listeners = /* @__PURE__ */ new Map(); /** By position (`#0`, `#1`, …): tasks have no names. */ tasks = /* @__PURE__ */ new Map(); /** By position: derivations have no names and are new at every run. */ derives = /* @__PURE__ */ new Map(); registrations = /* @__PURE__ */ new Map(); }; var isStateInit = (value2) => value2 !== null && typeof value2 === "object" && typeof value2.init === "function"; var CtxImpl = class _CtxImpl { host; signal; rootId; codeVersion; principal; /** Code holders (tasks): every call they make starts a fresh root. */ holder; #refs = []; #release; /** Aborted when the ctx closes: awaited `get`s give up. */ #closing = new AbortController(); #closed = false; /** True after the call or root that received this ctx settled: it may no longer start work. */ get closed() { return this.#closed; } /** Throws unless this ctx may start work on `host` (the called reference's host) now: not expired, aborted or foreign. */ check(host2) { if (this.host !== host2) throw invalid("foreign_ctx", "ctx belongs to another host"); if (this.#closed) throw new ChordError("aborted", "ctx expired: its call already settled", { reason: "expired" }); if (this.signal.aborted) { const reason = this.signal.reason; if (reason instanceof ChordError && reason.code === "superseded") throw new ChordError("superseded", reason.message, { cause: reason }); throw new ChordError("aborted", "ctx aborted", { cause: reason }); } } /** `signals`: the ctx is aborted with the first of them; it stops following them when it closes. */ constructor(host2, fields) { this.host = host2; const linked = linkedSignal(fields.signals); this.signal = linked.signal; this.#release = linked.release; this.rootId = fields.rootId; this.codeVersion = fields.codeVersion; this.principal = fields.principal; this.holder = fields.holder ?? false; } async get(type, input, options) { this.check(this.host); const ref = this.host.acquire(type, input, { principal: this.principal, ...options }, "call"); this.#refs.push(ref); await awaitReady(ref, [this.signal, this.#closing.signal], this.host.readyTimeout); return ref.proxy; } /** * A context for a call into `object`: same root and code version, its own call-owned references, aborted on forced * disposal. Code inside an object acts as the host principal. */ child(object) { return new _CtxImpl(this.host, { signals: [this.signal, object.callAbort.signal], rootId: this.rootId, codeVersion: this.codeVersion, principal: this.host.principal }); } close() { this.#closed = true; this.#closing.abort(new ChordError("aborted", "ctx expired: its call already settled", { reason: "expired" })); this.#release(); for (const ref of this.#refs.splice(0)) ref.release(); } }; function linkedSignal(sources) { const controller = new AbortController(); const detach = []; const release = () => { for (const remove of detach.splice(0)) remove(); }; for (const source of sources) { if (source.aborted) { release(); controller.abort(source.reason); break; } const forward = () => { release(); controller.abort(source.reason); }; source.addEventListener("abort", forward, { once: true }); detach.push(() => source.removeEventListener("abort", forward)); } return { signal: controller.signal, release }; } async function awaitReady(ref, signals, ms) { const timeout = new AbortController(); const timer = Number.isFinite(ms) ? setTimeout(() => timeout.abort(new ChordError("unavailable", `${ref.address} was not ready after ${ms} ms`, { reason: "ready_timeout" })), ms) : void 0; timer?.unref?.(); const linked = linkedSignal([...signals, timeout.signal]); try { await raceSignal(ref.ready, linked.signal); } catch (error) { ref.release(); const reason = linked.signal.reason; throw reason instanceof ChordError && reason.reason === "ready_timeout" ? reason : error; } finally { clearTimeout(timer); linked.release(); } } async function raceSignal(promise, signal) { if (signal.aborted) throw new ChordError("aborted", "call aborted", { cause: signal.reason }); let onAbort = noop; const aborted = new Promise((_, reject2) => { onAbort = () => reject2(new ChordError("aborted", "call aborted", { cause: signal.reason })); signal.addEventListener("abort", onAbort, { once: true }); }); try { return await Promise.race([promise, aborted]); } finally { signal.removeEventListener("abort", onAbort); } } var REF_BASE = /* @__PURE__ */ new Set([ "contract", "status", "ready", "variant", "shape", "error", "onShape", "onStatus", "release" ]); function notifyStatus(listeners, status, error, report2) { if (listeners.size === 0) return; const current = [...listeners]; enqueue(() => { for (const listener of current) { try { listener(status, error); } catch (thrown) { report2(thrown); } } }); } var refsByProxy = /* @__PURE__ */ new WeakMap(); var refOf = (value2) => value2 !== null && typeof value2 === "object" ? refsByProxy.get(value2) : void 0; var MemberReadable = class { #ref; #name; #values = /* @__PURE__ */ new Set(); #events = /* @__PURE__ */ new Set(); /** Event listeners that have not had their snapshot yet (added while the member was not readable). */ #unseen = /* @__PURE__ */ new Set(); #source; #upstream = noop; /** The value listeners last saw; a first upstream snapshot equal to it is not re-announced. */ #seen; constructor(ref, name) { this.#ref = ref; this.#name = name; } get value() { const ref = this.#ref; if (ref.status === "released" || ref.status === "gone") throw ref.terminalError(); if (ref.status !== "live") throw notReady(`${ref.address}.${this.#name}`); const view2 = ref.currentView(); if (view2 === void 0) throw notReady(`${ref.address}.${this.#name}`); const source = this.#current(); if (source === void 0) { throw new ChordError("not_in_variant", `${ref.address}: ${this.#name} is not a state member of variant ${variantOf(view2) ?? "(local)"}`); } return source.value; } /** Not hydrated while the reference is not live, or while its source (a forwarded remote member) is not. */ get [HYDRATION]() { const ref = this.#ref; if (ref.status === "live") return hydrationOf(this.#current()); return { hydrated: false, whenHydrated: async () => { await ref.ready; await hydrationOf(this.#current())?.whenHydrated(); } }; } subscribe(listener) { this.#values.add(listener); const source = this.#readable(); if (source !== void 0) { this.#seen = { value: source.value }; this.#safe(() => listener(source.value)); } this.bind(); return () => this.#remove(this.#values, listener); } events(listener) { this.#events.add(listener); const source = this.#readable(); if (source !== void 0) { const value2 = source.value; this.#seen = { value: value2 }; this.#safe(() => listener({ type: "snap", value: value2 })); } else this.#unseen.add(listener); this.bind(); return () => { this.#unseen.delete(listener); this.#remove(this.#events, listener); }; } /** (Re)subscribes upstream to the current source; a changed source is announced as a reset. */ bind() { const source = this.#values.size + this.#events.size > 0 ? this.#current() : void 0; if (source === this.#source) return; this.#upstream(); this.#upstream = noop; this.#source = source; if (source === void 0) return; let first = true; this.#upstream = source.events((event) => { const isFirst = first; first = false; if (isFirst && this.#unseen.size === 0 && this.#seen !== void 0 && event.value === this.#seen.value) return; this.#seen = { value: event.value }; const translated = isFirst ? { type: "reset", value: event.value } : event; const events = [...this.#events]; const values = [...this.#values]; const unseen = new Set(this.#unseen); this.#unseen.clear(); enqueue(() => { for (const listener of events) { if (!this.#events.has(listener)) continue; const delivered = unseen.has(listener) ? { type: "snap", value: event.value } : translated; this.#safe(() => listener(delivered)); } for (const listener of values) if (this.#values.has(listener)) this.#safe(() => listener(event.value)); }); }); } stop() { this.#values.clear(); this.#events.clear(); this.#unseen.clear(); this.bind(); } #current() { const view2 = this.#ref.currentView(); if (view2 === void 0 || !this.#ref.inVariant(view2, this.#name)) return void 0; const value2 = view2[this.#name]; return isSource(value2) || isReadableLike(value2) ? value2 : void 0; } /** The current source if its value can be read now (live, in the variant, hydrated). */ #readable() { const source = this.#current(); return source === void 0 || hydrationOf(source)?.hydrated === false ? void 0 : source; } #remove(set, listener) { set.delete(listener); this.bind(); } #safe(fn) { try { fn(); } catch (error) { this.#ref.object.host.report(error); } } }; var RefImpl = class { object; contract; owner; address; proxy; ready; status; error; shapeListeners = /* @__PURE__ */ new Set(); /** Internal: notified when the reference becomes gone (links forward it). */ goneListeners = /* @__PURE__ */ new Set(); readables = /* @__PURE__ */ new Map(); /** Weak: a retired code version's service (and the factory code it holds) must not be kept alive by its consumers. */ bindings = /* @__PURE__ */ new WeakMap(); views = /* @__PURE__ */ new WeakMap(); callers = /* @__PURE__ */ new Map(); constructor(object, contract, owner) { this.object = object; this.contract = contract; this.owner = owner; this.address = object.address; this.status = object.status === "live" ? "live" : "loading"; this.ready = object.ready.then(() => { if (this.status === "gone") throw this.error; if (this.status === "released") throw new ChordError("released", `reference ${this.address} released`); }); this.ready.catch(noop); this.proxy = new Proxy(this, { get: (target, property) => { if (REF_BASE.has(property)) { const value2 = Reflect.get(target, property, target); return typeof value2 === "function" ? value2.bind(target) : value2; } if (typeof property !== "string" || property === "then") return void 0; return target.member(property); }, has: (target, property) => REF_BASE.has(property) || target.member(String(property)) !== void 0 }); refsByProxy.set(this.proxy, this); } get variant() { const view2 = this.currentView(); return view2 === void 0 ? void 0 : variantOf(view2); } get shape() { const installed = this.status === "live" ? this.object.currentInstalled() : void 0; const view2 = installed === void 0 ? void 0 : this.#safeView(installed); if (installed === void 0 || view2 === void 0) return void 0; return { variant: variantOf(view2), members: Object.keys(view2).filter((key) => key !== "variant"), contract: installed.service.contract.fingerprint }; } onShape(listener) { this.shapeListeners.add(listener); return () => this.shapeListeners.delete(listener); } onStatus(listener) { this.#statusListeners.add(listener); return () => this.#statusListeners.delete(listener); } #statusListeners = /* @__PURE__ */ new Set(); #statusChanged() { notifyStatus(this.#statusListeners, this.status, this.error, (error) => this.object.host.report(error)); } release() { if (this.status === "released" || this.status === "gone") return; this.status = "released"; this.#stopReadables(); this.#statusChanged(); this.object.release(this); } // ─── internal ─── live() { if (this.status !== "loading") return; this.status = "live"; this.#statusChanged(); this.shapeChanged(); } gone(error) { if (this.status === "released" || this.status === "gone") return; this.status = "gone"; this.error = error; this.#stopReadables(); this.#statusChanged(); for (const listener of [...this.goneListeners]) listener(error); } shapeChanged() { for (const readable of this.readables.values()) readable.bind(); const shape = this.shape; if (shape === void 0) return; const listeners = [...this.shapeListeners]; enqueue(() => { for (const listener of listeners) { try { listener(shape); } catch (error) { this.object.host.report(error); } } }); } terminalError() { return this.status === "gone" ? this.error : new ChordError("released", `reference ${this.address} released`); } /** The implementation as this consumer sees it (through an accepts adapter when bound to another version). */ viewOf(installed) { let adapter = this.bindings.get(installed.service); if (!this.bindings.has(installed.service)) { adapter = bind(installed.service.contract, installed.service.options.accepts, this.contract); this.bindings.set(installed.service, adapter); } if (adapter === void 0) return installed.implementation; let view2 = this.views.get(installed.implementation); if (view2 === void 0) { view2 = adapter(installed.implementation); this.views.set(installed.implementation, view2); } return view2; } currentView() { if (this.status !== "live") return void 0; const installed = this.object.currentInstalled(); return installed === void 0 ? void 0 : this.#safeView(installed); } /** The view, or undefined when this consumer no longer binds (reported; calls reject with the bind error). */ #safeView(installed) { try { return this.viewOf(installed); } catch (error) { this.object.host.report(error); return void 0; } } describe(variant, member) { const variants = this.contract.description.variants; if (variant !== void 0) return variants[variant]?.[member]; for (const members of Object.values(variants)) if (members[member] !== void 0) return members[member]; return void 0; } /** Remote consumers see only the members their contract lists for the current variant. */ inVariant(view2, name) { return this.contract.scope !== "remote" || this.describe(variantOf(view2), name) !== void 0; } member(name) { const declared = this.describe(void 0, name); const view2 = this.currentView(); const value2 = view2?.[name]; if (declared?.kind === "method" || declared === void 0 && typeof value2 === "function") { return this.caller(name); } if (declared?.kind === "state" || declared === void 0 && (isSource(value2) || isReadableLike(value2))) { let readable = this.readables.get(name); if (readable === void 0) { readable = new MemberReadable(this, name); this.readables.set(name, readable); } return readable; } if (view2 === void 0 || !this.inVariant(view2, name)) return void 0; return value2 instanceof ExposedImpl ? value2.resource.value : value2; } caller(name) { let caller = this.callers.get(name); if (caller === void 0) { const noArgs = takesNoArgs(this.contract, name); caller = (...given) => { const [args, ctx] = noArgs ? [void 0, given[0]] : given; if (this.object.host.inFactory > 0) { throw new ChordError("not_ready", `${this.address}.${name} called inside a factory`); } if (ctx !== void 0 && !(ctx instanceof CtxImpl)) { return Promise.reject(invalid("foreign_ctx", "ctx must come from a Chord host")); } if (ctx === void 0 || ctx.holder) { try { ctx?.check(this.object.host); } catch (error) { return Promise.reject(error); } return this.object.host.root((root2) => this.object.call(this, name, args, root2), { signal: ctx?.signal }); } return this.object.call(this, name, args, ctx); }; this.callers.set(name, caller); } return caller; } #stopReadables() { for (const readable of this.readables.values()) readable.stop(); } }; var variantOf = (implementation) => typeof implementation.variant === "string" ? implementation.variant : void 0; var sameSelect = (a, b) => a === b || a !== void 0 && b !== void 0 && String(a) === String(b); var ObjectImpl = class { host; address; typeName; input; principal; /** The one object of a `lifetime: "host"` type: only such objects may register. */ hostLifetime; status = "activating"; ready = Promise.resolve(); service; /** Calls carrying an older code version are rejected upgrade_conflict (state bump or exclusive resource replaced). */ minCodeVersion = 0; decls = new Decls(); /** Declarations of a factory run that has not been installed yet (activation or reload candidate). */ pending = /* @__PURE__ */ new Set(); implementations = /* @__PURE__ */ new Map(); refs = /* @__PURE__ */ new Set(); leases = /* @__PURE__ */ new Map(); openOrder = []; callAbort = new AbortController(); activationAbort = new AbortController(); gate; /** Served only by a running reload's pending code version: listeners, tasks and registrations start at its commit. */ deferredRuntime = false; #subscriptions = /* @__PURE__ */ new Map(); #invocations = /* @__PURE__ */ new Set(); #tasks = /* @__PURE__ */ new Map(); /** Aborted task runs that have not finished yet. */ #stopped = /* @__PURE__ */ new Set(); /** Bumped by every start and stop of the tasks: a delayed start that is no longer the latest is dropped. */ #taskRuns = 0; #linger; #disposal; constructor(host2, identity) { this.host = host2; this.address = identity.address; this.typeName = identity.typeName; this.input = identity.input; this.principal = identity.principal; this.hostLifetime = identity.hostLifetime; } start(after) { this.ready = this.#activate(after).then(() => { for (const ref of this.refs) ref.live(); this.#maybeIdle(); }, (cause) => { if (this.status === "activating") this.status = "failed"; this.host.forget(this); const error = asChordError(cause, `activating ${this.address}`); for (const ref of this.refs) ref.gone(error); throw error; }); this.ready.catch(noop); } /** * Activation as one transaction: factory, opens, then a synchronous finalization (registry check and publication, * implementations, runtime). Every failure, including one during finalization or a release while listeners start, * unwinds everything this activation did. */ async #activate(after) { await after?.catch(noop); const signal = this.activationAbort.signal; const abandoned = () => new ChordError("aborted", `${this.address} abandoned`); if (signal.aborted) throw abandoned(); const service2 = this.host.service(this.typeName); if (service2 === void 0) throw new ChordError("unavailable", `${this.typeName} is not served`, { reason: "unknown_type" }); this.service = service2; const run2 = this.run(service2, void 0); try { await this.open(run2.created.resources, run2.created.refs, signal); if (signal.aborted) throw abandoned(); await this.waitHydrated(run2); if (signal.aborted) throw abandoned(); await this.initCells(run2, signal); if (signal.aborted) throw abandoned(); this.host.checkRegistrations(this, run2.decls); this.decls = run2.decls; for (const [retained, services] of this.host.retainedCodeVersions()) { if (services.get(this.typeName) === service2) { this.implementations.set(retained, { codeVersion: retained, service: service2, implementation: run2.implementation, decls: run2.decls }); } } const pending = this.host.pendingCodeVersion(); if (!this.implementations.has(this.host.currentCodeVersion()) && (pending === void 0 || !this.implementations.has(pending))) { throw new ChordError("superseded", `${this.address}: code changed during activation`); } for (const derived of run2.created.derives) derived.attach(); this.refreshSchemas(); this.deferredRuntime = !this.implementations.has(this.host.currentCodeVersion()); if (!this.deferredRuntime) { if (run2.decls.registrations.size > 0) this.host.registriesChanged(this); this.startRuntime(); } if (signal.aborted) throw abandoned(); this.status = "live"; } catch (error) { this.callAbort.abort(error); this.stopListeners(); this.stopTasks(); const registered = this.decls.registrations.size > 0 && !this.deferredRuntime; this.implementations.clear(); this.decls = new Decls(); if (registered) this.host.registriesChanged(); await Promise.allSettled([...this.#invocations, ...this.#stopped]); await this.unwind(run2).catch((cleanup) => this.host.report(cleanup)); throw error; } finally { this.pending.delete(run2.decls); } } /** * Runs the factory synchronously. With `previous`, runs in recording mode for a reload prepare. The run's * declarations stay in `pending` (visible to the cycle check) until the caller installs or discards them. */ run(service2, previous) { const self = new SelfImpl(this, previous); const discard = (run3) => { this.pending.delete(run3.decls); void this.unwind(run3).catch((cleanup) => this.host.report(cleanup)); }; this.pending.add(self.run(void 0).decls); this.host.inFactory++; let implementation; try { implementation = service2.factory(this.input, this.principal, self); } catch (error) { this.host.inFactory--; discard(self.run(void 0)); throw error; } this.host.inFactory--; const run2 = self.run(implementation); try { if (implementation instanceof Promise) throw invalid("implementation", `${this.typeName}: factories must be synchronous`); validateImplementation(service2, run2.implementation); } catch (error) { discard(run2); throw error; } return run2; } /** Opens resources (dependencies first) and waits for references; settles all before failing. */ async open(resources, refs, signal) { const opening = /* @__PURE__ */ new Map(); const openOne = (resource) => { let promise = opening.get(resource); if (promise === void 0) { promise = (async () => { await Promise.all(resource.deps.map((dep) => !(dep instanceof ResourceImpl) ? dep.ready : resources.includes(dep) ? openOne(dep) : void 0)); await resource.open(signal, this.openOrder); })(); opening.set(resource, promise); } return promise; }; const ready = this.withinReadyTimeout(Promise.all(refs.map((ref) => ref.ready)), () => refs.filter((ref) => ref.status === "loading").map((ref) => ref.address)); const results = await Promise.allSettled([...resources.map(openOne), ready]); const failure = results.find((result) => result.status === "rejected"); if (failure !== void 0) throw failure.reason; } /** * Waits until the state sources a run exposes or derives from have their values: a local object goes live only with * its state, also where it forwards or derives from a remote reference's member. */ async waitHydrated(run2) { const exposed = Object.entries(run2.implementation ?? {}); const pending = unhydrated([...run2.created.derives, ...exposed.map(([, value2]) => value2)]); if (pending.length === 0) return; await this.withinReadyTimeout(Promise.all(pending.map((hydration) => hydration.whenHydrated())), () => [ ...exposed.filter(([, value2]) => hydrationOf(value2)?.hydrated === false).map(([name]) => `${this.address}.${name}`), ...run2.created.derives.filter((derived) => hydrationOf(derived) !== void 0).map((derived) => `derive ${derived.key}`) ]); } /** `work` within the host's `readyTimeout`; a timeout names what is still awaited. */ async withinReadyTimeout(work, waiting) { const ms = this.host.readyTimeout; if (!Number.isFinite(ms)) return work; let timer; const timeout = new Promise((_, reject2) => { timer = setTimeout(() => { const list = waiting(); reject2(new ChordError("unavailable", `${this.address} was not ready after ${ms} ms${list.length === 0 ? "" : `, waiting on ${list.join(", ")}`} (a reference cycle across hosts?)`, { reason: "ready_timeout" })); }, ms); timer.unref?.(); }); timeout.catch(noop); try { return await Promise.race([work, timeout]); } finally { clearTimeout(timer); } } /** * Fills the cells `run` created with `init`, each in a root of its own, in parallel; settles all before failing. Runs * after the run's resources are open and its references live, before anything reads the cells. */ async initCells(run2, signal) { if (run2.created.inits.length === 0) return; const results = await Promise.allSettled(run2.created.inits.map(async ([cell, init]) => { const value2 = await this.host.root((ctx) => init(ctx), { signal }); if (!signal.aborted) cell.initialize(value2); })); const failure = results.find((result) => result.status === "rejected"); if (failure !== void 0) throw failure.reason; } /** Undoes the objects a run created: closes its resources (reverse order) and releases its references. */ async unwind(run2) { for (const derived of run2.created.derives) derived.detach(); const resources = [...run2.created.resources].sort((a, b) => this.openOrder.indexOf(b) - this.openOrder.indexOf(a)); await runAll([ ...resources.map((resource) => () => resource.close(this.openOrder)), ...run2.created.refs.map((ref) => () => ref.release()) ], `${this.address}: cleanup failed`); } currentInstalled() { return this.implementations.get(this.host.currentCodeVersion()) ?? [...this.implementations.values()].at(-1); } /** Every state source exposed as a remote state member gets that member's schema (checked per write in dev mode). */ refreshSchemas() { const schemas = /* @__PURE__ */ new Map(); for (const installed of this.implementations.values()) { for (const [source, list] of memberSchemas(installed.service, installed.implementation)) { schemas.set(source, [...schemas.get(source) ?? [], ...list]); } } for (const decls of this.allDecls()) { for (const source of [...decls.cells.values(), ...decls.derives.values()]) source.schemas = schemas.get(source) ?? []; } } allDecls() { const result = /* @__PURE__ */ new Set([this.decls, ...this.pending]); for (const installed of this.implementations.values()) result.add(installed.decls); return result; } // ─── runtime: listeners and tasks of the current declarations ─── /** The resource a listener is declared on, or its source itself. */ #origin(decl) { return resourceOf(decl.source) ?? decl.source; } /** Resolves a listener's subscribable: `select(resource value)`, `select(source)` or the source itself. */ #listenSource(key, decl) { const resource = resourceOf(decl.source); const value2 = resource === void 0 ? decl.source : resource.value; const source = decl.select === void 0 ? value2 : decl.select(value2); if (typeof source?.subscribe !== "function") throw invalid("implementation", `listener ${key}: source has no subscribe()`); return source; } /** * Subscribes the listeners of `decls` without delivering: events are buffered until `activate`. A listener declared * on the same resource (or source) with the same `select` keeps its subscription without calling `select` again * (only its function is swapped, by name), so sources that accept a single subscriber survive reloads. Throws, with * nothing left subscribed, if any source fails. */ stageListeners(decls) { const fresh = /* @__PURE__ */ new Map(); const kept = /* @__PURE__ */ new Set(); try { for (const [key, decl] of decls.listeners) { const origin = this.#origin(decl); const previous = this.#subscriptions.get(key); if (previous !== void 0 && previous.origin === origin && sameSelect(previous.select, decl.select)) { kept.add(key); continue; } const source = this.#listenSource(key, decl); const entry2 = { origin, select: decl.select, unsubscribe: noop, buffer: [] }; fresh.set(key, entry2); if (isSource(source) || isReadableLike(source)) { entry2.buffer = void 0; entry2.subscribe = () => { entry2.unsubscribe = source.subscribe((event) => this.#deliver(key, event)); }; continue; } entry2.unsubscribe = source.subscribe((event) => { if (entry2.buffer !== void 0) entry2.buffer.push(event); else this.#deliver(key, event); }); } } catch (error) { for (const entry2 of fresh.values()) entry2.unsubscribe(); throw error; } return { activate: () => { for (const [key, subscription] of this.#subscriptions) { if (kept.has(key)) continue; subscription.unsubscribe(); this.#subscriptions.delete(key); } for (const [key, entry2] of fresh) { this.#subscriptions.set(key, entry2); entry2.subscribe?.(); const buffered = entry2.buffer ?? []; entry2.buffer = void 0; for (const event of buffered) this.#deliver(key, event); } }, discard: () => { for (const entry2 of fresh.values()) entry2.unsubscribe(); } }; } startRuntime() { this.stageListeners(this.decls).activate(); this.startTasks(); } /** * Starts the tasks of the current declarations. Runs aborted by `stopTasks` (a reload, a gate) may still be finishing: * their replacements start once they all finished, or after `taskStopDeadline` (reported), so the old and the new run * of a task never work side by side. A later start or stop, or the object leaving `live`, cancels a delayed start. */ startTasks() { const run2 = ++this.#taskRuns; const start = () => { for (const key of this.decls.tasks.keys()) this.#startTask(key, this.host.currentCodeVersion()); }; if (this.#stopped.size === 0) { start(); return; } const deadline = this.host.taskStopDeadline; let timer; const late = new Promise((resolve2) => { timer = setTimeout(() => resolve2(true), deadline); }); const stopped = Promise.allSettled([...this.#stopped]).then(() => false); void Promise.race([stopped, late]).then((missed) => { clearTimeout(timer); if (run2 !== this.#taskRuns || this.status !== "live") return; if (missed) { this.host.report(new ChordError("aborted", `${this.address}: aborted tasks still running after ${deadline} ms; starting their replacements anyway`)); } start(); }); } stopListeners() { for (const subscription of this.#subscriptions.values()) subscription.unsubscribe(); this.#subscriptions.clear(); } #startTask(key, codeVersion) { const fn = this.decls.tasks.get(key); if (fn === void 0) return; const controller = new AbortController(); const ctx = new CtxImpl(this.host, { signals: [controller.signal, this.callAbort.signal], rootId: `task:${this.address}:${key}`, codeVersion, principal: this.host.principal, holder: true }); const done = (async () => { try { await fn(ctx); } catch (error) { if (!ctx.signal.aborted) this.host.report(error); } finally { ctx.close(); if (this.#tasks.get(key)?.controller === controller) this.#tasks.delete(key); } })(); this.#tasks.set(key, { controller, done }); } /** * Aborts running tasks (and cancels a delayed start). Their completion is awaited by the next `startTasks` and at * disposal; each leaves `#stopped` when it finishes. */ stopTasks() { this.#taskRuns++; for (const task of this.#tasks.values()) { task.controller.abort(new ChordError("aborted", "task stopped")); const done = task.done; this.#stopped.add(done); void done.finally(() => this.#stopped.delete(done)); } this.#tasks.clear(); } /** * Delivers one listener event, in publication order. The invocation is a root that references a lease on this object * (a reload gate waits for it like for any other root that touched the object), counts as an invocation (disposal * waits for it) and is cancelled by forced disposal. */ #deliver(key, event) { enqueue(() => { if (this.gate !== void 0) { this.gate.queue.push({ key, event }); return; } const decl = this.decls.listeners.get(key); if (decl === void 0 || this.status !== "live" && this.status !== "activating") return; const invocation = this.host.root((root2) => { this.lease(root2); const child = root2.child(this); return this.#invoke(async () => { try { await decl.fn(event, child); } finally { child.close(); } }); }); invocation.catch((error) => this.host.report(error)); }); } /** Opens the object's gate and replays queued listener events through the current listeners. */ openGate() { const gate = this.gate; this.gate = void 0; for (const { key, event } of gate?.queue ?? []) this.#deliver(key, event); } // ─── calls ─── /** Admission for a call or write carrying `ctx`. */ admit(ctx) { if (this.host.failed !== void 0) { throw new ChordError("unavailable", `${this.host.name} failed and admits no work`, { reason: "host_failed" }); } if (!this.host.hasCodeVersion(ctx.codeVersion)) throw new ChordError("superseded", `code version ${ctx.codeVersion} is retired`); if (ctx.codeVersion < this.minCodeVersion) { throw new ChordError("upgrade_conflict", `${this.address} was upgraded in code version ${this.minCodeVersion}`); } if (this.gate !== void 0 && !this.gate.grandfathered.has(ctx.rootId)) { throw new ChordError("upgrade_conflict", `${this.address} is being upgraded`); } } /** Records that the root of `ctx` touched this object (a reload gate grandfathers it). */ lease(ctx) { if (this.leases.has(ctx.rootId)) return; this.leases.set(ctx.rootId, ctx.codeVersion); this.host.touch(ctx.rootId, this); } guard = (ctx) => { if (this.host.inFactory > 0) throw invalid("read_only", "cells cannot be written while a factory runs"); if (ctx instanceof CtxImpl) ctx.check(this.host); if (ctx.signal.aborted) throw new ChordError("aborted", "write with an aborted ctx"); if (this.status === "disposed") throw new ChordError("gone", `${this.address} is disposed`); if (ctx.rootId.startsWith("task:")) return; this.admit(ctx); }; async call(ref, name, args, ctx) { ctx.check(this.host); if (ref.status === "released" || ref.status === "gone") throw ref.terminalError(); if (ref.status === "loading") await raceSignal(ref.ready, ctx.signal); if (ref.status !== "live" || this.status !== "live") { throw ref.status === "live" ? new ChordError("gone", `${this.address} is ${this.status}`) : ref.terminalError(); } this.admit(ctx); const installed = this.implementations.get(ctx.codeVersion); if (installed === void 0) throw new ChordError("superseded", `${this.address} has no implementation for code version ${ctx.codeVersion}`); const view2 = ref.viewOf(installed); const variant = variantOf(view2); const wanted = ref.describe(ref.contract.scope === "remote" ? variant : LOCAL_VARIANT, name); const fn = view2[name]; if (typeof fn !== "function" || ref.contract.scope === "remote" && wanted?.kind !== "method") { throw new ChordError("not_in_variant", `${this.address}: ${name} is not a method of variant ${variant ?? "(local)"}`); } const direct = view2 === installed.implementation; const schema = direct ? installed.service.contract.description.variants[variant ?? LOCAL_VARIANT]?.[name] : wanted; const method2 = schema?.kind === "method" ? schema : void 0; const validated = ref.contract.scope === "remote" ? method2 : void 0; const takesArgs = method2 === void 0 || method2.args !== void 0; const returns = method2 === void 0 || method2.result !== void 0; let input; if (takesArgs) { const own = ref.contract.scope === "remote" && wanted?.kind === "method" ? wanted.args : void 0; const sent = own === void 0 ? args : cleaned(own, args); input = validated?.args ? cleaned(validated.args, sent) : sent; if (validated?.args) assertValid("args", `${this.address}.${name} args`, validated.args, input); } this.lease(ctx); const child = ctx.child(this); let result; try { result = await this.#invoke(() => takesArgs ? fn(input, child) : fn(child)); } finally { child.close(); } if (!returns) return void 0; if (validated?.result) assertValid("result", `${this.address}.${name} result`, validated.result, result); return result; } /** Runs an invocation (method or listener) that keeps the object alive until it settles. */ #invoke(fn) { const invocation = (async () => fn())(); this.#invocations.add(invocation); const done = () => { this.#invocations.delete(invocation); if (this.#invocations.size === 0) this.#maybeIdle(); }; invocation.then(done, done); return invocation; } rootEnded(rootId) { this.leases.delete(rootId); const gate = this.gate; if (gate?.grandfathered.delete(rootId) && gate.grandfathered.size === 0) gate.drained(); } // ─── lifetime ─── addRef(ref) { this.refs.add(ref); if (this.#linger !== void 0) clearTimeout(this.#linger); this.#linger = void 0; } release(ref) { if (this.refs.delete(ref)) this.#maybeIdle(); } /** No references and no invocations: dispose (after `linger` for a live object). */ #maybeIdle() { if (this.refs.size > 0 || this.#invocations.size > 0 || this.#linger !== void 0) return; if (this.status === "activating") { this.#background(this.dispose(false)); return; } if (this.status !== "live") return; const linger = this.service?.options.linger ?? 0; if (linger <= 0) { this.#background(this.dispose(false)); return; } this.#linger = setTimeout(() => { this.#linger = void 0; if (this.refs.size === 0 && this.#invocations.size === 0 && this.status === "live") { this.#background(this.dispose(false)); } }, linger); this.#linger.unref?.(); } #background(disposal) { disposal.catch((error) => this.host.report(error)); } /** * Disposes the object. Forced: references become gone at once (new calls are rejected) and running invocations are * cancelled. Either way, resources close only after every invocation settled. */ dispose(forced, reason) { this.#disposal ??= this.#dispose(forced, reason); return this.#disposal; } async #dispose(forced, reason) { const wasActivating = this.status === "activating"; this.status = "disposing"; this.host.forget(this); if (this.#linger !== void 0) clearTimeout(this.#linger); this.#linger = void 0; const error = reason ?? new ChordError("gone", `${this.address} was disposed`); if (forced) { for (const ref of this.refs) ref.gone(error); this.callAbort.abort(error); } this.activationAbort.abort(error); if (wasActivating) await this.ready.catch(noop); for (const ref of this.refs) ref.gone(error); const decls = this.allDecls(); this.stopTasks(); this.stopListeners(); await Promise.allSettled([...this.#invocations]); const registered = [...decls].some((set) => set.registrations.size > 0) && !this.deferredRuntime; this.implementations.clear(); this.decls = new Decls(); if (registered) this.host.registriesChanged(); const refs = /* @__PURE__ */ new Set(); for (const set of decls) { for (const derived of set.derives.values()) derived.detach(); for (const ref of set.refs.values()) refs.add(ref); } try { await runAll([ () => Promise.allSettled([...this.#stopped]), ...[...this.openOrder].reverse().map((resource) => () => resource.close(this.openOrder)), ...[...refs].map((ref) => () => ref.release()) ], `${this.address}: disposal failed`); } finally { this.status = "disposed"; this.host.disposed(this); } } }; var isReadableLike = (value2) => value2 !== null && typeof value2 === "object" && typeof value2.events === "function" && typeof value2.subscribe === "function"; function memberSchemas(service2, implementation) { const schemas = /* @__PURE__ */ new Map(); const contract = service2.contract; if (contract.scope !== "remote") return schemas; const members = contract.description.variants[variantOf(implementation) ?? ""] ?? {}; for (const [name, description] of Object.entries(members)) { const source = implementation[name]; if (description.kind !== "state" || !isSource(source)) continue; schemas.set(source, [...schemas.get(source) ?? [], description.schema]); } return schemas; } function validateImplementation(service2, implementation) { const contract = service2.contract; const fail = (message) => { throw invalid("implementation", `${contract.name}: ${message}`); }; if (implementation === null || typeof implementation !== "object") fail("factory must return an object"); let members; if (contract.scope === "remote") { const variant = implementation.variant; if (typeof variant !== "string" || contract.description.variants[variant] === void 0) fail(`unknown variant ${String(variant)}`); members = contract.description.variants[variant]; for (const key of Object.keys(implementation)) if (key !== "variant" && members[key] === void 0) fail(`${key} is not in variant ${String(variant)}`); } else { members = contract.description.variants[LOCAL_VARIANT] ?? {}; } for (const [name, description] of Object.entries(members)) { const value2 = implementation[name]; if (description.kind === "method" && typeof value2 !== "function") fail(`${name} must be a method`); if (description.kind === "state" && !isSource(value2) && !isReadableLike(value2)) { fail(`${name} must be a cell, a derivation or a held object's state member`); } if (description.kind === "opaque" && value2 === void 0) fail(`${name} is missing`); } } var SelfImpl = class { #object; #previous; #decls = new Decls(); #created = { refs: [], resources: [], derives: [], inits: [] }; constructor(object, previous) { this.#object = object; this.#previous = previous; } run(implementation) { return { implementation, decls: this.#decls, created: this.#created }; } /** Refuses a second declaration of `name` of one kind in this run (TypeScript cannot see string names). */ #unique(map2, kind, name) { if (typeof name !== "string" || name === "") throw new TypeError(`${kind} needs a name`); if (map2.has(name)) throw invalid("duplicate", `${this.#object.typeName}: duplicate ${kind} name ${name}`); } state(name, schema, initial, options = {}) { this.#unique(this.#decls.cells, "state", name); const version = options.version ?? 1; if (!Number.isInteger(version) || version < 1) throw new TypeError(`state ${name}: version must be a positive integer`); const host2 = this.#object.host; const init = isStateInit(initial) ? initial.init : void 0; let cell = this.#previous?.cells.get(name); if (cell === void 0) { cell = new Cell(name, schema, init === void 0 ? { value: initial } : void 0, this.#object.guard, { dev: host2.dev, report: (error) => host2.report(error) }); if (init !== void 0) this.#created.inits.push([cell, init]); } this.#decls.cells.set(name, cell); this.#decls.cellSchemas.set(name, schema); this.#decls.cellVersions.set(name, version); if (options.migrate !== void 0) this.#decls.cellMigrations.set(name, options.migrate); return cell; } get(type, input, options) { const host2 = this.#object.host; const candidate = this.#previous !== void 0; const address = host2.addressOf(type, input, options?.principal ?? host2.principal, candidate); const declared = this.#decls.refs.get(address); if (declared !== void 0) { if (declared.contract.fingerprint !== type.fingerprint) { throw invalid("duplicate", `${this.#object.typeName}: ${type.name} held twice with different contracts`); } return declared.proxy; } const existing = this.#previous?.refs.get(address); if (existing !== void 0 && existing.contract.fingerprint === type.fingerprint && existing.status !== "gone" && existing.status !== "released") { this.#decls.refs.set(address, existing); return existing.proxy; } const ref = host2.acquire(type, input, options, "object", this.#object, candidate); this.#decls.refs.set(address, ref); this.#created.refs.push(ref); return ref.proxy; } resource(name, declaration) { this.#unique(this.#decls.resources, "resource", name); if (!Number.isInteger(declaration.version)) throw new TypeError(`resource ${name}: version must be an integer`); const deps = (declaration.deps ?? []).map((dep) => { const resolved = refOf(dep) ?? resourceOf(dep); if (resolved === void 0) { throw new TypeError(`resource ${name}: deps must be references or resources of this object`); } return resolved; }); const existing = this.#previous?.resources.get(name); const kept = existing !== void 0 && existing.spec.version === declaration.version && existing.exclusive === (declaration.exclusive === true) && existing.deps.length === deps.length && existing.deps.every((dep, index) => dep === deps[index]); const resource = kept ? existing : new ResourceImpl(name, declaration, deps); if (kept) this.#decls.adopted.set(existing, declaration); else this.#created.resources.push(resource); this.#decls.resources.set(name, resource); return new ResourceFacade(resource, this.#object.host); } on(name, source, ...rest) { this.#unique(this.#decls.listeners, "listener", name); const [select, fn] = rest.length === 1 ? [void 0, rest[0]] : [rest[0], rest[1]]; if (typeof fn !== "function") throw new TypeError(`listener ${name}: the handler must be a function`); if (select !== void 0 && typeof select !== "function") throw new TypeError(`listener ${name}: select must be a function`); this.#decls.listeners.set(name, { source, select, fn }); } task(fn) { if (typeof fn !== "function") throw new TypeError("a task must be a function"); this.#decls.tasks.set(`#${this.#decls.tasks.size}`, fn); } derive(sources, fn) { if (typeof fn !== "function") throw new TypeError("a derivation needs a function"); const host2 = this.#object.host; const key = `#${this.#decls.derives.size}`; const derived = new Derived(key, sources, fn, { dev: host2.dev, report: (error) => host2.report(error) }); this.#created.derives.push(derived); this.#decls.derives.set(key, derived); return derived; } expose(resource) { const resolved = resourceOf(resource); if (resolved === void 0 || ![...this.#decls.resources.values()].includes(resolved)) { throw new TypeError("expose takes a resource declared by this object"); } return new ExposedImpl(resolved); } register(registry2, entries2) { if (!this.#object.hostLifetime) throw invalid("not_host_lifetime", `${this.#object.typeName}: only objects of lifetime "host" may register`); for (const [key, value2] of Object.entries(entries2)) { const id = `${registry2.name}\0${key}`; this.#unique(this.#decls.registrations, "register", id); this.#decls.registrations.set(id, { registry: registry2, key, value: value2 }); } } }; // node_modules/@earendil-works/chord/dist/reload.js function dryRun(candidate) { const { run: run2, migrated, service: service2, object } = candidate; const exposed = memberSchemas(service2, run2.implementation); for (const [cell, value2] of migrated) { for (const schema of exposed.get(cell) ?? []) assertValid("state", `${object.address}: migrated state ${cell.key}`, schema, value2); } const values = new Map(migrated); const derives = [...run2.decls.derives].sort(([, a], [, b]) => a.seq - b.seq); for (const [name, derived] of derives) { const value2 = derived.compute((source) => values.has(source) ? values.get(source) : candidateValue(source)); for (const schema of exposed.get(derived) ?? []) assertValid("state", `${object.address}: derivation ${name}`, schema, value2); values.set(derived, value2); } candidate.derived = values; } function candidateValue(source) { try { return source.value; } catch (error) { if (isChordError(error, "not_ready") || isChordError(error, "not_in_variant")) return void 0; throw error; } } function reloadError(error) { const cause = asChordError(error, "reload"); if (/^reload aborted/.test(cause.message.replace(/^[^:]*: /, ""))) return cause; return new ChordError(cause.code, `reload aborted: ${cause.message}`, { cause, reason: cause.reason, details: cause.details }); } async function runReload(host2, plugins, options) { const reload = await beginReload(host2, plugins, options); try { await reload.prepare(); return reload.commit(); } finally { endReload(host2); } } async function beginReload(host2, plugins, options, codeVersion) { if (host2.reloading) { throw new ChordError("unavailable", "a reload is already running", { reason: "reload_running" }); } host2.reloading = true; try { return new Reload(host2, await host2.resolve(plugins), options, codeVersion); } catch (error) { host2.reloadEnded(); throw reloadError(error); } } function endReload(host2) { host2.reloadEnded(); } var ABORTED_ROOTS_GRACE = 1e3; function until(host2, ready, ms) { if (ready()) return Promise.resolve(true); return new Promise((resolve2) => { const timer = setTimeout(() => { stop2(); resolve2(false); }, ms); const stop2 = host2.watchRetirement(() => { if (!ready()) return; clearTimeout(timer); stop2(); resolve2(true); }); }); } var Reload = class { host; facets; options; /** Code version id to commit (issued by the coordinating server); a local reload allocates one. */ codeVersion; candidates = /* @__PURE__ */ new Map(); abort = new AbortController(); closed = []; services = /* @__PURE__ */ new Map(); changed = /* @__PURE__ */ new Set(); removed = /* @__PURE__ */ new Set(); tables = /* @__PURE__ */ new Map(); gated = []; rolledBack = false; /** Objects of types this reload adds, with their staged listeners: their runtime starts at commit. */ deferred = /* @__PURE__ */ new Map(); /** This reload's run of each facet it loads (`FacetApi.signal`): installed at commit, aborted at rollback. */ facetRuns = /* @__PURE__ */ new Map(); constructor(host2, facets, options, codeVersion) { this.host = host2; this.facets = facets; this.options = options; this.codeVersion = codeVersion; } /** Everything up to the commit. On failure the host is rolled back and the underlying error is thrown. */ async prepare() { try { await this.waitForRetirement(); this.load(); this.checkProviders(); await this.prepareAll(); this.host.gating = this.changed; await this.withDeadline(this.prepareAll(), "activations in flight did not finish"); this.checkRefs(); this.tables = this.host.buildTables(this.entries()); await this.gate(); this.#alive(); this.#stage(); } catch (error) { await this.rollback(); throw reloadError(error); } } /** What a successful prepare will commit (a child sends it with its vote). */ summary() { const live = [...this.candidates.values()].filter(({ object }) => object.status === "live"); return { codeVersion: this.host.pending?.id ?? this.host.current.id, previous: this.host.current.id, changed: [...this.changed], removed: [...this.removed], prepared: live.map(({ object }) => object.address), gated: this.gated.map(({ object }) => object.address) }; } /** After every await of prepare: a rollback meanwhile (abort from the coordinator) ends it. */ #alive() { if (this.rolledBack) throw new ChordError("aborted", "reload aborted while preparing", { reason: "aborted" }); } /** * Subscribes every new listener now, buffering its events, so a failing source fails prepare instead of leaving a * half-installed code version. Objects running only the new code (types this reload adds) are staged too. */ #stage() { for (const candidate of this.candidates.values()) { if (candidate.object.status === "live") candidate.staged = candidate.object.stageListeners(candidate.run.decls); } for (const object of this.host.objects.values()) { if (object.status === "live" && object.deferredRuntime) this.deferred.set(object, object.stageListeners(object.decls)); } } /** * Retires the previous code version before loading the next one (a host keeps two). Waits until its roots ended and, * on a child host, its parent retired it too (`retire` frame), at most `retireDeadline`. Past the deadline it is * retired anyway: its remaining roots are aborted first and get a short grace to stop, so they see cancellation * before the resources only that code version used are closed; their next call is `superseded`. */ async waitForRetirement() { const host2 = this.host; const previous = host2.previous; if (previous === void 0) return; const deadline = this.options.retireDeadline ?? 5e3; const gone = () => host2.previous !== previous; if (!await until(host2, () => gone() || host2.retirable(), deadline) && !gone()) { previous.abort.abort(new ChordError("superseded", `code version ${previous.id} was retired`)); await until(host2, () => gone() || previous.roots.size === 0, Math.min(deadline, ABORTED_ROOTS_GRACE)); } if (!gone()) host2.retire(); } /** Builds the candidate service table: unchanged services plus everything the given facets serve. */ load() { const replaced = /* @__PURE__ */ new Set([...this.facets.map((facet) => facet.name), ...this.options.remove ?? []]); const current = this.host.current.services; this.services = new Map([...current].filter(([, service2]) => !replaced.has(service2.facet))); for (const facet of this.facets) { const run2 = new AbortController(); this.facetRuns.set(facet.name, run2); facet.setup(this.host.facetApi(((type, factory, options = {}) => this.host.serveInto(this.services, makeService(type, factory, options, facet.name))), run2.signal)); } for (const [name, service2] of this.services) if (current.get(name) !== service2) this.changed.add(name); for (const name of current.keys()) if (!this.services.has(name)) this.removed.add(name); this.host.pending = { id: this.host.nextCodeVersionId(this.codeVersion), services: this.services, tables: /* @__PURE__ */ new Map(), roots: /* @__PURE__ */ new Map(), abort: new AbortController() }; } async withDeadline(promise, what) { const deadline = this.options.gateDeadline ?? 5e3; let timer; const timeout = new Promise((_, reject2) => { timer = setTimeout(() => reject2(new ChordError("aborted", `${what} within ${deadline} ms`, { reason: "deadline" })), deadline); }); try { return await Promise.race([promise, timeout]); } finally { clearTimeout(timer); } } /** Same contract version must cover the old description (forgotten bump); facets' `uses` must bind. */ checkProviders() { for (const name of this.changed) { const next = this.services.get(name).contract; const old = this.host.current.services.get(name)?.contract; if (old === void 0 || old.version !== next.version) continue; const reason = coverage(next.description, old.description); if (reason !== void 0) { throw new ChordError("contract_mismatch", `${name}@${next.version} changed incompatibly without a version bump (${reason})`); } } const facets = new Map(this.host.facets); for (const facet of this.facets) facets.set(facet.name, facet); for (const name of this.options.remove ?? []) facets.delete(name); for (const facet of facets.values()) { for (const token of facet.uses ?? []) { const service2 = this.services.get(token.name); if (service2 !== void 0 && this.changed.has(token.name)) bind(service2.contract, service2.options.accepts, token); } } } /** * Prepares every live object of a changed type, including objects that activate meanwhile, to a fixpoint. Objects * already running the new code (types this reload adds) are skipped. */ async prepareAll() { for (; ; ) { const todo = [...this.host.objects.values()].filter((object) => this.changed.has(object.typeName) && !this.candidates.has(object) && object.service !== this.services.get(object.typeName) && (object.status === "live" || object.status === "activating")); if (todo.length === 0) return; await Promise.allSettled(todo.map((object) => object.ready)); this.#alive(); for (const object of todo) { if (object.status === "live" && object.service !== this.services.get(object.typeName)) { await this.prepareObject(object); this.#alive(); } } } } /** Runs the new factory in recording mode and opens what can be opened without the gate. */ async prepareObject(object) { const service2 = this.services.get(object.typeName); const run2 = object.run(service2, object.decls); const candidate = { object, service: service2, run: run2, reasons: [], atGate: [], replaced: [], migrated: /* @__PURE__ */ new Map(), derived: /* @__PURE__ */ new Map(), staged: void 0 }; this.candidates.set(object, candidate); for (const [name, cell] of run2.decls.cells) { if (object.decls.cells.get(name) !== cell) continue; const from = object.decls.cellVersions.get(name) ?? 1; const to = run2.decls.cellVersions.get(name) ?? 1; if (to < from) throw invalid("state", `${object.address}: version of state ${name} lowered from ${from} to ${to}`); if (to > from) candidate.reasons.push(`state ${name} version ${from} -> ${to}`); else if (!deepEqual2(cell.schema, run2.decls.cellSchemas.get(name))) throw invalid("state", `${object.address}: schema of state ${name} changed without a version bump`); } const deferred2 = /* @__PURE__ */ new Set(); for (const resource of run2.created.resources) { if (resource.exclusive || resource.deps.some((dep) => deferred2.has(dep))) deferred2.add(resource); } for (const resource of deferred2) { candidate.atGate.push(resource); const old = object.decls.resources.get(resource.key); if (resource.exclusive && old?.exclusive && old.state === "open") candidate.replaced.push(old); } if (deferred2.size > 0) candidate.reasons.push("exclusive resource replaced"); await object.open(run2.created.resources.filter((resource) => !deferred2.has(resource)), run2.created.refs, this.abort.signal); await object.waitHydrated(run2); if (candidate.reasons.length === 0) { await object.initCells(run2, this.abort.signal); dryRun(candidate); } } /** * Every reference that survives into the new code version must bind to the new provider. Browser references don't block a * reload: after the commit they get `upgrade_required` instead. */ checkRefs() { const dropped = /* @__PURE__ */ new Set(); for (const { object, run: run2 } of this.candidates.values()) { for (const ref of object.decls.refs.values()) if (![...run2.decls.refs.values()].includes(ref)) dropped.add(ref); } for (const object of this.host.objects.values()) { const service2 = this.services.get(object.typeName); if (service2 === void 0 || !this.changed.has(object.typeName)) continue; for (const ref of object.refs) { if (ref === this.host.hostObjects.get(object.typeName)) continue; if (!dropped.has(ref) && ref.owner !== "client") bind(service2.contract, service2.options.accepts, ref.contract); } } } entries() { return this.host.liveDecls(true).filter(([object]) => !this.removed.has(object.typeName)).map(([object, decls]) => [object, this.candidates.get(object)?.run.decls ?? decls]); } /** * Admission cut, wait for grandfathered roots (deadline), replace exclusive resources, migrate into private * copies. Old tasks of gated objects are stopped at the cut; listener events are queued until commit or abort. */ async gate() { this.gated = [...this.candidates.values()].filter((candidate) => candidate.reasons.length > 0 && candidate.object.status === "live"); const drained = this.gated.map(({ object }) => { const grandfathered = new Set(object.leases.keys()); return new Promise((resolve2) => { object.gate = { grandfathered, queue: [], drained: resolve2 }; object.stopTasks(); if (grandfathered.size === 0) resolve2(); }); }); const deadline = this.options.gateDeadline ?? 5e3; let timer; const timedOut = await Promise.race([ Promise.all(drained).then(() => false), new Promise((resolve2) => { timer = setTimeout(() => resolve2(true), deadline); }) ]); clearTimeout(timer); if (timedOut) { const blocking = this.gated.flatMap(({ object }) => [...object.gate?.grandfathered ?? []]); throw new ChordError("aborted", `gate deadline (${deadline} ms) missed; blocking roots: ${blocking.join(", ")}`, { reason: "gate_deadline", details: { blocking } }); } this.#alive(); for (const candidate of this.gated) { const { object } = candidate; for (const old of candidate.replaced) { this.closed.push([object, old]); await old.close(object.openOrder); } await object.open(candidate.atGate, [], this.abort.signal); this.#alive(); await object.initCells(candidate.run, this.abort.signal); this.#alive(); } for (const candidate of this.gated) this.migrate(candidate); } migrate(candidate) { const { object, run: run2 } = candidate; for (const [name, cell] of run2.decls.cells) { if (object.decls.cells.get(name) !== cell) continue; const schema = run2.decls.cellSchemas.get(name); const from = object.decls.cellVersions.get(name) ?? 1; const fn = run2.decls.cellMigrations.get(name); let value2; if ((run2.decls.cellVersions.get(name) ?? 1) > from && fn !== void 0) value2 = fn(structuredClone(cell.value), from); else if (!deepEqual2(cell.schema, schema)) value2 = cell.value; else continue; assertValid("state", `${object.address}: migrated state ${name}`, schema, value2); candidate.migrated.set(cell, value2); } dryRun(candidate); } async rollback() { if (this.rolledBack) return; this.rolledBack = true; this.abort.abort(new ChordError("aborted", "reload aborted")); for (const run2 of this.facetRuns.values()) run2.abort(new ChordError("aborted", "reload aborted")); const report2 = (error) => this.host.report(error); for (const candidate of this.candidates.values()) candidate.staged?.discard(); for (const staged of this.deferred.values()) staged.discard(); for (const { object, run: run2 } of this.candidates.values()) { object.pending.delete(run2.decls); await object.unwind(run2).catch(report2); } for (const [object, resource] of this.closed) { if (resource.state !== "closed") continue; await resource.open(new AbortController().signal, object.openOrder).catch((error) => { resource.markUnavailable(); report2(new ChordError("unavailable", `${object.address}: resource ${resource.key} could not be restored`, { cause: error })); }); } for (const { object } of this.gated) { if (object.gate === void 0) continue; object.openGate(); object.startTasks(); } const reason = new ChordError("gone", "reload aborted; type not served"); for (const object of [...this.host.objects.values()]) { if (!this.host.current.services.has(object.typeName)) await object.dispose(true, reason).catch(report2); } this.host.pending = void 0; this.host.gating = void 0; } /** * Publishes the new code version in one synchronous step. Everything fallible ran in prepare (factories, opens, * migration and derivation checks against the prospective schemas, listener subscriptions), so this only installs. * Observers (state, shapes, registries, listener roots) are notified after the whole step. If an install still * throws, the code version is half-published: the host fails terminally and admits no more work. */ commit(beforeStart) { try { return holdPublications(() => { const report2 = this.#publish(); beforeStart?.(); for (const start of this.#starts.splice(0)) start(); return report2; }); } catch (error) { this.host.fail(error); throw reloadError(error); } } /** Work that may start new-code-version roots, run after installation (and after the coordinator's commit frames). */ #starts = []; #publish() { const host2 = this.host; const previous = host2.current; const next = host2.pending; host2.pending = void 0; host2.codeVersions.set(next.id, next); host2.previous = previous; host2.current = next; const prepared = []; for (const object of [...host2.objects.values()]) { if (this.removed.has(object.typeName)) continue; const candidate = this.candidates.get(object); if (candidate === void 0) { const installed = object.implementations.get(previous.id); if (installed !== void 0) object.implementations.set(next.id, { ...installed, codeVersion: next.id }); continue; } if (object.status !== "live") continue; this.install(candidate, next.id); prepared.push(object.address); } for (const { object, run: run2, staged } of this.candidates.values()) { if (object.status === "live") continue; staged?.discard(); object.pending.delete(run2.decls); object.unwind(run2).catch((error) => host2.report(error)); } for (const [object, staged] of this.deferred) { if (object.status !== "live") { staged.discard(); continue; } object.deferredRuntime = false; staged.activate(); this.#starts.push(() => object.startTasks()); } host2.installTables(next, this.tables, true); const reason = new ChordError("gone", "type no longer served"); for (const object of [...host2.objects.values()]) { if (this.removed.has(object.typeName)) object.dispose(true, reason).catch((error) => host2.report(error)); } for (const name of this.removed) host2.hostObjects.delete(name); for (const facet of this.facets) host2.facets.set(facet.name, facet); for (const name of this.options.remove ?? []) host2.facets.delete(name); for (const [name, run2] of this.facetRuns) this.#starts.push(() => host2.facetRunChanged(name, run2)); for (const name of this.options.remove ?? []) this.#starts.push(() => host2.facetRunChanged(name, void 0)); host2.gating = void 0; for (const [name, service2] of this.services) { if (hostLifetime(service2) && this.changed.has(name) && host2.hostObjects.get(name)?.object?.status !== "live") { this.#starts.push(() => host2.startHostObject(service2)); } const pinned = host2.hostObjects.get(name); if (!hostLifetime(service2) && pinned !== void 0) { host2.hostObjects.delete(name); this.#starts.push(() => pinned.release()); } } return { codeVersion: next.id, previous: previous.id, changed: [...this.changed], removed: [...this.removed], prepared, gated: this.gated.map(({ object }) => object.address) }; } install(candidate, codeVersion) { const { object, run: run2, service: service2 } = candidate; const gated = candidate.reasons.length > 0; object.pending.delete(run2.decls); object.stopTasks(); if (gated) { const kept = new Set(run2.decls.derives.values()); for (const derived of object.decls.derives.values()) if (!kept.has(derived)) derived.detach(); } object.decls = run2.decls; object.service = service2; for (const [resource, declaration] of run2.decls.adopted) resource.adopt(declaration); object.implementations.set(codeVersion, { codeVersion, service: service2, implementation: run2.implementation, decls: run2.decls }); if (gated) { object.minCodeVersion = codeVersion; for (const old of [...object.implementations.keys()]) if (old !== codeVersion) this.host.retireImplementation(object, old); } object.refreshSchemas(); for (const [name, cell] of run2.decls.cells) { const schema = run2.decls.cellSchemas.get(name); if (candidate.migrated.has(cell)) cell.reset(candidate.migrated.get(cell), schema); else cell.schema = schema; } const created = new Set(run2.created.derives); for (const derived of run2.created.derives) derived.attach(candidate.derived); for (const derived of run2.decls.derives.values()) if (!created.has(derived)) derived.refresh(); candidate.staged?.activate(); this.#starts.push(() => object.startTasks()); object.openGate(); const pinned = this.host.hostObjects.get(object.typeName); if (pinned?.object === object && pinned.contract.fingerprint !== service2.contract.fingerprint) { const replacement = new RefImpl(object, service2.contract, "host"); object.addRef(replacement); this.host.hostObjects.set(object.typeName, replacement); pinned.release(); } for (const ref of [...object.refs]) { if (ref.owner === "client") { try { bind(service2.contract, service2.options.accepts, ref.contract); } catch (error) { ref.gone(new ChordError("upgrade_required", `${object.typeName} changed; reload the web facets`, { cause: error })); object.release(ref); continue; } } ref.shapeChanged(); } } }; // node_modules/@earendil-works/chord/dist/wire.js var PROTOCOL_VERSION = 4; var Id = int({ minimum: 1 }); var Name = str({ minLength: 1 }); var Reload2 = int({ minimum: 1 }); var List = (item) => arr(item); var Open = (properties) => obj(properties, { open: true }); var RootRefSchema = Open({ rootId: Name, codeVersion: int({ minimum: 1 }) }); var WireErrorSchema = Open({ code: oneOf(CHORD_ERROR_CODES), message: str(), reason: opt(str()) }); var WireReloadOptionsSchema = Open({ remove: opt(List(str())), gateDeadline: opt(num()), retireDeadline: opt(num()) }); var WireReloadReportSchema = Open({ codeVersion: int(), previous: int(), changed: List(str()), removed: List(str()), prepared: List(str()), gated: List(str()) }); var FrameSchema = union([ Open({ t: lit("hello"), /** The link protocol version (`PROTOCOL_VERSION`; absent: 1). Peers of another version close the link. */ protocol: opt(int({ minimum: 1 })), name: Name, kind: union([lit("host"), lit("client")]), codeVersion: int({ minimum: 0 }), /** The sender acknowledges what it receives (`ack`). Flow control applies only when both hellos say so. */ acks: opt(bool()) }), /** Every frame received so far except `ack` frames, counted from the link's start, once processed. */ Open({ t: lit("ack"), n: int({ minimum: 1 }) }), Open({ t: lit("hold"), id: Id, /** * Target host name, resolved by the consumer from the token's `host` and `input`. Relays route on it after * checking it against both; the target refuses an input its own contract places elsewhere. */ at: Name, /** The consumer's contract token; the provider checks that it covers it. Remote tokens carry `host`. */ token: DescriptionSchema, /** Normalized with the consumer's contract. */ input: unknown(), /** A forwarded principal. Honored only on frames from the parent link. */ principal: opt(unknown()), /** * The client connection the hold comes from: an opaque id the server assigned to it, forwarded by relays. Honored * only on frames from the parent link; present means the hold is a client's (not a reload participant). */ origin: opt(Name), /** False: no state for this hold until `sub` (a method-only binding). */ state: opt(lit(false)), /** The consumer already has values (a re-hold after a link loss): its unit is not urgent. */ again: opt(lit(true)) }), Open({ t: lit("release"), id: Id }), Open({ t: lit("shape"), id: Id, variant: opt(str()), members: List(str()), contract: str(), /** The state members whose snapshots follow this frame; the consumer applies the shape once they are here. */ state: List(Name) }), Open({ t: lit("gone"), id: Id, error: WireErrorSchema }), Open({ t: lit("call"), id: Id, hold: Id, member: Name, args: opt(unknown()), /** Absent: the receiving host starts a root (browsers never carry roots). */ root: opt(RootRefSchema) }), Open({ t: lit("result"), id: Id, value: opt(unknown()), error: opt(WireErrorSchema) }), Open({ t: lit("cancel"), id: Id }), /** Starts the state of a hold opened with `state: false` (answered with a unit). */ Open({ t: lit("sub"), hold: Id }), /** Stops the state of a hold. */ Open({ t: lit("unsub"), hold: Id }), /** Sends member `m` again as a reset (a relay that had to drop what waited for it). */ Open({ t: lit("resync"), hold: Id, m: Name }), Open({ t: lit("state"), hold: Id, m: Name, /** `snap`/`reset` without `value`: the value is `undefined`. */ as: union([lit("snap"), lit("reset"), lit("ops"), lit("error")]), value: opt(unknown()), /** Encoded with the stream's delta encoder; the decoder validates each op. */ ops: opt(List(List(unknown()))), error: opt(WireErrorSchema) }), Open({ t: lit("ping"), n: int() }), Open({ t: lit("pong"), n: int() }), /** Ends the link (consumed by the link itself). `reason: "protocol-version "`: the sender speaks version n. */ Open({ t: lit("bye"), reason: opt(str()) }), /** The root ended at its origin: its leases on objects end (sent on every link the root's calls used). */ Open({ t: lit("root_end"), rootId: Name }), Open({ t: lit("invalidate"), pattern: map(unknown()) }), Open({ t: lit("prepare"), reload: Reload2, codeVersion: int({ minimum: 1 }), plugins: List(str()), options: WireReloadOptionsSchema }), Open({ t: lit("vote"), reload: Reload2, error: opt(WireErrorSchema), report: opt(WireReloadReportSchema) }), Open({ t: lit("commit"), reload: Reload2 }), /** The parent dropped `codeVersion`: roots of it may still come from there no longer. */ Open({ t: lit("retire"), codeVersion: int({ minimum: 1 }) }), Open({ t: lit("abort"), reload: Reload2 }) ]); var isFrame = compile(FrameSchema).check; function toWireError(error) { if (error instanceof ChordError) { return error.reason === void 0 ? { code: error.code, message: error.message } : { code: error.code, message: error.message, reason: error.reason }; } return { code: "unavailable", message: error instanceof Error ? error.message : String(error) }; } var fromWireError = (error) => new ChordError(error.code, error.message.replace(/^[a-z_]+(\/[a-z_]+)?: /, ""), { reason: error.reason }); // node_modules/@earendil-works/chord/dist/cluster.js var reloadSeq = 0; var modulesFor = (remote2, hostName) => { let best; for (const entry2 of Object.entries(remote2 ?? {})) { if (hostName?.startsWith(entry2[0]) && (best === void 0 || entry2[0].length > best[0].length)) best = entry2; } return [...best?.[1] ?? []]; }; function awaitVote(connection, id, deadline) { let timer; const vote = new Promise((resolve2, reject2) => { connection.votes.set(id, { resolve: resolve2, reject: reject2 }); timer = setTimeout(() => { connection.votes.delete(id); reject2(new ChordError("aborted", `votes missing after ${deadline} ms`, { reason: "vote_deadline" })); }, deadline); }); return vote.finally(() => clearTimeout(timer)); } async function runClusterReload(host2, plugins, options) { const synced = [...host2.connections].filter((connection) => connection.role === "downstream" && connection.peer.kind === "host" && connection.open && connection.synced); const codeVersion = host2.peekNextCodeVersion(); const reload = await beginReload(host2, plugins, options, codeVersion); try { await reload.waitForRetirement(); const participants = synced.filter((connection) => connection.open); const id = ++reloadSeq; const deadline = options.voteDeadline ?? (options.gateDeadline ?? 5e3) + (options.retireDeadline ?? 5e3) + 5e3; const votes = participants.map((connection) => { const name = connection.name ?? "child"; const vote = awaitVote(connection, id, deadline); connection.send({ t: "prepare", reload: id, codeVersion, plugins: modulesFor(options.remote, name), options: { remove: modulesFor(options.remoteRemove, name), gateDeadline: options.gateDeadline, retireDeadline: options.retireDeadline } }); return vote.then((report3) => [name, report3], (error) => { const cause = reloadError(error); throw new ChordError(cause.code, `${name}: ${cause.message}`, { cause, reason: cause.reason }); }); }); let local2 = false; const results = await Promise.allSettled([ reload.prepare().then(() => { local2 = true; }), ...votes ]); const failed = results.find((result) => result.status === "rejected"); if (failed !== void 0) { for (const connection of participants) { connection.votes.delete(id); connection.send({ t: "abort", reload: id }); } if (local2) await reload.rollback(); throw failed.reason; } const report2 = reload.commit(() => { for (const connection of participants) { connection.send({ t: "commit", reload: id }); connection.peerCodeVersion = codeVersion; } }); host2.rememberRemoteModules(options.remote, options.remoteRemove); const children2 = {}; for (const result of results.slice(1)) { if (result.status !== "fulfilled") continue; const [name, child] = result.value; if (child !== void 0) children2[name] = child; } return { ...report2, children: children2 }; } finally { endReload(host2); } } async function reconcileChild(host2, connection) { for (; ; ) { if (!connection.open) return; if (host2.reloading) { await host2.whenReloadEnded(); continue; } const codeVersion = host2.current.id; if (connection.peerCodeVersion === codeVersion) { connection.markSynced(codeVersion); host2.announceRetired(connection); return; } host2.announceRetired(connection); const id = ++reloadSeq; const vote = awaitVote(connection, id, 3e4); connection.send({ t: "prepare", reload: id, codeVersion, plugins: modulesFor(host2.remoteModules, connection.name), options: { remove: modulesFor(host2.remoteRemoved, connection.name) } }); await vote; if (host2.current.id !== codeVersion) { connection.send({ t: "abort", reload: id }); continue; } connection.send({ t: "commit", reload: id }); connection.peerCodeVersion = codeVersion; } } var ChildReloads = class { #host; #operation; constructor(host2) { this.#host = host2; } handle(connection, frame) { if (frame.t === "prepare") { if (this.#operation !== void 0) { const error = new ChordError("unavailable", "a reload is already running", { reason: "reload_running" }); connection.send({ t: "vote", reload: frame.reload, error: toWireError(error) }); return; } const operation2 = { id: frame.reload, reload: void 0, state: "loading", cancelled: false }; this.#operation = operation2; this.#run(operation2, connection, frame).catch((error) => this.#host.report(error)); return; } const operation = this.#operation; if (operation === void 0 || operation.id !== frame.reload) return; if (frame.t === "abort") { this.#cancel(operation); return; } if (operation.state !== "prepared") return; this.#operation = void 0; try { operation.reload?.commit(); } finally { endReload(this.#host); } } /** The parent link died: nobody will commit, so roll back (now, or once pending work settles). */ parentLost() { if (this.#operation !== void 0) this.#cancel(this.#operation); } #cancel(operation) { operation.cancelled = true; if (operation.state !== "prepared") { operation.reload?.rollback().catch((error) => this.#host.report(error)); return; } this.#operation = void 0; operation.reload?.rollback().catch((error) => this.#host.report(error)).finally(() => endReload(this.#host)); } async #run(operation, connection, frame) { const cancelled = () => new ChordError("aborted", "reload aborted by the server", { reason: "aborted" }); try { operation.reload = await beginReload(this.#host, frame.plugins, frame.options, frame.codeVersion); if (operation.cancelled) throw cancelled(); operation.state = "preparing"; await operation.reload.prepare(); if (operation.cancelled) throw cancelled(); operation.state = "prepared"; connection.send({ t: "vote", reload: frame.reload, report: operation.reload.summary() }); } catch (error) { if (operation.reload !== void 0) { await operation.reload.rollback().catch((cleanup) => this.#host.report(cleanup)); endReload(this.#host); } if (this.#operation === operation) this.#operation = void 0; if (!operation.cancelled) connection.send({ t: "vote", reload: frame.reload, error: toWireError(error) }); } } }; // node_modules/@earendil-works/chord/dist/link.js var BaseLink = class { #handler; #queue = []; #closeHandlers = /* @__PURE__ */ new Set(); #closed = false; /** The tail of frames being decoded asynchronously; undefined when none is. */ #decoding; get closed() { return this.#closed; } send(frame) { if (this.#closed) return void 0; return this.transmit(frame); } receive(handler) { this.#handler = handler; for (const frame of this.#queue.splice(0)) handler(frame); } onClose(handler) { if (this.#closed) handler("closed"); else this.#closeHandlers.add(handler); } close(reason = "closed") { if (this.#closed) return; try { this.transmit({ t: "bye", reason }); } catch { } this.ended(reason); } /** Delivers a parsed frame from the transport, after any frame still being decoded (`deliverLater`). */ deliver(frame) { if (this.#decoding !== void 0) this.deliverLater(Promise.resolve(frame)); else this.#deliverNow(frame); } /** Delivers a frame that is still being decoded (inflated in a browser); later frames wait for it, in order. */ deliverLater(frame) { const next = (this.#decoding ?? Promise.resolve()).then(() => frame).then((decoded) => this.#deliverNow(decoded), () => this.close("protocol error")).finally(() => { if (this.#decoding === next) this.#decoding = void 0; }); this.#decoding = next; } #deliverNow(frame) { if (this.#closed) return; if (frame.t === "bye") { this.ended(frame.reason ?? "peer closed"); return; } if (this.#handler === void 0) this.#queue.push(frame); else this.#handler(frame); } /** Marks the link closed (transport ended) and notifies once. */ ended(reason) { if (this.#closed) return; this.#closed = true; this.#queue = []; try { this.shutdown(); } finally { for (const handler of this.#closeHandlers) handler(reason); this.#closeHandlers.clear(); } } }; var PACK_THRESHOLD = 64 * 1024; var zlib = globalThis.process?.getBuiltinModule?.("node:zlib"); var Packed = class { bytes; constructor(bytes2) { this.bytes = bytes2; } }; function unpack(value2) { if (!(value2 instanceof Packed)) return value2; if (zlib === void 0) throw new TypeError("packed payload without zlib"); return JSON.parse(zlib.inflateRawSync(value2.bytes).toString("utf8")); } var PAYLOAD_FIELDS = /* @__PURE__ */ new Set(["args", "value"]); function payloadField(frame) { if (frame.t === "call") return frame.args === void 0 ? void 0 : "args"; if (frame.t === "result" || frame.t === "state") return frame.value === void 0 ? void 0 : "value"; return void 0; } function envelopeOf(frame, field) { const envelope = { ...frame }; delete envelope[field]; return envelope; } function packedBytes(value2, compress, json) { if (value2 instanceof Packed) return value2.bytes; if (!compress || zlib === void 0 || json === void 0 || json.length <= PACK_THRESHOLD) return void 0; return zlib.deflateRawSync(json, { level: 1 }); } function packObject(frame, compress) { const field = payloadField(frame); if (field === void 0) return frame; const value2 = frame[field]; const bytes2 = packedBytes(value2, compress, compress && zlib !== void 0 ? JSON.stringify(value2) : void 0); if (bytes2 === void 0) return frame; return { ...envelopeOf(frame, field), z: [field, Buffer.from(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength).toString("base64")] }; } function unpackObject(message) { if (message === null || typeof message !== "object" || !("z" in message)) return message; const { z, ...rest } = message; if (!Array.isArray(z) || !PAYLOAD_FIELDS.has(z[0]) || typeof z[1] !== "string") throw new TypeError("bad packed payload"); return { ...rest, [z[0]]: new Packed(Buffer.from(z[1], "base64")) }; } var encoder2 = new TextEncoder(); var decoder2 = new TextDecoder(); function packMessage(frame, compress) { const field = payloadField(frame); if (field === void 0) return JSON.stringify(frame); const value2 = frame[field]; const json = value2 instanceof Packed ? void 0 : JSON.stringify(value2); const bytes2 = packedBytes(value2, compress, json); const envelope = envelopeOf(frame, field); if (bytes2 === void 0) { const head2 = JSON.stringify(envelope); return `${head2.slice(0, -1)},${JSON.stringify(field)}:${json}}`; } const head = encoder2.encode(JSON.stringify({ ...envelope, z: field })); const message = new Uint8Array(4 + head.byteLength + bytes2.byteLength); new DataView(message.buffer).setUint32(0, head.byteLength); message.set(head, 4); message.set(bytes2, 4 + head.byteLength); return message; } function splitMessage(data) { const length = new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0); const envelope = JSON.parse(decoder2.decode(data.subarray(4, 4 + length))); if (envelope === null || typeof envelope !== "object") throw new TypeError("bad envelope"); const { z: field, ...rest } = envelope; if (typeof field !== "string" || !PAYLOAD_FIELDS.has(field)) throw new TypeError("bad packed payload"); return { envelope: rest, field, bytes: data.slice(4 + length) }; } function inflateMessage(data) { if (zlib === void 0) throw new TypeError("inflateMessage needs zlib"); const { envelope, field, bytes: bytes2 } = splitMessage(data); const head = JSON.stringify(envelope); return `${head.slice(0, -1)},${JSON.stringify(field)}:${zlib.inflateRawSync(bytes2).toString("utf8")}}`; } async function inflate(bytes2) { const stream = new Blob([bytes2]).stream().pipeThrough(new DecompressionStream("deflate-raw")); return JSON.parse(await new Response(stream).text()); } function parseFrame(data) { const frame = unpackObject(JSON.parse(data)); if (frame === null || typeof frame !== "object" || typeof frame.t !== "string") throw new TypeError("not a frame"); return frame; } var MemoryLink = class extends BaseLink { peer; #severed = false; #pending = Promise.resolve(); /** Simulates a dead network: frames are dropped silently in both directions and no close is signalled. */ sever() { this.#severed = true; if (this.peer) this.peer.#severed = true; } transmit(frame) { if (this.#severed) return void 0; const data = JSON.stringify(packObject(frame, true)); const peer = this.peer; this.#pending = this.#pending.then(() => { if (!this.#severed && peer !== void 0) peer.deliver(parseFrame(data)); }); return data.length; } shutdown() { const peer = this.peer; this.#pending = this.#pending.then(() => peer?.ended("peer closed")); } }; function memoryLinkPair() { const a = new MemoryLink(); const b = new MemoryLink(); a.peer = b; b.peer = a; return [a, b]; } var IpcLink = class extends BaseLink { #channel; #onMessage = (message) => { const frame = message?.chord2; if (frame === void 0) return; try { this.deliver(unpackObject(frame)); } catch { this.close("protocol error"); } }; #onDisconnect = () => this.ended("ipc disconnected"); /** * A write can fail after the peer exited and before the channel reports `disconnect` (an `ack` or a `bye` sent then). * The peer is gone either way: the link ends, as on `disconnect`. */ #onSent = (error) => { if (error !== null) this.ended(`ipc send failed: ${error.code ?? error.message}`); }; constructor(channel) { super(); this.#channel = channel; channel.on("message", this.#onMessage); channel.on("disconnect", this.#onDisconnect); } transmit(frame) { if (this.#channel.connected) this.#channel.send({ chord2: packObject(frame, true) }, this.#onSent); return void 0; } shutdown() { this.#channel.off("message", this.#onMessage); this.#channel.off("disconnect", this.#onDisconnect); } }; var ipcLink = (channel) => new IpcLink(channel); var OPEN = 1; var WebSocketLink = class extends BaseLink { #socket; #compress; #outbox = []; constructor(socket, options = {}) { super(); this.#socket = socket; this.#compress = options.compress ?? false; const acceptPacked = options.acceptPacked ?? true; if ("binaryType" in socket) socket.binaryType = "arraybuffer"; socket.addEventListener("open", () => { for (const data of this.#outbox.splice(0)) socket.send(data); }); socket.addEventListener("message", (event) => { try { const data = event.data; if (typeof data === "string") { this.deliver(parseFrame(data)); return; } if (!acceptPacked) throw new TypeError("packed frames are not accepted"); const bytes2 = data instanceof ArrayBuffer ? new Uint8Array(data) : ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : void 0; if (bytes2 === void 0) throw new TypeError("unknown message type"); const { envelope, field, bytes: payload } = splitMessage(bytes2); if (zlib !== void 0) this.deliver({ ...envelope, [field]: new Packed(payload) }); else this.deliverLater(inflate(payload).then((value2) => ({ ...envelope, [field]: value2 }))); } catch { this.close("protocol error"); } }); socket.addEventListener("close", () => this.ended("socket closed")); } transmit(frame) { const data = packMessage(frame, this.#compress); if (this.#socket.readyState === OPEN) this.#socket.send(data); else this.#outbox.push(data); return typeof data === "string" ? data.length : data.byteLength; } shutdown() { this.#outbox = []; if (this.#socket.readyState <= OPEN) this.#socket.close(1e3); } }; var webSocketLink = (socket, options) => new WebSocketLink(socket, options); // node_modules/@earendil-works/chord/dist/connection.js var DEFAULT_WINDOW = 256 * 1024; var PENDING_WINDOWS = 4; var unavailable = (message) => new ChordError("unavailable", message); var RelayRef = class { origin; frame; principal; /** The client connection the hold comes from (forwarded downstream); undefined for a host's own holds. */ originTag; /** The origin's authorization generation when the hold arrived; a revocation bumps it. */ generation; down; downId = 0; closed = false; /** The origin wants state (forwarded as `state: false`, `sub`, `unsub`). */ stateWanted; /** The members of the first unit, still to forward past the origin's window. */ urgent; #shaped = false; /** Members whose ops this relay dropped: downstream ops are ignored until their reset arrives. */ awaiting = /* @__PURE__ */ new Set(); calls = /* @__PURE__ */ new Map(); #waiters = /* @__PURE__ */ new Set(); constructor(origin, frame, principal, originTag) { this.origin = origin; this.frame = frame; this.principal = principal; this.originTag = originTag; this.stateWanted = frame.state !== false; this.generation = origin.generation; } get originId() { return this.frame.id; } get client() { return this.originTag !== void 0; } /** Still authorized as it was when it arrived. */ get current() { return !this.closed && this.origin.open && this.origin.generation === this.generation; } /** The downstream connection once the route resolved; rejects if the relay closes first. */ ready() { if (this.closed) return Promise.reject(new ChordError("gone", "relayed hold closed")); if (this.down !== void 0) return Promise.resolve(this.down); return new Promise((resolve2, reject2) => { this.#waiters.add({ resolve: resolve2, reject: reject2 }); }); } /** Forwards the hold to `down`. */ open(down) { if (this.closed) return; this.down = down; this.downId = down.nextId(); down.relayedRefs.set(this.downId, this); const { at, token, input } = this.frame; down.send({ t: "hold", id: this.downId, at, token, input, principal: this.principal, ...this.originTag === void 0 ? {} : { origin: this.originTag }, ...this.stateWanted ? {} : { state: false }, ...this.frame.again === true ? { again: true } : {} }); for (const waiter of this.#waiters) waiter.resolve(down); this.#waiters.clear(); } /** The origin starts or stops the hold's state. */ wantState(wanted) { if (this.closed || wanted === this.stateWanted) return; this.stateWanted = wanted; if (!wanted) { this.origin.dropHoldState(this.originId); this.awaiting.clear(); } this.down?.send({ t: wanted ? "sub" : "unsub", hold: this.downId }); } /** * The origin could not keep up and dropped what waited for member `m` (or asked for it again): ask downstream for a * reset, and ignore its ops until the reset arrives. */ resync(m) { if (this.closed) return; this.awaiting.add(m); this.down?.send({ t: "resync", hold: this.downId, m }); } /** A shape from downstream: forwarded at once; the members of the first unit then skip the origin's window. */ shape(frame) { if (this.closed) return; if (!this.#shaped) { this.#shaped = true; if (this.frame.again !== true) this.urgent = new Set(frame.state); } this.origin.send({ ...frame, id: this.originId }); } /** A state frame from downstream. */ state(frame) { if (this.closed) return; const m = frame.m; if (this.awaiting.has(m)) { if (frame.as === "ops") return; this.awaiting.delete(m); } const out = { ...frame, hold: this.originId }; if (this.urgent?.delete(m) === true) { this.origin.send(out); return; } this.origin.sendState(out, { resync: () => this.resync(m) }); } /** * Closes the relay. `releaseDown`: the downstream hold is still there (origin released, disconnected, or was * revoked): cancel its calls and release it. `notify`: tell the origin the hold is gone. */ close(error, options) { if (this.closed) return; this.closed = true; for (const call2 of [...this.calls.values()]) call2.cancel(error, options.releaseDown); this.origin.dropHoldState(this.originId); if (this.origin.served.get(this.originId)?.kind === "relay") this.origin.served.delete(this.originId); if (this.down !== void 0) { this.down.relayedRefs.delete(this.downId); if (options.releaseDown) this.down.send({ t: "release", id: this.downId }); } for (const waiter of this.#waiters) waiter.reject(error); this.#waiters.clear(); if (options.notify) this.origin.send({ t: "gone", id: this.originId, error: toWireError(error) }); } }; var RelayCall = class { relay; originId; downId; #pending; #settled = false; constructor(relay, originId, downId, pending) { this.relay = relay; this.originId = originId; this.downId = downId; this.#pending = pending; relay.calls.set(originId, this); relay.down?.relayedCalls.set(downId, this); } finish(error, value2) { if (this.#settled) return; this.#settled = true; this.relay.calls.delete(this.originId); this.relay.down?.relayedCalls.delete(this.downId); if (error === void 0) this.#pending.resolve(value2); else this.#pending.reject(error); } /** Cancels downstream (when the downstream hold still exists) and settles with `error`. */ cancel(error, forward) { if (this.#settled) return; if (forward) this.relay.down?.send({ t: "cancel", id: this.downId }); this.finish(error); } }; var Connection = class { host; link; /** `upstream`: the link to this host's parent. `downstream`: a link this host accepted. */ role; peer; /** A client connection's opaque id: holds that come over it (directly or relayed) carry it as their `origin`. */ originId; principal; restamp; /** Bumped when the stamped principal is revoked or re-stamped: relays from before are refused. */ generation = 0; /** The code version a downstream host peer runs (from its hello, then from commits). */ peerCodeVersion; /** A downstream host peer is usable once it runs this host's current code version (see `HostImpl.peerHello`). */ synced; /** Code versions this child announced while behind this host (fenced): this host never ran them. */ fencedCodeVersions = /* @__PURE__ */ new Set(); /** Roots of fenced code versions, by the code version of this host they were admitted in. */ #admitted = /* @__PURE__ */ new Map(); #parentName; #syncWaiters = /* @__PURE__ */ new Set(); #nextId = 1; #lastSeen = Date.now(); #pinger; #maxCalls; // flow control (see the class comment) #window; #pendingCap; /** Whether the peer acknowledges frames; until its hello is here, frames are counted in case it does. */ #peerAcks; #sent = 0; #acked = 0; /** Sizes of the frames sent after the last acknowledged one. */ #unacked = []; #unackedBytes = 0; #pending = /* @__PURE__ */ new Map(); /** Bytes of ops waiting in `#pending`. */ #pendingBytes = 0; #received = 0; #ackTimer; #onProtocolMismatch; /** The peer speaks another protocol version: nothing it sends is processed any more. */ #mismatched = false; // consumer side: what this host asked of the peer remotes = /* @__PURE__ */ new Map(); calls = /* @__PURE__ */ new Map(); // provider side: what the peer asked of this host served = /* @__PURE__ */ new Map(); incoming = /* @__PURE__ */ new Map(); // downstream side of relays: ids this host used on this link relayedRefs = /* @__PURE__ */ new Map(); relayedCalls = /* @__PURE__ */ new Map(); /** Reload votes awaited from this (child) peer. */ votes = /* @__PURE__ */ new Map(); constructor(host2, link, config) { this.host = host2; this.link = link; this.role = config.role; this.peer = config.peer; this.originId = config.originId; this.principal = config.principal; this.restamp = config.restamp; this.synced = !(this.role === "downstream" && this.peer.kind === "host"); this.#window = config.window ?? DEFAULT_WINDOW; this.#pendingCap = config.pendingCap ?? this.#window * PENDING_WINDOWS; this.#maxCalls = config.maxCalls ?? Number.POSITIVE_INFINITY; this.#onProtocolMismatch = config.onProtocolMismatch; link.onClose((reason) => this.#closed(reason)); link.receive((frame) => this.#receive(frame)); this.send({ t: "hello", protocol: PROTOCOL_VERSION, name: host2.name, kind: host2.kind, codeVersion: host2.current.id, acks: true }); const interval = config.pingInterval ?? 15e3; const timeout = config.pingTimeout ?? 45e3; if (link.closed) return; let n = 0; this.#pinger = setInterval(() => { if (Date.now() - this.#lastSeen > timeout) link.close("ping timeout"); else this.send({ t: "ping", n: ++n }); }, interval); this.#pinger.unref?.(); } get open() { return !this.link.closed; } /** The peer's name: configured for downstream hosts, announced by the parent upstream. */ get name() { return this.role === "upstream" ? this.#parentName : this.peer.name; } send(frame) { const size = this.link.send(frame) ?? 0; if (this.#peerAcks === false) return; this.#sent++; this.#unacked.push(size); this.#unackedBytes += size; } /** Sends a state frame, or keeps it waiting (see the class comment). */ sendState(frame, overflow) { const key = stateKey(frame.hold, frame.m); let slot = this.#pending.get(key); if (slot === void 0 && !this.#full) { this.send(frame); return; } if (slot?.kind === "unit") return; if (slot === void 0) { slot = { kind: "state", hold: frame.hold, m: frame.m, base: void 0, ops: [], size: 0, lazy: false, overflow }; this.#pending.set(key, slot); } if (frame.as !== "ops") { this.#pendingBytes -= slot.size; slot.base = frame; slot.ops = []; slot.size = 0; slot.lazy = false; return; } if (slot.lazy) return; const size = JSON.stringify(frame.ops).length; if (this.#pendingBytes + size > this.#pendingCap) this.#makeRoom(size, slot); if (slot.lazy || this.#pending.get(key) !== slot) return; for (const op2 of frame.ops ?? []) slot.ops.push(op2); slot.size += size; this.#pendingBytes += size; } /** Replaces the oldest waiting streams by resets until `size` more bytes fit; `current` last, if needed. */ #makeRoom(size, current) { for (const slot of [...this.#pending.values()]) { if (this.#pendingBytes + size <= this.#pendingCap) return; if (slot.kind === "state" && slot !== current && slot.size > 0) this.#drop(slot); } if (this.#pendingBytes + size > this.#pendingCap) this.#drop(current); } /** Drops what waits for a stream: a lazy reset replaces it (provider), or the relay asks downstream for one. */ #drop(slot) { this.#pendingBytes -= slot.size; slot.base = void 0; slot.ops = []; slot.size = 0; if ("resend" in slot.overflow) { slot.lazy = true; return; } this.#pending.delete(stateKey(slot.hold, slot.m)); slot.overflow.resync(); } /** Sends member `m` of a served hold again as a reset, through the window (`resync`). */ #resetLater(hold, m, resend) { const key = stateKey(hold, m); let slot = this.#pending.get(key); if (slot?.kind === "unit") return; if (slot === void 0) { slot = { kind: "state", hold, m, base: void 0, ops: [], size: 0, lazy: true, overflow: { resend } }; this.#pending.set(key, slot); } else { this.#pendingBytes -= slot.size; slot.base = void 0; slot.ops = []; slot.size = 0; slot.lazy = true; } this.#flush(); } /** Queues a hold's lazy unit (a waiting one already covers this one), and sends what the window allows. */ #queueUnit(hold, flush) { const key = unitKey(hold); if (!this.#pending.has(key)) this.#pending.set(key, { kind: "unit", hold, flush }); this.#flush(); } /** Forgets what waits for a stream that ended. */ dropState(hold, m) { const slot = this.#pending.get(stateKey(hold, m)); if (slot?.kind !== "state") return; this.#pendingBytes -= slot.size; this.#pending.delete(stateKey(hold, m)); } /** Forgets everything that waits for a hold (released, closed, or its state stopped). */ dropHoldState(hold) { for (const [key, slot] of this.#pending) { if (slot.hold !== hold) continue; if (slot.kind === "state") this.#pendingBytes -= slot.size; this.#pending.delete(key); } } /** What waits for the peer: slots and bytes of ops (for tests and diagnostics). */ get waiting() { return { slots: this.#pending.size, bytes: this.#pendingBytes }; } get #full() { return this.#peerAcks === true && this.#unackedBytes >= this.#window; } #onAck(n) { if (this.#peerAcks !== true || n < this.#acked || n > this.#sent) { this.#violation(`ack ${n} outside ${this.#acked}..${this.#sent}`); return; } for (const size of this.#unacked.splice(0, n - this.#acked)) this.#unackedBytes -= size; this.#acked = n; this.#flush(); } /** Sends waiting slots in creation order while the window allows. */ #flush() { for (const [key, slot] of this.#pending) { if (this.#full) return; this.#pending.delete(key); if (slot.kind === "unit") { slot.flush(); continue; } this.#pendingBytes -= slot.size; if (slot.lazy) { if ("resend" in slot.overflow) slot.overflow.resend(); continue; } if (slot.base !== void 0) this.send(slot.base); if (slot.ops.length > 0) this.send({ t: "state", hold: slot.hold, m: slot.m, as: "ops", ops: slot.ops }); } } /** Acknowledges once the frames that arrived together are processed: one ack per burst, not per frame. */ #scheduleAck() { if (this.#peerAcks !== true || this.#ackTimer !== void 0 || !this.open) return; this.#ackTimer = setTimeout(() => { this.#ackTimer = void 0; this.link.send({ t: "ack", n: this.#received }); }, 0); this.#ackTimer.unref?.(); } nextId() { return this.#nextId++; } /** Resolves once the peer is usable (a host peer runs this host's code version); rejects if the link closes first. */ whenSynced() { if (!this.open) return Promise.reject(unavailable(`link to ${this.name ?? "peer"} closed`)); if (this.synced) return Promise.resolve(); return new Promise((resolve2, reject2) => this.#syncWaiters.add({ resolve: resolve2, reject: reject2 })); } markSynced(codeVersion) { this.peerCodeVersion = codeVersion; this.synced = true; for (const waiter of this.#syncWaiters) waiter.resolve(void 0); this.#syncWaiters.clear(); } /** Revokes the stamped principal: relays made under it close at once (also those still resolving a route). */ revoke(next) { this.generation++; const error = new ChordError("gone", "principal invalidated"); for (const served of [...this.served.values()]) { if (served.kind === "relay") served.relay.close(error, { releaseDown: true, notify: true }); } if (next === void 0) this.link.close("principal invalidated"); else this.principal = next; } /** * Stops talking to a peer of another protocol version: reports it and closes the link. The close reason tells the * peer this side's version (`protocol-version `); a peer that noticed first told us the same way (`#closed`). */ #protocolMismatch(peer, close = true) { if (this.#mismatched) return; this.#mismatched = true; try { this.#onProtocolMismatch?.({ local: PROTOCOL_VERSION, peer }); } catch (error) { this.host.report(error); } if (close) this.link.close(`protocol-version ${PROTOCOL_VERSION}`); } /** A protocol violation: the peer cannot be trusted with anything on this link any more. */ #violation(reason) { this.host.report(new ChordError("invalid", `link to ${this.name ?? "peer"}: ${reason}`, { reason: "protocol" })); this.link.close(`protocol error: ${reason}`); } // ─── consumer side (RemoteChannel) ─── hold(binding, again) { const id = this.nextId(); this.remotes.set(id, binding); binding.bound(this, id); const principal = binding.principal; this.send({ t: "hold", id, at: binding.at, token: binding.contract.description, input: binding.input, ...principal === void 0 ? {} : { principal }, ...binding.stateOn ? {} : { state: false }, ...again ? { again: true } : {} }); return id; } release(id) { this.remotes.delete(id); this.send({ t: "release", id }); } wantState(hold, wanted) { if (this.remotes.has(hold)) this.send({ t: wanted ? "sub" : "unsub", hold }); } call(hold, member, args, root2, signal) { const id = this.nextId(); return new Promise((resolve2, reject2) => { if (signal.aborted) { reject2(new ChordError("aborted", "call aborted")); return; } const onAbort = () => { this.calls.delete(id); this.send({ t: "cancel", id }); reject2(new ChordError("aborted", "call aborted", { cause: signal.reason })); }; signal.addEventListener("abort", onAbort, { once: true }); const done = () => signal.removeEventListener("abort", onAbort); this.calls.set(id, { resolve: (value2) => { done(); resolve2(value2); }, reject: (error) => { done(); reject2(error); } }); const carried = root2 !== void 0 && this.host.kind === "host"; if (carried) this.host.rootSent(root2.rootId, this); this.send({ t: "call", id, hold, member, args, ...carried ? { root: root2 } : {} }); }); } // ─── receiving ─── #receive(frame) { if (this.#mismatched) return; this.#lastSeen = Date.now(); if (!isFrame(frame)) { this.#violation("malformed frame"); return; } if (frame.t === "ack") { this.#onAck(frame.n); return; } this.#received++; try { this.#dispatch(frame); } catch (error) { this.host.report(error); } this.#scheduleAck(); } #dispatch(frame) { switch (frame.t) { case "hello": this.#onHello(frame); return; case "ping": this.send({ t: "pong", n: frame.n }); return; case "pong": case "bye": case "ack": return; case "hold": this.#onHold(frame); return; case "release": this.#dropServed(frame.id, true); return; case "call": this.#onCall(frame); return; case "cancel": this.incoming.get(frame.id)?.abort(new ChordError("aborted", "cancelled by caller")); return; case "sub": case "unsub": this.#onWantState(frame.hold, frame.t === "sub"); return; case "resync": this.#onResync(frame.hold, frame.m); return; case "shape": case "gone": this.#onHoldReply(frame); return; case "result": this.#onResult(frame); return; case "state": this.#onState(frame); return; case "root_end": this.#admitted.delete(frame.rootId); this.host.foreignRootEnded(frame.rootId, this); return; case "invalidate": case "prepare": case "commit": case "abort": case "retire": if (this.role !== "upstream") { this.#violation(`${frame.t} from a child`); return; } if (frame.t === "invalidate") { this.host.invalidatePrincipal(frame.pattern).catch((error) => this.host.report(error)); } else if (frame.t === "retire") this.host.allowRetire(frame.codeVersion); else this.host.onReloadFrame(this, frame); return; case "vote": { if (this.role !== "downstream" || this.peer.kind !== "host") { this.#violation("vote from a non-participant"); return; } const vote = this.votes.get(frame.reload); this.votes.delete(frame.reload); if (frame.error === void 0) vote?.resolve(frame.report); else vote?.reject(fromWireError(frame.error)); return; } } } /** * The peer's hello must agree with what this side decided about it. A downstream host announces its code version: * the host either finds it in step or fences and reconciles it (never renumbers). */ #onHello(frame) { if ((frame.protocol ?? 1) !== PROTOCOL_VERSION) { this.#protocolMismatch(frame.protocol ?? 1); return; } if (this.#peerAcks === void 0) { this.#peerAcks = frame.acks === true; if (!this.#peerAcks) { this.#unacked = []; this.#unackedBytes = 0; } } if (this.role === "upstream") { if (frame.kind !== "host") { this.#violation("parent is not a host"); return; } this.#parentName ??= frame.name; return; } if (frame.kind !== this.peer.kind || this.peer.name !== void 0 && frame.name !== this.peer.name) { this.#violation(`hello ${frame.kind} ${frame.name} does not match the accepted ${this.peer.kind} ${this.peer.name ?? ""}`); return; } if (this.peer.kind === "host") this.host.peerHello(this, frame.codeVersion); } /** * Principal stamping: a downstream link acts as the principal it was accepted with, whatever the frame says. Only * the parent may forward a principal (relayed requests); its own requests act as the principal given to `connect`. */ #principalOf(frame) { if (this.role === "downstream") return this.principal; return frame.principal ?? this.principal; } /** * The client connection a hold comes from: this link's own id for a client link (whatever the frame says), the id * the parent forwarded for a relayed hold, none for a host's own holds. */ #originOf(frame) { return this.role === "downstream" ? this.originId : frame.origin; } #onHold(frame) { if (this.served.has(frame.id)) { this.#violation(`duplicate hold id ${frame.id}`); return; } const principal = this.#principalOf(frame); const origin = this.#originOf(frame); const client = origin !== void 0; if (frame.at !== this.host.name) { if (this.role === "upstream") { this.send({ t: "gone", id: frame.id, error: toWireError(unavailable(`${this.host.name} does not relay`)) }); return; } const relay = new RelayRef(this, frame, principal, origin); this.served.set(frame.id, { kind: "relay", relay }); this.host.relay(relay).catch((error) => relay.close(error, { releaseDown: true, notify: true })); return; } let ref; try { const token = contractFromDescription(frame.token); if (token.scope !== "remote") throw invalid("token", `${token.name} is local; only remote types cross links`); if (resolveHost(token.name, token.description.host, frame.input) !== this.host.name) throw invalid("placement", `${token.name} with this input does not live on ${this.host.name}`); const acquired = this.host.acquire(token, frame.input, { principal }, client ? "client" : "connection"); if (!(acquired instanceof RefImpl)) throw unavailable("not a local reference"); ref = acquired; } catch (error) { this.send({ t: "gone", id: frame.id, error: toWireError(client ? clientError(error) : error) }); return; } const served = { kind: "local", id: frame.id, ref, principal, origin, wantsState: frame.state !== false, again: frame.again === true, unitSent: false, feeds: /* @__PURE__ */ new Map() }; this.served.set(frame.id, served); ref.onShape(() => this.#unit(served)); ref.goneListeners.add((error) => { if (this.served.get(frame.id) !== served) return; this.#dropServed(frame.id, false); this.send({ t: "gone", id: frame.id, error: toWireError(error) }); }); if (ref.status === "live") this.#unit(served); } /** The state members of `shape` the consumer's contract knows. */ #stateMembers(ref, shape) { return shape.members.filter((name) => ref.describe(shape.variant, name)?.kind === "state"); } /** * The shape changed (live, reload, variant) or state was asked for: members that left lose their stream, and a unit * goes out with the shape and the members the consumer lacks. The first unit of a hold (not a re-hold) is urgent. */ #unit(served) { if (this.served.get(served.id) !== served) return; const shape = served.ref.shape; if (shape === void 0) return; const members = new Set(served.wantsState ? this.#stateMembers(served.ref, shape) : []); for (const [name, feed] of served.feeds) { if (members.has(name)) continue; feed.unsubscribe(); served.feeds.delete(name); this.dropState(served.id, name); } const urgent = served.feeds.size === 0 && !(served.again && !served.unitSent); if (urgent) this.#sendUnit(served); else this.#queueUnit(served.id, () => this.#sendUnit(served)); } /** Builds and sends a unit from the current shape and values. */ #sendUnit(served) { this.#pending.delete(unitKey(served.id)); if (this.served.get(served.id) !== served) return; const shape = served.ref.shape; if (shape === void 0) return; served.unitSent = true; const members = served.wantsState ? this.#stateMembers(served.ref, shape) : []; const listed = members.filter((name) => !served.feeds.has(name)); const snaps = []; const attached = []; for (const name of listed) { const attachment = this.#attach(served, name); if (attachment === void 0) continue; snaps.push(attachment.frame); attached.push([name, attachment.feed]); } this.send({ t: "shape", id: served.id, ...shape.variant === void 0 ? {} : { variant: shape.variant }, members: shape.members, contract: shape.contract, state: snaps.map((frame) => frame.m) }); for (const frame of snaps) this.send(frame); for (const [name, feed] of attached) this.#replay(served, name, feed); } /** * Subscribes member `name` for the consumer: its snapshot (as a frame of type `as`), and a feed whose later events * wait in its buffer until the snapshot is sent (`#replay`). Undefined if the member is not readable state. */ #attach(served, name, as = "snap") { const readable = served.ref.member(name); if (readable === void 0 || typeof readable.events !== "function") return void 0; const feed = { unsubscribe: () => { }, encoder: encoder(), buffer: [] }; let first; feed.unsubscribe = readable.events((event) => { if (first === void 0) first = event; else if (feed.buffer !== void 0) feed.buffer.push(event); else this.#feedEvent(served, name, feed, event); }); served.feeds.set(name, feed); return { frame: this.#snapshot(served, name, as, first?.value), feed }; } /** Sends the events that arrived while the snapshot was built, now that it is sent. */ #replay(served, name, feed) { const buffered = feed.buffer ?? []; feed.buffer = void 0; for (const event of buffered) this.#feedEvent(served, name, feed, event); } /** A member's state frame for a snapshot or reset: validated against the schema the consumer is promised. */ #snapshot(served, name, as, value2) { try { validateSnapshot(served.ref, name, value2); } catch (error) { return { t: "state", hold: served.id, m: name, as: "error", error: toWireError(error) }; } return { t: "state", hold: served.id, m: name, as, ...value2 === void 0 ? {} : { value: value2 } }; } /** A later event of a member's stream: ops are encoded, a reset starts a new stream. */ #feedEvent(served, name, feed, event) { if (served.feeds.get(name) !== feed) return; const overflow = { resend: () => this.#resend(served, name) }; if (event.type === "ops") { this.sendState({ t: "state", hold: served.id, m: name, as: "ops", ops: feed.encoder.encode(event.ops) }, overflow); return; } feed.encoder = encoder(); this.sendState(this.#snapshot(served, name, "reset", event.value), overflow); } /** Sends member `name` again, now, as a reset from its current value (a lazy slot is flushed, or `resync`). */ #resend(served, name) { const feed = served.feeds.get(name); if (feed === void 0 || this.served.get(served.id) !== served) return; feed.unsubscribe(); served.feeds.delete(name); const attachment = this.#attach(served, name, "reset"); if (attachment === void 0) return; this.send(attachment.frame); this.#replay(served, name, attachment.feed); } /** The consumer starts (`sub`) or stops (`unsub`) the state of a hold. */ #onWantState(hold, wanted) { const served = this.served.get(hold); if (served === void 0) return; if (served.kind === "relay") { served.relay.wantState(wanted); return; } if (served.wantsState === wanted) return; served.wantsState = wanted; if (wanted) { this.#unit(served); return; } for (const feed of served.feeds.values()) feed.unsubscribe(); served.feeds.clear(); this.dropHoldState(hold); } /** The consumer (a relay that dropped ops) asks for member `m` again as a reset. */ #onResync(hold, m) { const served = this.served.get(hold); if (served === void 0) return; if (served.kind === "relay") { served.relay.resync(m); return; } if (!served.feeds.has(m)) return; this.#resetLater(hold, m, () => this.#resend(served, m)); } /** Forgets a served hold: unsubscribes its feeds and releases it (locally or downstream). */ #dropServed(id, release) { const served = this.served.get(id); if (served === void 0) return; this.served.delete(id); if (served.kind === "relay") { served.relay.close(new ChordError("released", "relayed hold released"), { releaseDown: release, notify: false }); return; } for (const feed of served.feeds.values()) feed.unsubscribe(); served.feeds.clear(); this.dropHoldState(id); if (release) served.ref.release(); } #onCall(frame) { if (this.incoming.has(frame.id)) { this.#violation(`duplicate call id ${frame.id}`); return; } if (this.incoming.size >= this.#maxCalls) { const error = new ChordError("unavailable", `Too many calls in progress on this connection (${this.#maxCalls}); wait for earlier ones to finish`, { reason: "too_many_calls" }); this.send({ t: "result", id: frame.id, error: toWireError(error) }); return; } const served = this.served.get(frame.hold); if (served === void 0) { this.send({ t: "result", id: frame.id, error: toWireError(new ChordError("gone", "unknown hold")) }); return; } const controller = new AbortController(); this.incoming.set(frame.id, controller); const root2 = this.peer.kind === "host" ? this.#admitRoot(frame.root) : void 0; const principal = served.kind === "local" ? served.principal : served.relay.principal; const options = { principal, signal: controller.signal }; const run2 = served.kind === "local" ? (ctx) => served.ref.object.call(served.ref, frame.member, unpack(frame.args), ctx) : (ctx) => relayCall( served.relay, frame, { rootId: ctx.rootId, codeVersion: ctx.codeVersion }, // The root's signal: a cancel from the origin and the root's code version retiring both reach it. ctx.signal, this.host ); const result = root2 === void 0 ? this.host.root(run2, options) : this.host.joinRoot(root2.rootId, root2.codeVersion, run2, { ...options, from: this }); result.then((value2) => this.send({ t: "result", id: frame.id, value: value2 }), (error) => this.send({ t: "result", id: frame.id, error: toWireError(error) })).finally(() => this.incoming.delete(frame.id)); } /** * A child that connected behind this host runs roots of a code version this host never had (it was fenced and is * being, or was, reconciled). Such a root is admitted in this host's current code version at its first call here, * and keeps that code version for its later calls (`superseded` once this host retired it). Other unknown code * versions stay `superseded`. */ #admitRoot(root2) { if (root2 === void 0 || this.role !== "downstream" || !this.fencedCodeVersions.has(root2.codeVersion)) return root2; if (this.host.hasCodeVersion(root2.codeVersion)) return root2; let codeVersion = this.#admitted.get(root2.rootId); if (codeVersion === void 0) { codeVersion = this.host.current.id; this.#admitted.set(root2.rootId, codeVersion); } return { rootId: root2.rootId, codeVersion }; } #onHoldReply(frame) { const relay = this.relayedRefs.get(frame.id); if (relay !== void 0) { if (frame.t === "gone") relay.close(fromWireError(frame.error), { releaseDown: false, notify: true }); else relay.shape(frame); return; } const binding = this.remotes.get(frame.id); if (binding === void 0) return; if (frame.t === "gone") { this.remotes.delete(frame.id); binding.gone(fromWireError(frame.error)); return; } binding.shaped({ variant: frame.variant, members: frame.members, contract: frame.contract }, frame.state); } #onResult(frame) { const relayed = this.relayedCalls.get(frame.id); if (relayed !== void 0) { relayed.finish(frame.error === void 0 ? void 0 : fromWireError(frame.error), frame.value); return; } const pending = this.calls.get(frame.id); this.calls.delete(frame.id); if (frame.error !== void 0) { pending?.reject(fromWireError(frame.error)); return; } let value2; try { value2 = unpack(frame.value); } catch (error) { pending?.reject(error); return; } pending?.resolve(value2); } #onState(frame) { const relay = this.relayedRefs.get(frame.hold); if (relay !== void 0) { relay.state(frame); return; } const binding = this.remotes.get(frame.hold); if (binding === void 0) return; if (!(frame.value instanceof Packed)) { binding.state(frame.m, frame); return; } let value2; try { value2 = unpack(frame.value); } catch (error) { binding.state(frame.m, { as: "error", error: toWireError(error) }); return; } binding.state(frame.m, { ...frame, value: value2 }); } // ─── lifetime ─── #closed(reason) { const told = /^protocol-version (\d+)$/.exec(reason); if (told !== null && Number(told[1]) !== PROTOCOL_VERSION) this.#protocolMismatch(Number(told[1]), false); if (this.#pinger !== void 0) clearInterval(this.#pinger); if (this.#ackTimer !== void 0) clearTimeout(this.#ackTimer); this.#pending.clear(); this.#pendingBytes = 0; const error = unavailable(`link to ${this.name ?? "peer"} closed: ${reason}`); for (const id of [...this.served.keys()]) this.#dropServed(id, true); for (const controller of this.incoming.values()) controller.abort(error); this.incoming.clear(); for (const pending of this.calls.values()) pending.reject(error); this.calls.clear(); for (const vote of this.votes.values()) vote.reject(error); this.votes.clear(); for (const waiter of this.#syncWaiters) waiter.reject(error); this.#syncWaiters.clear(); for (const relay of [...this.relayedRefs.values()]) relay.close(error, { releaseDown: false, notify: true }); this.relayedRefs.clear(); this.relayedCalls.clear(); const bindings = [...this.remotes.values()]; this.remotes.clear(); for (const binding of bindings) { if (this.role === "upstream") binding.detached(); else binding.gone(error); } this.host.connectionClosed(this); } }; var stateKey = (hold, m) => `s${hold}\0${m}`; var unitKey = (hold) => `u${hold}`; async function relayCall(relay, frame, root2, signal, host2) { const abortError2 = () => new ChordError("aborted", "call cancelled", { cause: signal.reason }); if (signal.aborted) throw abortError2(); let onAbort = () => { }; let onCancel = () => { }; const aborted = new Promise((_, reject2) => { onAbort = () => reject2(abortError2()); signal.addEventListener("abort", onAbort, { once: true }); }); aborted.catch(() => { }); try { const down = await Promise.race([relay.ready(), aborted]); if (relay.closed) throw new ChordError("gone", "relayed hold closed"); return await new Promise((resolve2, reject2) => { const call2 = new RelayCall(relay, frame.id, down.nextId(), { resolve: resolve2, reject: reject2 }); onCancel = () => call2.cancel(abortError2(), true); signal.addEventListener("abort", onCancel, { once: true }); host2.rootSent(root2.rootId, down); down.send({ t: "call", id: call2.downId, hold: relay.downId, member: frame.member, args: frame.args, root: root2 }); }); } finally { signal.removeEventListener("abort", onAbort); signal.removeEventListener("abort", onCancel); } } function validateSnapshot(ref, member, value2) { if (value2 === void 0) return; const installed = ref.object.currentInstalled(); if (installed === void 0) return; const view2 = ref.currentView(); const variant = view2 === void 0 ? variantOf(installed.implementation) : variantOf(view2); const description = view2 !== void 0 && view2 !== installed.implementation ? ref.contract.description.variants[variant ?? LOCAL_VARIANT]?.[member] : installed.service.contract.description.variants[variantOf(installed.implementation) ?? LOCAL_VARIANT]?.[member]; if (description?.kind === "state") assertValid("state", `${ref.address}.${member}`, description.schema, value2); } function clientError(error) { if (error instanceof ChordError && error.code === "contract_mismatch") { return new ChordError("upgrade_required", error.message, { cause: error }); } return error; } // node_modules/@earendil-works/chord/dist/remote.js var noop2 = () => { }; function deferred() { let resolve2 = noop2; let reject2 = noop2; const promise = new Promise((done, fail) => { resolve2 = done; reject2 = fail; }); promise.catch(noop2); return { promise, resolve: resolve2, reject: reject2 }; } var RemoteBinding = class { host; key; contract; at; input; /** Forwarded principal (omitted for the host's own principal). */ principal; /** `ended`: gone with `error`, or closed (no views, no linger). */ status = "loading"; error; /** The applied shape (kept while re-holding). */ shape; /** Every state member of the applied shape has a value (stays true while re-holding). */ hydrated = false; /** The current hold; undefined while resolving the route or after a link loss. */ hold; /** What the provider was told: state for this hold (`state: false` / `sub` / `unsub`). */ stateOn = false; views = /* @__PURE__ */ new Set(); members = /* @__PURE__ */ new Map(); /** Lingering since (no views): set by the host. */ lingerTimer; #listeners = /* @__PURE__ */ new Map(); /** A shape waiting for its snapshots. */ #pending; /** Resolved while a shape is applied for the current hold (calls wait for it). */ #live = deferred(); /** A shape was applied once (a re-hold says `again`). */ #applied = false; /** Waiting for the first hydration (`whenHydrated`). */ #hydrationWaiters = /* @__PURE__ */ new Set(); constructor(host2, fields) { this.host = host2; this.key = fields.key; this.contract = fields.contract; this.at = fields.at; this.input = fields.input; this.principal = fields.principal; } get ended() { return this.status === "ended"; } /** Views that want state. */ get stateViews() { let count = 0; for (const view2 of this.views) if (view2.wantsState) count++; return count; } /** Whether the next hold should carry state: some view wants it, or nobody is left (lingering keeps it). */ get wantsState() { return this.views.size === 0 ? this.stateOn : this.stateViews > 0; } /** Whether a re-hold is `again` (the binding already has a shape). */ get applied() { return this.#applied; } // ─── views ─── addView(view2) { this.views.add(view2); if (this.lingerTimer !== void 0) { clearTimeout(this.lingerTimer); this.lingerTimer = void 0; } this.#syncState(); view2.update(); } removeView(view2) { if (!this.views.delete(view2)) return; for (const set of this.#listeners.values()) for (const readable of set) if (readable.view === view2) set.delete(readable); if (this.views.size === 0) { this.host.bindingIdle(this); return; } this.#syncState(); } /** Tells the provider to start or stop state when the views' wish changed. */ #syncState() { if (this.ended || this.views.size === 0) return; const wanted = this.stateViews > 0; if (wanted === this.stateOn) return; this.stateOn = wanted; if (!wanted) { this.hydrated = false; this.members.clear(); if (this.#pending !== void 0) this.#pending.missing.clear(); } if (this.hold !== void 0) this.hold.channel.wantState(this.hold.id, wanted); for (const view2 of this.views) view2.update(); } listen(name, readable) { let set = this.#listeners.get(name); if (set === void 0) { set = /* @__PURE__ */ new Set(); this.#listeners.set(name, set); } set.add(readable); } unlisten(name, readable) { this.#listeners.get(name)?.delete(readable); } /** Whether `name` is a state member of the applied variant (as the consumer's contract describes it). */ inVariant(name) { const variant = this.shape?.variant; return variant !== void 0 && this.contract.description.variants[variant]?.[name]?.kind === "state"; } /** The members' value as views read it: throws `not_ready` before the first hydration, `not_in_variant` outside it. */ read(name) { if (this.status === "ended") throw this.terminalError(); if (!this.hydrated) throw new ChordError("not_ready", `${this.key}.${name} is not hydrated yet`); if (!this.inVariant(name)) throw new ChordError("not_in_variant", `${this.key}: ${name} is not a state member of variant ${this.shape?.variant}`); return this.members.get(name)?.value; } /** Resolves once the binding has its state (rejects if it ends first). */ whenHydrated() { if (this.hydrated) return Promise.resolve(); if (this.ended) return Promise.reject(this.terminalError()); return new Promise((resolve2, reject2) => this.#hydrationWaiters.add({ resolve: resolve2, reject: reject2 })); } #settleHydration(error) { const waiters = [...this.#hydrationWaiters]; this.#hydrationWaiters.clear(); for (const waiter of waiters) { if (error === void 0) waiter.resolve(); else waiter.reject(error); } } readable(name) { return this.status !== "ended" && this.hydrated && this.inVariant(name); } terminalError() { return this.error ?? new ChordError("released", `reference ${this.key} released`); } // ─── driven by the host and the link ─── bound(channel, id) { this.hold = { channel, id }; this.stateOn = this.wantsState; } /** A unit begins: `listed` are the members whose snapshots follow. */ shaped(shape, listed) { if (this.ended) return; const previous = this.#pending; const missing = new Set(listed); if (previous !== void 0) for (const name of previous.missing) missing.add(name); const wanted = new Set(this.#stateMembers(shape)); for (const name of [...missing]) if (!wanted.has(name) || !this.stateOn) missing.delete(name); this.#pending = { shape, missing, values: previous?.values ?? /* @__PURE__ */ new Map() }; if (missing.size === 0) this.#apply(); } /** A state frame for member `name` of the current hold. */ state(name, frame) { if (this.ended) return; if (frame.as === "error") { const error = fromWireError(frame.error); if (!this.hydrated && this.stateOn) { this.gone(error); this.close(); } else this.host.report(error); return; } const pending = this.#pending; if (frame.as === "ops") { const target = pending?.values.get(name) ?? this.members.get(name); if (target === void 0) return; const ops3 = target.decoder.decode(frame.ops ?? []); target.value = applyImmutable(target.value, ops3); if (target === this.members.get(name)) this.#deliver(name, { type: "ops", ops: ops3, value: target.value }); return; } const fresh = { value: frame.value, decoder: decoder() }; if (pending !== void 0 && (pending.missing.has(name) || pending.values.has(name))) { pending.values.set(name, fresh); pending.missing.delete(name); if (pending.missing.size === 0) this.#apply(); return; } if (!this.inVariant(name)) return; this.members.set(name, fresh); this.#deliver(name, { type: "reset", value: fresh.value }); } /** The provider ended the object (or refused the hold): every view is gone. */ gone(error) { if (this.ended) return; this.status = "ended"; this.error = error; this.hold = void 0; this.#pending = void 0; this.#live.reject(error); this.#settleHydration(error); for (const view2 of [...this.views]) view2.end(error); this.#listeners.clear(); this.host.forgetBinding(this); } /** The link went down but may come back (upstream reconnect): keep the values, wait for the re-hold. */ detached() { if (this.ended) return; this.hold = void 0; this.#pending = void 0; if (this.status === "live") { this.status = "loading"; this.#live = deferred(); } for (const view2 of this.views) view2.update(); } /** No views and no linger: releases the hold and forgets the binding. */ close() { if (this.lingerTimer !== void 0) clearTimeout(this.lingerTimer); this.lingerTimer = void 0; if (this.hold !== void 0) this.hold.channel.release(this.hold.id); this.hold = void 0; if (this.status !== "ended") { this.status = "ended"; const error = new ChordError("released", `reference ${this.key} released`); this.#live.reject(error); this.#settleHydration(error); for (const view2 of [...this.views]) view2.end(error); } this.#pending = void 0; this.#listeners.clear(); this.host.forgetBinding(this); } /** Calls `name` over the current hold, waiting for it while the binding (re)holds. */ async call(name, args, ctx) { for (; ; ) { if (this.ended) throw this.terminalError(); if (this.status !== "live" || this.hold === void 0) await raceSignal(this.#live.promise, ctx.signal); const hold = this.hold; if (hold === void 0 || this.status !== "live") continue; const variant = this.shape?.variant; const description = variant === void 0 ? void 0 : this.contract.description.variants[variant]?.[name]; if (description?.kind !== "method") { throw new ChordError("not_in_variant", `${this.key}: ${name} is not a method of variant ${variant}`); } const sent = description.args === void 0 ? void 0 : cleaned(description.args, args); return hold.channel.call(hold.id, name, sent, { rootId: ctx.rootId, codeVersion: ctx.codeVersion }, ctx.signal); } } #stateMembers(shape) { const members = shape.variant === void 0 ? void 0 : this.contract.description.variants[shape.variant]; if (members === void 0) return []; return shape.members.filter((name) => members[name]?.kind === "state"); } /** Applies the pending shape with its snapshots: members outside the variant go, new values are announced. */ #apply() { const pending = this.#pending; if (pending === void 0) return; this.#pending = void 0; this.shape = pending.shape; const wanted = new Set(this.#stateMembers(pending.shape)); for (const name of [...this.members.keys()]) if (!wanted.has(name)) this.members.delete(name); for (const [name, state] of pending.values) if (wanted.has(name)) this.members.set(name, state); this.hydrated = this.stateOn && [...wanted].every((name) => this.members.has(name)); if (this.hydrated) this.#settleHydration(); this.status = "live"; this.#applied = true; this.#live.resolve(); for (const view2 of this.views) view2.update(); for (const [name, state] of pending.values) { if (wanted.has(name)) this.#deliver(name, { type: "reset", value: state.value }); } for (const view2 of [...this.views]) view2.shapeChanged(); } #deliver(name, event) { const readables = this.#listeners.get(name); if (readables === void 0) return; for (const readable of [...readables]) readable.deliver(event); } }; var ViewReadable = class { view; #name; #values = /* @__PURE__ */ new Set(); #events = /* @__PURE__ */ new Set(); /** Event listeners that have not had their snapshot yet. */ #unseen = /* @__PURE__ */ new Set(); constructor(view2, name) { this.view = view2; this.#name = name; } get value() { if (this.view.ended) throw this.view.terminalError(); return this.view.binding.read(this.#name); } /** Not hydrated before the binding's first snapshots (a local object forwarding this member waits for them). */ get [HYDRATION]() { const binding = this.view.binding; if (binding.hydrated) return void 0; return { hydrated: false, whenHydrated: () => binding.whenHydrated() }; } subscribe(listener) { this.#values.add(listener); this.#attach(); if (this.#readable()) this.#safe(() => listener(this.view.binding.members.get(this.#name)?.value)); return () => this.#remove(this.#values, listener); } events(listener) { this.#events.add(listener); this.#attach(); if (this.#readable()) { const value2 = this.view.binding.members.get(this.#name)?.value; this.#safe(() => listener({ type: "snap", value: value2 })); } else this.#unseen.add(listener); return () => { this.#unseen.delete(listener); this.#remove(this.#events, listener); }; } deliver(event) { for (const listener of [...this.#events]) { if (!this.#unseen.delete(listener)) this.#safe(() => listener(event)); else if (event.type !== "ops") this.#safe(() => listener({ type: "snap", value: event.value })); } for (const listener of [...this.#values]) this.#safe(() => listener(event.value)); } stop() { this.#values.clear(); this.#events.clear(); this.#unseen.clear(); this.view.binding.unlisten(this.#name, this); } #readable() { return !this.view.ended && this.view.binding.readable(this.#name); } #attach() { if (!this.view.ended) this.view.binding.listen(this.#name, this); } #remove(set, listener) { set.delete(listener); if (this.#values.size + this.#events.size === 0) this.view.binding.unlisten(this.#name, this); } #safe(fn) { try { fn(); } catch (error) { this.view.binding.host.report(error); } } }; var RemoteRef = class { object = void 0; binding; owner; /** False: a method-only view (`state: false`), ready once live. */ wantsState; proxy; ready; status = "loading"; error; shapeListeners = /* @__PURE__ */ new Set(); #statusListeners = /* @__PURE__ */ new Set(); #readables = /* @__PURE__ */ new Map(); #callers = /* @__PURE__ */ new Map(); #settleReady; constructor(binding, owner, wantsState) { this.binding = binding; this.owner = owner; this.wantsState = wantsState; const ready = deferred(); this.ready = ready.promise; this.#settleReady = ready; this.proxy = new Proxy(this, { get: (target, property) => { if (REF_BASE.has(property)) { const value2 = Reflect.get(target, property, target); return typeof value2 === "function" ? value2.bind(target) : value2; } if (typeof property !== "string" || property === "then") return void 0; return target.member(property); } }); refsByProxy.set(this.proxy, this); } get contract() { return this.binding.contract; } get address() { return this.binding.key; } get ended() { return this.status === "released" || this.status === "gone"; } get variant() { return this.binding.shape?.variant; } get shape() { return this.binding.shape; } onShape(listener) { this.shapeListeners.add(listener); return () => this.shapeListeners.delete(listener); } onStatus(listener) { this.#statusListeners.add(listener); return () => this.#statusListeners.delete(listener); } terminalError() { return this.status === "gone" ? this.error : new ChordError("released", `reference ${this.address} released`); } release() { if (this.ended) return; this.status = "released"; this.#notify(); this.#settleReady?.reject(new ChordError("released", `reference ${this.address} released`)); this.#settleReady = void 0; for (const readable of this.#readables.values()) readable.stop(); this.binding.removeView(this); } // ─── driven by the binding ─── /** Follows the binding: live once it has a shape (and, for a state view, its state) over a current hold. */ update() { if (this.ended) return; const binding = this.binding; const live = binding.status === "live" && binding.hold !== void 0 && (!this.wantsState || binding.hydrated); const next = live ? "live" : "loading"; if (next === this.status) return; this.status = next; this.#notify(); if (live) { this.#settleReady?.resolve(); this.#settleReady = void 0; } } end(error) { if (this.ended) return; const released = error instanceof ChordError && error.code === "released"; this.status = released ? "released" : "gone"; if (!released) this.error = error; this.#notify(); this.#settleReady?.reject(error); this.#settleReady = void 0; for (const readable of this.#readables.values()) readable.stop(); } shapeChanged() { const shape = this.binding.shape; if (shape === void 0 || this.ended) return; for (const listener of [...this.shapeListeners]) { try { listener(shape); } catch (error) { this.binding.host.report(error); } } } #notify() { notifyStatus(this.#statusListeners, this.status, this.error, (error) => this.binding.host.report(error)); } // ─── members ─── member(name) { const description = Object.values(this.contract.description.variants).map((members) => members[name]).find((member) => member !== void 0); if (description?.kind === "method") return this.#caller(name); if (description?.kind === "state") { let readable = this.#readables.get(name); if (readable === void 0) { readable = new ViewReadable(this, name); this.#readables.set(name, readable); } return readable; } return void 0; } #caller(name) { let caller = this.#callers.get(name); if (caller === void 0) { const noArgs = takesNoArgs(this.contract, name); caller = (...given) => { const [args, ctx] = noArgs ? [void 0, given[0]] : given; const host2 = this.binding.host; if (host2.inFactory > 0) throw new ChordError("not_ready", `${this.address}.${name} called inside a factory`); if (ctx !== void 0 && !(ctx instanceof CtxImpl)) { return Promise.reject(invalid("foreign_ctx", "ctx must come from a Chord host")); } try { ctx?.check(host2); } catch (error) { return Promise.reject(error); } if (ctx === void 0 || ctx.holder) { return host2.root((root2) => this.#call(name, args, root2), { signal: ctx?.signal }); } return this.#call(name, args, ctx); }; this.#callers.set(name, caller); } return caller; } #call(name, args, ctx) { if (this.ended) return Promise.reject(this.terminalError()); return this.binding.call(name, args, ctx); } }; // node_modules/@earendil-works/chord/dist/host.js var defineFacet = (setup, options = {}) => ({ setup, uses: options.uses }); var isFacet = (ref) => typeof ref === "object" && !(ref instanceof URL); var EMPTY_TABLE = /* @__PURE__ */ new Map(); function createHost(options) { return new HostImpl(options); } var HostImpl = class { name; dev; principal; inFactory = 0; failed; objects = /* @__PURE__ */ new Map(); codeVersions = /* @__PURE__ */ new Map(); facets = /* @__PURE__ */ new Map(); /** The host's own references to its `lifetime: "host"` objects, by type name. */ hostObjects = /* @__PURE__ */ new Map(); kind; connections = /* @__PURE__ */ new Set(); upstream; /** Shared bindings of this host's remote references, by key (re-held after an upstream reconnect). */ bindings = /* @__PURE__ */ new Map(); /** Bindings without references that linger, least recently released first. */ #lingering = /* @__PURE__ */ new Map(); #lingerTypes; #lingerCap; #originSeq = 0; #routes = /* @__PURE__ */ new Map(); #childReloads = new ChildReloads(this); /** Child facet modules by host-name prefix, from every committed cluster reload (for reconciling children). */ remoteModules = {}; /** Removal tombstones also reconcile children that connected during a transaction. */ remoteRemoved = {}; /** Highest code version the parent has retired. */ #retireAllowed = 0; /** Roots that came over a link, by the link they came from. */ /** Foreign roots (started on another host), by the links they came over. */ #foreign = /* @__PURE__ */ new Map(); /** Roots started on this host that have not ended. */ #local = /* @__PURE__ */ new Set(); #incarnation = Math.random().toString(36).slice(2, 8); /** Links each root's calls went out on. */ #rootLinks = /* @__PURE__ */ new Map(); current; previous; /** The code version a running reload will commit; new types activated during prepare install their implementation for it. */ pending; /** Activations of these types wait (reload gate). */ /** Types behind a reload gate: new objects of these types are rejected `upgrade_conflict` until commit/abort. */ gating; reloading = false; taskStopDeadline; readyTimeout; #principalKey; /** `principalKey(self)`: an equal principal is the host's own, whatever object carries it. */ #selfKey; /** Called when a root ends, the parent allows a retirement, or the previous code version retires. */ #retirementWatchers = /* @__PURE__ */ new Set(); /** Child hosts whose hello arrived while a reload ran: synced or reconciled once it ends. */ #deferredHellos = /* @__PURE__ */ new Map(); /** Resolved when the running reload ends. */ #reloadWaiters = []; /** Aborted when the installed run of a facet (by name) is replaced, removed, or the host closes. */ #facetRuns = /* @__PURE__ */ new Map(); #onError; #loader; #dying = /* @__PURE__ */ new Map(); #touches = /* @__PURE__ */ new Map(); #order = /* @__PURE__ */ new Map(); #subscribers = /* @__PURE__ */ new Map(); #codeVersionSeq = 1; #rootSeq = 0; constructor(options) { this.name = options.name ?? "host"; this.dev = options.dev ?? false; if (!("self" in options)) throw new TypeError("createHost needs `self`: the principal of the host's own code"); this.principal = options.self; this.#principalKey = options.principalKey ?? canonicalJson; this.#selfKey = this.#principalKey(options.self); this.#onError = options.onError ?? ((error) => console.error("chord:", error)); this.#loader = options.loader; this.taskStopDeadline = options.taskStopDeadline ?? 5e3; this.readyTimeout = options.readyTimeout ?? 6e4; this.#lingerTypes = options.linger?.types ?? {}; this.#lingerCap = options.linger?.cap ?? 4; this.current = { id: this.#codeVersionSeq, services: /* @__PURE__ */ new Map(), tables: /* @__PURE__ */ new Map(), roots: /* @__PURE__ */ new Map(), abort: new AbortController() }; this.codeVersions.set(this.current.id, this.current); this.kind = options.kind ?? "host"; if (options.upstream !== void 0) this.connect(options.upstream.link, options.upstream); } /** Whether `principal` is the host's own: the same key as `self`. */ isSelf(principal) { return principal === this.principal || this.#principalKey(principal) === this.#selfKey; } get codeVersion() { return this.current.id; } get previousCodeVersion() { return this.previous?.id; } /** Allocates the next code version id, or adopts one issued by the coordinating server. */ nextCodeVersionId(given) { if (given !== void 0 && given <= this.current.id) throw new Error(`code version ${given} is not newer than ${this.current.id}`); this.#codeVersionSeq = given ?? this.#codeVersionSeq + 1; return this.#codeVersionSeq; } report(error) { this.#onError(error); } // ─── serving ─── serve = ((type, factory, options = {}) => { if (this.reloading) throw new Error("host.serve during a reload; serve from a facet passed to reload()"); this.serveInto(this.current.services, makeService(type, factory, options, "")); if (options.lifetime === "host") this.startHostObject(this.current.services.get(type.name)); }); install(facet, options = {}) { if (this.reloading) throw new Error("host.install during a reload"); if (this.facets.has(facet.name)) throw new Error(`facet ${facet.name} is already installed; use reload()`); const added = []; const run2 = new AbortController(); try { facet.setup(this.facetApi(((type, factory, options2 = {}) => { const service2 = makeService(type, factory, options2, facet.name); this.serveInto(this.current.services, service2); added.push(service2); }), run2.signal)); } catch (error) { run2.abort(error); throw error; } this.facets.set(facet.name, facet); this.facetRunChanged(facet.name, run2); const refs = added.filter(hostLifetime).map((service2) => this.startHostObject(service2)); return options.awaitHostObjects === false ? Promise.resolve() : this.#hostObjectsReady(refs); } async load(plugins, options = {}) { const facets = await this.resolve(plugins); await Promise.all(facets.map((facet) => this.install(facet, options))); } ready() { return this.#hostObjectsReady(this.hostObjects.values()); } #hostObjectsReady(refs) { const ready = Promise.all([...refs].map((ref) => ref.ready)).then(() => { }); ready.catch(() => { }); return ready; } /** Turns plugin references into facets, importing modules through the loader. */ async resolve(plugins) { const modules = plugins.filter((ref) => !isFacet(ref)); if (modules.length > 0 && this.#loader === void 0) { throw new Error("loading modules needs createHost({ loader }), e.g. nodeLoader from @earendil-works/chord/node"); } const loaded = modules.length > 0 && this.#loader !== void 0 ? await this.#loader(modules) : []; return plugins.map((ref) => isFacet(ref) ? ref : loaded.shift()); } facetApi(serve, signal) { return { serve, name: this.name, root: (fn, options) => this.root(fn, options), registry: (registry2) => this.registry(registry2), signal }; } /** The installed run of facet `name` is now `run` (or none): the run it replaces is aborted. */ facetRunChanged(name, run2) { const replaced = this.#facetRuns.get(name); if (run2 === void 0) this.#facetRuns.delete(name); else this.#facetRuns.set(name, run2); if (replaced !== run2) replaced?.abort(new ChordError("gone", `facet ${name} was replaced or removed`)); } serveInto(services, service2) { if (services.has(service2.contract.name)) throw new Error(`${service2.contract.name} is already served`); const placement = service2.contract.description.host; if (placement !== void 0 && !placedOn(placement, this.name)) { throw new TypeError(`${service2.contract.name} is declared for ${placementText(placement)}, but ${this.name} serves it`); } services.set(service2.contract.name, service2); } startHostObject(service2) { const ref = this.acquire(service2.contract, {}, void 0, "host"); this.hostObjects.set(service2.contract.name, ref); ref.ready.catch((error) => this.report(error)); return ref; } // ─── HostCore ─── currentCodeVersion() { return this.current.id; } hasCodeVersion(codeVersion) { return this.codeVersions.has(codeVersion); } *retainedCodeVersions() { for (const [id, state] of this.codeVersions) yield [id, state.services]; if (this.pending !== void 0) yield [this.pending.id, this.pending.services]; } pendingCodeVersion() { return this.pending?.id; } /** The current code for a type; a type a running reload adds is served from the pending code version. */ service(typeName2) { return this.current.services.get(typeName2) ?? this.pending?.services.get(typeName2); } addressOf(type, input, principal, candidate = false) { const elsewhere = this.#elsewhere(type, input); if (elsewhere !== void 0) return this.#remoteKey(type, elsewhere.input, principal); const service2 = this.#bindingService(type.name, candidate); if (service2 === void 0) throw unknownType(type.name, this.name); return this.#locate(service2, input, principal).address; } /** * Where an object of a remote type lives when that is another host: its name and the input normalized with the * caller's contract (the placement is read from it). `undefined`: here (and every local type). */ #elsewhere(type, input) { const placement = type.description.host; if (type.scope !== "remote" || placement === void 0) return void 0; const normalized = normalize(type.input, input, `${type.name} input`); const at = resolveHost(type.name, placement, normalized); return at === this.name ? void 0 : { at, input: normalized }; } /** * The address of `input` for `principal`, normalized. A `lifetime: "host"` type has one object: the empty input, for * the host principal, whoever asks; another input is invalid. */ #locate(service2, input, principal) { const { name, input: schema } = service2.contract; const normalized = normalize(schema, input, `${name} input`); const identity = canonicalIdentity(schema, normalized); if (hostLifetime(service2)) { if (identity !== canonicalIdentity(schema, normalize(schema, {}, `${name} input`))) throw invalid("input", `${name} has lifetime "host": its one object has the empty input`); principal = this.principal; } return { address: `${name}\0${identity}\0${this.#principalPart(principal)}`, normalized, principal }; } #principalPart(principal) { return this.isSelf(principal) ? "host" : `p:${this.#principalKey(principal)}`; } /** * The key of a remote binding: type, input identity (fields equal to their defaults omitted, as for local objects), * principal key and the consumer's contract fingerprint. No host name: the type and the input decide where it lives. */ #remoteKey(type, normalized, principal) { const identity = canonicalIdentity(type.input, normalized); return `@${type.name}\0${identity}\0${this.#principalPart(principal)}\0${type.fingerprint}`; } acquire(type, input, options, owner, from, candidate = false) { const elsewhere = this.#elsewhere(type, input); if (elsewhere !== void 0) return this.#acquireRemote(type, elsewhere.at, elsewhere.input, options, owner); const service2 = this.#bindingService(type.name, candidate); if (service2 === void 0) throw unknownType(type.name, this.name); bind(service2.contract, service2.options.accepts, type); const { address, normalized, principal } = this.#locate(service2, input, options?.principal ?? this.principal); const placement = service2.contract.description.host; if (placement !== void 0 && resolveHost(type.name, placement, normalized) !== this.name) { throw invalid("placement", `${type.name} with this input lives on ${resolveHost(type.name, placement, normalized)}, not ${this.name}`); } let object = this.objects.get(address); if (from !== void 0 && object !== void 0 && (object === from || reaches(object, from))) { throw invalid("cycle", `${from.address} -> ${address} would create a ref cycle`); } if (object === void 0) { if (this.gating?.has(type.name)) { throw new ChordError("upgrade_conflict", `${type.name} is being upgraded; retry`, { reason: "gate" }); } const dying = this.#dying.get(address); if (dying?.status === "disposed") this.#dying.delete(address); object = new ObjectImpl(this, { address, typeName: type.name, input: normalized, principal, hostLifetime: hostLifetime(service2) }); this.objects.set(address, object); object.start(dying?.status === "disposing" ? dying.dispose(false) : void 0); } const ref = new RefImpl(object, type, owner); object.addRef(ref); return ref; } forget(object) { if (this.objects.get(object.address) === object) this.objects.delete(object.address); if (object.status === "disposing") this.#dying.set(object.address, object); } disposed(object) { if (this.#dying.get(object.address) === object) this.#dying.delete(object.address); } touch(rootId, object) { let touched = this.#touches.get(rootId); if (touched === void 0) { touched = /* @__PURE__ */ new Set(); this.#touches.set(rootId, touched); } touched.add(object); } // ─── roots ─── async root(fn, options = {}) { const rootId = `${this.name}#${this.#incarnation}:${++this.#rootSeq}`; this.#local.add(rootId); return this.joinRoot(rootId, options.codeVersion ?? this.current.id, fn, options); } /** * Runs `fn` inside root `rootId` of `codeVersion`. A root counts as running here (for retirement) while any of its * calls runs here. A root that started here ends when its last call here settles, even if it went out and came back * over a link meanwhile. A foreign root (started elsewhere, arriving `from` a link) keeps its leases on objects * until every link it came over announced its end (`root_end`) or closed, so its re-entry stays grandfathered. */ async joinRoot(rootId, codeVersionId, fn, options = {}) { const codeVersion = this.codeVersions.get(codeVersionId); if (codeVersion === void 0) { this.#local.delete(rootId); throw new ChordError("superseded", `code version ${codeVersionId} is not retained by ${this.name}`); } if (options.from !== void 0 && !this.#local.has(rootId)) { let links = this.#foreign.get(rootId); if (links === void 0) { links = /* @__PURE__ */ new Set(); this.#foreign.set(rootId, links); } links.add(options.from); } codeVersion.roots.set(rootId, (codeVersion.roots.get(rootId) ?? 0) + 1); const ctx = new CtxImpl(this, { // Forced retirement aborts what still runs on a code version before closing what only it used. signals: options.signal === void 0 ? [codeVersion.abort.signal] : [options.signal, codeVersion.abort.signal], rootId, codeVersion: codeVersion.id, principal: options.principal ?? this.principal }); try { return await fn(ctx); } finally { ctx.close(); const count = (codeVersion.roots.get(rootId) ?? 1) - 1; if (count > 0) codeVersion.roots.set(rootId, count); else { codeVersion.roots.delete(rootId); if (!this.#foreign.has(rootId)) this.endRoot(rootId); this.maybeRetire(); this.#retirementChanged(); } } } /** Remembers that calls of `rootId` went out on `connection`, which must hear when the root ends. */ rootSent(rootId, connection) { let links = this.#rootLinks.get(rootId); if (links === void 0) { links = /* @__PURE__ */ new Set(); this.#rootLinks.set(rootId, links); } links.add(connection); } /** A root ended (here, or announced by its origin): end its leases and tell the links it went out on. */ endRoot(rootId) { this.#local.delete(rootId); this.#foreign.delete(rootId); for (const object of this.#touches.get(rootId) ?? []) object.rootEnded(rootId); this.#touches.delete(rootId); for (const connection of this.#rootLinks.get(rootId) ?? []) connection.send({ t: "root_end", rootId }); this.#rootLinks.delete(rootId); } /** * `from` announced the end of a foreign root (or closed). Honored only if the root came over `from`: a peer cannot * end leases of roots it never carried. The root ends when no link that brought it remains. */ foreignRootEnded(rootId, from) { const links = this.#foreign.get(rootId); if (links === void 0 || !links.delete(from) || links.size > 0) return; const running = [...this.codeVersions.values()].some((codeVersion) => codeVersion.roots.has(rootId)); if (running) this.#foreign.delete(rootId); else this.endRoot(rootId); } /** * Retires the previous code version once no local roots of it remain. A child also waits for its parent to retire * it: roots span hosts, and an old root on the server may still call in. */ maybeRetire() { if (this.retirable()) this.retire(); } /** The previous code version has no roots left here and, on a child host, the parent has retired it. */ retirable() { const previous = this.previous; if (previous === void 0 || previous.roots.size > 0) return false; return this.kind === "client" || this.upstream === void 0 || previous.id <= this.#retireAllowed; } allowRetire(codeVersion) { this.#retireAllowed = Math.max(this.#retireAllowed, codeVersion); this.maybeRetire(); this.#retirementChanged(); } /** Calls `watcher` whenever a root ends, the parent allows a retirement, or a code version retires. */ watchRetirement(watcher) { this.#retirementWatchers.add(watcher); return () => this.#retirementWatchers.delete(watcher); } #retirementChanged() { for (const watcher of [...this.#retirementWatchers]) watcher(); } /** * Tells a child that is now in step which code versions this host retired before: every id below the oldest one it * retains (ids only grow). Without it, a child reconciled after those retirements would never hear a `retire` frame for * its own previous code version and would wait for the retire deadline at every reload. */ announceRetired(connection) { const retired = (this.previous ?? this.current).id - 1; if (retired > 0) connection.send({ t: "retire", codeVersion: retired }); } /** Drops the previous code version: its implementations and the declarations only it used. */ retire() { const previous = this.previous; if (previous === void 0) return; this.previous = void 0; this.codeVersions.delete(previous.id); for (const object of [...this.objects.values()]) this.retireImplementation(object, previous.id); for (const connection of this.connections) { if (connection.role === "downstream" && connection.peer?.kind === "host") connection.send({ t: "retire", codeVersion: previous.id }); } this.#retirementChanged(); } /** * Drops an object's implementation for `codeVersion` and what only that implementation declared, in disposal order: * resources close (reverse open order) before its references are released. */ retireImplementation(object, codeVersion) { const installed = object.implementations.get(codeVersion); object.implementations.delete(codeVersion); if (installed === void 0 || installed.decls === object.decls) return; const kept = object.allDecls(); const keep = (pick) => new Set([...kept].flatMap((decls) => [...pick(decls)])); const refs = keep((decls) => decls.refs.values()); const resources = keep((decls) => decls.resources.values()); const derives = keep((decls) => decls.derives.values()); for (const derived of installed.decls.derives.values()) if (!derives.has(derived)) derived.detach(); const closing = [...installed.decls.resources.values()].filter((resource) => !resources.has(resource)).sort((a, b) => object.openOrder.indexOf(b) - object.openOrder.indexOf(a)); const releasing = [...installed.decls.refs.values()].filter((ref) => !refs.has(ref)); object.refreshSchemas(); runAll([ ...closing.map((resource) => () => resource.close(object.openOrder)), ...releasing.map((ref) => () => ref.release()) ], `${object.address}: retiring code version ${codeVersion} failed`).catch((error) => this.report(error)); } // ─── registries ─── registry(registry2) { const table = (ctx) => { const codeVersion = ctx === void 0 ? this.current : this.codeVersions.get(ctx.codeVersion); if (codeVersion === void 0) throw new ChordError("superseded", `code version ${ctx?.codeVersion} is retired`); return codeVersion.tables.get(registry2.name) ?? EMPTY_TABLE; }; return { table, get: (key, ctx) => table(ctx).get(key), subscribe: (listener) => { let set = this.#subscribers.get(registry2.name); if (set === void 0) { set = /* @__PURE__ */ new Set(); this.#subscribers.set(registry2.name, set); } const wrapped = listener; set.add(wrapped); wrapped(table(), this.current.id); return () => set.delete(wrapped); } }; } /** Builds registry tables from (object, declarations) pairs. Throws registry_conflict. */ buildTables(entries2) { const byRegistry = /* @__PURE__ */ new Map(); for (const [object, decls] of entries2) { for (const { registry: registry2, key, value: value2 } of decls.registrations.values()) { let entries3 = byRegistry.get(registry2.name); if (entries3 === void 0) { entries3 = /* @__PURE__ */ new Map(); byRegistry.set(registry2.name, entries3); } const existing = entries3.get(key); if (existing !== void 0 && existing.object !== object && registry2.conflict === "error") { throw invalid("registry_conflict", `${registry2.name}: ${key} registered by ${existing.object.address} and ${object.address}`); } entries3.set(key, { value: value2, object }); let order = this.#order.get(registry2.name); if (order === void 0) { order = /* @__PURE__ */ new Map(); this.#order.set(registry2.name, order); } if (!order.has(key)) order.set(key, order.size); } } const tables = /* @__PURE__ */ new Map(); for (const [name, entries3] of byRegistry) { const order = this.#order.get(name); const sorted = [...entries3].sort(([a], [b]) => order.get(a) - order.get(b)); tables.set(name, new Map(sorted.map(([key, entry2]) => [key, entry2.value]))); } return tables; } /** * The service a reference binds against: the current one, or for a prepare rerun (`candidate`) the running reload's * pending one, so consumer and provider of a breaking change can reload in one batch. */ #bindingService(typeName2, candidate) { return (candidate ? this.pending?.services.get(typeName2) : void 0) ?? this.service(typeName2); } /** Live objects with their current declarations; objects waiting for a reload's commit only with `candidates`. */ liveDecls(candidates = false) { return [...this.objects.values()].filter((object) => object.status === "live" && (candidates || !object.deferredRuntime)).map((object) => [object, object.decls]); } checkRegistrations(object, decls) { if (decls.registrations.size === 0) return; this.buildTables([...this.liveDecls().filter(([other]) => other !== object), [object, decls]]); } registriesChanged(including) { const entries2 = this.liveDecls().filter(([object]) => object !== including); if (including !== void 0) entries2.push([including, including.decls]); this.installTables(this.current, this.buildTables(entries2)); } /** A commit failed midway: from now on this host admits no work (calls fail `unavailable/host_failed`). */ fail(error) { this.failed ??= error; this.report(error); } /** Installs tables for `codeVersion`; subscribers hear about changed tables of the current code version (all tables when `all`). */ installTables(codeVersion, tables, all = false) { const before = codeVersion.tables; codeVersion.tables = tables; if (codeVersion !== this.current) return; for (const [name, listeners] of this.#subscribers) { const next = tables.get(name) ?? EMPTY_TABLE; if (!all && sameTable(before.get(name) ?? EMPTY_TABLE, next)) continue; const current = [...listeners]; enqueue(() => { for (const listener of current) { try { listener(next, codeVersion.id); } catch (error) { this.report(error); } } }); } } // ─── public API ─── async get(type, input, options) { const ref = this.acquire(type, input, options, "external"); await awaitReady(ref, options?.signal === void 0 ? [] : [options.signal], this.readyTimeout); return ref.proxy; } hold(type, input, options) { return this.acquire(type, input, options, "external").proxy; } async invalidatePrincipal(match) { const matches = typeof match === "function" ? match : (principal) => matchesPattern(match, principal); const reason = new ChordError("gone", "principal invalidated"); for (const connection of [...this.connections]) { if (connection.role !== "downstream" || !matches(connection.principal)) continue; connection.revoke(connection.restamp?.(connection.principal)); } if (typeof match !== "function") { for (const connection of this.connections) { if (connection.role === "downstream" && connection.peer.kind === "host") { connection.send({ t: "invalidate", pattern: match }); } } } const matching = [...this.objects.values()].filter((object) => !this.isSelf(object.principal) && matches(object.principal)); await disposeHoldersFirst(matching, reason, "invalidatePrincipal: disposal failed"); } // ─── links ─── accept(link, options) { const kind = options.kind ?? "client"; if (kind === "host" && options.name === void 0) throw new TypeError("accepting a host link needs its name"); this.#addConnection(link, { ...options, role: "downstream", principal: options.principal, peer: { kind, name: kind === "host" ? options.name : void 0 }, // Holds over a client link carry its id as their origin (also when relayed), so hosts can tell clients apart. ...kind === "client" ? { originId: `${this.#incarnation}:${++this.#originSeq}` } : {} }); } connect(link, options) { if (!("principal" in options)) throw new TypeError("connect needs `principal`: what the parent's own requests act as"); if (this.upstream?.open) this.upstream.link.close("replaced"); const connection = this.#addConnection(link, { ...options, role: "upstream", principal: options.principal, peer: { kind: "host", name: void 0 } }); this.upstream = connection; for (const binding of this.bindings.values()) if (binding.hold === void 0 && !binding.ended) connection.hold(binding, binding.applied); } #addConnection(link, config) { const connection = new Connection(this, link, config); if (connection.open) this.connections.add(connection); return connection; } route(prefix, route2) { if (typeof route2.principal !== "function") throw new TypeError(`route ${prefix} needs \`principal\`: what the routed hosts act as when they call back`); this.#routes.set(prefix, route2); } /** * A child host announced its code version. In step: usable. Behind: fenced (nothing is routed to it) and reconciled * by a reload of its modules to this host's code version, never by renumbering what it runs. Ahead: impossible, * refused. */ peerHello(connection, codeVersion) { if (codeVersion > this.current.id) { this.report(new ChordError("invalid", `${connection.name} runs code version ${codeVersion}, ahead of ${this.current.id}`)); connection.link.close("code version ahead of parent"); return; } if (this.reloading) { connection.peerCodeVersion = codeVersion; if (codeVersion < this.current.id) connection.fencedCodeVersions.add(codeVersion); this.#deferredHellos.set(connection, codeVersion); return; } if (codeVersion === this.current.id) { connection.markSynced(codeVersion); this.announceRetired(connection); return; } connection.peerCodeVersion = codeVersion; connection.fencedCodeVersions.add(codeVersion); reconcileChild(this, connection).catch((error) => { this.report(error); connection.link.close("reconciliation failed"); }); } /** The host's reload slot was freed: children that said hello meanwhile are synced or reconciled now. */ reloadEnded() { this.reloading = false; const deferred2 = [...this.#deferredHellos]; this.#deferredHellos.clear(); for (const [connection, codeVersion] of deferred2) if (connection.open) this.peerHello(connection, codeVersion); for (const resolve2 of this.#reloadWaiters.splice(0)) resolve2(); this.maybeRetire(); } /** Resolves once no reload runs on this host. */ whenReloadEnded() { if (!this.reloading) return Promise.resolve(); return new Promise((resolve2) => this.#reloadWaiters.push(resolve2)); } connectionClosed(connection) { this.connections.delete(connection); this.#deferredHellos.delete(connection); for (const [rootId, links] of [...this.#foreign]) if (links.has(connection)) this.foreignRootEnded(rootId, connection); for (const links of this.#rootLinks.values()) links.delete(connection); if (this.upstream === connection) { this.upstream = void 0; this.#childReloads.parentLost(); } } /** The last reference to `binding` was released: it lingers if its type does (`HostOptions.linger`), else closes. */ bindingIdle(binding) { const ms = Object.hasOwn(this.#lingerTypes, binding.contract.name) ? this.#lingerTypes[binding.contract.name] : 0; if (ms <= 0 || binding.ended || !binding.applied || this.#lingerCap <= 0) { binding.close(); return; } binding.lingerTimer = setTimeout(() => binding.close(), ms); binding.lingerTimer.unref?.(); this.#lingering.set(binding.key, binding); for (const oldest of this.#lingering.values()) { if (this.#lingering.size <= this.#lingerCap) break; oldest.close(); } } forgetBinding(binding) { if (this.bindings.get(binding.key) === binding) this.bindings.delete(binding.key); if (this.#lingering.get(binding.key) === binding) this.#lingering.delete(binding.key); } /** A downstream host connection by its configured name (never by what a peer announced). */ #childNamed(name) { return [...this.connections].find((connection) => connection.open && connection.role === "downstream" && connection.peer.kind === "host" && connection.peer.name === name); } /** * A reference to an object on host `at`: a view onto the binding for its key, created (and held) if there is none. * `input` is normalized with `type` (the hold carries it so). */ #acquireRemote(type, at, input, options, owner) { const principal = options?.principal === void 0 || this.isSelf(options.principal) ? void 0 : options.principal; const key = this.#remoteKey(type, input, principal ?? this.principal); let binding = this.bindings.get(key); const fresh = binding === void 0 || binding.ended; if (binding === void 0 || binding.ended) { binding = new RemoteBinding(this, { key, contract: type, at, input, principal }); this.bindings.set(key, binding); } const ref = new RemoteRef(binding, owner, options?.state !== false); this.#lingering.delete(key); binding.addView(ref); if (fresh) this.#hold(binding); return ref; } /** Holds `binding` over the link to its host: the parent's, a child's, or one a route resolves. */ #hold(binding) { const bindWhenSynced = (connection) => { connection.then((target) => { if (!binding.ended && binding.hold === void 0) target.hold(binding, false); }, (error) => binding.gone(error)); }; const direct = this.#childNamed(binding.at); if (this.upstream !== void 0) this.upstream.hold(binding, false); else if (direct !== void 0) bindWhenSynced(direct.whenSynced().then(() => direct)); else { const route2 = this.#routeFor(binding.at); if (route2 === void 0) binding.gone(new ChordError("unavailable", `no route to ${binding.at}`)); else bindWhenSynced(this.#resolve(route2, binding.at, binding.principal ?? this.principal)); } } #routeFor(hostName) { let best; for (const entry2 of this.#routes) { if (hostName.startsWith(entry2[0]) && (best === void 0 || entry2[0].length > best[0].length)) best = entry2; } return best?.[1]; } /** Authorize, resolve (maybe spawn) and admit the host, authorize again; returns the synced connection to the host. */ async #resolve(route2, hostName, principal, still = () => true) { const refuse = () => new ChordError("forbidden", `${hostName} refused for this principal`); const revoked = () => new ChordError("gone", "principal invalidated while resolving"); if (!await route2.authorize(principal, hostName)) throw refuse(); if (!still()) throw revoked(); const link = await route2.resolve(hostName); let connection = [...this.connections].find((candidate) => candidate.link === link); connection ??= this.#addConnection(link, { role: "downstream", principal: route2.principal(hostName), peer: { kind: "host", name: hostName } }); if (!still()) throw revoked(); if (!await route2.authorize(principal, hostName)) throw refuse(); if (!still()) throw revoked(); await connection.whenSynced(); if (!still()) throw revoked(); return connection; } /** * Relays a reference from `relay.origin` to the host it names. Relaying needs a route: it is where authorization * lives. The origin's authorization generation is re-checked after every await and right before forwarding, so a * revocation during resolution wins. */ async relay(relay) { const at = relay.frame.at; const placement = contractFromDescription(relay.frame.token).description.host; if (placement === void 0 || resolveHost(relay.frame.token.name, placement, relay.frame.input) !== at) throw invalid("placement", `${relay.frame.token.name} with this input does not live on ${at}`); const route2 = this.#routeFor(at); if (route2 === void 0) { throw this.#childNamed(at) === void 0 ? new ChordError("unavailable", `no route to ${at}`) : new ChordError("forbidden", `no route authorizes ${at}`); } const down = await this.#resolve(route2, at, relay.principal, () => relay.current); if (!relay.current) return; relay.open(down); } reload(plugins, options = {}) { if (this.kind !== "client" && this.upstream !== void 0) { return Promise.reject(new ChordError("unavailable", "reloads are coordinated by the server", { reason: "not_coordinator" })); } const children2 = [...this.connections].some((connection) => connection.role === "downstream" && connection.peer.kind === "host"); return children2 || options.remote !== void 0 || options.remoteRemove !== void 0 ? runClusterReload(this, plugins, options) : runReload(this, plugins, options); } onReloadFrame(connection, frame) { this.#childReloads.handle(connection, frame); } rememberRemoteModules(remote2, remove) { for (const prefix of /* @__PURE__ */ new Set([...Object.keys(remote2 ?? {}), ...Object.keys(remove ?? {})])) { const modules = remote2?.[prefix] ?? []; const removed = remove?.[prefix] ?? []; this.remoteModules[prefix] = [.../* @__PURE__ */ new Set([...this.remoteModules[prefix] ?? [], ...modules])].filter((module) => !removed.includes(module)); this.remoteRemoved[prefix] = [.../* @__PURE__ */ new Set([...this.remoteRemoved[prefix] ?? [], ...removed])].filter((module) => !modules.includes(module)); } } /** The code version id the next reload would commit. */ peekNextCodeVersion() { return this.#codeVersionSeq + 1; } /** * Disposes every object, holders before what they reference (a resource's `close` may still use the objects its object * references, local or remote), then closes the links. */ async close() { for (const binding of [...this.#lingering.values()]) binding.close(); this.hostObjects.clear(); for (const name of [...this.#facetRuns.keys()]) this.facetRunChanged(name, void 0); const reason = new ChordError("gone", `host ${this.name} closed`); try { await disposeHoldersFirst([...this.objects.values()], reason, "host close: disposal failed"); } finally { for (const connection of [...this.connections]) connection.link.close("host closed"); } } }; async function disposeHoldersFirst(objects, reason, message) { const remaining = new Set(objects); const held2 = (object) => { const result = /* @__PURE__ */ new Set(); for (const decls of object.allDecls()) { for (const ref of decls.refs.values()) if (ref.object !== void 0) result.add(ref.object); } return result; }; const refs = new Map(objects.map((object) => [object, held2(object)])); const errors = []; while (remaining.size > 0) { const heldByRemaining = /* @__PURE__ */ new Set(); for (const object of remaining) for (const target of refs.get(object) ?? []) heldByRemaining.add(target); let wave = [...remaining].filter((object) => !heldByRemaining.has(object)); if (wave.length === 0) wave = [...remaining]; for (const object of wave) remaining.delete(object); const results = await Promise.allSettled(wave.map((object) => object.dispose(true, reason))); for (const result of results) if (result.status === "rejected") errors.push(result.reason); } if (errors.length === 1) throw errors[0]; if (errors.length > 1) throw new AggregateError(errors, message); } function sameTable(a, b) { if (a.size !== b.size) return false; const keys = [...a.keys()]; return [...b].every(([key, value2], index) => keys[index] === key && a.get(key) === value2); } var matchesPattern = (pattern, principal) => principal !== null && typeof principal === "object" && Object.entries(pattern).every(([key, value2]) => deepEqual2(principal[key], value2)); var unknownType = (type, host2) => new ChordError("unavailable", `${type} is not served by ${host2}`, { reason: "unknown_type" }); function reaches(from, to) { const seen = /* @__PURE__ */ new Set(); const stack = [from]; while (stack.length > 0) { const object = stack.pop(); if (object === to) return true; if (seen.has(object)) continue; seen.add(object); for (const decls of object.allDecls()) { for (const ref of decls.refs.values()) if (ref.object !== void 0) stack.push(ref.object); } } return false; } // node_modules/@earendil-works/chord/dist/registry.js var defineRegistry = (name, options = {}) => ({ name, conflict: options.conflict ?? "error" }); // node_modules/@earendil-works/chord/dist/state.js var StateSubscriber = class { #listener; #reportError; #pending = []; #running = false; #started = false; #closed = false; constructor(listener, reportError) { this.#listener = listener; this.#reportError = reportError; } push(frame) { if (this.#closed) return; if (this.#pending.length === 100) { const hydration = this.#started ? void 0 : this.#pending[0]; this.#pending.length = 0; if (hydration !== void 0) this.#pending.push(hydration); } this.#pending.push(frame); } drain() { if (this.#running || this.#closed) return; this.#running = true; for (let frame = this.#pending.shift(); frame !== void 0; frame = this.#pending.shift()) { this.#started = true; try { const result = this.#listener(frame.value, frame.context, frame.delivery); if (isPromiseLike(result)) { void Promise.resolve(result).then(() => this.#resume(), (error) => { this.#report(error); this.#resume(); }); return; } } catch (error) { this.#report(error); } } this.#running = false; } clear() { this.#pending.length = 0; } close() { this.#closed = true; this.clear(); } #resume() { this.#running = false; this.drain(); } #report(error) { try { this.#reportError(toError(error)); } catch (reportError) { reportErrorAsync(toError(reportError)); } } }; var ReplicatedStatePublisher = class { #listeners = /* @__PURE__ */ new Map(); #reportError; #publications = []; #value; #sequence = 0; #delivering = false; constructor(initial, reportError = reportErrorAsync) { this.#value = initial; this.#reportError = reportError; } get value() { return this.#value; } snapshot() { return { value: this.#value, sequence: this.#sequence }; } subscribe(listener) { const { value: value2, sequence: sequence3 } = this.snapshot(); const subscriber = new StateSubscriber(listener, this.#reportError); this.#listeners.set(subscriber, sequence3); subscriber.push({ value: value2, context: BACKGROUND_CONTEXT, delivery: { kind: "hydrate", sequence: sequence3 } }); subscriber.drain(); return () => { subscriber.close(); this.#listeners.delete(subscriber); }; } /** Publish an already-prepared immutable revision. Subscriber failures are reported, never thrown. */ publish(value2, context) { this.#value = value2; this.#sequence += 1; this.#publications.push({ value: value2, sequence: this.#sequence, context }); if (this.#delivering) return; this.#delivering = true; try { for (let publication = this.#publications.shift(); publication !== void 0; publication = this.#publications.shift()) { const delivery = { kind: "update", sequence: publication.sequence }; for (const [subscriber, hydratedSequence] of [...this.#listeners]) { if (publication.sequence <= hydratedSequence) continue; subscriber.push({ value: publication.value, context: publication.context, delivery }); subscriber.drain(); } } } finally { this.#delivering = false; } } }; var MutableReplicatedStateImpl = class { #tracker; #publisher; #changing = false; constructor(initial) { this.#tracker = track(initial); this.#publisher = new ReplicatedStatePublisher(this.#tracker.value); } get value() { return this.#tracker.value; } change(context, mutate) { if (this.#changing) throw new Error("Replicated state cannot be changed reentrantly from a change callback"); this.#changing = true; let prepared; try { const change = this.#tracker.beginChange(); try { const outcome = mutate(change.state); if (isPromiseLike(outcome)) { void Promise.resolve(outcome).catch(() => void 0); throw new TypeError("Replicated state change callbacks must be synchronous"); } prepared = change.prepare(); } catch (error) { change.abort(); throw error; } } finally { this.#changing = false; } this.#tracker.adopt(prepared); if (prepared.ops.length > 0) this.#publisher.publish(prepared.value, context); } replace(context, value2) { if (this.#changing) throw new Error("Replicated state cannot be replaced from a change callback"); const prepared = this.#tracker.prepareReplace(value2); this.#tracker.adopt(prepared); if (prepared.ops.length > 0) this.#publisher.publish(prepared.value, context); } subscribe(listener) { return this.#publisher.subscribe(listener); } }; var AttachedReplicatedStateImpl = class { #publisher; #attachment; #reportError; #cursor; #disposed = false; constructor(attachment, options) { const { snapshot } = attachment; assertCursor(snapshot.cursor, "snapshot"); this.#attachment = attachment; this.#cursor = snapshot.cursor; this.#reportError = options.onError ?? reportErrorAsync; this.#publisher = new ReplicatedStatePublisher(snapshot.value, (error) => this.#report(error)); } get value() { return this.#publisher.value; } subscribe(listener) { return this.#publisher.subscribe(listener); } activate() { this.#attachment.activate((frame) => this.#receive(frame)); } dispose() { if (this.#disposed) return; this.#disposed = true; this.#attachment.dispose(); } #receive(frame) { if (this.#disposed) return; try { assertCursor(frame.cursor, "frame"); const expected = this.#cursor + 1; if (frame.cursor !== expected) { throw new Error(`Replicated state source cursor has a gap: expected ${expected}, received ${frame.cursor}`); } this.#cursor = frame.cursor; this.#publisher.publish(frame.value, frame.context); } catch (error) { this.#fail(toError(error)); } } #fail(error) { if (this.#disposed) return; this.#disposed = true; try { this.#attachment.dispose(); } catch (disposeError) { this.#report(new AggregateError([error, disposeError], "Replicated state source contract failed")); return; } this.#report(error); } #report(error) { try { this.#reportError(toError(error)); } catch (reportError) { reportErrorAsync(toError(reportError)); } } }; function attachReplicatedStateSource(source, options = {}) { const attachment = source.attach(); try { const state = new AttachedReplicatedStateImpl(attachment, options); state.activate(); return state; } catch (error) { try { attachment.dispose(); } catch (disposeError) { throw new AggregateError([error, disposeError], "Failed to attach replicated state source"); } throw error; } } function assertCursor(cursor, kind) { if (!Number.isSafeInteger(cursor)) throw new TypeError(`Replicated state source ${kind} cursor must be a safe integer`); } function isPromiseLike(value2) { return (typeof value2 === "object" && value2 !== null || typeof value2 === "function") && typeof value2.then === "function"; } function reportErrorAsync(error) { queueMicrotask(() => { throw error; }); } function toError(error) { return error instanceof Error ? error : new Error(String(error)); } function replicatedState(initialOrSource, options) { if (isReplicatedStateSource(initialOrSource)) return attachReplicatedStateSource(initialOrSource, options); return new MutableReplicatedStateImpl(initialOrSource); } function isReplicatedStateSource(value2) { return typeof value2.attach === "function"; } // node_modules/@earendil-works/chord/dist/watch.js var STOPPED = { kind: "stopped" }; var ABORTED = { kind: "aborted" }; var ENDED = { kind: "ended" }; var WatchLifecycle = class { #detach; #report; #closed = Promise.withResolvers(); #value; #listener; #started = false; #scheduled = false; #running = false; /** Set by `endAfterDelivery()`: the source sends nothing more; the watch ends `ended` once it delivered the rest. */ #ending = false; /** Set once, by `#terminate()`. */ #end; #abortSignal; #abortListener; constructor(value2, detach, report2) { this.#value = value2; this.#detach = detach; this.#report = report2; } get value() { return this.#value; } get closed() { return this.#closed.promise; } start(listener) { if (this.#started) throw new Error("Watch is already started"); if (this.#end !== void 0) throw new Error("Watch is stopped"); this.#started = true; this.#listener = listener; this.#schedule(); } stop() { this.#terminate(STOPPED); return this.#closed.promise; } /** End the watch `aborted` when `signal` aborts; installed once. */ observeAbort(signal) { if (this.#abortSignal !== void 0) throw new Error("Watch abort is already installed"); if (this.#end !== void 0) return; this.#abortSignal = signal; this.#abortListener = () => this.abort(); signal.addEventListener("abort", this.#abortListener, { once: true }); if (signal.aborted) this.abort(); } /** End the watch `aborted`: its acquisition context was aborted. */ abort() { this.#terminate(ABORTED); } /** * End the watch with `end` at once, dropping what is pending: the source closed (`ended`), or failed for good * (`failed`). A failure the source may recover from is a `failed` call instead (`fail()`). */ end(end) { this.#terminate(end); } /** * The source sends nothing more after its last frames, such as a retirement: take no more frames, deliver what is * pending, then end `ended`, after the running call returns, if one runs. Until then the watch stays attached, so * `end()`, `stop()`, an abort, and a throwing listener still end it first; a watch never started keeps what is * pending until one of them or `start()`. */ endAfterDelivery() { if (this.#end !== void 0 || this.#ending) return; this.#ending = true; if (!this.#running && this.pendingContext === void 0) this.#terminate(ENDED); } /** Whether the watch still takes frames: it has not ended, and its source has not ended. */ get open() { return this.#end === void 0 && !this.#ending; } /** Something became pending: deliver it once started, never inline. */ pending() { if (this.#started) this.#schedule(); } #schedule() { if (this.#scheduled || this.#running || this.#end !== void 0) return; this.#scheduled = true; queueMicrotask(() => { this.#scheduled = false; void this.#drain(); }); } /** * Deliver until nothing is pending; what arrives during a call is taken after it returns. A watch whose source ended * ends once it delivered the rest and its last call returned. */ async #drain() { if (this.#running || this.#end !== void 0 || !this.#started) return; this.#running = true; try { while (this.#end === void 0) { const listener = this.#listener; if (listener === void 0) break; const frameContext = this.pendingContext; if (frameContext === void 0) { if (this.#ending) this.#terminate(ENDED); break; } const call2 = this.next(); if (call2.kind === "value") this.#value = call2.value; try { await listener(call2, withoutAbortSignal(frameContext)); } catch (error) { if (this.#end === void 0) { this.#report?.(error); this.#terminate({ kind: "listener_error", error: toError2(error) }); } break; } } } finally { this.#running = false; } } /** * End the watch, once: detach, drop what is pending and every reference into the source, and settle `closed`. A * callback already running keeps running. */ #terminate(end) { if (this.#end !== void 0) return; this.#end = end; const detach = this.#detach; this.#detach = void 0; this.#report = void 0; this.#listener = void 0; this.release(); if (this.#abortListener !== void 0) this.#abortSignal?.removeEventListener("abort", this.#abortListener); this.#abortSignal = void 0; this.#abortListener = void 0; detach?.(); this.#closed.resolve(end); } }; function toError2(error) { if (error instanceof Error) return error; try { return new Error(String(error)); } catch { return new Error(Object.prototype.toString.call(error)); } } var Watch = class extends WatchLifecycle { #ops = new PendingOps(); /** * The source's call while it is the only frame pending: the call passes it on, with no allocation. A second frame * drops it, so its ops stay only as long as `PendingOps` keeps them (folded, or dropped by an overflow). */ #lone; /** The newest frame's value and context, while a call is pending; the context also marks that one is. */ #newestValue; #newestContext; /** A pending frame was a retirement. */ #retired = false; /** The newest frame was a failure: the next call is this. */ #failure; /** A failure came after the last value call: the next value call replaces the root, and ops are not kept. */ #replace = false; /** * Queue the frame `call`. A value's ops turn the previous value into it, and `retired` says the document the watch * follows was retired, which the next value call reports however many frames it folds; a failure is `fail()`. */ advance(call2, context) { if (call2.kind === "failed") { this.#fail(call2, context); return; } if (!this.open) return; if (this.#replace) { this.#failure = void 0; } else { this.#lone = this.#newestContext === void 0 ? call2 : void 0; this.#ops.push(call2.ops); } this.#newestValue = call2.value; this.#newestContext = context; if (call2.retired) this.#retired = true; this.pending(); } /** * Queue a failure the source may recover from: the pending frames are dropped (`value` keeps the last delivered * value), the listener gets `{ kind: "failed", error }`, and the next value arrives as `[["r", value]]`. */ fail(error, context) { this.#fail({ kind: "failed", error }, context); } #fail(call2, context) { if (!this.open) return; this.#ops.take(); this.#lone = void 0; this.#newestValue = void 0; this.#failure = call2; this.#newestContext = context; this.#replace = true; this.pending(); } get pendingContext() { return this.#newestContext; } next() { if (this.#replace) return this.#nextAfterFailure(); const lone = this.#lone; const value2 = this.#newestValue; const batch = this.#ops.take(); const retired = this.#retired; this.#lone = void 0; this.#newestValue = void 0; this.#newestContext = void 0; this.#retired = false; if (lone !== void 0) return lone; const ops3 = batch.kind === "ops" ? batch.ops : [["r", value2]]; return { kind: "value", value: value2, ops: ops3, retired }; } /** The pending failure, or the value after one as a root replacement (no ops were kept since the failure). */ #nextAfterFailure() { const failure = this.#failure; this.#newestContext = void 0; if (failure !== void 0) { this.#failure = void 0; return failure; } const value2 = this.#newestValue; const retired = this.#retired; this.#newestValue = void 0; this.#retired = false; this.#replace = false; return { kind: "value", value: value2, ops: [["r", value2]], retired }; } release() { this.#ops.take(); this.#lone = void 0; this.#newestValue = void 0; this.#newestContext = void 0; this.#retired = false; this.#failure = void 0; this.#replace = false; } }; var MAX_PENDING_NATIVE_VALUES = 100; var NativeWatch = class extends WatchLifecycle { /** * Pending values in order, each followed by its frame's context, from `#head` on. Values are kept raw: the call a * listener gets is built when it is taken, so a value an overflow drops costs no object. A read index instead of * `shift()` takes a backlog in constant time per value; taken slots are cleared at once, the array is emptied once * all of it was taken, and its taken prefix dropped after 100 values. */ #pending = []; #head = 0; #replace; /** A pending failure and its frame's context; nothing else is pending meanwhile. */ #failure; #failureContext; constructor(value2, detach, report2, replace) { super(value2, detach, report2); this.#replace = replace; } /** Queue `value`, the source's new value; it replaces a pending failure. */ advance(value2, context) { if (!this.open) return; this.#failure = void 0; this.#failureContext = void 0; if (this.#pending.length - this.#head < 2 * MAX_PENDING_NATIVE_VALUES) { this.#pending.push(value2, context); } else { this.#pending.length = 0; this.#head = 0; this.#pending.push(this.#replace === void 0 ? value2 : this.#replace(), context); } this.pending(); } /** * Queue a failure the source may recover from: the pending values are dropped (`value` keeps the last delivered * value), and the listener gets `{ kind: "failed", error }`. */ fail(error, context) { if (!this.open) return; this.#pending.length = 0; this.#head = 0; this.#failure = { kind: "failed", error }; this.#failureContext = context; this.pending(); } get pendingContext() { return this.#failureContext ?? this.#pending[this.#head + 1]; } next() { const failure = this.#failure; if (failure !== void 0) { this.#failure = void 0; this.#failureContext = void 0; return failure; } const head = this.#head; const pending = this.#pending; const value2 = pending[head]; if (head + 2 === pending.length) { pending.length = 0; this.#head = 0; } else if (head + 2 >= 2 * MAX_PENDING_NATIVE_VALUES) { pending.copyWithin(0, head + 2); pending.length -= head + 2; this.#head = 0; } else { pending[head] = void 0; pending[head + 1] = void 0; this.#head = head + 2; } return { kind: "value", value: value2 }; } release() { this.#pending.length = 0; this.#head = 0; this.#replace = void 0; this.#failure = void 0; this.#failureContext = void 0; } }; // node_modules/@earendil-works/chord/dist/websocket.js function acceptWebSockets(server, host2, options) { server.on("connection", (socket, request2) => { const link = webSocketLink(socket, { compress: true, acceptPacked: false }); Promise.resolve(options.principal(request2)).then((principal) => { if (principal === void 0) link.close("unauthorized"); else host2.accept(link, { ...options, principal, kind: "client" }); }, () => link.close("authentication failed")); }); } // node_modules/@earendil-works/pi-durable/dist/ids.js function idFromNumber(value2) { return value2; } function seqFromNumber(value2) { return value2; } // node_modules/@earendil-works/pi-durable/dist/documents.js function defineDoc(definition) { validateDefinition(definition); return { definition }; } function defineDocFamily(definition) { validateDefinition(definition); return { definition }; } function validateDefinition(definition) { if (!Number.isSafeInteger(definition.version) || definition.version < 1) { throw new TypeError(`Document ${definition.kind} version must be a positive integer`); } } function resolveAddress(definition, args) { let index = 0; let scope; switch (definition.scope) { case "session": scope = { kind: "session" }; break; case "conversation": scope = { kind: "conversation", conversationId: ownerId(args[index++], definition) }; break; case "task": scope = { kind: "task", taskId: ownerId(args[index++], definition) }; break; } const key = definition.family === true ? args[index++] : void 0; const address = key === void 0 ? { kind: definition.kind, scope } : { kind: definition.kind, scope, key }; return { address, id: addressId(address), nextArgument: index }; } function ownerId(value2, definition) { if (typeof value2 !== "number" || !Number.isSafeInteger(value2)) { throw new TypeError(`Document ${definition.kind} requires a ${definition.scope} ID`); } return idFromNumber(value2); } function addressId(address) { const owner = address.scope.kind === "session" ? null : address.scope.kind === "conversation" ? address.scope.conversationId : address.scope.taskId; return JSON.stringify([address.kind, address.scope.kind, owner, address.key ?? null]); } function documentCreate(definition, address, id) { const key = address.key === void 0 ? {} : { key: address.key }; switch (address.scope.kind) { case "session": return { id, kind: address.kind, ...key, scope: address.scope }; case "task": return { id, kind: address.kind, ...key, scope: address.scope }; case "conversation": return { id, kind: address.kind, ...key, scope: address.scope, history: definition.history, fork: definition.fork }; } } function checkRecordScope(definition, record2) { if (record2.scope.kind !== definition.scope || record2.scope.kind === "conversation" && (record2.history !== definition.history || record2.fork !== definition.fork)) { throw new TypeError(`Document ${record2.id} (${record2.kind}) does not match the supplied definition semantics`); } } function checkRecordVersion(definition, record2, version) { if (version > definition.version) { throw new Error(`Document ${record2.id} (${record2.kind}) has newer version ${version} than ${definition.version}`); } if (version < definition.version && definition.migrate === void 0) { throw new Error(`Document ${record2.id} (${record2.kind}) requires migration from version ${version}`); } } function materializeDocument(definition, stored) { return materializeDocumentValue(definition, stored.record, stored.version, stored.value); } function materializeDocumentValue(definition, record2, version, value2) { checkRecordScope(definition, record2); checkRecordVersion(definition, record2, version); if (version === definition.version) return value2; return copyJson(definition.migrate(value2, version)); } // node_modules/@earendil-works/pi-durable/dist/entries.js function defineEntry(kind) { if (typeof kind !== "string" || kind.length === 0) throw new TypeError("Entry kind must be a non-empty string"); return { kind, is: (entry2) => entry2 !== void 0 && entry2.kind === kind }; } var UserEntry = defineEntry("pi.user"); var AssistantEntry = defineEntry("pi.assistant"); var SystemEntry = defineEntry("pi.system"); var ToolResultEntry = defineEntry("pi.tool-result"); var ResetEntry = defineEntry("pi.reset"); var CompactionEntry = defineEntry("pi.compaction"); // node_modules/@earendil-works/pi-durable/dist/errors.js function errorMessage(error) { if (error instanceof Error) return error.message; try { return String(error); } catch { return Object.prototype.toString.call(error); } } var ReadAfterWrite = class extends Error { /** `operation` names the call, such as `Tx.task()`. */ constructor(operation) { super(`${operation} cannot read tables after the first table write`); this.name = "ReadAfterWrite"; } }; var StorageRequestError = class extends Error { constructor(message) { super(message); this.name = "StorageRequestError"; } }; var SessionFailed = class extends Error { constructor(cause) { super("Session failed after a storage error; close and reopen it", { cause }); this.name = "SessionFailed"; } }; var ReadOnlySession = class extends Error { constructor(what) { super(`Read-only Harness: ${what} refused`); this.name = "ReadOnlySession"; } }; var ConversationBusy = class extends Error { conversationId; constructor(conversationId) { super(`Conversation ${conversationId} is busy`); this.name = "ConversationBusy"; this.conversationId = conversationId; } }; var ConversationIdle = class extends Error { conversationId; constructor(conversationId) { super(`Conversation ${conversationId} is idle`); this.name = "ConversationIdle"; this.conversationId = conversationId; } }; // node_modules/@earendil-works/pi-durable/dist/timers.js var MAX_TIMER_DELAY = 2147483647; var inWorkers = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers"; var nativeTimer = (callback, delayMs, options) => { if (options.background && inWorkers) return void 0; const arm = (remaining) => { const next = setTimeout(() => { if (remaining > MAX_TIMER_DELAY) handle = arm(remaining - MAX_TIMER_DELAY); else callback(); }, Math.min(remaining, MAX_TIMER_DELAY)); if (options.background) next.unref?.(); return next; }; let handle = arm(Math.max(0, delayMs)); if (options.background && typeof handle.unref !== "function") { clearTimeout(handle); return void 0; } return () => clearTimeout(handle); }; function hostTimers(setTimer, report2) { const arm = setTimer ?? nativeTimer; let reported = false; const fallBack = (error) => { if (reported) return; reported = true; report2(error); }; return { foreground: (callback, delayMs) => { try { const disarm = arm(callback, delayMs, { background: false }); if (disarm !== void 0) return disarm; fallBack(new Error("HarnessOptions.setTimer declined a timer that paces work; using setTimeout")); } catch (error) { fallBack(error); } return nativeTimer(callback, delayMs, { background: false }); }, background: (callback, delayMs) => { try { return arm(callback, delayMs, { background: true }); } catch (error) { report2(error); return void 0; } } }; } // node_modules/@earendil-works/pi-durable/dist/session/cached-storage.js var CachedStorage = class { #storage; #conversations = new Lru(4096); /** Live (non-terminal) task records, until their task ends. */ #liveTasks = /* @__PURE__ */ new Map(); /** * Tasks some task waits on, or waited on until its wait ended and it has not moved on since: how many such waits name * each, and its ended record once it ended. */ #awaited = /* @__PURE__ */ new Map(); /** Per task that waits, or left its wait and has not changed since: what it waited on, and whether it left. */ #waits = /* @__PURE__ */ new Map(); // Ended task records carry inputs and outcomes, such as a tool call's arguments, so fewer are kept. #tasks = new Lru(256); #submissions = new Lru(256); /** Per record with a read in flight: the reads, and how many published commits wrote it since the first began. */ #reads = /* @__PURE__ */ new Map(); constructor(storage2) { this.#storage = storage2; } /** The backend's own scope for a publication (`Storage.publishing`), when it has one. */ get publishing() { return scopeOf(this.#storage); } /** Pass the batches through; the Session applies each one with `published()` when it publishes it. */ commit(batches, receive, context) { return this.#storage.commit(batches, receive, context); } /** * Apply a batch `commit()` stored, when the Session publishes it. Its records are frozen in place and kept as * they are, no copies: the Session's components that follow the publication, such as the scheduler's live tasks, * hold the same objects, so a live task's record is in memory once. */ published(writes) { for (const write of writes) { switch (write.type) { case "conversation": this.#wrote(write.value.id); this.#conversations.set(write.value.id, freeze(write.value)); break; case "task": this.#wrote(write.value.id); this.#followWait(write.value); this.#keepTask(freeze(write.value)); break; case "submission": this.#wrote(write.value.id); this.#submissions.set(write.value.id, freeze(write.value)); break; } } } mintId() { return this.#storage.mintId(); } conversation(id, context) { const cached = this.#conversations.get(id); if (cached !== void 0) return Promise.resolve(cached); return this.#keep(id, () => this.#storage.conversation(id, context), (record2) => this.#conversations.set(id, record2)); } scanConversations(query, limit, cursor, context) { return this.#storage.scanConversations(query, limit, cursor, context); } entry(first, second, third) { if (third === void 0) return this.#storage.entry(first, second); return this.#storage.entry(first, second, third); } findLatestHeadMarker(conversationId, atOrBeforeEntryId, context) { return this.#storage.findLatestHeadMarker(conversationId, atOrBeforeEntryId, context); } scanEntries(query, limit, cursor, context) { return this.#storage.scanEntries(query, limit, cursor, context); } task(id, context) { const cached = this.#liveTasks.get(id) ?? this.#awaited.get(id)?.record ?? this.#tasks.get(id); if (cached !== void 0) return Promise.resolve(cached); return this.#keep(id, () => this.#storage.task(id, context), (record2) => this.#keepTask(record2)); } scanTasks(query, limit, cursor, context) { return this.#storage.scanTasks(query, limit, cursor, context); } /** Cached as written only: this process writes every submission it settles, and a read one is usually settled. */ submission(id, context) { const cached = this.#submissions.get(id); return cached === void 0 ? this.#storage.submission(id, context) : Promise.resolve(cached); } scanSubmissions(query, limit, cursor, context) { return this.#storage.scanSubmissions(query, limit, cursor, context); } submissionByRequest(conversationId, requestId, context) { return this.#storage.submissionByRequest(conversationId, requestId, context); } findDocument(address, at, context) { return this.#storage.findDocument(address, at, context); } document(id, at, context) { return this.#storage.document(id, at, context); } scanDocuments(query, limit, cursor, context) { return this.#storage.scanDocuments(query, limit, cursor, context); } close(context) { return this.#storage.close(context); } /** Records kept per table, for tests that bound memory. */ get sizes() { return { conversations: this.#conversations.size, tasks: this.#tasks.size, liveTasks: this.#liveTasks.size, awaited: this.#awaited.size, waits: this.#waits.size, submissions: this.#submissions.size }; } /** Keep a frozen task record: a live one until its task ends, an ended one in the bounded table. */ #keepTask(record2) { if (record2.state.status === "terminal") { this.#liveTasks.delete(record2.id); const awaited = this.#awaited.get(record2.id); if (awaited !== void 0) awaited.record = record2; else this.#tasks.set(record2.id, record2); return; } this.#tasks.delete(record2.id); this.#liveTasks.set(record2.id, record2); } /** * Follow a task's waits from its written record: a wait holds the ended records of its `on` until the task, after * leaving the wait, changes again (or ends), as the phase that resumes reads their outcomes before its next commit. */ #followWait(record2) { const wait = this.#waits.get(record2.id); if (record2.state.status === "waiting") { if (wait !== void 0) this.#release(wait.on); const on = record2.state.on; this.#waits.set(record2.id, { on, left: false }); for (const id of on) { let awaited = this.#awaited.get(id); if (awaited === void 0) { const ended = this.#tasks.get(id); this.#tasks.delete(id); awaited = ended === void 0 ? { count: 0 } : { count: 0, record: ended }; this.#awaited.set(id, awaited); } awaited.count++; } return; } if (wait === void 0) return; if (!wait.left && record2.state.status !== "terminal") { wait.left = true; return; } this.#waits.delete(record2.id); this.#release(wait.on); } /** A wait on `on` is over: what no other wait names goes back to the bounded table. */ #release(on) { for (const id of on) { const awaited = this.#awaited.get(id); if (--awaited.count > 0) continue; this.#awaited.delete(id); if (awaited.record !== void 0) this.#tasks.set(id, awaited.record); } } /** A published commit wrote record `id`: a read of it in flight may have missed that. */ #wrote(id) { const reads = this.#reads.get(id); if (reads !== void 0) reads.version++; } /** Resolve with a read's result, frozen; keep a found record unless a commit published since wrote it. */ async #keep(id, read2, keep) { let reads = this.#reads.get(id); if (reads === void 0) { reads = { count: 0, version: 0 }; this.#reads.set(id, reads); } reads.count++; const version = reads.version; let value2; try { value2 = await read2(); } finally { if (--reads.count === 0) this.#reads.delete(id); } if (value2 === void 0) return void 0; freeze(value2); if (reads.version === version) keep(value2); return value2; } /** Records with a read in flight, for tests that bound memory. */ get readsInFlight() { return this.#reads.size; } }; function scopeOf(storage2) { if (storage2.publishing === void 0) return void 0; return (publish2, context) => storage2.publishing(publish2, context); } var Lru = class { #map = /* @__PURE__ */ new Map(); #capacity; constructor(capacity) { this.#capacity = capacity; } get(key) { const value2 = this.#map.get(key); if (value2 === void 0) return void 0; this.#map.delete(key); this.#map.set(key, value2); return value2; } get size() { return this.#map.size; } delete(key) { this.#map.delete(key); } set(key, value2) { this.#map.delete(key); this.#map.set(key, value2); if (this.#map.size > this.#capacity) this.#map.delete(this.#map.keys().next().value); } }; function freeze(value2) { if (value2 === null || typeof value2 !== "object" || Object.isFrozen(value2)) return value2; for (const child of Object.values(value2)) freeze(child); return Object.freeze(value2); } // node_modules/@earendil-works/pi-durable/dist/session/conversation-tails.js var LATEST_CAPACITY = 4096; var MARKER_CAPACITY = 256; var ConversationTails = class { /** Newest entry ID per conversation, `null` for none. */ #latest = /* @__PURE__ */ new Map(); /** Newest head marker per conversation, `null` for none. */ #markers = /* @__PURE__ */ new Map(); /** The newest entry ID visible from `conversationId`, read with `read` when not known. */ async latest(conversationId, read2) { const known = use(this.#latest, conversationId); if (known !== void 0) return known ?? void 0; const latest = await read2(); put(this.#latest, conversationId, latest ?? null, LATEST_CAPACITY); return latest; } /** * The newest visible head marker at or before `atOrBefore` (any when `undefined`), read with `read` when not known. * The newest marker answers every cutoff at or after it, and no marker answers every cutoff. */ async marker(conversationId, atOrBefore, read2) { const marker = use(this.#markers, conversationId); if (marker === null) return void 0; if (marker !== void 0 && (atOrBefore === void 0 || atOrBefore >= marker.id)) return marker; const latest = this.#latest.get(conversationId); const found = await read2(); if (atOrBefore === void 0 || latest !== void 0 && (latest ?? 0) <= atOrBefore) { put(this.#markers, conversationId, found === void 0 ? null : freeze2(found), MARKER_CAPACITY); } return found; } /** The newest entry ID of `conversationId` if known, without reading: `null` for none, `undefined` when unknown. */ peek(conversationId) { return this.#latest.get(conversationId); } /** At a commit's publication: apply a durable batch. */ apply(writes) { for (const write of writes) { if (write.type === "conversation") { if (write.value.parent !== void 0) continue; put(this.#latest, write.value.id, null, LATEST_CAPACITY); put(this.#markers, write.value.id, null, MARKER_CAPACITY); } else if (write.type === "entry") { const entry2 = write.value; put(this.#latest, entry2.conversationId, entry2.id, LATEST_CAPACITY); if (entry2.head !== void 0) { put(this.#markers, entry2.conversationId, freeze2(entry2), MARKER_CAPACITY); } } } } /** How many conversations have their newest entry kept, for tests that bound memory. */ get size() { return this.#latest.size; } /** How many conversations have their newest head marker kept, for tests that bound memory. */ get markers() { return this.#markers.size; } }; function use(map2, key) { const value2 = map2.get(key); if (value2 === void 0) return void 0; map2.delete(key); map2.set(key, value2); return value2; } function put(map2, key, value2, capacity) { map2.delete(key); map2.set(key, value2); if (map2.size > capacity) map2.delete(map2.keys().next().value); } function freeze2(value2) { if (value2 === null || typeof value2 !== "object" || Object.isFrozen(value2)) return value2; for (const child of Object.values(value2)) freeze2(child); return Object.freeze(value2); } // node_modules/@earendil-works/pi-durable/dist/session/keys.js function taskKey(id) { return `task:${id}`; } function conversationKey(id) { return `conv:${id}`; } function entryKey(id) { return `entry:${id}`; } function appendKey(id) { return `entry-append:${id}`; } function submissionKey(id) { return `sub:${id}`; } function requestKey(conversationId, requestId) { return `req:${conversationId}:${requestId}`; } function documentKey(addressId2) { return `doc:${addressId2}`; } function incarnationKey(id) { return `docid:${id}`; } function scopeKey(scope) { switch (scope.kind) { case "session": return "docscope:session"; case "conversation": return `docscope:c${scope.conversationId}`; case "task": return `docscope:t${scope.taskId}`; } } function ownedKey(node) { return `owned:${node}`; } var ROOTS_KEY = "roots"; var INTENT_KEY = "intent"; var ALL_KEYS = "*"; function tableKeys(writes, keys) { for (const write of writes) { switch (write.type) { case "conversation": keys.push(conversationKey(write.value.id)); break; case "entry": keys.push(entryKey(write.value.id), appendKey(write.value.conversationId)); break; case "task": keys.push(taskKey(write.value.id)); break; case "submission": keys.push(submissionKey(write.value.id)); if (write.value.requestId !== void 0) { keys.push(requestKey(write.value.conversationId, write.value.requestId)); } break; } } } var GatedStorage = class { #storage; #touch; constructor(storage2, touch) { this.#storage = storage2; this.#touch = touch; } commit(batches, receive, context) { return this.#storage.commit(batches, receive, context); } mintId() { return this.#storage.mintId(); } conversation(id, context) { return this.#after(conversationKey(id), () => this.#storage.conversation(id, context)); } scanConversations(query, limit, cursor, context) { return this.#after(ALL_KEYS, () => this.#storage.scanConversations(query, limit, cursor, context)); } entry(first, second, third) { if (third === void 0) { const id2 = first; return this.#after(entryKey(id2), () => this.#storage.entry(id2, second)); } const conversationId = first; const id = second; return this.#after(conversationKey(conversationId), () => this.#after(entryKey(id), () => this.#storage.entry(conversationId, id, third))); } findLatestHeadMarker(conversationId, atOrBeforeEntryId, context) { return this.#afterEntries(conversationId, () => this.#storage.findLatestHeadMarker(conversationId, atOrBeforeEntryId, context)); } scanEntries(query, limit, cursor, context) { return this.#afterEntries(query.conversationId, () => this.#storage.scanEntries(query, limit, cursor, context)); } task(id, context) { return this.#after(taskKey(id), () => this.#storage.task(id, context)); } scanTasks(query, limit, cursor, context) { return this.#after(ALL_KEYS, () => this.#storage.scanTasks(query, limit, cursor, context)); } submission(id, context) { return this.#after(submissionKey(id), () => this.#storage.submission(id, context)); } scanSubmissions(query, limit, cursor, context) { return this.#after(ALL_KEYS, () => this.#storage.scanSubmissions(query, limit, cursor, context)); } submissionByRequest(conversationId, requestId, context) { return this.#after(requestKey(conversationId, requestId), () => this.#storage.submissionByRequest(conversationId, requestId, context)); } findDocument(address, at, context) { return this.#after(documentKey(addressId(address)), () => this.#storage.findDocument(address, at, context)); } document(id, at, context) { return this.#after(incarnationKey(id), () => this.#storage.document(id, at, context)); } scanDocuments(query, limit, cursor, context) { return this.#after(scopeKey(query.scope), () => this.#storage.scanDocuments(query, limit, cursor, context)); } close(context) { return this.#storage.close(context); } /** * Run a read of a conversation's entries once neither its appends nor its creation has an unpublished writer: Storage * refuses the read of a conversation it does not have yet. */ #afterEntries(conversationId, read2) { return this.#after(conversationKey(conversationId), () => this.#after(appendKey(conversationId), read2)); } /** Run `read` once `key` has no unpublished writer. */ #after(key, read2) { const gate = this.#touch(key); return gate === void 0 ? read2() : gate.then(read2); } }; // node_modules/@earendil-works/pi-durable/dist/types.js var ROOT_CONVERSATION_ID = 1; // node_modules/@earendil-works/pi-durable/dist/session/forks.js var SCAN_PAGE_SIZE = 256; async function prepareForkDocumentCopies(storage2, parentConversationId, at, childConversationId, context) { const entry2 = await storage2.entry(parentConversationId, at, context); if (entry2 === void 0) throw new Error(`Entry ${at} is not visible from conversation ${parentConversationId}`); const copies = []; const copiedAddresses = /* @__PURE__ */ new Set(); await collectCopies(storage2, { kind: "conversation", conversationId: entry2.entry.conversationId }, entry2.commitSeq, "asOf", childConversationId, copies, copiedAddresses, context); await collectCopies(storage2, { kind: "conversation", conversationId: parentConversationId }, "current", "current", childConversationId, copies, copiedAddresses, context); return copies; } async function collectCopies(storage2, scope, at, policy, childConversationId, copies, copiedAddresses, context) { let cursor; do { const page3 = await storage2.scanDocuments({ scope, at }, SCAN_PAGE_SIZE, cursor, context); for (const source of page3.items) { if (source.scope.kind !== "conversation" || source.fork !== policy) continue; const id = await storage2.mintId(); const identity = { id, kind: source.kind, ...source.key === void 0 ? {} : { key: source.key }, scope: { kind: "conversation", conversationId: childConversationId } }; const record2 = source.history === "latest" ? { ...identity, history: "latest", fork: source.fork } : { ...identity, history: "rewindable", fork: source.fork }; const copyAddress = addressId(record2); if (copiedAddresses.has(copyAddress)) { const member = record2.key === void 0 ? record2.kind : `${record2.kind}/${record2.key}`; throw new Error(`Fork selects multiple source documents for ${member}`); } copiedAddresses.add(copyAddress); copies.push({ record: record2, source: { id: source.id, at } }); } cursor = page3.next; } while (cursor !== void 0); } // node_modules/@earendil-works/pi-durable/dist/session/transaction.js function applySubmissionChange(current, change) { if (current.status === "done" || current.status === "unanswered") return current; if (change.status === "placed") { if (current.status !== "queued") throw new Error(`Submission ${current.id} is not queued`); const status = current.type === "input" ? "placed" : "done"; return { ...current, status, entry: change.entry }; } if (change.status === "done" && current.status !== "placed") { throw new Error(`Submission ${current.id} is not a placed input or continue`); } return { ...current, ...change }; } var INTERNAL_SCAN_PAGE_SIZE = 256; var EMPTY_OPERATIONS = []; var TABLE_JSON_COPY_OPTIONS = { omitUndefinedProperties: true }; var Transaction = class { #host; #context; #scope; #pendingOperations = /* @__PURE__ */ new Set(); #sealed = false; #hasTableWrite = false; /** Atomic batch; conversation and entry writes stage eagerly, while task and document writes assemble later. */ #writes = []; #createdConversationIds = /* @__PURE__ */ new Set(); #forkSourceConversationIds = /* @__PURE__ */ new Set(); #forkSourceDocumentIds = /* @__PURE__ */ new Set(); /** One entry per task touched by a public read, candidate write, or document-owner lookup. */ #tasksById = /* @__PURE__ */ new Map(); /** Submissions created by this transaction, by ID. */ #submissions = /* @__PURE__ */ new Map(); /** Submission settlements and placements in staging order; resolved against the latest candidate record during assembly. */ #submissionChanges = []; /** Write and publication plans of every staged incarnation, built during assembly. */ #plans = []; /** Every document acquisition or retirement marker in staging order. */ #documents = []; /** Latest transaction-local incarnation or retirement marker at each logical address. */ #latestDocumentByAddress = /* @__PURE__ */ new Map(); constructor(host2, context, scope = {}) { this.#host = host2; this.#context = context; this.#scope = scope; } /** Internal: the context of the commit this transaction belongs to. */ get context() { return this.#context; } /** Internal: whether a table write was staged, after which table reads throw `ReadAfterWrite`. */ get hasTableWrite() { return this.#hasTableWrite; } /** * Internal: whether `tx` is a transaction of a commit of the Session behind `host`. A brand check, so a proxy or * another wrapper of a transaction is not one. */ static ownedBy(tx, host2) { return #host in tx && tx.#host === host2; } // ─── Table reads ──────────────────────────────────────────────────────── conversation(id) { return this.#read("conversation", () => this.#host.storage.conversation(id, this.#context)); } entry(first, second) { const kind = typeof first === "number" ? void 0 : first.kind; const id = typeof first === "number" ? first : second; return this.#read("entry", async () => { const entry2 = (await this.#host.storage.entry(id, this.#context))?.entry; return kind === void 0 || entry2?.kind === kind ? entry2 : void 0; }); } task(id) { return this.#read("task", () => this.#committedTask(id)); } scanConversations(query, limit, cursor) { return this.#read("scanConversations", () => this.#host.storage.scanConversations(query, limit, cursor, this.#context)); } scanEntries(query, limit, cursor) { return this.#read("scanEntries", () => this.#host.storage.scanEntries(query, limit, cursor, this.#context)); } latestHeadMarker(conversationId) { return this.#read("latestHeadMarker", () => this.#host.latestHeadMarker(conversationId, this.#context)); } latestEntryId(conversationId) { return this.#read("latestEntryId", () => this.#host.latestEntryId(conversationId, this.#context)); } scanTasks(query, limit, cursor) { return this.#read("scanTasks", () => this.#host.storage.scanTasks(query, limit, cursor, this.#context)); } /** Internal: committed submission record. */ submission(id) { return this.#read("submission", () => this.#host.storage.submission(id, this.#context)); } /** Committed submission with a conversation-scoped request ID. */ submissionByRequest(conversationId, requestId) { return this.#read("submissionByRequest", () => this.#host.storage.submissionByRequest(conversationId, requestId, this.#context)); } // ─── Table writes ─────────────────────────────────────────────────────── createConversation(options) { return this.#write(() => this.#stageConversation(void 0, options.ownership)); } /** Internal final-form bootstrap path for the reserved root identity. */ createRootConversation() { return this.#write(() => this.#stageConversation(void 0, { kind: "ownerless" }, ROOT_CONVERSATION_ID)); } forkConversation(parentConversationId, at, options) { return this.#write(() => this.#stageConversation({ conversationId: parentConversationId, at }, options.ownership)); } async #stageConversation(parent, ownership, reservedId) { const ownerTaskId = ownership.kind === "task" ? ownership.taskId : void 0; const id = reservedId ?? await this.#host.storage.mintId(); this.#assertOpen(); let owner; if (ownerTaskId !== void 0) { const task = await this.#currentTask(ownerTaskId); this.#assertOpen(); if (task === void 0) throw new Error(`Conversation owner task ${ownerTaskId} does not exist`); owner = { conversationId: task.conversationId, taskId: ownerTaskId }; } const record2 = { id, ...parent === void 0 ? {} : { parent }, ...owner === void 0 ? {} : { owner } }; const copies = parent === void 0 ? [] : await prepareForkDocumentCopies(this.#host.storage, parent.conversationId, parent.at, id, this.#context); this.#assertOpen(); for (const copy2 of copies) { this.#forkSourceDocumentIds.add(copy2.source.id); const entry2 = { addressId: addressId(copy2.record), address: copy2.record, target: { kind: "fork-copy", ...copy2 }, retireOnCommit: false }; this.#documents.push(entry2); this.#latestDocumentByAddress.set(entry2.addressId, entry2); } if (parent !== void 0) this.#forkSourceConversationIds.add(parent.conversationId); this.#createdConversationIds.add(id); this.#writes.push({ type: "conversation", value: record2 }); await this.#host.conversationCreated(this, record2); this.#assertOpen(); return record2; } beforeInput(conversationId, input) { return this.#read("beforeInput", () => this.#host.beforeInput(this, conversationId, input, this.#context)); } appendEntry(first, second, third) { if (typeof first === "number") return this.#appendEntry(first, second); return this.#appendEntry(second, { ...third, kind: first.kind }); } #appendEntry(conversationId, value2) { return this.#write(async () => { await this.#requireConversation(conversationId); this.#assertOpen(); const id = await this.#host.storage.mintId(); this.#assertOpen(); const { head, ...rest } = value2; const record2 = copyJson({ ...rest, id, conversationId, head: head === "self" ? id : head, byTaskId: this.#scope.taskId }, TABLE_JSON_COPY_OPTIONS); this.#writes.push({ type: "entry", value: record2 }); return record2; }); } createTask(task, input, options) { return this.#write(async () => { if (options.submissionIds !== void 0) { let trace; let links; for (let index = 0; index < options.submissionIds.length; index++) { const id2 = options.submissionIds[index]; const submission2 = this.#submissions.get(id2) ?? await this.#host.storage.submission(id2, this.#context); if (index === 0) trace = submission2?.trace ?? "root"; else if (submission2?.trace !== void 0) { links ??= []; links.push(submission2.trace); } } options = { ...options, trace, links }; } const ownership = options.ownership; let owner; if (ownership.kind === "task") { owner = await this.#currentTask(ownership.taskId); this.#assertOpen(); if (owner === void 0) throw new Error(`Task owner ${ownership.taskId} does not exist`); if (options.background === true) throw new TypeError("A child task cannot be background"); if (options.conversationId !== void 0 && options.conversationId !== owner.conversationId) { throw new Error(`A child task lives in its owner's conversation ${owner.conversationId}`); } } const conversationId = owner?.conversationId ?? options.conversationId ?? this.#scope.conversationId; if (conversationId === void 0) throw new TypeError("Tx.createTask() requires options.conversationId"); await this.#requireConversation(conversationId); this.#assertOpen(); const definition = task.definition; const checkpoint = definition.initial(input); const id = await this.#host.storage.mintId(); this.#assertOpen(); const record2 = copyJson({ id, conversationId, kind: definition.name, version: definition.version, input, trace: options.trace === "root" ? void 0 : options.trace ?? this.#context.telemetry.propagation?.inject(this.#context.telemetry), links: options.links, ...owner === void 0 ? {} : { owner: owner.id }, background: options.background ?? false, abortRequested: false, ...options.abandonOnRestart === true ? { abandonOnRestart: true } : {}, state: { status: "pending", checkpoint } }, TABLE_JSON_COPY_OPTIONS); this.#tasksById.set(id, { write: { kind: "create", record: record2 } }); return id; }); } /** Create a raw submission record with a fresh ID; no admission rules apply. */ createSubmission(create) { return this.#write(async () => { await this.#requireConversation(create.conversationId); this.#assertOpen(); const id = await this.#host.storage.mintId(); this.#assertOpen(); const record2 = copyJson({ ...create, id }, TABLE_JSON_COPY_OPTIONS); this.#submissions.set(id, record2); return record2; }); } /** * Settle a submission. Resolved during assembly against the transaction's latest candidate record, falling back to * committed state, so it is not a caller table read and works after the first table write. */ settleSubmission(id, settlement) { this.#assertOpen(); this.#hasTableWrite = true; this.#submissionChanges.push({ id, change: copyJson(settlement) }); } /** Place a queued submission at `entry`; resolved during assembly like `settleSubmission()`. */ placeSubmission(id, entry2) { this.#assertOpen(); this.#hasTableWrite = true; this.#submissionChanges.push({ id, change: { status: "placed", entry: entry2 } }); } /** Internal: replace one task record completely. Tasks change their own state through their runtime. */ setTask(value2) { this.#assertOpen(); this.#hasTableWrite = true; const task = this.#taskEntry(value2.id); const candidate = task.write?.record; if (candidate?.state.status === "terminal") { throw new Error(`Task ${value2.id} already has a terminal candidate`); } if (candidate !== void 0 && candidate.conversationId !== value2.conversationId) { throw new Error(`Task ${value2.id} cannot change conversations`); } task.write = { kind: task.write?.kind === "create" ? "create" : "replace", record: copyJson(this.#stampTimes(value2, candidate), TABLE_JSON_COPY_OPTIONS) }; } /** Internal: candidate records of the tasks this transaction created or replaced so far. */ stagedTasks() { const records = []; for (const task of this.#tasksById.values()) if (task.write !== void 0) records.push(task.write.record); return records; } /** Internal: IDs of the tasks this transaction creates. */ createdTaskIds() { const ids = []; for (const [id, task] of this.#tasksById) if (task.write?.kind === "create") ids.push(id); return ids; } /** Internal: conversations this transaction created or forked so far. */ stagedConversations() { const records = []; for (const write of this.#writes) if (write.type === "conversation") records.push(write.value); return records; } doc(token, ...args) { try { this.#assertOpen(); const definition = token.definition; const resolved = resolveAddress(definition, args); this.#assertTaskDocumentsOpen(resolved); const latest = this.#latestDocumentByAddress.get(resolved.id); if (latest !== void 0 && !latest.retireOnCommit) { if (latest.draftPromise !== void 0) return latest.draftPromise; if (latest.target?.kind === "fork-copy") { latest.draftPromise = this.#track(this.#acquireForkCopy(latest, definition, latest.target)); return latest.draftPromise; } } const seed = definition.family === true ? copyJson(args[resolved.nextArgument]) : void 0; const docEntry = { addressId: resolved.id, address: resolved.address, definition, retireOnCommit: false }; this.#documents.push(docEntry); this.#latestDocumentByAddress.set(docEntry.addressId, docEntry); docEntry.draftPromise = this.#track(this.#acquire(docEntry, seed, latest?.retireOnCommit === true)); return docEntry.draftPromise; } catch (error) { return Promise.reject(error); } } retireDoc(token, ...args) { try { this.#assertOpen(); const definition = token.definition; const resolved = resolveAddress(definition, args); const latest = this.#latestDocumentByAddress.get(resolved.id); if (latest?.retireOnCommit) return Promise.resolve(); if (latest?.target?.kind === "fork-copy") { checkRecordScope(definition, latest.target.record); latest.retireOnCommit = true; return Promise.resolve(); } if (latest?.draftPromise !== void 0) { latest.retireOnCommit = true; return this.#track(latest.draftPromise.then(() => void 0)); } const entry2 = { addressId: resolved.id, address: resolved.address, definition, retireOnCommit: true }; this.#documents.push(entry2); this.#latestDocumentByAddress.set(entry2.addressId, entry2); return this.#track(this.#findRetirement(entry2)); } catch (error) { return Promise.reject(error); } } async #acquire(entry2, seed, skipLoad) { const definition = entry2.definition; const loaded = skipLoad ? void 0 : await this.#host.load(definition, entry2.addressId, entry2.address, this.#context); this.#assertOpen(); if (loaded !== void 0) { checkRecordScope(definition, loaded.record); checkRecordVersion(definition, loaded.record, loaded.storedVersion); entry2.target = { kind: "loaded", document: loaded }; entry2.change = loaded.tracker.beginChange(); return entry2.change.state; } const scope = entry2.address.scope; if (scope.kind === "conversation") await this.#requireConversation(scope.conversationId); if (scope.kind === "task") { const task = await this.#currentTask(scope.taskId); if (task === void 0) throw new Error(`Task ${scope.taskId} does not exist`); if (task.state.status === "terminal") throw new Error(`Task ${scope.taskId} is terminal`); } this.#assertOpen(); const value2 = copyJson(definition.family === true ? definition.initial(seed) : definition.initial()); const id = await this.#host.storage.mintId(); this.#assertOpen(); const tracker = track(value2); entry2.target = { kind: "created", record: documentCreate(definition, entry2.address, id), version: definition.version, tracker }; entry2.change = tracker.beginChange(); return entry2.change.state; } async #acquireForkCopy(entry2, definition, target) { const stored = await this.#host.storage.document(target.source.id, target.source.at, this.#context); this.#assertOpen(); if (stored === void 0) { throw new Error(`Fork source document ${target.source.id} cannot be read`); } if (stored.record.scope.kind !== "conversation" || stored.record.kind !== target.record.kind || stored.record.key !== target.record.key || stored.record.history !== target.record.history || stored.record.fork !== target.record.fork) { throw new Error(`Fork source document ${target.source.id} does not match the copied record`); } const value2 = materializeDocumentValue(definition, target.record, stored.version, stored.value); const tracker = track(value2); entry2.definition = definition; entry2.target = { kind: "created", record: target.record, version: definition.version, tracker }; entry2.change = tracker.beginChange(); return entry2.change.state; } async #findRetirement(entry2) { const gate = this.#host.touch(documentKey(entry2.addressId)); if (gate !== void 0) await gate; const record2 = this.#host.cached(entry2.addressId)?.record ?? await this.#host.storage.findDocument(entry2.address, "current", this.#context); this.#assertOpen(); if (record2 === void 0) return; checkRecordScope(entry2.definition, record2); entry2.target = { kind: "retire-only", record: record2 }; } // ─── Settlement ───────────────────────────────────────────────────────── /** Seal after callback failure: abort every change and observe every pending operation. */ async settleFailure() { this.#sealed = true; this.#abortChanges(); await Promise.allSettled(this.#pendingOperations); this.#context = BACKGROUND_CONTEXT; } /** * Seal after callback success, prepare every open change, and assemble the atomic batch. * Any failure aborts every change before Storage admission. */ async settleSuccess() { this.#sealed = true; if (this.#pendingOperations.size > 0) { this.#abortChanges(); await Promise.allSettled(this.#pendingOperations); this.#context = BACKGROUND_CONTEXT; throw new Error("Session commit callback settled before its pending Tx operations"); } try { for (const document of this.#documents) { if (document.change !== void 0) document.prepared = document.change.prepare(); } return await this.#assemble(); } catch (error) { this.#abortChanges(); throw error; } finally { this.#context = BACKGROUND_CONTEXT; } } /** Abort every prepared change after Storage failure or when no write is required. */ discard() { this.#abortChanges(); } /** * Internal: what a deferred commit holds in memory instead of storing (`CommitPersistence`), each changed incarnation * with its operations and whether they selected a base; `undefined` when the commit must be stored. It may be held * only when every write changes an existing `latest`, `initial`-fork conversation document at its stored version. */ holdable() { if (this.#writes.some((write) => write.type !== "document.change")) return void 0; const held2 = []; for (const plan of this.#plans) { const { content, change } = plan; if (content === void 0 && !plan.retire) continue; if (plan.retire || change?.loaded === void 0 || content?.type !== "document.change") return void 0; const { loaded, prepared, version } = change; const record2 = loaded.record; if (record2.scope.kind !== "conversation" || record2.history !== "latest" || record2.fork !== "initial") { return void 0; } if (loaded.storedVersion !== version) return void 0; held2.push({ loaded, ops: prepared.ops, base: content.content.kind === "base" }); } return held2; } /** * Internal: adopt a held commit (`holdable()`) in memory only. Trackers advance by pointer swap; stored versions and * delta counts stay, as nothing was stored. Returns its publication for the Session's own observers. */ adoptHeld() { const publications = []; for (const plan of this.#plans) { const { tracker, prepared, version, loaded } = plan.change; if (prepared.ops.length === 0) { prepared.abort(); continue; } tracker.adopt(prepared); const record2 = loaded.record; const conversationId = plan.conversationId; publications.push({ type: "document", kind: "change", record: record2, conversationId, version, value: prepared.value, ops: prepared.ops }); } return publications; } /** * Internal: store the held changes of every incarnation this commit reads or writes in front of its own, so a stored * document never misses an operation its later ones build on. Called right before Storage admission, after every * fallible step, because it empties the holds: a failure earlier keeps them. Returns the holds it took. */ takeHeld(held2) { const taken = []; for (const plan of this.#plans) { if (!("createdAt" in plan.record)) continue; const hold = held2(plan.record.id); if (hold === void 0) continue; taken.push(hold); const own = plan.content?.type === "document.change" ? plan.content : void 0; const change = plan.change; const value2 = change?.prepared.value ?? hold.loaded.tracker.value; const stored = storeHeld(hold, change?.prepared.ops ?? EMPTY_OPERATIONS, value2, hold.loaded.valueVersion); const content = own?.content.kind === "base" ? own.content : stored.content; const write = { type: "document.change", id: plan.record.id, content }; if (own !== void 0) this.#writes[this.#writes.indexOf(own)] = write; else this.#insertBeforeRetirement(write); if (!plan.retire) plan.held = { ops: stored.ops, observed: own !== void 0 }; plan.content = write; if (plan.record.scope.kind === "conversation") plan.conversationId = plan.record.scope.conversationId; } return taken; } /** * Internal: the keys this commit writes, once settled (and after `takeHeld()`), for the Session's unpublished write * set: its table records, and the address and incarnation of every document it creates, changes, or retires, with * the scope of every incarnation it creates or retires. */ writeKeys() { const keys = []; tableKeys(this.#writes, keys); for (const plan of this.#plans) { if (plan.content === void 0 && !plan.retire) continue; keys.push(documentKey(plan.addressId), incarnationKey(plan.record.id)); if (plan.retire || !("createdAt" in plan.record)) keys.push(scopeKey(plan.record.scope)); } return keys; } /** Stage a content write for an incarnation, before its retirement when it retires. */ #insertBeforeRetirement(write) { const at = this.#writes.findIndex((candidate) => candidate.type === "document.retire" && candidate.id === write.id); if (at < 0) this.#writes.push(write); else this.#writes.splice(at, 0, write); } /** Adopt every prepared change by pointer swap after Storage success and describe the publication. */ adopt(seq) { const publications = []; let stored; for (const plan of this.#plans) { const committed = "createdAt" in plan.record; let record2 = committed ? plan.record : { ...plan.record, createdAt: seq }; if (plan.retire) record2 = { ...record2, retiredAt: seq }; const change = plan.change; if (change !== void 0) { const { tracker, prepared, loaded, version } = change; if (loaded === void 0 ? !plan.retire : prepared.ops.length > 0) tracker.adopt(prepared); else prepared.abort(); if (loaded !== void 0) { if (loaded.storedVersion < version) loaded.storedVersion = version; if (plan.content?.type === "document.change") { if (plan.content.content.kind === "base") loaded.deltasSinceBase = 0; else loaded.deltasSinceBase++; } } else if (!plan.retire) { this.#host.install({ addressId: plan.addressId, record: record2, storedVersion: version, valueVersion: version, deltasSinceBase: 0, tracker }); } } if (plan.retire && committed) this.#host.evict(plan.addressId, record2.id); const publication = describe(plan, record2); if (publication === void 0) continue; const held2 = plan.held; if (held2 === void 0) { publications.push(publication); stored?.push(publication); continue; } stored ??= [...publications]; if (held2.observed) publications.push(publication); stored.push(publication.type === "document" && publication.kind === "change" ? { ...publication, ops: held2.ops } : publication); } return { observed: publications, stored: stored ?? publications }; } async #assemble() { const storage2 = this.#host.storage; const plans = this.#plans; for (const document of this.#documents) { const plan = planDocument(document); if (plan !== void 0) plans.push(plan); } this.#rejectForkSourceWrites(plans); await this.#validateOwners(); for (const [id, task] of this.#tasksById) { if (task.write?.kind !== "replace") continue; const committed = await this.#committedTask(id); if (committed === void 0) throw new Error(`Task ${id} does not exist`); if (committed.state.status === "terminal") throw new Error(`Task ${id} is already terminal`); if (committed.conversationId !== task.write.record.conversationId) { throw new Error(`Task ${id} cannot change conversations`); } } const terminalTaskIds = /* @__PURE__ */ new Set(); for (const task of this.#tasksById.values()) { if (task.write?.record.state.status === "terminal") terminalTaskIds.add(task.write.record.id); } if (terminalTaskIds.size > 0) { const retiring = /* @__PURE__ */ new Set(); for (const plan of plans) { const scope = plan.record.scope; if (scope.kind !== "task" || !terminalTaskIds.has(scope.taskId)) continue; plan.retire = true; retiring.add(plan.record.id); } const retire = (record2) => { if (retiring.has(record2.id)) return; plans.push({ addressId: addressId(record2), record: record2, retire: true }); retiring.add(record2.id); }; for (const taskId of terminalTaskIds) { if (this.#tasksById.get(taskId)?.write?.kind === "create") continue; const gate = this.#host.touch(scopeKey({ kind: "task", taskId })); if (gate !== void 0) await gate; const known = this.#host.taskDocuments(taskId); if (known !== void 0) { for (const record2 of known) retire(record2); continue; } let cursor; do { const page3 = await storage2.scanDocuments({ scope: { kind: "task", taskId }, at: "current" }, INTERNAL_SCAN_PAGE_SIZE, cursor, this.#context); for (const record2 of page3.items) retire(record2); cursor = page3.next; } while (cursor !== void 0); } } for (const plan of plans) { if (!publishes(plan)) continue; const scope = plan.record.scope; if (scope.kind === "conversation") plan.conversationId = scope.conversationId; if (scope.kind !== "task") continue; const task = this.#taskEntry(scope.taskId); if (task.publicationConversationId === void 0) { const current = await this.#currentTask(scope.taskId); if (current !== void 0) task.publicationConversationId = current.conversationId; } plan.conversationId = task.publicationConversationId; } for (const { id, change } of this.#submissionChanges) { const current = this.#submissions.get(id) ?? await storage2.submission(id, this.#context); if (current === void 0) throw new Error(`Submission ${id} does not exist`); const next = applySubmissionChange(current, change); if (next !== current) this.#submissions.set(id, next); } const writes = this.#writes; for (const value2 of this.#submissions.values()) writes.push({ type: "submission", value: value2 }); for (const task of this.#tasksById.values()) { if (task.write !== void 0) writes.push({ type: "task", value: task.write.record }); } for (const plan of plans) { const change = plan.change; if (plan.content?.type === "document.change" && plan.content.content.kind === "delta" && change?.loaded) { const { prepared, version } = change; const info = { deltasSinceBase: change.loaded.deltasSinceBase }; if (change.definition.checkpointWhen(prepared.value, prepared.ops, info)) { plan.content = { ...plan.content, content: { version, kind: "base", value: prepared.value } }; } } if (plan.content !== void 0) writes.push(plan.content); if (plan.retire) writes.push({ type: "document.retire", id: plan.record.id }); } return writes; } // ─── Helpers ──────────────────────────────────────────────────────────── /** * New owned work needs a live owner, judged on the owner's final candidate: not `completing`, terminal, or * abort-marked. A task therefore cannot create owned work in the commit that finishes it (spec §5.5). */ async #validateOwners() { const owners = []; for (const write of this.#writes) { if (write.type !== "conversation" || write.value.owner === void 0) continue; owners.push({ what: "Conversation owner task", taskId: write.value.owner.taskId }); } for (const task of this.#tasksById.values()) { const record2 = task.write?.kind === "create" ? task.write.record : void 0; if (record2?.owner !== void 0) owners.push({ what: "Task owner", taskId: record2.owner }); } for (const { what, taskId } of owners) { const task = await this.#currentTask(taskId); if (task === void 0) throw new Error(`${what} ${taskId} does not exist`); if (task.state.status === "terminal" || task.state.status === "completing") { throw new Error(`${what} ${taskId} is ${task.state.status}`); } if (task.abortRequested) throw new Error(`${what} ${taskId} is abort-marked`); } } #rejectForkSourceWrites(plans) { for (const plan of plans) { if (plan.content === void 0 && !plan.retire) continue; const record2 = plan.record; if (this.#forkSourceDocumentIds.has(record2.id)) { throw new Error(`Cannot change fork source document ${record2.id} in the fork transaction`); } const scope = plan.record.scope; if (scope.kind === "conversation" && this.#forkSourceConversationIds.has(scope.conversationId) && record2.scope.kind === "conversation" && record2.fork === "current") { throw new Error(`Cannot fork conversation ${scope.conversationId} while changing its current-policy documents`); } } } #abortChanges() { for (const document of this.#documents) document.change?.abort(); } #assertOpen() { if (this.#sealed) throw new Error("Transaction has settled"); } #assertTaskDocumentsOpen(resolved) { const scope = resolved.address.scope; if (scope.kind === "task" && this.#tasksById.get(scope.taskId)?.write?.record.state.status === "terminal") { throw new Error(`Task ${scope.taskId} is terminal`); } } /** Register an operation so callback settlement can reject and drain it. */ #track(operation) { this.#pendingOperations.add(operation); const settle3 = () => { this.#pendingOperations.delete(operation); }; operation.then(settle3, settle3); return operation; } #read(method2, read2) { try { this.#assertOpen(); if (this.#hasTableWrite) throw new ReadAfterWrite(`Tx.${method2}()`); return this.#track(read2()); } catch (error) { return Promise.reject(error); } } #write(write) { try { this.#assertOpen(); this.#hasTableWrite = true; return this.#track(write()); } catch (error) { return Promise.reject(error); } } async #requireConversation(id) { if (this.#createdConversationIds.has(id)) return; if (await this.#host.storage.conversation(id, this.#context) === void 0) { throw new Error(`Conversation ${id} does not exist`); } } /** * Lifecycle times: `startedAt` on the first change to `running`, `endedAt` on the change to `terminal`. Once set, * they carry over from the replaced record; records written before they existed lack them. */ #stampTimes(value2, candidate) { const status = value2.state.status; const now = (stamps) => stamps ? this.#host.now() : void 0; const startedAt = candidate?.startedAt ?? value2.startedAt ?? now(status === "running"); const endedAt = candidate?.endedAt ?? value2.endedAt ?? now(status === "terminal"); if (startedAt === value2.startedAt && endedAt === value2.endedAt) return value2; return { ...value2, ...startedAt === void 0 ? {} : { startedAt }, ...endedAt === void 0 ? {} : { endedAt } }; } #taskEntry(id) { let task = this.#tasksById.get(id); if (task === void 0) { task = {}; this.#tasksById.set(id, task); } return task; } /** Latest candidate task record, falling back to committed state; not a caller table read. */ async #currentTask(id) { return this.#tasksById.get(id)?.write?.record ?? await this.#committedTask(id); } #committedTask(id) { const task = this.#taskEntry(id); task.committedRead ??= this.#host.storage.task(id, this.#context); return task.committedRead; } }; function planDocument(document) { const target = document.target; if (target === void 0) return void 0; const plan = { addressId: document.addressId, retire: document.retireOnCommit }; switch (target.kind) { case "created": { const prepared = document.prepared; const { record: record2, version, tracker } = target; const content = { version, kind: "base", value: prepared.value }; return { ...plan, record: record2, content: { type: "document.create", record: record2, content }, change: { tracker, prepared, version } }; } case "fork-copy": return { ...plan, record: target.record, content: { type: "document.copy", record: target.record, source: target.source } }; case "retire-only": return { ...plan, record: target.record }; case "loaded": { const loaded = target.document; const definition = document.definition; const prepared = document.prepared; const version = definition.version; const id = loaded.record.id; const content = loaded.storedVersion < version ? { type: "document.change", id, content: { version, kind: "base", value: prepared.value } } : prepared.ops.length > 0 ? { type: "document.change", id, content: { version, kind: "delta", ops: prepared.ops } } : void 0; const change = { tracker: loaded.tracker, prepared, version, loaded, definition }; return { ...plan, record: loaded.record, change, ...content === void 0 ? {} : { content } }; } } } function describe(plan, record2) { const conversationId = plan.conversationId; if (plan.retire) return { type: "document", kind: "retire", record: record2, conversationId }; if (plan.content?.type === "document.copy") { return { type: "document.copy", record: record2, conversationId, source: plan.content.source }; } const change = plan.change; if (change === void 0 || !publishes(plan)) return void 0; const ops3 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops; return { type: "document", kind: "change", record: record2, conversationId, version: change.version, value: change.prepared.value, ops: ops3 }; } function storeHeld(hold, ops3, value2, version) { hold.pending.push(ops3); const taken = hold.pending.take(); if (taken.kind === "replace") return { content: { version, kind: "base", value: value2 }, ops: [["r", value2]] }; if (hold.base) return { content: { version, kind: "base", value: value2 }, ops: taken.ops }; return { content: { version, kind: "delta", ops: taken.ops }, ops: taken.ops }; } function publishes(plan) { return plan.retire || plan.change?.loaded === void 0 || plan.content !== void 0; } // node_modules/@earendil-works/pi-durable/dist/session/writer.js var SESSION_FAILED = { kind: "failed" }; var Writer = class { #host; /** Admitted parts not yet sent, in line order. */ #queue = []; #flight = false; /** Per key, the newest unpublished part that writes it. */ #unpublished = /* @__PURE__ */ new Map(); /** The newest admitted part that is not yet published, whose gate opens after every earlier one's. */ #newest; constructor(host2) { this.#host = host2; } /** Admit a part: register its keys, queue it, and send it at once unless a call is in flight. */ admit(spec) { const outcome = Promise.withResolvers(); outcome.promise.catch(() => { }); const part = { ...spec, outcome: outcome.promise, settle: { resolve: () => { part.done = true; outcome.resolve(); }, reject: (error) => { part.done = true; outcome.reject(error); } }, done: false, gate: Promise.withResolvers() }; for (const key of spec.keys) this.#unpublished.set(key, part); this.#newest = part; this.#queue.push(part); this.#pump(); return part; } /** The gate of the newest unpublished writer of `key`, or `undefined` when it has none. */ gate(key) { return this.#unpublished.get(key)?.gate.promise; } /** Opens once every part admitted so far is published or never will be. */ drained() { return this.#newest?.gate.promise; } /** Keys with an unpublished writer, for tests that bound memory. */ get unpublished() { return this.#unpublished.size; } #pump() { if (this.#flight || this.#queue.length === 0) return; this.#flight = true; const parts = this.#queue; this.#queue = []; void this.#flush(parts); } /** * Send and publish `parts`, then what was admitted meanwhile. Never rejects: a throw the writer did not expect fails * the Session and settles the parts (`#abandon()`), so no outcome or gate is stranded. */ async #flush(parts) { try { await this.#send(parts); } catch (error) { this.#abandon(parts, error); } this.#flight = false; this.#pump(); } /** Store `parts` in one call, then publish them in order; settles every one of them. */ async #send(parts) { const stored = parts.filter((part) => part.writes !== void 0); const seqs = []; let result; let rejected; const failed = this.#host.failure(); if (failed !== void 0) { for (const part of parts) this.#unpublish(part, new SessionFailed(failed.error)); return; } if (stored.length > 0) { try { result = await this.#host.commit(stored.map((part) => part.writes), (index, seq) => { seqs[index] = seq; }, withoutAbortSignal(stored[0].context)); } catch (error) { rejected = { error }; } } const failedMeanwhile = this.#host.failure(); if (failedMeanwhile !== void 0) { for (const part of parts) this.#unpublish(part, new SessionFailed(failedMeanwhile.error)); return; } await this.#publish(parts, seqs, result, rejected); } /** * Publish the received parts and held ones in order, a macrotask apart, and settle every part (group commit §3.4). A * refusal, a rejection, or a part whose adoption or own observer throws fails the Session once the parts before it * are published and the host observers got the last one; the parts after it are not published, and reject with * `SessionFailed`. What the failure decides (the outcomes, and the gates of the parts it concerns) is settled only * once the Session failed, so no job waiting at such a gate, and no later outcome, goes on as if it had not. */ async #publish(parts, seqs, result, rejected) { let stop2; const failing = []; let storedIndex = 0; let published = 0; for (const part of parts) { const index = part.writes === void 0 ? void 0 : storedIndex++; if (stop2 !== void 0) { const outcome = rejected !== void 0 && index !== void 0 && stop2.external !== true ? { kind: "own", error: rejected.error } : SESSION_FAILED; failing.push({ part, outcome, published: false }); continue; } const seq = index === void 0 ? void 0 : seqs[index]; if (index !== void 0 && seq === void 0) { const error = rejected !== void 0 ? rejected.error : index === result?.stored && "refused" in result ? result.refused : new Error("Storage committed without adopting the batch"); failing.push({ part, outcome: { kind: "own", error }, published: false }); stop2 = { error, at: part.index }; continue; } if (published++ > 0) await hop(); let failure2; let external; const publication = this.#host.publishing(() => { external = this.#host.failure(); if (external !== void 0) return; this.#unregister(part); try { if (part.writes !== void 0) this.#host.stored(part.writes); failure2 = part.publish(seq); } catch (error) { failure2 = { kind: "adoption", error }; } }, part.context); if (publication !== void 0) await publication; if (external !== void 0) { stop2 = { error: external.error, at: part.index, external: true }; failing.push({ part, outcome: SESSION_FAILED, published: false }); continue; } if (failure2?.kind === "adoption") { failing.push({ part, outcome: { kind: "own", error: failure2.error }, published: true }); stop2 = { error: failure2.error, at: part.index }; continue; } if (rejected !== void 0) { if (failure2 !== void 0) this.#host.report(failure2.error, part.context); failing.push({ part, outcome: { kind: "own", error: rejected.error }, published: true }); continue; } if (failure2 !== void 0) stop2 = { error: failure2.error, at: part.index }; const failedMeanwhile = this.#host.failure(); if (failedMeanwhile !== void 0 && failure2 === void 0) { part.settle.reject(new SessionFailed(failedMeanwhile.error)); } else part.settle.resolve(); this.#open(part); } if (rejected !== void 0 || stop2 !== void 0) { await Promise.resolve(); const own = failing.flatMap(({ part, outcome }) => outcome.kind === "own" ? [part.index] : []); if (rejected !== void 0) this.#host.fail(rejected.error, own[0], own); else this.#host.fail(stop2.error, stop2.at, own); } for (const { part, outcome, published: stored } of failing) { const error = outcome.kind === "own" ? outcome.error : this.#failed(stop2.error); if (stored) { part.settle.reject(error); this.#open(part); } else this.#unpublish(part, error); } const failure = this.#host.failure(); if (failure !== void 0) { const queued = this.#queue; this.#queue = []; for (const part of queued) this.#unpublish(part, new SessionFailed(failure.error)); } } /** * A throw the writer itself did not expect while sending or publishing `parts` (a bug): fail the Session at the first * of them and settle every one not settled yet, so no outcome, gate, or close waits on it forever. */ #abandon(parts, error) { this.#host.fail(error, parts[0].index, []); for (const part of parts) { if (!part.done) this.#unpublish(part, this.#failed(error)); this.#unregister(part); this.#open(part); } } /** `SessionFailed` with the Session's failure, or with `error` until the Session records it. */ #failed(error) { const failure = this.#host.failure(); return new SessionFailed(failure !== void 0 ? failure.error : error); } /** Settle a part that will never be published: discard it, reject it with `error`, and open its gate. */ #unpublish(part, error) { part.discard(); part.settle.reject(error); this.#unregister(part); this.#open(part); } /** Drop the keys a part is still the newest writer of: it is published, or never will be. */ #unregister(part) { for (const key of part.keys) if (this.#unpublished.get(key) === part) this.#unpublished.delete(key); } /** Open a part's gate: the jobs waiting for it go on. The newest part's opens `drained()` too. */ #open(part) { part.gate.resolve(); if (this.#newest === part) this.#newest = void 0; } }; function hop() { if (typeof setImmediate === "function") return new Promise((resolve2) => setImmediate(resolve2)); const channel = new MessageChannel(); return new Promise((resolve2) => { channel.port1.onmessage = () => { channel.port1.close(); resolve2(); }; channel.port2.postMessage(void 0); }); } // node_modules/@earendil-works/pi-durable/dist/session/session.js var NO_HOLDS = []; var NO_OPERATIONS = []; var NO_KEYS = []; var MAX_OUTSTANDING = 256; var ignore = () => { }; var SETTLED = Promise.resolve(); var LISTENER = { kind: "listener_error" }; var TELEMETRY = { kind: "host_error", callback: "telemetry" }; function createSession(storage2, options) { const retention = options?.documentRetentionMs; const within = options?.persistWithinMs; return new SessionImpl(storage2, options?.now, retention === void 0 ? void 0 : () => retention, options?.setTimer, false, () => within); } var defaultRetention = 6e5; function documentRetention(ms) { return typeof ms === "number" && ms >= 0 ? ms : defaultRetention; } var DEFAULT_PERSIST_WITHIN_MS = 1e3; function persistWithin(ms) { return typeof ms === "number" && ms >= 0 ? ms : DEFAULT_PERSIST_WITHIN_MS; } var SessionImpl = class { #storage; /** Newest entry and head marker per conversation; none for a read-only Session, whose Storage another process writes. */ #tails; /** * The current documents of each live task this Session created, so the commit that ends a task retires them without * a scan. An entry starts with its task's creation and goes when the task is terminal: bounded by live tasks. */ #taskDocuments = /* @__PURE__ */ new Map(); #documents = /* @__PURE__ */ new Map(); /** Cached documents with no observer attached, by address, oldest use first, with the time of that use. */ #idleDocuments = /* @__PURE__ */ new Map(); /** Observers attached per document incarnation (record ID); a cached incarnation with any is never evicted. */ #attachedDocuments = /* @__PURE__ */ new Map(); #clock; #documentRetentionMs; #persistWithinMs; /** Documents with held changes, by incarnation (record ID); each is pinned in the cache until they are stored. */ #holds = /* @__PURE__ */ new Map(); #holdTimer; /** Refuse every commit with writes (`HarnessOptions.readOnly`). */ #readOnly; #timers; /** The pending eviction: a timer for the oldest idle document, or a line job when the retention is 0. */ #documentSweep; #sweepJobs = 0; /** Set once the host declined a background timer: then none is kept, and the end of each commit evicts what expired. */ #documentTimersUnavailable = false; /** Host commit observers (`observeCommits()`), each behind `guardObserver()`. */ #commitListeners = /* @__PURE__ */ new Set(); /** The Session's own listeners, such as its scheduler's: they keep memory in step with storage, so a throw fails it. */ #internalListeners = /* @__PURE__ */ new Set(); #closeListeners = /* @__PURE__ */ new Set(); #host; /** The record cache under the guard, which the writer applies each batch to at its publication. */ #cached; #writer; /** The Storage jobs on the line read through: each read waits while its key has an unpublished writer. */ #gated; /** The end of the last line job: the line runs one job at a time. */ #tail = Promise.resolve(); /** Line index of the next job; outcomes are delivered in this order. */ #nextIndex = 0; /** The job on the line, while one runs: its index, and whether it waited at a gate. */ #current; /** Settles once every job so far has its outcome delivered. */ #fence = Promise.resolve(); #lastDelivered = -1; /** Jobs that started on the line and have no outcome delivered yet (`MAX_OUTSTANDING`). */ #outstanding = 0; #room; /** The line index whose outcome the failure is: every later job's outcome is `SessionFailed`, except `#ownOutcomes`. */ #failAt; /** Later jobs whose outcome is their own error all the same: the other parts of a storage call that rejected. */ #ownOutcomes = /* @__PURE__ */ new Set(); #closing; #failure; /** Set when close reaches the backend: calls that fail from then on fail because it closes. */ #failed = Promise.withResolvers(); #closed = Promise.withResolvers(); get closed() { return this.#closed.promise; } /** Internal: the error that failed the Session, if one did. */ get failure() { return this.#failure; } /** Internal: rejects with `SessionFailed` the moment the Session fails, before work underway has ended; never resolves. */ get failed() { return this.#failed.promise; } constructor(storage2, now = Date.now, documentRetentionMs = () => void 0, setTimer, readOnly = false, persistWithinMs = () => void 0) { this.#failed.promise.catch(() => { }); this.#readOnly = readOnly; this.#cached = readOnly ? void 0 : new CachedStorage(storage2); this.#storage = new GuardedStorage(this.#cached ?? storage2, this); this.#gated = new GatedStorage(this.#storage, (key) => this.touch(key)); this.#writer = new Writer({ commit: (batches, receive, context) => this.#storage.commit(batches, receive, context), stored: (writes) => { this.#cached?.published(writes); this.#tails?.apply(writes); }, publishing: (publish2, context) => this.#publishing(publish2, context), failure: () => this.#failure, fail: (error, at, own) => this.fail(error, void 0, at, own), report: (error, context) => this.report(error, LISTENER, context) }); this.#tails = readOnly ? void 0 : new ConversationTails(); this.#timers = hostTimers(setTimer, (error) => this.report(error, { kind: "host_error", callback: "setTimer" })); this.#clock = () => { try { return now(); } catch { return Date.now(); } }; this.#documentRetentionMs = () => { try { return documentRetention(documentRetentionMs()); } catch { return documentRetention(void 0); } }; this.#persistWithinMs = () => { try { return persistWithin(persistWithinMs()); } catch { return persistWithin(void 0); } }; this.#host = { storage: this.#gated, touch: (key) => this.touch(key), now, cached: (id) => { const cached = this.#documents.get(id); if (cached !== void 0) this.#touchDocument(id); return cached; }, load: (definition, addressId2, address, context) => this.#loadDocument(definition, addressId2, address, context), install: (document) => { this.#documents.set(document.addressId, document); this.#touchDocument(document.addressId); }, evict: (id, recordId) => { if (this.#documents.get(id)?.record.id === recordId) this.#dropDocument(id); }, beforeInput: (tx, id, input, context) => this.beforeInput(tx, id, input, context), conversationCreated: (tx, record2) => this.conversationCreated(tx, record2), taskDocuments: (taskId) => this.#taskDocuments.get(taskId)?.values(), latestEntryId: (conversationId, context) => this.latestEntryId(conversationId, context), latestHeadMarker: (conversationId, context) => this.latestHeadMarker(conversationId, void 0, context) }; } /** The Storage behind the failure guard; every component reads and writes through it. */ get storage() { return this.#storage; } /** * Internal, on the Session line: the newest entry ID visible from `conversationId`. Known from the Session's own * commits when it owns its Storage, so it costs no Storage read after the first. */ latestEntryId(conversationId, context) { const gate = this.#touchEntries(conversationId); const latest = () => { const read2 = async () => (await this.#storage.scanEntries({ conversationId }, 1, void 0, context)).items[0]?.id; return this.#tails === void 0 ? read2() : this.#tails.latest(conversationId, read2); }; return gate === void 0 ? latest() : gate.then(latest); } /** * Internal: the newest entry ID of `conversationId` as known from the Session's commits right now, without reading; * `undefined` when not known (and always for a read-only Session). */ knownLatestEntryId(conversationId) { return this.#tails?.peek(conversationId); } /** * Internal, on the Session line: the newest visible head marker of `conversationId` at or before `atOrBefore`, any * when `undefined`; known from the Session's own commits as `latestEntryId()` is. */ latestHeadMarker(conversationId, atOrBefore, context) { const gate = this.#touchEntries(conversationId); const marker = () => { const read2 = () => this.#storage.findLatestHeadMarker(conversationId, atOrBefore, context); return this.#tails === void 0 ? read2() : this.#tails.marker(conversationId, atOrBefore, read2); }; return gate === void 0 ? marker() : gate.then(marker); } /** * On the line: wait for what an unpublished commit wrote of a conversation's entries, its appends and its creation, * as reading them from Storage fails for a conversation not stored yet. */ #touchEntries(conversationId) { const created = this.touch(conversationKey(conversationId)); const appended = this.touch(appendKey(conversationId)); if (created === void 0) return appended; return appended === void 0 ? created : Promise.all([created, appended]).then(ignore); } /** * Internal, on the line: wait, while `key` has an unpublished writer (for `ALL_KEYS`, while anything is unpublished), * until it is published, then check that the Session is healthy; `undefined` when there is nothing to wait for. A * job's every read touches its key first, so it never observes state an earlier job admitted and the writer has not * published. */ touch(key) { const gate = key === ALL_KEYS ? this.#writer.drained() : this.#writer.gate(key); if (gate === void 0) return void 0; if (this.#current !== void 0) this.#current.hit = true; return gate.then(() => this.#assertHealthy()); } commit(change, context, options) { return this.commitWith(change, context, void 0, options); } /** * Internal: fail the Session with `error`, the first wins, and report it once. Admission ends, and close listeners run * now; they read the error from `failure`. The Storage guard calls this; the Harness also calls it for a throw in its * scheduler's own commits. `at` is the line index of the job whose outcome the failure is, by default the job on the * line: later jobs' outcomes become `SessionFailed`. */ fail(error, context, at, own) { if (this.#failure !== void 0) return; this.#failure = { error }; this.#failAt = at ?? this.#current?.index ?? this.#lastDelivered; if (own !== void 0) for (const index of own) this.#ownOutcomes.add(index); this.#failed.reject(new SessionFailed(error)); this.close(BACKGROUND_CONTEXT).catch(() => { }); this.report(error, { kind: "storage_failed" }, context); } /** * Internal commit exposing the concrete transaction and its internal operations, such as the reserved-ID root * bootstrap and task replacement. `scope` sets the default `tx.createTask()` conversation and the task attributed to * appended entries. */ commitWith(change, context, scope, options) { let withinMs; try { this.assertUsable(); withinMs = this.#deferral(options?.persistence); } catch (error) { return Promise.reject(error); } return this.#enqueue((index) => this.#runCommit(change, context, scope, withinMs, index)); } /** * Internal: `commitWith()` for a commit of the scheduler's own whose outcome goes only to scheduler code. When it * writes nothing and waited at no gate, it is equivalent to having run before every unpublished commit, so its * outcome is delivered at once, ahead of theirs. One that writes is never exempt: its outcome waits for its own * publication and every earlier outcome, so no invocation starts on a reservation that is not durable. */ commitInternal(change, context) { try { this.assertUsable(); } catch (error) { return Promise.reject(error); } return this.#enqueue((index) => this.#runCommit(change, context, void 0, void 0, index), true); } /** * Internal, on the line: call `end` once the outcome of the job on the line settles, its commit published or never to * be; at once off the line. */ onJobEnd(end) { const job = this.#current; if (job === void 0) { end(); return; } if (job.ends === void 0) job.ends = [end]; else job.ends.push(end); } /** Internal: the Storage jobs on the line read through (`touch()`); off the line, use the plain one. */ get lineStorage() { return this.#gated; } /** How long a commit may stay in memory before it is stored; `undefined` when it is immediate. */ #deferral(persistence) { if (persistence?.kind !== "deferred") return void 0; const ms = persistence.withinMs; if (ms === void 0) return this.#persistWithinMs(); if (typeof ms !== "number" || !(ms >= 0)) { throw new TypeError("CommitPersistence.withinMs must be a number of milliseconds, 0 or more"); } return ms; } /** Internal: whether `tx` is the transaction of one of this Session's commits. */ ownsTransaction(tx) { return Transaction.ownedBy(tx, this.#host); } /** * Internal: run a read-only job on the mutation line so multi-read derivations observe one committed state. It reads * through `storage`, whose reads wait for the publication of what the line admitted before; its outcome comes after * theirs. */ readOnLine(job) { try { this.assertUsable(); } catch (error) { return Promise.reject(error); } return this.#enqueue(async () => { this.#assertHealthy(); return { value: await job(this.#gated) }; }); } /** * Internal: a conversation document's current incarnation and value, for a job already running on the line (see * `readOnLine()`). Absent documents are `undefined`. */ async conversationDocumentOnLine(token, conversationId, context) { const definition = token.definition; const resolved = resolveAddress(definition, [conversationId, context]); const loaded = await this.#loadDocument(definition, resolved.id, resolved.address, context); if (loaded === void 0) return void 0; checkRecordScope(definition, loaded.record); checkRecordVersion(definition, loaded.record, loaded.storedVersion); return { record: loaded.record, version: loaded.valueVersion, value: loaded.tracker.value }; } async snapshot(token, ...args) { this.assertUsable(); const definition = token.definition; const resolved = resolveAddress(definition, args); const context = args[resolved.nextArgument]; const cached = this.#documents.get(resolved.id); if (cached?.valueVersion === definition.version) this.#touchDocument(resolved.id); const loaded = cached?.valueVersion === definition.version ? cached : await this.#enqueue(async () => { this.#assertHealthy(); return { value: await this.#loadDocument(definition, resolved.id, resolved.address, context) }; }); if (loaded === void 0) return void 0; checkRecordScope(definition, loaded.record); checkRecordVersion(definition, loaded.record, loaded.storedVersion); return loaded.tracker.value; } watchDoc(token, ...args) { try { this.assertUsable(); const definition = token.definition; const resolved = resolveAddress(definition, args); const context = args[resolved.nextArgument]; const signal = context.abortSignal; return this.#attachDocument(definition, resolved, context, (loaded) => { const watch = this.#observeDocument(definition, loaded, createWatch, endWatch); if (signal !== void 0) watch.observeAbort(signal); return watch; }, abortWatch); } catch (error) { return Promise.reject(error); } } observeDoc(token, ...args) { try { this.assertUsable(); const definition = token.definition; const resolved = resolveAddress(definition, args); const observer = args[resolved.nextArgument]; const context = args[resolved.nextArgument + 1]; return this.#attachDocument(definition, resolved, context, (loaded) => { const guarded = this.#observeDocument(definition, loaded, (_value, detach, report2) => guardObserver(observer, detach, report2), endGuarded); return observation(loaded.tracker.value, guarded); }, detachObservation); } catch (error) { return Promise.reject(error); } } async snapshotAsOf(token, ...args) { this.assertUsable(); const definition = token.definition; const resolved = resolveAddress(definition, args); if (resolved.address.scope.kind !== "conversation") { throw new TypeError("Session.snapshotAsOf() requires a conversation document"); } const conversationId = resolved.address.scope.conversationId; const atValue = args[resolved.nextArgument]; if (typeof atValue !== "number" || !Number.isSafeInteger(atValue)) { throw new TypeError("Session.snapshotAsOf() requires an entry ID"); } const at = idFromNumber(atValue); const context = args[resolved.nextArgument + 1]; return this.#enqueue(async () => { this.#assertHealthy(); const storedEntry = await this.#gated.entry(conversationId, at, context); if (storedEntry === void 0) { throw new Error(`Entry ${at} is not visible from conversation ${conversationId}`); } const address = { ...resolved.address, scope: { kind: "conversation", conversationId: storedEntry.entry.conversationId } }; const record2 = await this.#gated.findDocument(address, storedEntry.commitSeq, context); if (record2 === void 0) return { value: void 0 }; const stored = await this.#gated.document(record2.id, storedEntry.commitSeq, context); if (stored === void 0) { throw new Error(`Historical document ${record2.id} (${record2.kind}) cannot be read`); } return { value: materializeDocument(definition, stored) }; }); } close(context) { if (this.#closing === void 0) { const cleanup = withoutAbortSignal(context); this.#closing = Promise.resolve().then(() => this.beforeClose()).then(() => this.#maintain(async () => { await this.#drained(); let unstored; if (this.#failure === void 0 && this.#holds.size > 0) { await this.#storeHolds(() => [...this.#holds.values()], cleanup).catch((error) => { unstored = { error }; }); } const observers = [...this.#commitListeners]; this.#commitListeners.clear(); const end = closeEnds(this.#failure).observer; for (const observer of observers) observer.end(end); this.#internalListeners.clear(); this.#documents.clear(); this.#idleDocuments.clear(); this.#attachedDocuments.clear(); this.#holds.clear(); this.#taskDocuments.clear(); try { await this.#storage.close(cleanup); } catch (error) { if (this.#failure === void 0) { this.#failure = { error }; this.report(error, { kind: "storage_failed" }); } throw error; } if (unstored !== void 0) throw unstored.error; })).finally(() => { const failure = this.#failure; this.#closed.resolve(failure === void 0 ? { kind: "closed" } : { kind: "failed", error: failure.error }); }); this.#notifyClose(); } return awaitWithContext(this.#closing, context); } /** Run the close listeners once, each on its own: one that fails is reported, and the rest still run. */ #notifyClose() { this.#documentSweep?.disarm?.(); this.#documentSweep = void 0; this.#holdTimer?.disarm(); this.#holdTimer = void 0; const listeners = [...this.#closeListeners]; this.#closeListeners.clear(); for (const listener of listeners) contained(listener, (error) => this.report(error)); } /** * Internal: the Session's failure, and errors of listeners, which never fail what ran them; `info` defaults to a * listener. A plain Session drops them. */ report(_error, _info = LISTENER, _context) { } async beforeInput(_tx, _id, _input, _context) { return []; } /** * Runs inside every transaction that creates or forks a conversation, after the conversation record is staged. A * plain Session stages nothing; a Harness stages its built-in documents. */ conversationCreated(_tx, _record) { return Promise.resolve(); } /** * Internal, for `beforeClose()`: on the line, after every commit admitted before the seal has settled, write what * `writes()` returns then straight to Storage. Nothing is published, since the Session is closing; a failed Session * writes nothing. */ writeAtClose(writes) { return this.#maintain(async (index) => { await this.#drained(); if (this.#failure !== void 0) return; const batch = writes(); if (batch.length === 0) return; const context = BACKGROUND_CONTEXT; await this.#writer.admit({ index, writes: batch, keys: [], context, publish: () => void 0, discard: ignore }).outcome; }); } /** * Keys a commit writes besides its records' (`Transaction.writeKeys()`), at its admission, on the line: a Harness * adds the ownership memberships its task changes write. A plain Session adds none. */ membershipKeys(_tx) { return NO_KEYS; } /** Runs after close seals admission and before the line closes Storage; must not reject. */ beforeClose() { return Promise.resolve(); } /** * Attach a synchronous observer of every stored commit, with exactly what it stored (`CommitPublication`). Its * `advance` runs one microtask after the commit's publication, which follows its storage call, in line order, before * every wait the commit resolves and every abort it queues (in no order with watch listeners); it must not block or * call Session operations. A throw is reported, detaches it, and ends it `listener_error`; `end` gets the Session's * close after its last publication. */ observeCommits(observer) { this.assertUsable(); const guarded = guardObserver(observer, () => this.#commitListeners.delete(guarded), (error, context) => this.report(error, LISTENER, context)); this.#commitListeners.add(guarded); return { detach: () => guarded.detach() }; } /** * Internal: follow every commit as memory adopts it (`ObservedCommit`), for the Session's own components, whose state * follows memory. A throw leaves that state behind, so it fails the Session instead of being reported. They run at * the commit's publication, synchronously, so they call no Storage method, run no task code, and contain every host * callback; the waits they resolve and the aborts they queue run after host observers. */ followCommits(listener) { this.assertUsable(); this.#internalListeners.add(listener); return () => this.#internalListeners.delete(listener); } /** * Register a listener called synchronously when close begins, also when a failure closes the Session (`failure` is * then set). It must not block or call Session operations; a throw is reported. */ subscribeClose(listener) { this.assertUsable(); this.#closeListeners.add(listener); return () => this.#closeListeners.delete(listener); } /** Drop every loaded tracker on the mutation line, held changes stored first; later access cold-loads from Storage. */ unloadDocuments() { return this.#maintain(async () => { if (this.#failure === void 0) await this.#storeHolds(() => [...this.#holds.values()], BACKGROUND_CONTEXT); this.#documents.clear(); this.#idleDocuments.clear(); }); } /** * Internal, for tests: how many documents the cache holds, how many are idle, how many incarnations are pinned, how * many sweep jobs were queued on the line so far, how many documents have held changes, how many live tasks have * their documents indexed, and how many conversations have their newest entry kept. */ get documentCacheSizes() { return { cached: this.#documents.size, idle: this.#idleDocuments.size, attached: this.#attachedDocuments.size, sweeps: this.#sweepJobs, held: this.#holds.size, taskDocuments: this.#taskDocuments.size, tails: this.#tails?.size ?? 0 }; } /** * Internal, for tests that bound memory: jobs whose outcome is not delivered yet, keys with an unpublished writer, * and record reads in flight in the cache. All three are 0 once the line is idle. */ get lineSizes() { return { outstanding: this.#outstanding, unpublished: this.#writer.unpublished, reads: this.#cached?.readsInFlight ?? 0 }; } /** Note a use of a cached document: an unattached one becomes the newest idle one, and an eviction is scheduled. */ #touchDocument(addressId2) { const cached = this.#documents.get(addressId2); if (cached === void 0 || this.#attachedDocuments.has(cached.record.id)) return; const now = this.#clock(); const since = this.#idleDocuments.get(addressId2); if (since !== void 0 && now - since < Math.min(1e3, this.#documentRetentionMs() / 10)) return; this.#idleDocuments.delete(addressId2); this.#idleDocuments.set(addressId2, now); if (this.#documentSweep === void 0 || this.#documentSweep.at > now + this.#documentRetentionMs()) { this.#scheduleDocumentSweep(); } } #dropDocument(addressId2) { this.#documents.delete(addressId2); this.#idleDocuments.delete(addressId2); } /** * Schedule the eviction of the oldest idle document: on the line at once when the retention is 0, otherwise when it * expires, by a background timer (`HarnessOptions.setTimer`), which never keeps the process alive. Where the host * declines it, as the default does in Cloudflare Workers, none is kept: the end of each commit evicts what expired. */ #scheduleDocumentSweep() { if (this.#closing !== void 0) return; let oldest; for (const [addressId2, since] of this.#idleDocuments) { if (this.#writer.gate(documentKey(addressId2)) !== void 0) continue; oldest = since; break; } if (oldest === void 0) return; const at = oldest + this.#documentRetentionMs(); if (this.#documentSweep !== void 0 && this.#documentSweep.at <= at) return; this.#documentSweep?.disarm?.(); const sweep = { at, disarm: void 0 }; const run2 = () => { this.#sweepJobs++; this.#maintain(async () => { if (this.#documentSweep === sweep) this.#documentSweep = void 0; if (this.#closing === void 0) this.#sweepDocuments(); }).catch(() => { }); }; const delay = at - this.#clock(); this.#documentSweep = sweep; if (delay <= 0) { queueMicrotask(run2); return; } if (this.#documentTimersUnavailable) { this.#documentSweep = void 0; return; } const disarm = this.#timers.background(run2, delay); if (disarm === void 0) { this.#documentTimersUnavailable = true; this.#documentSweep = void 0; return; } sweep.disarm = disarm; } /** On the line: evict the idle documents whose retention passed, oldest first, then schedule the next eviction. */ #sweepDocuments() { const expired = this.#clock() - this.#documentRetentionMs(); for (const [addressId2, since] of this.#idleDocuments) { if (since > expired) break; if (this.#writer.gate(documentKey(addressId2)) !== void 0) continue; this.#dropDocument(addressId2); } this.#scheduleDocumentSweep(); } /** Pin a cached incarnation for one observer; the returned release unpins it once, idle again after the last. */ #pinDocument(addressId2, recordId) { this.#attachedDocuments.set(recordId, (this.#attachedDocuments.get(recordId) ?? 0) + 1); this.#idleDocuments.delete(addressId2); let pinned = true; return () => { if (!pinned) return; pinned = false; const count = (this.#attachedDocuments.get(recordId) ?? 1) - 1; if (count > 0) { this.#attachedDocuments.set(recordId, count); return; } this.#attachedDocuments.delete(recordId); if (this.#documents.get(addressId2)?.record.id === recordId) this.#touchDocument(addressId2); }; } async #runCommit(change, context, scope, withinMs, index) { this.#assertHealthy(); context.abortSignal?.throwIfAborted(); const tx = new Transaction(this.#host, context, scope); let result; try { result = await change(tx); this.#assertHealthy(); } catch (error) { await tx.settleFailure(); this.#sweepDocuments(); throw error; } let writes; try { writes = await tx.settleSuccess(); } catch (error) { this.#sweepDocuments(); throw error; } if (writes.length === 0) { tx.discard(); this.#sweepDocuments(); return { value: result }; } if (this.#readOnly) { tx.discard(); this.#sweepDocuments(); throw new ReadOnlySession("a commit with writes"); } const discard = () => tx.discard(); if (withinMs !== void 0) { const held2 = tx.holdable(); if (held2 !== void 0) { const part2 = this.#writer.admit({ index, writes: void 0, keys: tx.writeKeys(), context, publish: () => this.#hold(tx, held2, withinMs, context), discard }); this.#sweepDocuments(); return { value: result, after: part2.outcome }; } } let membership = this.membershipKeys(tx); if (!Array.isArray(membership)) { try { membership = await membership; } catch (error) { tx.discard(); this.#sweepDocuments(); throw error; } } const taken = this.#holds.size === 0 ? NO_HOLDS : tx.takeHeld((id) => this.#holds.get(id)); const keys = tx.writeKeys(); for (const key of membership) keys.push(key); const part = this.#writer.admit({ index, writes, keys, context, publish: (seq) => this.#adopt(tx, writes, taken, seq, context), discard }); this.#sweepDocuments(); return { value: result, after: part.outcome }; } /** * Publish a stored commit (`Writer`), after the read caches took its writes: adopt it in memory and run its * publication. A throw of the adoption leaves memory behind durable state; the writer fails the Session with it. */ #adopt(tx, writes, taken, seq, context) { let documents; try { documents = tx.adopt(seq); } catch (error) { return { kind: "adoption", error }; } this.#indexTaskDocuments(tx, writes, documents, seq); const failure = this.#publish(seq, writes, documents, context); if (taken.length > 0) this.#release(taken); for (const document of documents.stored) this.#touchDocument(addressId(document.record)); return failure === void 0 ? void 0 : { kind: "listener", error: failure.error }; } /** * At publication: keep `#taskDocuments` in step with a stored commit: the tasks it creates start with no documents, * task documents it creates join their task, ones it retires leave, and tasks it ends leave the index. */ #indexTaskDocuments(tx, writes, documents, seq) { for (const id of tx.createdTaskIds()) this.#taskDocuments.set(id, /* @__PURE__ */ new Map()); if (this.#taskDocuments.size === 0) return; for (const change of documents.stored) { if (change.type !== "document" || change.record.scope.kind !== "task") continue; const known = this.#taskDocuments.get(change.record.scope.taskId); if (known === void 0) continue; if (change.kind === "retire") known.delete(change.record.id); else if (change.record.createdAt === seq) known.set(change.record.id, change.record); } for (const write of writes) { if (write.type === "task" && write.value.state.status === "terminal") this.#taskDocuments.delete(write.value.id); } } /** * Hold a deferred commit (`Transaction.holdable()`): adopt it in memory, queue its changes per document, pinned, by * the earliest deadline of the document's held commits, and show it to the Session's own observers. */ #hold(tx, held2, withinMs, context) { let documents; try { documents = tx.adoptHeld(); } catch (error) { return { kind: "adoption", error }; } const deadline = this.#clock() + withinMs; for (const { loaded, ops: ops3, base } of held2) { const id = loaded.record.id; let hold = this.#holds.get(id); if (hold === void 0) { const unpin = this.#pinDocument(loaded.addressId, id); hold = { loaded, pending: new PendingOps(), base: false, deadline, unpin }; this.#holds.set(id, hold); } hold.pending.push(ops3); if (base) hold.base = true; if (deadline < hold.deadline) hold.deadline = deadline; } this.#armHoldTimer(); const publication = { kind: "memory", changes: documents }; let failure; for (const listener of [...this.#internalListeners]) { try { listener(publication, context); } catch (error) { failure ??= { kind: "listener", error }; } } return failure; } /** * Store the held changes `select` returns in one storage transaction, through the writer, from a job on the line. A * barrier: they are selected once every commit admitted before is published. Memory already has them, so only * `observeCommits()` observers see it, published as the writer publishes it. A failure fails the Session, as every * Storage failure does. */ async #storeHolds(select, context) { await this.#drained(); const holds = select(); if (holds.length === 0) return; const writes = []; const changes = []; const keys = []; for (const hold of holds) { const { record: record2, tracker, valueVersion: version } = hold.loaded; const value2 = tracker.value; const stored = storeHeld(hold, NO_OPERATIONS, value2, version); writes.push({ type: "document.change", id: record2.id, content: stored.content }); keys.push(documentKey(hold.loaded.addressId), incarnationKey(record2.id)); const conversationId = record2.scope.kind === "conversation" ? record2.scope.conversationId : void 0; changes.push({ type: "document", kind: "change", record: record2, conversationId, version, value: value2, ops: stored.ops }); } const publish2 = (seq) => { for (const [index2, hold] of holds.entries()) { if (writes[index2].content.kind === "base") hold.loaded.deltasSinceBase = 0; else hold.loaded.deltasSinceBase++; } if (this.#commitListeners.size > 0) this.#notifyHosts({ seq, changes }, context); this.#release(holds); return void 0; }; const index = this.#current.index; await this.#writer.admit({ index, writes, keys, context, publish: publish2, discard: ignore }).outcome; } /** After their store: forget `holds`, unpin their documents, and move the timer to the next deadline. */ #release(holds) { for (const hold of holds) { this.#holds.delete(hold.loaded.record.id); hold.unpin(); } this.#armHoldTimer(); } /** * Keep a foreground timer (Workers refuse background ones) for the earliest deadline of the held documents. When it * fires, every held document is stored, in one storage transaction: a deadline is the latest time, and the others * cost little more in the same transaction. Once fired, it stays until its job ran. */ #armHoldTimer() { if (this.#closing !== void 0) return; let at = Infinity; for (const hold of this.#holds.values()) if (hold.deadline < at) at = hold.deadline; const armed = this.#holdTimer; if (armed !== void 0 && (armed.fired || armed.at === at)) return; armed?.disarm(); this.#holdTimer = void 0; if (at === Infinity) return; const timer = { at, disarm: () => { }, fired: false }; const run2 = () => { timer.fired = true; this.#maintain(async () => { if (this.#holdTimer === timer) this.#holdTimer = void 0; if (this.#closing !== void 0 || this.#failure !== void 0) return; await this.#storeHolds(() => [...this.#holds.values()], BACKGROUND_CONTEXT); }).catch(() => { }); }; this.#holdTimer = timer; timer.disarm = this.#timers.foreground(run2, Math.max(0, at - this.#clock())); } /** * At publication: publish a stored commit. Its telemetry events first, then the dispatch to `observeCommits()` * observers, queued before the waits and aborts the observers wake, then the Session's own observers. Never throws: * returns the first error of an own observer, which fails the Session once its storage call settled. */ #publish(seq, writes, documents, context) { const changes = this.#commitListeners.size > 0 || this.#internalListeners.size > 0 ? [] : void 0; for (const write of writes) { switch (write.type) { case "entry": this.#event("pi.durable.entry", { "entry.id": write.value.id, "entry.kind": write.value.kind }, context); changes?.push(write); break; case "submission": if (write.value.status === "done" || write.value.status === "unanswered") { this.#event("pi.durable.submission", { "submission.id": write.value.id, status: write.value.status, "answer.entry_id": write.value.answer }, context); } changes?.push(write); break; case "conversation": case "task": changes?.push(write); } } if (changes === void 0) return void 0; const tables = changes.length; for (const document of documents.observed) changes.push(document); const observed = { kind: "stored", seq, changes }; if (this.#commitListeners.size > 0) { const stored = documents.stored === documents.observed ? observed : { seq, changes: [...changes.slice(0, tables), ...documents.stored] }; this.#notifyHosts(stored, context); } let failure; for (const listener of [...this.#internalListeners]) { try { listener(observed, context); } catch (error) { failure ??= { error }; } } return failure; } /** At publication: record a commit's telemetry event. A throwing adapter is reported, as publication must not throw. */ #event(name, attributes, context) { try { context.telemetry.addEvent(name, attributes); } catch (error) { this.report(error, TELEMETRY, context); } } /** * At publication: call the `observeCommits()` observers with `publication` one microtask from now, and, queued * before the Session's own observers run, before the waits they resolve and the aborts they queue. The observers are * those attached when it runs; their guards report a throw. */ #notifyHosts(publication, context) { void SETTLED.then(() => { for (const observer of [...this.#commitListeners]) observer.advance(publication, context); }); } /** * On the line, as one job: load the document at `resolved` and `attach` to it, so the attachment sees every commit * after the value it starts from; `undefined` when absent. Cancelling `context` meanwhile rejects, after `cancel` * undid an attachment made. */ async #attachDocument(definition, resolved, context, attach, cancel) { const signal = context.abortSignal; let cancelled = signal?.aborted ?? false; const markCancelled = () => { cancelled = true; }; signal?.addEventListener("abort", markCancelled, { once: true }); try { const attached = await this.#enqueue(async () => { this.#assertHealthy(); if (cancelled) throw cancellationError(signal); const loaded = await this.#loadDocument(definition, resolved.id, resolved.address, context); if (cancelled) throw cancellationError(signal); return { value: loaded === void 0 ? void 0 : attach(loaded) }; }); if (attached === void 0) return void 0; if (cancelled) { cancel(attached); throw cancellationError(signal); } return attached; } finally { signal?.removeEventListener("abort", markCancelled); } } /** * Follow the address of the incarnation `loaded`, after checking the definition, with the observer `create` makes * from its value, the idempotent detach, and the report of a listener error; returns that observer. It gets each * committed change there, a retirement as a `retired` change with the last value it was given and no ops (a change * in the retiring commit is not published), and a later incarnation as a root replacement. When the commit makes the * owner task of a task document terminal, no later commit can change the address: after its changes the commit * subscription and the pin go, and `ownerEnded` tells the observer, which stays subscribed to the Session's close * until it ends (its detach drops that). `end` gets the Session's close. The current incarnation is pinned in the * cache. The detach runs before `end`; once it ran, nothing more of the commit underway reaches the observer or pins * anything for it. `create` and `ownerEnded` are module functions where they can be, so a watch keeps no closure of * its caller. */ #observeDocument(definition, loaded, create, ownerEnded) { checkRecordScope(definition, loaded.record); checkRecordVersion(definition, loaded.record, loaded.storedVersion); const addressId2 = loaded.addressId; const address = loaded.record; let incarnation = loaded.record.id; let value2 = loaded.tracker.value; let version = loaded.valueVersion; let unpin = this.#pinDocument(addressId2, incarnation); let unsubscribeCommit = () => { }; let unsubscribeClose = () => { }; let attached = true; const detach = () => { if (!attached) return; attached = false; unsubscribeCommit(); unsubscribeClose(); unpin(); }; const observer = create(loaded.tracker.value, detach, (error) => this.report(error)); unsubscribeCommit = this.followCommits((publication, context) => { if (!attached) return; let ownerEnds = false; for (const change of publication.changes) { if (change.type === "task") { const scope = address.scope; if (scope.kind === "task" && change.value.id === scope.taskId && change.value.state.status === "terminal") { ownerEnds = true; } continue; } if (change.type !== "document") continue; if (change.record.id !== incarnation && !atAddress(change.record, address)) continue; if (change.kind === "retire") { if (change.record.id !== incarnation) continue; const last = value2; incarnation = void 0; value2 = void 0; unpin(); observer.advance({ kind: "value", value: last, ops: NO_OPERATIONS, retired: true }, context); } else if (change.record.id !== incarnation || change.version !== version) { if (change.record.id !== incarnation) { incarnation = change.record.id; unpin(); unpin = this.#pinDocument(addressId2, incarnation); } version = change.version; value2 = change.value; observer.advance({ kind: "value", value: value2, ops: [["r", value2]], retired: false }, context); } else { if (change.ops.length === 0) continue; value2 = change.value; observer.advance({ kind: "value", value: value2, ops: change.ops, retired: false }, context); } if (!attached) return; } if (!ownerEnds) return; unsubscribeCommit(); unpin(); ownerEnded(observer); }); unsubscribeClose = this.subscribeClose(() => { detach(); observer.end(closeEndOf(observer, closeEnds(this.#failure))); }); return observer; } async #loadDocument(definition, addressId2, address, context) { const gate = this.touch(documentKey(addressId2)); if (gate !== void 0) await gate; const cached = this.#documents.get(addressId2); if (cached?.valueVersion === definition.version) { this.#touchDocument(addressId2); return cached; } if (cached !== void 0) { const id = cached.record.id; await this.#storeHolds(() => { const hold = this.#holds.get(id); return hold === void 0 ? NO_HOLDS : [hold]; }, context); this.#dropDocument(addressId2); } const record2 = await this.#storage.findDocument(address, "current", context); if (record2 === void 0) return void 0; const stored = await this.#storage.document(record2.id, "current", context); if (stored === void 0) throw new Error(`Current document ${record2.id} (${record2.kind}) cannot be read`); const value2 = materializeDocument(definition, stored); const loaded = { addressId: addressId2, record: stored.record, storedVersion: stored.version, valueVersion: definition.version, deltasSinceBase: stored.deltasSinceBase, tracker: track(value2) }; this.#documents.set(addressId2, loaded); this.#touchDocument(addressId2); return loaded; } /** * Run `job` on the line, after the jobs before it, and deliver its outcome in line order: once the jobs before it * have theirs, and once its commit, if it admitted one, is published. With `exempt`, a job that admitted no commit and * waited at no gate is delivered as soon as it has its outcome: it is equivalent to having run before every * unpublished commit. A job * whose outcome comes after the one a failure is gets `SessionFailed` instead. At most `MAX_OUTSTANDING` jobs wait * for their delivery; beyond it the line waits. */ #enqueue(work, exempt = false) { const job = { index: this.#nextIndex++, hit: false }; const ran = this.#tail.then(async () => { while (this.#outstanding >= MAX_OUTSTANDING) { this.#room ??= Promise.withResolvers(); await this.#room.promise; } this.#outstanding++; return this.#onLine(job, work); }); this.#tail = ran.then(ignore, ignore); const outcome = ran.then((result) => { if (result.after === void 0) { this.#ended(job); return result.value; } return result.after.then(() => { this.#ended(job); return result.value; }, (error) => { this.#ended(job); throw error; }); }, (error) => { this.#ended(job); throw error; }); outcome.catch(ignore); const fence = this.#fence; this.#fence = fence.then(() => outcome).then(ignore, ignore); const index = job.index; const deliver = (fenced) => (fenced ? fence.then(() => outcome) : outcome).then((value2) => { this.#delivered(index, fenced); if (this.#poisoned(index)) throw new SessionFailed(this.#failure.error); return value2; }, (error) => { this.#delivered(index, fenced); throw this.#poisoned(index) ? new SessionFailed(this.#failure.error) : error; }); return exempt ? ran.then((result) => deliver(job.hit || result.after !== void 0), () => deliver(job.hit)) : deliver(true); } /** Whether job `index` comes after the failure's boundary without an outcome of its own: it gets `SessionFailed`. */ #poisoned(index) { return this.#failAt !== void 0 && index > this.#failAt && !this.#ownOutcomes.has(index); } /** The job's outcome settled: run what waited for it (`onJobEnd()`). */ #ended(job) { const ends = job.ends; if (ends === void 0) return; job.ends = void 0; for (const end of ends) end(); } /** Run a job of the Session's own upkeep on the line: no outcome to deliver, so no fence and no `SessionFailed`. */ #maintain(work) { const job = { index: this.#nextIndex++, hit: false }; const ran = this.#tail.then(() => this.#onLine(job, work)); this.#tail = ran.then(ignore, ignore); return ran; } /** Hold the line for job `index` until its line part, through admission, is done. Gates are its touches' own. */ async #onLine(job, work) { this.#current = job; try { return await work(job.index); } finally { this.#current = void 0; } } /** A `*` barrier: once every part admitted so far is published, or never will be. */ async #drained() { const drained = this.#writer.drained(); if (drained !== void 0) await drained; } /** * Run one commit's publication inside the Storage's own scope (`Storage.publishing`), such as a store host's batch; * directly without one. `undefined` when it ran at once, else settles once it ran. A Storage that throws instead of * running it is reported, and the publication runs directly: the commit is stored, so it must be published. */ #publishing(publish2, context) { const publishing = this.#storage.publishing; if (publishing === void 0) { publish2(); return void 0; } const done = Promise.withResolvers(); let ran = false; const once = () => { if (ran) return; ran = true; try { publish2(); } finally { done.resolve(); } }; try { publishing(once, context); } catch (error) { this.report(error, LISTENER, context); once(); } return done.promise; } /** Job `index` has its outcome: let the line go on past `MAX_OUTSTANDING`. */ #delivered(index, fenced) { if (fenced) this.#lastDelivered = index; this.#outstanding--; const room = this.#room; if (room !== void 0 && this.#outstanding < MAX_OUTSTANDING) { this.#room = void 0; room.resolve(); } } /** Internal: throw `SessionFailed` when failed, else an error when closing. */ assertUsable() { this.#assertHealthy(); if (this.#closing !== void 0) throw new Error("Session is closed"); } #assertHealthy() { if (this.#failure !== void 0) throw new SessionFailed(this.#failure.error); } }; function contained(callback, onError) { try { const result = callback(); if (typeof result?.then === "function") Promise.resolve(result).catch(onError); } catch (error) { onError(error); } } function guardObserver(observer, detach, report2) { let attached = { observer, detach, report: report2 }; const release = () => { const released = attached; attached = void 0; released?.detach(); return released; }; const fault = (current, error, context) => { current.report(error, context); if (release() !== void 0) { const end = { kind: "listener_error", error: toError3(error) }; contained(() => current.observer.end(end), current.report); } }; return { advance: (call2, context) => { const current = attached; if (current === void 0) return; try { const result = current.observer.advance(call2, context); if (typeof result?.then === "function") { Promise.resolve(result).catch((error) => fault(current, error, context)); } } catch (error) { fault(current, error, context); } }, end: (end) => { const ending = release(); if (ending !== void 0) contained(() => ending.observer.end(end), ending.report); }, detach: () => { release(); } }; } function toError3(error) { if (error instanceof Error) return error; try { return new Error(String(error)); } catch { return new Error(Object.prototype.toString.call(error)); } } var ENDED2 = { kind: "ended" }; function closeEnds(failure) { if (failure !== void 0) { const failed = { kind: "failed", error: failure.error }; return { watch: failed, observer: failed }; } return { watch: ENDED2, observer: { kind: "failed", error: new Error("session closed") } }; } function closeEndOf(follower, ends) { return follower instanceof Watch ? ends.watch : ends.observer; } function observation(value2, guarded) { return { value: value2, detach: () => guarded.detach() }; } function createWatch(value2, detach, report2) { return new Watch(value2, detach, report2); } function abortWatch(watch) { watch.abort(); } function detachObservation(attached) { attached.detach(); } function endWatch(watch) { watch.endAfterDelivery(); } function endGuarded(guarded) { guarded.end(ENDED2); } function atAddress(record2, address) { if (record2.kind !== address.kind || record2.key !== address.key) return false; switch (record2.scope.kind) { case "session": return address.scope.kind === "session"; case "conversation": return address.scope.kind === "conversation" && address.scope.conversationId === record2.scope.conversationId; case "task": return address.scope.kind === "task" && address.scope.taskId === record2.scope.taskId; } } function cancellationError(signal) { return signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError"); } var GuardedStorage = class { #storage; #latch; /** Backend calls underway, also those off the Session line, which close waits for before it closes the backend. */ #underway = /* @__PURE__ */ new Set(); #closing = false; constructor(storage2, latch) { this.#storage = storage2; this.#latch = latch; } /** The backend's own scope for a publication (`Storage.publishing`), when it has one; not a call to guard. */ get publishing() { return scopeOf(this.#storage); } /** * Guarded like every call, but its outcome is the Session writer's to judge: it publishes the batches received first, * then fails the Session for a refusal or a rejection, and for a Session that failed meanwhile. */ async commit(batches, receive, context) { this.#assertHealthy(); if (this.#closing) throw new Error("Session is closed"); const call2 = (async () => this.#storage.commit(batches, receive, context))(); this.#underway.add(call2); try { return await call2; } finally { this.#underway.delete(call2); } } mintId() { return this.#call(void 0, () => this.#storage.mintId()); } conversation(id, context) { return this.#call(context, () => this.#storage.conversation(id, context)); } scanConversations(query, limit, cursor, context) { return this.#call(context, () => this.#storage.scanConversations(query, limit, cursor, context)); } entry(first, second, third) { if (third === void 0) { const context = second; return this.#call(context, () => this.#storage.entry(first, context)); } return this.#call(third, () => this.#storage.entry(first, second, third)); } findLatestHeadMarker(conversationId, atOrBeforeEntryId, context) { return this.#call(context, () => this.#storage.findLatestHeadMarker(conversationId, atOrBeforeEntryId, context)); } scanEntries(query, limit, cursor, context) { return this.#call(context, () => this.#storage.scanEntries(query, limit, cursor, context)); } task(id, context) { return this.#call(context, () => this.#storage.task(id, context)); } scanTasks(query, limit, cursor, context) { return this.#call(context, () => this.#storage.scanTasks(query, limit, cursor, context)); } submission(id, context) { return this.#call(context, () => this.#storage.submission(id, context)); } scanSubmissions(query, limit, cursor, context) { return this.#call(context, () => this.#storage.scanSubmissions(query, limit, cursor, context)); } submissionByRequest(conversationId, requestId, context) { return this.#call(context, () => this.#storage.submissionByRequest(conversationId, requestId, context)); } findDocument(address, at, context) { return this.#call(context, () => this.#storage.findDocument(address, at, context)); } document(id, at, context) { return this.#call(context, () => this.#storage.document(id, at, context)); } scanDocuments(query, limit, cursor, context) { return this.#call(context, () => this.#storage.scanDocuments(query, limit, cursor, context)); } /** * Close the backend once every call underway has settled, so none outlives it or fails after `closed` settles. New * calls are refused from here on: a paged read must not start its next page while the backend closes. */ async close(context) { this.#closing = true; await Promise.allSettled([...this.#underway]); return this.#storage.close(context); } /** * Run one backend call under the guard. `read` is a read's context; `commit()` and `mintId()` pass none, so nothing * exempts their errors: once a batch is admitted, whether it committed is unknown. A call still underway when another * fails the Session ends with `SessionFailed` too, whatever it returns: nothing it read or wrote is used. */ async #call(read2, run2) { this.#assertHealthy(); if (this.#closing) throw new Error("Session is closed"); let result; const call2 = (async () => run2())(); this.#underway.add(call2); try { result = await call2; } catch (error) { this.#assertHealthy(); const exempt = read2 !== void 0 && (error instanceof StorageRequestError || read2.abortSignal?.aborted === true); if (!exempt) this.#latch.fail(error); throw error; } finally { this.#underway.delete(call2); } this.#assertHealthy(); return result; } #assertHealthy() { const failure = this.#latch.failure; if (failure !== void 0) throw new SessionFailed(failure.error); } }; // node_modules/@earendil-works/pi-durable/dist/harness/agent.js var DEFAULT_RETRY_POLICY = { enabled: true, maxRetries: 3, baseDelayMs: 2e3, maxAgentDelayMs: 6e4 }; var DEFAULT_COMPACTION_POLICY = { enabled: true, reserveTokens: 16384, keepRecentTokens: 2e4, backgroundTokens: 32768 }; var DEFAULT_PROGRESS_POLICY = { partialIntervalMs: 100, outputIntervalMs: 100, persistWithinMs: 1e3 }; var INSTRUCTIONS_KEY = "instructions"; var AgentDoc = defineDoc({ kind: "pi.agent", version: 1, scope: "conversation", history: "rewindable", fork: "asOf", initial: () => ({}), checkpointWhen: () => true }); var DEFAULT_CONTEXT_RETENTION_MS = 6e5; var DEFAULT_RESTART_LIMIT = 5; function restartLimit(limit) { return typeof limit === "number" && limit >= 1 ? Math.floor(limit) : DEFAULT_RESTART_LIMIT; } function resolveSettings(settings) { const extensions = settings?.extensions; return { ...extensions === void 0 ? {} : { extensions }, stream: { ...settings?.stream }, retry: { ...DEFAULT_RETRY_POLICY, ...settings?.retry }, compaction: { ...DEFAULT_COMPACTION_POLICY, ...settings?.compaction }, // Field by field, so an explicitly undefined interval keeps its default. progress: { partialIntervalMs: settings?.progress?.partialIntervalMs ?? DEFAULT_PROGRESS_POLICY.partialIntervalMs, outputIntervalMs: settings?.progress?.outputIntervalMs ?? DEFAULT_PROGRESS_POLICY.outputIntervalMs, persistWithinMs: persistWithin(settings?.progress?.persistWithinMs) }, toolExecution: settings?.toolExecution ?? "parallel", steeringMode: settings?.steeringMode ?? "one-at-a-time", followUpMode: settings?.followUpMode ?? "one-at-a-time", contextRetentionMs: settings?.contextRetentionMs ?? DEFAULT_CONTEXT_RETENTION_MS, documentRetentionMs: documentRetention(settings?.documentRetentionMs), afterFailedRun: settings?.afterFailedRun ?? "keep", restartLimit: restartLimit(settings?.restartLimit) }; } async function configure(tx, conversationId, change) { const state = await tx.doc(AgentDoc, conversationId); applyChange(state, change); } async function addTools(tx, conversationId, added) { const state = await tx.doc(AgentDoc, conversationId); const tools = state.tools; if (Array.isArray(tools)) { for (const name of added) if (!tools.includes(name)) tools.push(name); } else if (tools?.remove.some((name) => added.includes(name))) { state.tools = { remove: tools.remove.filter((name) => !added.includes(name)) }; } const modelTools = state.modelTools; if (Array.isArray(modelTools)) { for (const name of added) if (!modelTools.includes(name)) modelTools.push(name); return; } const remove = (modelTools?.remove ?? []).filter((name) => !added.includes(name)); const add = [...modelTools?.add ?? []]; for (const name of added) if (!add.includes(name)) add.push(name); state.modelTools = { ...remove.length === 0 ? {} : { remove }, add }; } function applyChange(state, change) { const set = (key, value2) => { if (value2 === void 0) return; if (value2 === null) delete state[key]; else state[key] = value2; }; set("model", change.model === void 0 || change.model === null ? change.model : { ...change.model }); set("thinkingLevel", change.thinkingLevel); const extensions = change.extensions; set("extensions", extensions === void 0 || extensions === null ? extensions : isList(extensions) ? names(extensions) : { ...extensions.add === void 0 ? {} : { add: names(extensions.add) }, ...extensions.remove === void 0 ? {} : { remove: names(extensions.remove) } }); const tools = change.tools; set("tools", tools === void 0 || tools === null ? tools : isList(tools) ? names(tools) : { remove: names(tools.remove) }); const modelTools = change.modelTools; set("modelTools", modelTools === void 0 || modelTools === null ? modelTools : isList(modelTools) ? names(modelTools) : { ...modelTools.remove === void 0 ? {} : { remove: names(modelTools.remove) }, ...modelTools.add === void 0 ? {} : { add: names(modelTools.add) } }); set("instructions", change.instructions); set("cwd", change.cwd); const maxTurns = change.maxTurns; if (maxTurns !== void 0 && maxTurns !== null && !(Number.isSafeInteger(maxTurns) && maxTurns >= 1)) { throw new RangeError(`maxTurns must be an integer 1 or more, got ${String(maxTurns)}`); } set("maxTurns", maxTurns); } function isList(value2) { return Array.isArray(value2); } function names(items) { return items.map((item) => item.name); } async function createAgent(tx, conversation) { if (conversation.parent !== void 0) return; const agent = await tx.doc(AgentDoc, conversation.id); if (conversation.owner === void 0) return; const owner = await tx.doc(AgentDoc, conversation.owner.conversationId); Object.assign(agent, copyJson(owner)); } function agentHooks(agent, taskName) { const handlers = []; for (const extension of agent.extensions) { for (const hook2 of extension.hooks ?? []) { if (hook2.task === taskName) handlers.push({ extension: extension.name, handlers: hook2.handlers }); } } return handlers; } function sectionExtension(agent, key) { let found; for (const extension of agent.extensions) { const provides = extension.sections?.some((section2) => section2.key === key) === true; const wraps = extension.wraps?.some((wrap) => "section" in wrap && wrap.section === key) === true; if (provides || wraps) found = extension.name; } return found; } function toolExtension(agent, name) { let found; for (const extension of agent.extensions) { const provides = extension.tools?.some((tool) => tool.name === name) === true; const wraps = extension.wraps?.some((wrap) => "tool" in wrap && wrap.tool === name) === true; if (provides || wraps) found = extension.name; } return found; } function resolveAgent(state, snapshot, settings, report2) { const extensions = selectExtensions(state?.extensions, snapshot, settings); const composed = /* @__PURE__ */ new Map(); for (const extension of extensions) for (const tool of extension.tools ?? []) composed.set(tool.name, tool); const sections = /* @__PURE__ */ new Map(); for (const extension of extensions) { for (const section2 of extension.sections ?? []) sections.set(section2.key, section2); } for (const extension of extensions) { for (const wrap of extension.wraps ?? []) { const wrapReport = (error) => report2(error, extension.name); if ("tool" in wrap) applyWrap(composed, wrap.tool, (tool) => wrap.wrap(tool), (tool) => tool.name, wrapReport); else applyWrap(sections, wrap.section, (section2) => wrap.wrap(section2), (section2) => section2.key, wrapReport); } } const enabled = filterTools([...composed.values()], state?.tools); const callableBy = (caller) => (tool) => tool.callers === void 0 || tool.callers.includes(caller); const modelCallable = enabled.filter(callableBy("model")); const tools = offeredTools(modelCallable, state?.modelTools); const offered = new Set(tools); const deferred2 = modelCallable.filter((tool) => tool.deferred === true && !offered.has(tool)); const callable = enabled.filter(callableBy("tools")); const instructions = state?.instructions; const agentSections = [...sections.values()]; if (instructions !== void 0) agentSections.push({ key: INSTRUCTIONS_KEY, render: () => instructions }); const agent = { ...state?.model === void 0 ? {} : { model: state.model }, thinkingLevel: state?.thinkingLevel ?? "off", extensions, tools, callable, deferred: deferred2, sections: agentSections, ...instructions === void 0 ? {} : { instructions }, ...state?.cwd === void 0 ? {} : { cwd: state.cwd }, ...state?.maxTurns === void 0 ? {} : { maxTurns: state.maxTurns } }; return agent; } function offeredTools(tools, filter) { if (Array.isArray(filter)) return filterTools(tools, filter); const removed = new Set(filter?.remove ?? []); const kept = tools.filter((tool) => tool.deferred !== true && !removed.has(tool.name)); const byName = new Map(tools.map((tool) => [tool.name, tool])); const loaded = [...new Set(filter?.add ?? [])].flatMap((name) => { const tool = byName.get(name); return tool?.deferred === true && !removed.has(name) ? [tool] : []; }); return [...kept, ...loaded]; } function filterTools(tools, filter) { if (filter === void 0) return tools; if (Array.isArray(filter)) { const byName = new Map(tools.map((tool) => [tool.name, tool])); return [...new Set(filter)].flatMap((name) => { const tool = byName.get(name); return tool === void 0 ? [] : [tool]; }); } const removed = new Set(filter.remove); return tools.filter((tool) => !removed.has(tool.name)); } function selectExtensions(stored, snapshot, settings) { let selected; if (Array.isArray(stored)) selected = stored; else { const base = settings.extensions?.map((extension) => extension.name) ?? snapshot.installed().map((e) => e.name); const edit = stored; const removed = new Set(edit?.remove ?? []); selected = [...base, ...edit?.add ?? []].filter((name) => !removed.has(name)); } const extensions = []; for (const name of new Set(selected)) { const extension = snapshot.extension(name); if (extension !== void 0) extensions.push(extension); } return extensions; } function applyWrap(items, target, wrap, nameOf, report2) { const item = items.get(target); if (item === void 0) return; try { const wrapped = wrap(item); if (nameOf(wrapped) !== target) throw new Error(`Wrapper renamed ${target} to ${nameOf(wrapped)}`); items.set(target, wrapped); } catch (error) { items.delete(target); report2(error); } } // node_modules/@earendil-works/pi-ai/dist/utils/estimate.js var CHARS_PER_TOKEN = 3.5; var ESTIMATED_IMAGE_CHARS = 4800; function calculateContextTokens(usage) { return usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite; } function safeJsonStringify(value2) { try { return JSON.stringify(value2) ?? "undefined"; } catch { return "[unserializable]"; } } function estimateTextAndImageContentChars(content) { if (typeof content === "string") return content.length; let chars = 0; for (const block of content) chars += block.type === "text" ? block.text.length : ESTIMATED_IMAGE_CHARS; return chars; } function estimateTextTokens(text) { return Math.ceil(text.length / CHARS_PER_TOKEN); } function estimateTextAndImageContentTokens(content) { return Math.ceil(estimateTextAndImageContentChars(content) / CHARS_PER_TOKEN); } function estimateMessageTokens(message) { let chars = 0; if (message.role === "system") { return estimateTextTokens(getSystemMessageText(message)) + estimateToolsTokens(message.toolsAdded) + estimateToolsTokens(message.toolsRemoved); } if (message.role === "user") return estimateTextAndImageContentTokens(message.content); if (message.role === "toolResult") return estimateTextAndImageContentTokens(message.content); for (const block of message.content) { if (block.type === "text") { chars += block.text.length; } else if (block.type === "thinking") { chars += block.thinking.length; } else { chars += block.name.length + safeJsonStringify(block.arguments).length; } } return Math.ceil(chars / CHARS_PER_TOKEN); } function estimateToolsTokens(tools) { if (!tools || tools.length === 0) return 0; return estimateTextTokens(safeJsonStringify(tools)); } // node_modules/@earendil-works/pi-ai/dist/utils/retry.js function buildProviderErrorPattern(patterns) { return new RegExp(patterns.join("|"), "i"); } var NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([ // OpenCode Go/free-tier limits returned as 429 JSON error types by OpenCode's // Zen API. These are subscription/account limits, not transient throttles. "GoUsageLimitError", "FreeUsageLimitError", // OpenCode Go subscription-limit text asks users to enable available-balance // usage after rolling/weekly/monthly limits are reached. "Monthly usage limit reached", "available balance", // Generic quota/budget/billing exhaustion. `insufficient_quota` is OpenAI's // quota/billing error code; the other strings cover common gateway wording. "insufficient_quota", "out of budget", "quota exceeded", "billing", // Sign in with ChatGPT: the subscription's shared usage limit, which resets // after hours rather than seconds. "subscription_sharing_usage_limit_exceeded", // Sign in with ChatGPT on a ChatGPT Business plan: a usage limit set by the // workspace admin. Only the admin can raise it. "subscription_sharing_workplace_individual_usage_limit_exceeded", // Zhipu/Z.ai (open.bigmodel.cn) error code 1308: the plan's usage limit is // reached, sent as HTTP 429. It resets after hours, not seconds (#11537). '"code"\\s*:\\s*"?1308(?![0-9])' ]); var RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([ // Generic provider load, HTTP status, and server-side transient failures. "overloaded", "server_busy", "servers are currently busy", "currently experiencing high demand", "model is at capacity", "rate.?limit", "too many requests", // Status codes must not be part of a longer word or number, e.g. a request id or byte count (#11057). "(? { if (signal?.aborted) { reject2(new RetrySleepAbortError()); return; } const timeout = setTimeout(resolve2, ms); signal?.addEventListener("abort", () => { clearTimeout(timeout); reject2(new RetrySleepAbortError()); }, { once: true }); }); } async function retryAssistantCall(produce, policy, signal, callbacks) { const maxAttempts = policy?.enabled ? policy.maxRetries : 0; let attempt = 0; let lastRetry; for (; ; ) { const response = await produce(); if (response.stopReason === "aborted") { if (lastRetry) await callbacks?.onRetryFinished?.(false, lastRetry.attempt); return response; } if (response.stopReason !== "error") { if (lastRetry) await callbacks?.onRetryFinished?.(true, lastRetry.attempt); return response; } if (attempt >= maxAttempts || !isRetryableAssistantError(response)) { if (lastRetry) await callbacks?.onRetryFinished?.(false, lastRetry.attempt, response.errorMessage); return response; } attempt++; lastRetry = { attempt, errorMessage: response.errorMessage || "Unknown error" }; const delayMs = retryDelayMs(policy, attempt); await callbacks?.onRetryScheduled?.(attempt, maxAttempts, delayMs, lastRetry.errorMessage); try { await sleep(delayMs, signal); } catch (error) { await callbacks?.onRetryFinished?.(false, attempt, lastRetry.errorMessage); if (error instanceof RetrySleepAbortError) { const { errorMessage: _errorMessage, ...rest } = response; return { ...rest, stopReason: "aborted" }; } throw error; } await callbacks?.onRetryAttemptStart?.(); } } function isRetryableAssistantError(message) { if (message.stopReason !== "error" || !message.errorMessage) return false; const errorMessage2 = message.errorMessage; if (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage2)) return false; return RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage2); } // node_modules/@earendil-works/pi-durable/dist/tasks.js function defineTask(definition) { return { definition }; } // node_modules/@earendil-works/pi-durable/dist/harness/context.js var SCAN_PAGE_SIZE2 = 256; var EXCLUDED_STOP_REASONS = /* @__PURE__ */ new Set(["aborted", "error", "deferred"]); var MISSING_RESULT_TEXT = "Tool result unavailable: history ends before this call completed."; async function captureContextBounds(session, storage2, conversationId, context, at, visible = false) { let tail; if (at === void 0) { tail = await session.latestEntryId(conversationId, context); if (tail === void 0) return void 0; } else { if (!visible && await storage2.entry(conversationId, at, context) === void 0) { throw new Error(`Entry ${at} is not visible from conversation ${conversationId}`); } tail = at; } return { head: await session.latestHeadMarker(conversationId, tail, context), tail }; } async function readContextFrom(session, storage2, conversationId, context, at, previous) { const visible = at !== void 0 && previous !== void 0 && rangeEntry(previous, at) !== void 0; const bounds = await session.readOnLine((line) => captureContextBounds(session, line, conversationId, context, at, visible)); if (bounds === void 0) return { view: emptyView(), range: void 0 }; freezeJson(bounds.head); const current = at === void 0; let range; if (previous === void 0 || previous.bounds.head?.id !== bounds.head?.id) { const entries2 = freezeJson(await scanRange(storage2, conversationId, rangeQuery(bounds), context)); range = deriveRange(bounds, entries2, current); } else if (bounds.tail === previous.bounds.tail) { range = previous; } else if (bounds.tail < previous.bounds.tail) { range = deriveRange(bounds, previous.entries.filter((entry2) => entry2.id <= bounds.tail), false); } else { const minEntryId = idFromNumber(previous.bounds.tail + 1); const added = await scanRange(storage2, conversationId, { minEntryId, maxEntryId: bounds.tail }, context); range = extendRange(previous, bounds, freezeJson(added), current); } if (range.current && session.knownLatestEntryId(conversationId) !== range.bounds.tail) { range = { ...range, current: false }; } const { view: view2 } = range; return { view: { ...view2, entries: [...view2.entries], contributions: [...view2.contributions], messages: [...view2.messages] }, range }; } async function readContext(session, storage2, conversationId, context, at) { const bounds = await session.readOnLine((line) => captureContextBounds(session, line, conversationId, context, at)); return deriveContext(storage2, conversationId, bounds, context); } async function deriveContext(storage2, conversationId, bounds, context) { if (bounds === void 0) return emptyView(); return deriveRange(bounds, await scanRange(storage2, conversationId, rangeQuery(bounds), context), false).view; } function emptyView() { return { head: void 0, entries: [], contributions: [], messages: [] }; } function rangeEntry(range, id) { const entries2 = range.entries; let low = 0; let high = entries2.length - 1; while (low <= high) { const middle = low + high >>> 1; const entry2 = entries2[middle]; if (entry2.id === id) return entry2; if (entry2.id < id) low = middle + 1; else high = middle - 1; } return void 0; } function advanceRange(range, appended) { if (!range.current || appended.length === 0) return range; const added = freezeJson(appended); let head = range.bounds.head; for (const entry2 of added) if (entry2.head !== void 0) head = entry2; const bounds = { head, tail: added.at(-1).id }; if (head?.id === range.bounds.head?.id) return extendRange(range, bounds, added, true); const start = head.head; if (range.bounds.head !== void 0 && start < range.bounds.head.head) return void 0; const entries2 = [...range.entries, ...added].filter((entry2) => entry2.id >= start); return deriveRange(bounds, entries2, true); } function deriveRange(bounds, entries2, current) { const edits = /* @__PURE__ */ new Map(); for (const entry2 of entries2) for (const edit of entry2.edits ?? []) edits.set(edit.target, edit); const active = selectActive(bounds.head, entries2); const contributions = active.map((entry2) => contribute(entry2, edits.get(entry2.id))); const { settled, open } = settle([], contributions.flat()); const baseline = findBaseline(bounds.head, active); const messages = leadWithSystem([...settled, ...orderToolResults(open)], baseline); const view2 = { head: bounds.head, entries: active, contributions, messages }; return { bounds, entries: entries2, view: view2, edited: new Set(edits.keys()), settled, open, baseline, current }; } function extendRange(previous, bounds, added, current) { const entries2 = [...previous.entries, ...added]; if (added.some((entry2) => entry2.edits !== void 0 || entry2.head !== void 0 || previous.edited.has(entry2.id))) { return deriveRange(bounds, entries2, current); } const contributions = added.map((entry2) => contribute(entry2, void 0)); const { settled, open } = settle(previous.settled, [...previous.open, ...contributions.flat()]); const baseline = previous.baseline !== void 0 ? previous.baseline : findBaseline(bounds.head, added); const view2 = { head: bounds.head, entries: [...previous.view.entries, ...added], contributions: [...previous.view.contributions, ...contributions], messages: leadWithSystem([...settled, ...orderToolResults(open)], baseline) }; return { bounds, entries: entries2, view: view2, edited: previous.edited, settled, open, baseline, current }; } function findBaseline(head, entries2) { for (const entry2 of entries2) { if (head !== void 0 && entry2.id <= head.id) continue; if (SystemEntry.is(entry2)) return entry2.model?.find((message) => message.role === "system") ?? null; if (head === void 0 && carriesSystemMessage(entry2)) return null; } return void 0; } function carriesSystemMessage(entry2) { const isSystem = (message) => message.role === "system"; return (entry2.model?.some(isSystem) ?? false) || (entry2.edits?.some((edit) => edit.action === "replace" && edit.messages.some(isSystem)) ?? false); } function leadWithSystem(messages, baseline) { const index = messages.findIndex((message) => message.role === "system"); if (index <= 0) return messages; const system = messages[index]; if (system !== baseline && messages.slice(0, index).some((message) => message.role !== "user")) { return messages; } return [system, ...messages.slice(0, index), ...messages.slice(index + 1)]; } function contribute(entry2, edit) { if (edit?.action === "omit") return Object.freeze([]); const contributed = edit?.action === "replace" ? edit.messages : entry2.model ?? []; return Object.freeze(contributed.filter((message) => message.role !== "assistant" || !EXCLUDED_STOP_REASONS.has(message.stopReason))); } var deeplyFrozen = /* @__PURE__ */ new WeakSet(); function freezeJson(value2) { if (value2 === null || typeof value2 !== "object" || deeplyFrozen.has(value2)) return value2; for (const child of Object.values(value2)) freezeJson(child); Object.freeze(value2); deeplyFrozen.add(value2); return value2; } function settle(settled, open) { const last = open.findLastIndex((message) => message.role === "assistant"); if (last <= 0) return { settled, open }; return { settled: [...settled, ...orderToolResults(open.slice(0, last))], open: open.slice(last) }; } function rangeQuery(bounds) { const head = bounds.head; return head === void 0 ? { maxEntryId: bounds.tail } : { minEntryId: head.head, maxEntryId: bounds.tail }; } async function scanRange(storage2, conversationId, range, context) { const entries2 = []; let cursor; do { const page3 = await storage2.scanEntries({ conversationId, ...range }, SCAN_PAGE_SIZE2, cursor, context); entries2.push(...page3.items); cursor = page3.next; } while (cursor !== void 0); return entries2.reverse(); } function selectActive(head, range) { return head === void 0 ? range : [head, ...range.filter((entry2) => entry2.head === void 0)]; } function orderToolResults(messages) { const ordered = []; for (let index = 0; index < messages.length; index++) { const message = messages[index]; if (message.role === "toolResult") continue; ordered.push(message); if (message.role !== "assistant") continue; const calls = message.content.filter((content) => content.type === "toolCall"); if (calls.length === 0) continue; const results = /* @__PURE__ */ new Map(); for (let next = index + 1; next < messages.length && messages[next].role !== "assistant"; next++) { const candidate = messages[next]; if (candidate.role === "toolResult" && !results.has(candidate.toolCallId)) { results.set(candidate.toolCallId, next); } } for (const call2 of calls) { const resultIndex = results.get(call2.id); ordered.push(resultIndex === void 0 ? missingResult(call2, message.timestamp) : messages[resultIndex]); } } return ordered; } function missingResult(call2, timestamp) { return freezeJson({ role: "toolResult", toolCallId: call2.id, toolName: call2.name, content: [{ type: "text", text: MISSING_RESULT_TEXT }], isError: true, details: { reason: "missing_result" }, timestamp }); } // node_modules/@earendil-works/pi-ai/dist/utils/overflow.js var OVERFLOW_PATTERNS = [ /prompt (?:is )?too long/i, // Anthropic and z.ai token overflow /prompt exceeds max length/i, // z.ai CN endpoint token overflow /request_too_large/i, // Anthropic request byte-size overflow (HTTP 413) /input is too long for requested model/i, // Amazon Bedrock /exceeds the context window/i, // OpenAI (Completions & Responses API) /exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i, // OpenAI-compatible proxies (LiteLLM) /context window exceeded/i, // LiteLLM pre-call check (ContextWindowExceededError) /input token count.*exceeds the maximum/i, // Google (Gemini) /maximum prompt length is \d+/i, // xAI (Grok) /reduce the length of the messages/i, // Groq /maximum context length is \d+ tokens/i, // OpenRouter (most backends) /exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i, // OpenRouter/Poolside /input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i, // Together AI /exceeds the limit of \d+/i, // GitHub Copilot /exceeds the available context size/i, // llama.cpp server /greater than the context length/i, // LM Studio /context window exceeds limit/i, // MiniMax /exceeded model token limit/i, // Kimi For Coding /too large for model with \d+ maximum context length/i, // Mistral /prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i, // DS4 server /model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text /prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error /range of input length should be/i, // DashScope / Qwen Token Plan /context[_ ]length[_ ]exceeded/i, // Generic fallback /too many tokens/i, // Generic fallback /token limit exceeded/i, // Generic fallback // HTTP 413 means request-size overflow regardless of provider or body text. // Match raw SDK errors and formatProviderError's prefix, not a status mentioned in the body. /^(?:[\w -]+ API error \(413\):|413(?:\s|:|$))/i ]; var CEREBRAS_BODYLESS_OVERFLOW_PATTERN = /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i; var NON_OVERFLOW_PATTERNS = [ /^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError) /rate limit/i, // Generic rate limiting /too many requests/i // Generic HTTP 429 style ]; function isContextOverflow(message, contextWindow) { if (message.stopReason === "error" && message.errorMessage) { const isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage)); if (!isNonOverflow) { if (OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage))) { return true; } if (message.provider === "cerebras" && CEREBRAS_BODYLESS_OVERFLOW_PATTERN.test(message.errorMessage)) { return true; } } } if (contextWindow && message.stopReason === "stop") { const inputTokens = message.usage.input + message.usage.cacheRead; if (inputTokens > contextWindow) { return true; } } if (contextWindow && message.stopReason === "length" && message.usage.output === 0) { const inputTokens = message.usage.input + message.usage.cacheRead; if (inputTokens >= contextWindow * 0.99) { return true; } } return false; } function isRecoverableLength(message, desiredMaxOutput) { return message.stopReason === "length" && desiredMaxOutput > 0 && message.usage.output < desiredMaxOutput; } function getOverflowPatterns() { return [...OVERFLOW_PATTERNS]; } // node_modules/@earendil-works/pi-durable/dist/harness/inbox.js var InboxDoc = defineDoc({ kind: "pi.inbox", version: 1, scope: "conversation", history: "latest", fork: "initial", initial: () => ({ items: [] }), checkpointWhen: (value2) => value2.items.length === 0 }); async function prepareBoundary(tx, conversationId, modes, at, now, incoming) { const head = (await tx.latestHeadMarker(conversationId))?.head; const inbox = await tx.doc(InboxDoc, conversationId); const first = inbox.items.find((item) => item.mode === "steer" || item.mode === "followUp"); const input = first === void 0 ? incoming : inputDraft(first.content, first.entry, now); const entries2 = input !== void 0 && (at === "final" || hasQueuedReset(inbox.items)) ? await tx.beforeInput(conversationId, input) : []; return { entries: entries2, conversationId, inbox, steeringMode: modes.steeringMode, followUpMode: modes.followUpMode, head }; } async function applyBoundary(tx, boundary, at, now) { const { conversationId, inbox } = boundary; const items = inbox.items; const reset = hasQueuedReset(items); const final = at === "final" || reset; const pick = (mode, queueMode) => { const indexes = items.flatMap((item, index) => item.mode === mode ? [index] : []); return queueMode === "all" ? indexes : indexes.slice(0, 1); }; const writes = items.flatMap((item, index) => item.mode === "write" ? [index] : []); const users = [...pick("steer", boundary.steeringMode), ...final ? pick("followUp", boundary.followUpMode) : []]; users.sort((a, b) => a - b); let first; for (const index of writes) { const item = items[index]; const draft = item.entry; if (isStale(boundary, draft)) { tx.settleSubmission(item.id, { status: "unanswered", reason: "stale" }); continue; } const entry2 = await tx.appendEntry(conversationId, draft); if (draft.head !== void 0) boundary.head = draft.head === "self" ? entry2.id : draft.head; tx.placeSubmission(item.id, entry2.id); first ??= entry2.id; } const placed = []; for (const index of users) { if (placed.length === 0) for (const draft of boundary.entries) await tx.appendEntry(conversationId, draft); const item = items[index]; const entry2 = await appendInput(tx, conversationId, item.content, item.entry, now); tx.placeSubmission(item.id, entry2.id); placed.push(item.id); first ??= entry2.id; } const removed = [...writes, ...users].sort((a, b) => b - a); for (const index of removed) items.splice(index, 1); return { users: placed, first, reset }; } function appendInput(tx, conversationId, content, entry2, now) { return tx.appendEntry(conversationId, inputDraft(content, entry2, now)); } function inputDraft(content, entry2, now) { const message = { role: "user", content, timestamp: now }; return { ...entry2 === void 0 ? { kind: UserEntry.kind } : pickInputEntry(entry2), input: true, model: [message] }; } function pickInputEntry({ kind, data }) { const picked = data === void 0 ? { kind } : { kind, data }; return copyJson(picked, { omitUndefinedProperties: true }); } function isDisplayOnly(entry2) { return entry2.model === void 0 && entry2.head === void 0 && entry2.edits === void 0; } function hasQueuedReset(items) { return items.some((item) => item.mode === "write" && item.entry.head === "self"); } function isStale(boundary, entry2) { return typeof entry2.head === "number" && boundary.head !== void 0 && entry2.head < boundary.head; } async function removeInboxItem(tx, conversationId, id) { const items = (await tx.doc(InboxDoc, conversationId)).items; const index = items.findIndex((item) => item.id === id); if (index >= 0) items.splice(index, 1); } async function withdrawQueuedInputs(tx, conversationId, reason = "aborted") { const items = (await tx.doc(InboxDoc, conversationId)).items; for (let index = items.length - 1; index >= 0; index--) { const item = items[index]; if (item.mode === "write") continue; tx.settleSubmission(item.id, { status: "unanswered", reason }); items.splice(index, 1); } } // node_modules/@earendil-works/pi-durable/dist/harness/json.js function assignJson(target, key, value2, plan) { if (plan?.kind === "keep") return; const slots = target; const current = key === "__proto__" && !Object.hasOwn(slots, key) ? void 0 : slots[key]; const fields = plan?.kind === "fields" ? plan.fields : void 0; if (isRecord(current) && isRecord(value2)) { for (const name of Object.keys(current)) if (!Object.hasOwn(value2, name)) delete current[name]; for (const [name, child] of Object.entries(value2)) assignJson(current, name, child, fields?.get(name)); return; } if (Array.isArray(current) && Array.isArray(value2) && current.length <= value2.length) { const items = current; for (let index = 0; index < value2.length; index++) { if (index < items.length) assignJson(items, index, value2[index], fields?.get(index)); else items.push(value2[index]); } return; } if (plan?.kind === "append" && typeof current === "string" && typeof value2 === "string" && !Array.isArray(target) && current.length + plan.text.length === value2.length) { appendText(target, String(key), plan.text); return; } if (current !== value2) slots[key] = value2; } function isRecord(value2) { return typeof value2 === "object" && value2 !== null && !Array.isArray(value2); } // node_modules/@earendil-works/pi-durable/dist/harness/partial-changes.js var PartialChanges = class { #text = /* @__PURE__ */ new Map(); #toolCalls = /* @__PURE__ */ new Map(); /** Record what `event` changed. */ observe(event) { switch (event.type) { case "text_delta": case "thinking_delta": { const field = event.type === "text_delta" ? "text" : "thinking"; const pending = this.#text.get(event.contentIndex); if (pending?.field === field) pending.text += event.delta; else this.#text.set(event.contentIndex, { field, text: event.delta }); return; } case "toolcall_start": this.#toolCalls.set(event.contentIndex, { parser: new StreamingJsonParser(), changes: [], committed: false }); return; case "toolcall_delta": { const call2 = this.#toolCalls.get(event.contentIndex); if (call2 === void 0) return; for (const change of call2.parser.append(event.delta)) { if (change.path.length === 0 && !isRecord2(change.kind === "set" ? value(change) : null)) { this.#toolCalls.delete(event.contentIndex); return; } call2.changes.push(change); } return; } case "text_end": case "thinking_end": this.#text.delete(event.contentIndex); return; case "toolcall_end": this.#toolCalls.delete(event.contentIndex); return; } } /** * Take what changed since the last call, for a commit of `message`, a copy of the partial. Clears the arguments of * tracked tool calls in `message`: the commit writes them from the changes. Call `restore()` if the commit fails. */ take(message) { const blocks = /* @__PURE__ */ new Map(); for (const [index, { field, text }] of this.#text) { blocks.set(index, { kind: "fields", fields: /* @__PURE__ */ new Map([[field, { kind: "append", text }]]) }); } this.#text = /* @__PURE__ */ new Map(); const toolCalls = /* @__PURE__ */ new Map(); for (const [index, call2] of this.#toolCalls) { const block = message.content[index]; if (block?.type !== "toolCall") continue; toolCalls.set(index, { changes: call2.changes, snapshot: block.arguments }); block.arguments = {}; blocks.set(index, { kind: "fields", fields: /* @__PURE__ */ new Map([["arguments", { kind: "keep" }]]) }); call2.changes = []; } return { plan: { kind: "fields", fields: /* @__PURE__ */ new Map([["content", { kind: "fields", fields: blocks }]]) }, toolCalls }; } /** * Write `message` with what `commit` took into `generation.message`: the message leaf by leaf with the plan, then the * argument changes. A tracked call whose block is new here gets its arguments from its changes, which then start at * its first piece; when the committed message lost a call whose changes were already committed (another writer reset * it), the call falls back to its snapshot from now on. */ commit(generation, message, commit) { const committedBlocks = generation.message?.content.length ?? 0; const apply2 = []; for (const [index, { changes, snapshot }] of commit.toolCalls) { const call2 = this.#toolCalls.get(index); if (call2 === void 0) continue; const block = message.content[index]; if (index >= committedBlocks && call2.committed && block?.type === "toolCall") { this.#toolCalls.delete(index); block.arguments = snapshot; continue; } call2.committed = true; apply2.push([index, changes]); } assignJson(generation, "message", message, commit.plan); const content = generation.message?.content ?? []; for (const [index, changes] of apply2) { const block = content[index]; if (block?.type !== "toolCall") continue; for (const change of changes) applyChange2(block, change); } } /** Put back what a failed commit took, so the next commit applies it first. */ restore(commit) { for (const [index, { changes }] of commit.toolCalls) { const call2 = this.#toolCalls.get(index); if (call2 !== void 0) call2.changes = [...changes, ...call2.changes]; } } }; function applyChange2(block, change) { const key = change.path.at(-1); if (key === void 0) { const root2 = change.kind === "set" ? value(change) : void 0; if (isRecord2(root2)) block.arguments = root2; return; } let parent = block.arguments; for (const segment of change.path.slice(0, -1)) { parent = parent[segment]; } const slots = parent; if (change.kind === "set") slots[key] = value(change); else if (Array.isArray(parent)) slots[key] = `${slots[key]}${change.text}`; else appendText(parent, String(key), change.text); } function value(change) { return change.value; } function isRecord2(value2) { return typeof value2 === "object" && value2 !== null && !Array.isArray(value2); } // node_modules/@earendil-works/pi-durable/dist/harness/prompt.js function replaySections(messages) { const shown = /* @__PURE__ */ new Map(); for (const message of messages) { if (message.role !== "system" || message.sections === void 0) continue; for (const [key, value2] of Object.entries(message.sections)) { if (value2 === null) shown.delete(key); else shown.set(key, value2); } } return shown; } async function renderSections(sections, input, shown, report2, context) { const desired = /* @__PURE__ */ new Map(); for (const section2 of sections) { let text; try { text = await section2.render(input, context); } catch (error) { if (context.abortSignal?.aborted) throw error; report2(error, section2); const kept = shown.get(section2.key); if (kept !== void 0) desired.set(section2.key, kept); continue; } if (text === void 0) continue; desired.set(section2.key, section2.tag === false ? text : `<${section2.key}> ${text} `); } return desired; } function planSystemEntries(view2, desired, tools, timestamp) { const head = view2.head; if (head !== void 0 && !view2.entries.some((entry2) => SystemEntry.is(entry2) && entry2.id > head.id)) { const edits = view2.entries.filter((entry2) => SystemEntry.is(entry2)).map((entry2) => ({ target: entry2.id, action: "omit" })); const baseline = systemEntry(Object.fromEntries(desired), { toolsRemoved: [], toolsAdded: tools.map(toToolDeclaration) }, timestamp); return [edits.length === 0 ? baseline : { ...baseline, edits }]; } const sections = planSections(replaySections(view2.messages), desired); const changes = planTools(getCurrentTools(view2.messages), tools); if (changes.toolsRemoved.length === 0 && changes.toolsAdded.length === 0) { return sections.map((patch) => systemEntry(patch, void 0, timestamp)); } if (sections.length === 0) return [systemEntry(void 0, changes, timestamp)]; return sections.map((patch, index) => systemEntry(patch, index === sections.length - 1 ? changes : void 0, timestamp)); } function planTools(offered, desired) { const wanted = new Map(desired.map((tool) => [tool.name, tool])); const kept = offered.filter((tool) => { const next = wanted.get(tool.name); return next !== void 0 && declarationsEqual(tool, next); }); const keptNames = new Set(kept.map((tool) => tool.name)); const added = desired.filter((tool) => !keptNames.has(tool.name)); const replayed = [...kept, ...added]; if (replayed.some((tool, index) => tool.name !== desired[index].name)) { return { toolsRemoved: offered.map((tool) => ({ name: tool.name })), toolsAdded: desired.map(toToolDeclaration) }; } return { toolsRemoved: offered.filter((tool) => !keptNames.has(tool.name)).map((tool) => ({ name: tool.name })), toolsAdded: added.map(toToolDeclaration) }; } function planSections(shown, desired) { const patchedOrder = [ ...[...shown.keys()].filter((key) => desired.has(key)), ...[...desired.keys()].filter((key) => !shown.has(key)) ]; const desiredOrder = [...desired.keys()]; if (patchedOrder.some((key, index) => key !== desiredOrder[index])) { return [Object.fromEntries([...shown.keys()].map((key) => [key, null])), Object.fromEntries(desired)]; } const patch = {}; for (const [key, value2] of shown) { const next = desired.get(key); if (next !== value2) patch[key] = next ?? null; } for (const [key, value2] of desired) if (!shown.has(key)) patch[key] = value2; return Object.keys(patch).length === 0 ? [] : [patch]; } function systemEntry(sections, tools, timestamp) { const message = { role: "system", content: "", ...sections === void 0 ? {} : { sections }, ...tools === void 0 || tools.toolsRemoved.length === 0 ? {} : { toolsRemoved: tools.toolsRemoved }, ...tools === void 0 || tools.toolsAdded.length === 0 ? {} : { toolsAdded: tools.toolsAdded }, timestamp }; return { model: [message] }; } // node_modules/@earendil-works/pi-ai/dist/utils/uuid.js var MAX_UUID_V7_TIMESTAMP = 281474976710655; var MAX_SEQUENCE = (1n << 41n) - 1n; var lastOrdinaryTimestamp = -1; var sequence2; function uuidv7(timestampMs) { const requestedTimestamp = timestampMs ?? Date.now(); if (!Number.isInteger(requestedTimestamp) || requestedTimestamp < 0 || requestedTimestamp > MAX_UUID_V7_TIMESTAMP) { throw new RangeError(`UUIDv7 timestamp must be an integer between 0 and ${MAX_UUID_V7_TIMESTAMP}`); } const effectiveTimestamp = timestampMs === void 0 ? Math.max(requestedTimestamp, lastOrdinaryTimestamp) : timestampMs; if (timestampMs === void 0) lastOrdinaryTimestamp = effectiveTimestamp; const bytes2 = new Uint8Array(16); globalThis.crypto.getRandomValues(bytes2); if (sequence2 === void 0) { sequence2 = BigInt(bytes2[1]) << 32n | BigInt(bytes2[2]) << 24n | BigInt(bytes2[3]) << 16n | BigInt(bytes2[4]) << 8n | BigInt(bytes2[5]); } else { if (sequence2 === MAX_SEQUENCE) throw new RangeError("UUIDv7 generator sequence exhausted"); sequence2++; } const timestamp = BigInt(effectiveTimestamp); for (let index = 5; index >= 0; index--) { bytes2[index] = Number(timestamp >> BigInt((5 - index) * 8)) & 255; } bytes2[6] = 112 | Number(sequence2 >> 37n & 0x0fn); bytes2[7] = Number(sequence2 >> 29n & 0xffn); bytes2[8] = 128 | Number(sequence2 >> 23n & 0x3fn); bytes2[9] = Number(sequence2 >> 15n & 0xffn); bytes2[10] = Number(sequence2 >> 7n & 0xffn); bytes2[11] = Number((sequence2 & 0x7fn) << 1n) | bytes2[11] & 1; const hex = Array.from(bytes2, (byte) => byte.toString(16).padStart(2, "0")); return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`; } // node_modules/@earendil-works/pi-durable/dist/harness/provider.js var ProviderDoc = defineDoc({ kind: "pi.provider", version: 1, scope: "conversation", history: "latest", fork: "initial", initial: () => ({ sessionId: uuidv7() }), checkpointWhen: () => true }); async function ensureProviderSessionId(runtime, context) { const existing = await runtime.snapshot(ProviderDoc, runtime.conversationId, context); if (existing !== void 0) return existing.sessionId; let created; await runtime.commit(async (tx) => { created = (await tx.doc(ProviderDoc, runtime.conversationId)).sessionId; return void 0; }, context); if (created === void 0) throw new Error(`Conversation ${runtime.conversationId} has no provider session ID`); return created; } // node_modules/@earendil-works/pi-durable/dist/harness/telemetry.js var entry = { description: "A transcript entry was committed; content remains in the transcript.", attributes: { "entry.id": { type: "number", required: true, description: "Transcript entry ID.", cardinality: "high" }, "entry.kind": { type: "string", required: true, description: "Entry kind." } } }; var reportAttributes = { type: { type: "string", required: true, description: "Report kind (`Report.kind`).", values: [ "storage_failed", "task_faulted", "task_interrupted", "migration_failed", "task_outdated", "task_error", "extension_error", "host_error", "listener_error" ] }, reason: { type: "string", required: false, description: "Fixed report reason, when present.", values: ["threw", "restart_limit", "interrupted", "missing_task", "task_too_old", "migration_failed"] }, extension: { type: "string", required: false, description: "Registered extension name, when present." }, "task.id": { type: "number", required: false, description: "Task ID, when present.", cardinality: "high" } }; var report = { description: "A report was raised; no error payload is recorded.", attributes: reportAttributes }; var submission = { description: "A submission was settled.", attributes: { "submission.id": { type: "number", required: true, description: "Submission ID.", cardinality: "high" }, status: { type: "string", required: true, values: ["done", "unanswered"], description: "Settlement." }, "answer.entry_id": { type: "number", required: false, description: "Answer entry ID.", cardinality: "high" } } }; var DURABLE_TELEMETRY_SCHEMA = defineTelemetrySchema({ version: 1, spans: { "pi.durable.submit": { description: "Public submission admission: with its commit for submit(draft, context); the admission only for submit(tx, draft), whose result is provisional (a later throw or storage failure of the caller's commit leaves no submission) and whose commit events go to the caller's commit context. Inputs and continues start root traces linked to their callers; writes are children. Internal admissions emit phase events only.", parents: { kind: "any" }, startAttributes: { "submission.type": { type: "string", required: true, values: ["input", "write", "continue"], description: "Submission type." }, "conversation.id": { type: "number", required: true, description: "Conversation ID.", cardinality: "high" } }, endAttributes: { "submission.id": { type: "number", description: "Admitted submission ID.", cardinality: "high" }, result: { type: "string", values: ["queued", "placed", "done"], description: "Admission result; stale writes report done." } }, events: { "pi.durable.entry": entry, "pi.durable.submission": submission, "pi.durable.report": report }, status: { default: "ok", errorWhen: "Admission failed." } }, "pi.durable.phase": { description: "One task phase invocation, including its decision commit, never a wait or restart.", parents: { kind: "any" }, startAttributes: { "task.id": { type: "number", required: true, description: "Task ID.", cardinality: "high" }, "task.name": { type: "string", required: true, description: "Registered task name." }, phase: { type: "string", required: true, description: "Invoked checkpoint phase." }, kind: { type: "string", required: true, values: ["run", "abort"], description: "Invocation kind." }, attempt: { type: "number", required: false, description: "Checkpoint attempt, when present." }, resumed: { type: "boolean", required: true, description: "First invocation of a task loaded in this process." } }, endAttributes: { outcome: { type: "string", description: "next=, waiting, sleeping, done, faulted, or aborted." }, "tool.name": { type: "string", description: "Called tool name." }, "tool.call_id": { type: "string", description: "Transcript tool call ID.", cardinality: "high" } }, events: { "pi.durable.entry": entry, "pi.durable.submission": submission, "pi.durable.report": report, "pi.durable.checkpoint": { description: "The running checkpoint's phase changed while the invocation continued.", attributes: { phase: { type: "string", required: true, description: "Committed checkpoint phase." } } }, "pi.durable.replay": { description: "A nested tool call was answered from its stored result.", attributes: { "tool.name": { type: "string", required: true, description: "Called tool name." } } } }, status: { default: "ok", errorWhen: "Faulted, failed, and orphaned outcomes use task_faulted, task_failed, and task_orphaned with a diagnostic capped at 500 characters. Aborted outcomes keep status ok." } } }, metrics: { "pi.durable.reports": { kind: "counter", description: "Reports raised, grouped by their fixed kind.", attributes: { type: reportAttributes.type } }, "pi.durable.phase.duration": { kind: "histogram", unit: "ms", description: "Duration of a phase invocation and its decision commit.", attributes: { task: { type: "string", required: true, description: "Registered task name." }, phase: { type: "string", required: true, description: "Invoked checkpoint phase." }, outcome: { type: "string", required: true, description: "Phase outcome." } } }, "pi.durable.tasks.active": { kind: "upDownCounter", description: "Live tasks held by this Harness; decremented at terminal settlement or close.", attributes: { task: { type: "string", required: true, description: "Registered task name." } } } } }); // node_modules/@earendil-works/pi-durable/dist/harness/util.js var Waiters = class { #sets = /* @__PURE__ */ new Map(); add(key, context) { const signal = context.abortSignal; if (signal?.aborted) return Promise.reject(signal.reason); const waiter = Promise.withResolvers(); const set = this.#sets.get(key) ?? /* @__PURE__ */ new Set(); this.#sets.set(key, set.add(waiter)); if (signal !== void 0) { const onAbort = () => { set.delete(waiter); if (set.size === 0 && this.#sets.get(key) === set) this.#sets.delete(key); waiter.reject(signal.reason); }; signal.addEventListener("abort", onAbort, { once: true }); const detach = () => signal.removeEventListener("abort", onAbort); waiter.promise.then(detach, detach); } return waiter.promise; } keys() { return [...this.#sets.keys()]; } resolve(key, value2) { const set = this.#sets.get(key); this.#sets.delete(key); for (const waiter of set ?? []) waiter.resolve(value2); } rejectAll(error) { const sets = [...this.#sets.values()]; this.#sets.clear(); for (const set of sets) for (const waiter of set) waiter.reject(error); } }; async function scanAll(scan2) { const items = []; let cursor; do { const page3 = await scan2(cursor); items.push(...page3.items); cursor = page3.next; } while (cursor !== void 0); return items; } function closedError(session) { const failure = session.failure; return failure === void 0 ? new Error("Harness is closed") : new SessionFailed(failure.error); } function optionalExtension(extension) { return extension === void 0 ? {} : { extension }; } function conversationEntries(conversationId, query) { return { conversationId, ...query.minEntryId === void 0 ? {} : { minEntryId: query.minEntryId }, ...query.maxEntryId === void 0 ? {} : { maxEntryId: query.maxEntryId }, ...query.order === void 0 ? {} : { order: query.order } }; } // node_modules/@earendil-works/pi-durable/dist/harness/scheduler.js var SCAN_PAGE_SIZE3 = 256; var NO_KEYS2 = []; var LIVE_STATUSES = ["pending", "running", "waiting", "sleeping", "completing"]; function blockedSubject(record2) { return { id: record2.id, kind: record2.kind, version: record2.version, abortRequested: record2.abortRequested, ...record2.abortReason === void 0 ? {} : { abortReason: record2.abortReason }, status: record2.state.status }; } var CONTINUE = { kind: "continue" }; var PROGRESS = { kind: "progress" }; var END = { kind: "end" }; var CASCADE = { kind: "cascade" }; function conversationNode(id) { return `c${id}`; } function conversationOf(node) { return Number(node.slice(1)); } function parentNode(node) { return node.owner ?? conversationNode(node.conversationId); } var TaskScheduler = class { #telemetry; #activeMetric; #durationMetric; #resumed = /* @__PURE__ */ new Set(); #session; #storage; #registry; #models; #agent; #settings; #env; #now; #onWake; #timers; #writeAtClose; #report; #settleOutcome; #limitsRestarts; #withdrawInputs; #conversation; #context; #live = /* @__PURE__ */ new Map(); /** * Context range last read through a task runtime, per conversation: a later read, by any of its tasks, scans only * newer entries. `idleSince` is the Harness time it was first seen idle. Derived and never persisted; dropped at the * first idle check after `settings.contextRetentionMs` of idleness, and at close. */ #contexts = /* @__PURE__ */ new Map(); /** * The idle kept contexts by `idleSince`, oldest first: the expiry order, so neither an expiry nor finding the next one * scans `#contexts`. Kept sorted also when the Harness clock goes back (see `#markIdle`). */ #idleOrder = /* @__PURE__ */ new Map(); /** At least the newest `idleSince` in `#idleOrder` (drops do not lower it): an older one added means the clock went back. */ #idleNewest = Number.NEGATIVE_INFINITY; /** * A chain load resolved what `#idle()` treats as unknown for a live task whose chain is not loaded, which can make a * scope idle without a task change below it; the next idle check then covers every scope. */ #unknownsChanged = false; /** Idle checks that covered every scope (`idleState`, a test diagnostic). */ #fullIdleChecks = 0; /** Background timer for the earliest expiry of an idle context, unless the host declined it (see `#scheduleExpiry`). */ #expiry; #invocations = /* @__PURE__ */ new Map(); #taskWaiters = new Waiters(); /** Idle waiters by conversation; `undefined` waits for the whole Harness. */ #idleWaiters = new Waiters(); /** Definition whose migration failed per task; retried only once the registry resolves another definition. */ #failedMigrations = /* @__PURE__ */ new Map(); /** Owner task of each known conversation, `null` when ownerless. */ #edges = /* @__PURE__ */ new Map(); /** Ownership fields of the known terminal tasks, which walks pass through. */ #settled = /* @__PURE__ */ new Map(); /** * Ended tasks and conversations found unneeded, dropped by `#sweep()` at the end of the commit's publication unless * needed again by then: a chain load installs nodes before the work below them is listed. */ #dropQueue = /* @__PURE__ */ new Set(); /** * The tree downward around live work: each node's child nodes that are live tasks or have nodes listed below them. * A conversation's parent is its owner task; ownerless ones with work are in `#roots`. */ #below = /* @__PURE__ */ new Map(); #roots = /* @__PURE__ */ new Set(); /** Known tasks whose chain may not be loaded; every other known task's chain is, and stays, loaded. */ #unloaded = /* @__PURE__ */ new Set(); /** Live tasks with cancellation intent, the sources of abort cascades (spec §5.4). */ #intent = /* @__PURE__ */ new Set(); /** Live tasks that are not `completing`, the candidates of a reservation pass; ones found waiting leave it. */ #runnable = /* @__PURE__ */ new Set(); /** `completing` tasks the next finalize pass checks: those that started holding, and holders of ended work. */ #finalizeChecks = /* @__PURE__ */ new Set(); /** Live waiting tasks by each task in their `on`. */ #waiters = /* @__PURE__ */ new Map(); /** * `failFast` waiters the next reconcile checks for a failed task in `on`: at open, when they start waiting, and when * one of their tasks fails. */ #failFastChecks = /* @__PURE__ */ new Set(); /** * Live tasks with `abandonOnRestart` found at open, from an earlier Harness. The first reservation pass abort-marks * them with reason `restart`, so nothing of them or below them runs a phase again. */ #abandoned = /* @__PURE__ */ new Set(); /** Sleeping live tasks, due or not: the candidates of `#wakeDue()`. */ #sleeping = /* @__PURE__ */ new Set(); /** Timer for the earliest sleep, without `onWake` while scheduling runs. */ #timer; /** Last value given to `onWake`, so an unchanged one is not repeated. */ #lastWake; #wakeScheduled = false; #blockedListeners = /* @__PURE__ */ new Set(); /** A registry snapshot that threw for the task graph was reported; it is read per record, so only once. */ #snapshotReported = false; #reconcileScheduled = false; /** Reconcile passes underway with work (they may overlap): their commits finalize held outcomes and mark cascades. */ #reconcilingWork = 0; #sweepScheduled = false; /** * Nodes chain loads installed for jobs whose commits are not published yet, with how many such jobs: the sweep keeps * them, as work a pending commit creates below them needs them. */ #pins = /* @__PURE__ */ new Map(); /** Keys the decision region underway reads (`#region()`); set only while one runs. */ #reading; #cascadePending = false; #unsubscribeRegistry = () => { }; #enabled = false; #held = false; #readOnly; #closing = false; #dirty = false; #draining = false; constructor(options) { this.#telemetry = options.telemetry ?? NOOP_TELEMETRY_CONTEXT; const metrics = createTypedMetrics(this.#telemetry.metrics ?? NOOP_TELEMETRY_METRICS, [DURABLE_TELEMETRY_SCHEMA]); this.#activeMetric = metrics.upDownCounter("pi.durable.tasks.active"); this.#durationMetric = metrics.histogram("pi.durable.phase.duration"); this.#session = options.session; this.#storage = options.storage; this.#registry = options.registry; this.#models = options.models; this.#agent = options.agent; this.#settings = options.settings; this.#env = options.env; this.#now = options.now; this.#onWake = options.onWake; this.#timers = options.timers; this.#writeAtClose = options.writeAtClose; this.#report = options.report; this.#settleOutcome = options.settleOutcome; this.#limitsRestarts = options.limitsRestarts; this.#withdrawInputs = options.withdrawInputs; this.#conversation = options.conversation; this.#context = options.context; this.#readOnly = options.readOnly === true; } /** Start no run invocation until `release()`; running invocations and abort handlers go on. Overrides `resume()`. */ hold() { this.#held = true; } release() { this.#held = false; this.#kick(); } /** * Load live tasks and change surviving `running` tasks back to `pending`, counting the restart. Dispatches nothing; * a task cut off `settings.restartLimit` times ends when it is next reserved. Read-only, it only loads them: a * `running` task stays as committed, running in another process. */ async open(context) { this.#session.followCommits((publication) => this.#observe(publication)); this.#session.subscribeClose(() => this.#seal()); this.#unsubscribeRegistry = this.#registry.subscribe(() => { this.#notifyBlocked(); this.#kick(); }); await this.#session.commitWith(async (tx) => { await this.#published(); const scans = []; for (const status of LIVE_STATUSES) { scans.push(await scanAll((cursor) => tx.scanTasks({ status }, SCAN_PAGE_SIZE3, cursor))); } for (const records of scans) { for (const record2 of records) { this.#track(void 0, record2); this.#resumed.add(record2.id); if (this.#readOnly) continue; if (record2.abandonOnRestart === true) this.#abandoned.add(record2.id); if (record2.state.status === "running") { const restarts = (record2.restarts ?? 0) + 1; const pending = withState(record2, { status: "pending", checkpoint: record2.state.checkpoint }); tx.setTask({ ...pending, restarts }); } if (record2.state.status === "waiting" && record2.state.policy === "failFast") { this.#failFastChecks.add(record2.id); } } } }, context); if (this.#readOnly) return; this.#cascadePending = true; this.#scheduleReconcile(); this.#scheduleWake(); } /** * Enable scheduling, and make sleeping tasks that are due runnable: a host wakeup, such as an alarm, calls this. * Idempotent; the kick does nothing once closing. */ resume() { if (this.#readOnly) throw new ReadOnlySession("running tasks"); this.#enabled = true; this.#wakeDue(); this.#kick(); } /** * Why a live task cannot run here, if it cannot: its definition is missing, too old, or failed to migrate. A running * task, an abort-marked one, and one holding its outcome are never blocked. */ blockedReason(record2) { const status = record2.status; if (status === "running" || status === "completing" || status === "terminal") return void 0; if (record2.abortRequested && record2.abortReason === void 0) return void 0; let snapshot; try { snapshot = this.#registry.snapshot(); } catch (error) { if (!this.#snapshotReported) this.#report(error, REGISTRY); this.#snapshotReported = true; return void 0; } const fit = this.#fit(record2, snapshot.task(record2.kind)); if ("reason" in fit) return fit.reason; return fit.migrates && fit.task.definition.migrate === void 0 ? "migration_failed" : void 0; } /** Call `listener` when blocked reasons may have changed without a commit: a registry change or a failed migration. */ subscribeBlocked(listener) { this.#blockedListeners.add(listener); return () => this.#blockedListeners.delete(listener); } /** * When the Session next needs a process: `active` while some task can run now or is running, `at` for the earliest * sleep not yet due. Synchronous, from memory; a task whose chain is not loaded yet counts as it would alone. */ wake() { if (this.#readOnly) return { active: false }; const now = this.#now(); const active = this.#invocations.size > 0 || this.#reconcilingWork > 0 || this.#reconcileWork() || this.#runsNow(now); let at; for (const id of this.#sleeping) { const record2 = this.#live.get(id); const state = record2?.state; if (state?.status === "sleeping" && !record2.abortRequested && state.until > now) { at = at === void 0 ? state.until : Math.min(at, state.until); } } return at === void 0 ? { active } : { active, at }; } /** Whether a live task can run now: the scan `wake()` needs only while no invocation runs. */ #runsNow(now) { let snapshot; let snapshotFailed = false; for (const record2 of this.#live.values()) { const state = record2.state; if (state.status === "completing") continue; if (state.status === "waiting" && !record2.abortRequested && state.on.some((id) => this.#live.has(id))) continue; if (state.status === "sleeping" && !record2.abortRequested && state.until > now) continue; if (record2.abortRequested && this.#waitingOn(record2, true).length > 0) continue; if (!record2.abortRequested || record2.abortReason !== void 0) { if (snapshot === void 0 && !snapshotFailed) { try { snapshot = this.#registry.snapshot(); } catch (error) { snapshotFailed = true; this.#report(error, REGISTRY); } } if (snapshot !== void 0 && "reason" in this.#fit(record2, snapshot.task(record2.kind))) continue; } return true; } return false; } /** * Signal every invocation and wait for them, then write the tasks they ran back as `pending` (`writeAtClose`), as a * close is not a crash. Close calls this after every close listener has run, so waits, watches, and streams a task * holds end with the Session's reason, not as cancelled by its signal. */ async join() { const stopped = [...this.#invocations.values()]; for (const invocation of stopped) invocation.controller.abort(); await Promise.allSettled(stopped.map((invocation) => invocation.done)); if (stopped.length === 0) return; await this.#writeAtClose(() => stopped.flatMap((invocation) => { const record2 = this.#live.get(invocation.taskId); if (record2?.state.status !== "running") return []; const value2 = withState(record2, { status: "pending", checkpoint: record2.state.checkpoint }); return [{ type: "task", value: value2 }]; })).catch(() => { }); } /** * Commit the abort mark, or settle a task that no registered definition can take as `orphaned` when nothing it owns * is live, then join the run invocation seen on the line; the commit listener signalled it. The abort invocation * starts once the task's ordinary owned work is gone. A `completing` task is only marked. A request replaces a * `restart` mark, so a task abandoned after a restart that waits for its definition is orphaned; with `keepRestart`, * as for an owner's own cleanup, such a task keeps its mark and waits on. */ async abort(id, context, keepRestart = false) { const marked = await this.#session.commitWith(async (tx) => { const current = await tx.task(id); if (current === void 0) throw new Error(`Task ${id} does not exist`); if (current.state.status === "terminal") return { result: "terminal" }; const invocation = this.#invocations.get(id); if (keepRestart && current.abortReason === "restart") return { result: "marked" }; if (invocation === void 0 && current.state.status !== "completing") { await this.#loadScopes(false); const owns = await this.#region(() => this.#hasOwnedLive(id), this.#reloadScopes); const resolution = owns ? void 0 : this.#resolve(current, this.#registry.snapshot()); if (resolution?.kind === "blocked") { await this.#terminate(tx, current, { status: "orphaned", reason: resolution.reason }); return { result: "marked" }; } } if (!current.abortRequested || current.abortReason !== void 0) tx.setTask(withAbortMark(current)); return { result: "marked", run: invocation?.mode === "run" ? invocation : void 0 }; }, context); if (marked.run !== void 0) { await awaitWithContext(Promise.race([marked.run.done, this.#session.failed]), context); } return marked.result; } async waitForTask(id, context) { const found = await this.#session.readOnLine(async (storage2) => { if (this.#closing) throw closedError(this.#session); if (await this.#region(() => this.#liveRecord(id) !== void 0)) { return { promise: this.#taskWaiters.add(id, context) }; } const record2 = await storage2.task(id, context); if (record2 === void 0) throw new Error(`Task ${id} does not exist`); return { promise: Promise.resolve(record2) }; }); return found.promise; } /** * Resolve when ordinary traversal from the conversation, or from every ownerless conversation, reaches no live * non-background task. */ waitForIdle(conversationId, context) { if (this.#closing) return Promise.reject(closedError(this.#session)); if (this.#idle(conversationId)) return Promise.resolve(); this.#scheduleReconcile(); return this.#idleWaiters.add(conversationId, context); } /** * `Conversation.abort()`: in one commit, withdraw the queued inputs and mark every live non-background task that * ordinary traversal from the conversation reaches; resolves once the scope is idle. With `background`, traversal * crosses background boundaries, and the wait also covers every task it reached. */ async abortConversation(conversationId, background, context) { const reached = await this.#session.commitWith(async (tx) => { await this.#published(); const queued = await this.#loadScopes(true); const scope = { conversation: conversationId }; const reached2 = []; const walk = this.#walkDown(conversationNode(conversationId), { crossBackground: background }); for (const { node, live } of walk) { if (live === void 0 || node.background && !background) continue; reached2.push(live.id); if (!live.abortRequested || live.abortReason !== void 0) tx.setTask(withAbortMark(live)); } for (const id of queued) { if (this.#inScope({ conversation: id }, scope, background) === true) await this.#withdrawInputs(tx, id); } return reached2; }, context); if (background) for (const id of reached) await this.waitForTask(id, context); await this.waitForIdle(conversationId, context); } /** * The ownership memberships a commit's task and conversation changes write (group commit §4.2), at its admission on * the line: for each task created or made terminal, every node from its parent up to and including the nearest live * task, or the root conversation, and then the roots too, conservatively, as a pending end may unlist the root a * creation finds listed; for each conversation created under a task, that task; and the intent set when * a change sets, upgrades, or clears a mark or a held or ended outcome other than `completed`. Loads the chains it * walks; the nodes it installs stay until the commit is published. */ membershipKeys(tx) { const tasks = tx.stagedTasks(); const conversations = tx.stagedConversations(); if (tasks.length === 0 && conversations.every((record2) => record2.owner === void 0)) return NO_KEYS2; return this.#membershipKeys(tx, tasks, conversations); } async #membershipKeys(tx, tasks, conversations) { const keys = /* @__PURE__ */ new Set(); const overlay = overlayOf(tx); const created = new Set(tx.createdTaskIds()); const reached = /* @__PURE__ */ new Map(); for (const record2 of tasks) { if (intentChanges(this.#live.get(record2.id), record2)) keys.add(INTENT_KEY); if (!created.has(record2.id) && record2.state.status !== "terminal") continue; await this.#loadChain(parentOf(record2), overlay); if (await this.#chainKeys(parentNode(record2), overlay, keys, reached) !== void 0) keys.add(ROOTS_KEY); } for (const conversation of conversations) { const owner = conversation.owner?.taskId; if (owner === void 0) continue; await this.#loadChain({ task: owner }, overlay); await this.#chainKeys(owner, overlay, keys, reached); } return [...keys]; } /** * Add the memberships from `start` up to and including the nearest live task, or the root conversation, which it * returns. `reached` keeps, per node walked, where its walk ended, so a chain several tasks of a commit share is * walked once. Whether an ancestor is live can change under a pending commit, so it is read after a touch of the * ancestor's record: a pending end must not stop the chain early. Ownership fields are immutable, so not keyed. */ async #chainKeys(start, overlay, keys, reached) { const walked = []; let root2; let node = start; while (node !== void 0 && node !== null) { const key = ownedKey(node); if (reached.has(key)) { root2 = reached.get(key); break; } keys.add(key); walked.push(key); if (typeof node === "string") { const conversation = conversationOf(node); node = this.#edge(conversation, overlay); if (node === null) root2 = conversation; continue; } let record2 = overlay.tasks.get(node); if (record2 === void 0) { const gate = this.#session.touch(taskKey(node)); if (gate !== void 0) { await gate; await this.#loadChain({ task: node }, overlay); } record2 = this.#live.get(node); } if (record2 !== void 0 && record2.state.status !== "terminal") break; const above = record2 ?? this.#settled.get(node); node = above === void 0 ? void 0 : parentNode(above); } for (const key of walked) reached.set(key, root2); return root2; } // ─── Scheduling ──────────────────────────────────────────────────────── #observe(publication) { if (publication.kind === "memory") return; this.#advanceContexts(publication); const updated = []; const failed = []; let changed = false; let dirty = /* @__PURE__ */ new Set(); for (const change of publication.changes) { if (change.type === "conversation" && !this.#edges.has(change.value.id)) { this.#setEdge(change.value.id, change.value.owner?.taskId ?? null); } } for (const change of publication.changes) { if (change.type !== "task") continue; changed = true; const record2 = change.value; const previous = this.#live.get(record2.id); if (failedOutcome(record2) && (previous === void 0 || !failedOutcome(previous))) failed.push(record2.id); this.#track(previous, record2); const liveness = previous === void 0 || record2.state.status === "terminal"; if (liveness && dirty !== void 0 && !this.#addScopesAbove(record2, dirty)) dirty = void 0; if (record2.state.status === "terminal") { this.#failedMigrations.delete(record2.id); this.#failFastChecks.delete(record2.id); this.#taskWaiters.resolve(record2.id, record2); this.#scheduleReconcile(); continue; } if (record2.abortRequested && previous?.abortRequested !== true) { this.#cascadePending = true; const invocation = this.#invocations.get(record2.id); if (invocation?.mode === "run") queueMicrotask(() => invocation.controller.abort()); } if (previous?.abortReason !== void 0 && record2.abortReason === void 0) this.#cascadePending = true; const status = record2.state.status; if (status === "completing" && previous?.state.status !== "completing") { if (cancellationIntent(record2)) this.#cascadePending = true; this.#scheduleReconcile(); } if (status === "waiting" && record2.state.policy === "failFast" && previous?.state.status !== "waiting") { this.#failFastChecks.add(record2.id); this.#scheduleReconcile(); } if (previous === void 0) updated.push(record2); } for (const id of failed) { for (const waiter of this.#waiters.get(id) ?? []) { const state = this.#live.get(waiter)?.state; if (state?.status === "waiting" && state.policy === "failFast") { this.#failFastChecks.add(waiter); this.#scheduleReconcile(); } } } for (const change of publication.changes) { if (change.type !== "submission" || change.value.status !== "queued" || change.value.type !== "input") continue; if (this.#intent.size === 0) continue; const conversation = change.value.conversationId; if (!this.#known(conversationNode(conversation)) || this.#belowCancelled({ conversation })) { this.#cascadePending = true; } } for (const record2 of updated) { if (this.#unloaded.has(record2.id)) this.#scheduleReconcile(); else if (!record2.background && !record2.abortRequested && this.#belowCancelled(parentOf(record2))) { this.#cascadePending = true; } } if (this.#cascadePending) this.#scheduleReconcile(); this.#sweep(); if (!changed) return; this.#settleIdle(dirty); this.#kick(); } /** * Add the conversations above `record` to `scopes`: those whose idleness a change of it can change. False when a node * on the way up is not known. A conversation already in `scopes` has everything above it there too. */ #addScopesAbove(record2, scopes) { for (const step of this.#above(parentOf(record2))) { if ("unknown" in step) return false; if (!("conversation" in step)) continue; if (scopes.has(step.conversation)) return true; scopes.add(step.conversation); } return true; } /** * Extend each kept context range with the entries a stored commit appended to its conversation, so a later read * needs no Storage scan. Entries are published in ID order. */ #advanceContexts(publication) { if (this.#contexts.size === 0) return; let appended; for (const change of publication.changes) { if (change.type !== "entry" || !this.#contexts.has(change.value.conversationId)) continue; appended ??= /* @__PURE__ */ new Map(); const entries2 = appended.get(change.value.conversationId); if (entries2 === void 0) appended.set(change.value.conversationId, [change.value]); else entries2.push(change.value); } if (appended === void 0) return; for (const [conversationId, entries2] of appended) { const kept = this.#contexts.get(conversationId); if (kept.idleSince !== void 0 && kept.expiresAt !== void 0 && kept.expiresAt <= this.#now()) { this.#dropContext(conversationId); continue; } const range = advanceRange(kept.range, entries2); if (range === void 0) this.#dropContext(conversationId); else kept.range = range; } } /** * `settings.contextRetentionMs`, or 0 when the host's settings throw. Kept contexts are only a cache, so dropping them * is safe, while a throw here would escape commit listeners, reconciliation, and the expiry timer. */ #contextRetentionMs() { try { return this.#settings().contextRetentionMs; } catch (error) { this.#report(error, SETTINGS); return 0; } } /** * After task changes below the conversations in `scopes` (every conversation when undefined): resolve their idle * waiters and the Harness-wide ones, drop the kept contexts whose idle period passed the retention, then start or end * the idle periods of the kept contexts in `scopes`. A task change can change the idleness only of the conversations * above it, so no check visits every conversation. */ #settleIdle(scopes) { const all = scopes === void 0 || this.#unknownsChanged; this.#unknownsChanged = false; if (all) this.#fullIdleChecks++; this.#resolveIdleWaiters(all ? void 0 : scopes); const now = this.#now(); const retention = this.#contextRetentionMs(); this.#expireContexts(now, retention); for (const conversationId of all ? [...this.#contexts.keys()] : scopes) { const kept = this.#contexts.get(conversationId); if (kept === void 0) continue; if (!this.#idle(conversationId)) { if (kept.idleSince === void 0) continue; kept.idleSince = void 0; this.#idleOrder.delete(conversationId); } else if (retention <= 0) { this.#dropContext(conversationId); } else if (kept.idleSince === void 0) { kept.idleSince = now; kept.expiresAt = now + retention; this.#markIdle(conversationId, now); } } this.#scheduleExpiry(); } /** Resolve the idle waiters of the conversations in `scopes` (all when undefined) and the Harness-wide ones. */ #resolveIdleWaiters(scopes) { for (const conversationId of this.#idleWaiters.keys()) { if (conversationId !== void 0 && scopes !== void 0 && !scopes.has(conversationId)) continue; if (this.#idle(conversationId)) this.#idleWaiters.resolve(conversationId); } } /** * Drop the kept contexts idle for at least `retention`, oldest first, with the retention setting of now; with no * retention, every idle one, also those whose idle period a clock that went back makes start after now. */ #expireContexts(now, retention) { for (const [conversationId, idleSince] of this.#idleOrder) { if (retention > 0 && now - idleSince < retention) return; this.#dropContext(conversationId); } } /** * Start the idle period of a kept context in the expiry order. An idle period older than the newest one kept, which * a clock that went back starts, sorts the order again, and the newest is taken from what is kept then. */ #markIdle(conversationId, idleSince) { this.#idleOrder.delete(conversationId); this.#idleOrder.set(conversationId, idleSince); if (this.#idleOrder.size === 1 || idleSince >= this.#idleNewest) { this.#idleNewest = idleSince; return; } const sorted = [...this.#idleOrder].sort((a, b) => a[1] - b[1]); this.#idleOrder.clear(); for (const [id, since] of sorted) this.#idleOrder.set(id, since); this.#idleNewest = sorted.at(-1)[1]; } /** Forget a kept context, idle or not, and its place in the expiry order. */ #dropContext(conversationId) { this.#contexts.delete(conversationId); this.#idleOrder.delete(conversationId); } /** * Drop the expired contexts when the earliest idle context expires, on a background timer, which never keeps the * process alive. Where the host declines it, as the default does in Cloudflare Workers, none is kept: a pending timer * could keep a Durable Object from being evicted, and eviction frees the contexts. There, task changes alone check * expiry. */ #scheduleExpiry() { const oldest = this.#idleOrder.values().next(); const at = oldest.done === true ? void 0 : oldest.value + this.#contextRetentionMs(); if (this.#expiry !== void 0 && this.#expiry.at === at) return; this.#expiry?.disarm(); this.#expiry = void 0; if (at === void 0 || this.#closing) return; const disarm = this.#timers.background(() => { this.#expiry = void 0; this.#expireContexts(this.#now(), this.#contextRetentionMs()); this.#scheduleExpiry(); }, at - this.#now()); if (disarm !== void 0) this.#expiry = { at, disarm }; } // ─── Ownership ─────────────────────────────────────────────────────────── #scheduleReconcile() { if (this.#reconcileScheduled || this.#closing || this.#readOnly) return; this.#reconcileScheduled = true; queueMicrotask(() => void this.#reconcile()); } /** * One commit that applies what committed records imply: abort marks below live owners with cancellation intent * (spec §5.4), `failFast` marks (spec §5.5), withdrawn queued inputs below cancelled owners, and the final terminal * record of every `completing` task whose ordinary owned work is gone. The durable records are the intent, so this * also repairs whatever a crash left unapplied. Resolves idle waiters that the loaded edges decide. */ async #reconcile() { this.#reconcileScheduled = false; const work = this.#reconcileWork(); if (work) this.#reconcilingWork++; try { await this.#session.commitInternal(async (tx) => { if (this.#closing) return; const cascade = this.#cascadePending; const queued = await this.#loadScopes(cascade); if (cascade) this.#cascadePending = false; const marks = await this.#marks(); for (const { record: record2, reason } of marks.values()) tx.setTask(withAbortMark(record2, reason)); const withdrawn = await this.#region(() => queued.filter((id) => this.#belowCancelled({ conversation: id })), this.#reloadScopes); for (const id of withdrawn) await this.#withdrawInputs(tx, id); await this.#finalize(tx); }, this.#context); } catch (error) { this.#failSession(error); } if (work) this.#reconcilingWork--; this.#scheduleWake(); if (this.#unknownsChanged) { this.#settleIdle(void 0); return; } this.#resolveIdleWaiters(void 0); this.#expireContexts(this.#now(), this.#contextRetentionMs()); this.#scheduleExpiry(); } /** * The abort marks committed records imply (spec §5.4, §5.5): every live foreground task below a live owner with * cancellation intent, which is its nearest one (a walk down from each owner with intent does not enter another, * whose own walk covers what is below it), and every other live task in the `on` of a `failFast` waiter one of whose * tasks failed. A cascade from an abandoned owner passes its `restart` reason on; any other intent marks, or upgrades * a `restart` mark, as a request. Collected first, so a request wins over a `restart` mark of the same pass. Derived * again on every pass, as loading edges can reveal a cancelled owner. Takes the `failFast` checks once its decision * stands. */ async #marks() { const ended = /* @__PURE__ */ new Map(); for (; ; ) { const plan = await this.#region(() => this.#planMarks(ended), this.#reloadScopes); if ("missing" in plan) { for (const id of plan.missing) ended.set(id, await this.#session.lineStorage.task(id, this.#context) ?? null); continue; } for (const id of plan.checked) this.#failFastChecks.delete(id); return plan.marks; } } /** `#marks()`' decision region; the `failFast` members it needs from Storage first, when `ended` lacks them. */ #planMarks(ended) { const marks = /* @__PURE__ */ new Map(); const mark = (found, reason) => { const record2 = this.#liveRecord(found.id); const staged = marks.get(record2.id); if (staged !== void 0 && staged.reason === void 0) return; if (record2.abortRequested && (record2.abortReason === void 0 || reason !== void 0)) return; marks.set(record2.id, { record: record2, reason }); }; this.#record(INTENT_KEY); for (const id of this.#intent) { const owner = this.#liveRecord(id); const restart = owner.abortReason === "restart" && !failedOutcome(owner); for (const { node, live } of this.#walkDown(id, { enter: (below) => !this.#intent.has(below) })) { if (live !== void 0 && !node.background) mark(live, restart ? "restart" : void 0); } } const checked = [...this.#failFastChecks]; const missing = []; for (const id of checked) { const waiter = this.#liveRecord(id); if (waiter?.state.status !== "waiting") continue; let failed = false; for (const member of waiter.state.on) { const record2 = this.#liveRecord(member) ?? ended.get(member); if (record2 === void 0) missing.push(member); else if (record2 !== null && failedOutcome(record2)) failed = true; } if (!failed) continue; for (const member of waiter.state.on) { const record2 = this.#liveRecord(member); if (record2 !== void 0 && !failedOutcome(record2)) mark(record2, void 0); } } return missing.length > 0 ? { missing } : { marks, checked }; } /** Whether a reconcile pass would have work: a cascade to derive, held outcomes to check, or `failFast` waits. */ #reconcileWork() { return this.#cascadePending || this.#finalizeChecks.size > 0 || this.#failFastChecks.size > 0; } /** * Write the terminal record of every `completing` task without live ordinary owned work. Finalizing one can free its * owner, so this repeats over the commit's candidates until nothing changes. A held scheduler outcome gets its * Harness cleanup here. In rounds, one decision region each: the holders the tasks of one round free are checked once, * in the next, so a holder of many tasks that end together is walked once, not once per task. The first round takes * the `completing` tasks to check once its decision stands. */ async #finalize(tx) { const done = /* @__PURE__ */ new Set(); const overlay = overlayOf(tx); let round; for (; ; ) { const ids = round; const plan = await this.#region(() => this.#planRound(ids ?? [...this.#finalizeChecks], overlay, done), this.#reloadScopes); if (ids === void 0) this.#finalizeChecks.clear(); for (const { record: record2, terminal } of plan.finalized) { tx.setTask(terminal); overlay.tasks.set(record2.id, terminal); done.add(record2.id); const outcome = record2.state.outcome; if (outcome.status === "faulted" || outcome.status === "orphaned") { await this.#settleOutcome(tx, record2, outcome); } } if (plan.next.length === 0) return; round = plan.next; } } /** * One finalize round's decision: which of `ids` are `completing` without live ordinary owned work below them, in the * commit's candidates with the round's own finalizations applied in order, and the holders above them to check next. * Tasks finalized have no live work below them, so walks down skip them. It applies the round's terminal records to * `overlay` while it decides and takes them back before it returns, so a run again starts from the same candidates. */ #planRound(ids, overlay, finalized) { const tasks = overlay.tasks; const round = /* @__PURE__ */ new Set(); const done = { has: (id) => round.has(id) || finalized.has(id) }; const ended = []; const next = /* @__PURE__ */ new Set(); try { for (const id of ids) { const staged = tasks.get(id); const record2 = staged ?? this.#liveRecord(id); if (record2?.state.status !== "completing" || done.has(id)) continue; if (this.#hasOwnedLive(id, overlay, done)) continue; const terminal = withState(record2, { status: "terminal", outcome: record2.state.outcome }); tasks.set(id, terminal); round.add(id); ended.push({ record: record2, terminal, staged: staged !== void 0 }); const above = this.#nearestLiveAbove(record2, overlay); if (above?.state.status === "completing") next.add(above.id); } } finally { for (const { record: record2, staged } of ended) { if (staged) tasks.set(record2.id, record2); else tasks.delete(record2.id); } } return { finalized: ended, next: [...next] }; } /** * Load the owner chains of the tasks in `#unloaded` and, with `queued`, of every conversation with queued inputs, * on the Session line; returns the latter. Reads through the line's Storage, so it may run inside a commit callback. */ async #loadScopes(queued) { for (const id of [...this.#unloaded]) { if (this.#unloaded.has(id)) await this.#loadChain({ task: id }); } if (!queued) return []; const submissions = await scanAll((cursor) => this.#session.lineStorage.scanSubmissions({ status: "queued" }, SCAN_PAGE_SIZE3, cursor, this.#context)); const inputs = submissions.filter((submission2) => submission2.type === "input"); const conversations = [...new Set(inputs.map((submission2) => submission2.conversationId))]; for (const id of conversations) await this.#loadChain({ conversation: id }); return conversations; } /** * Load the owner edges and task nodes from `start` up to its ownerless root. A known task outside `#unloaded` ends the * walk, as its chain is loaded; the `#unloaded` tasks passed have a loaded chain afterwards. Loading a node can reveal * a cancelled owner, so it makes the next reconcile derive cascade marks. The nodes it installs stay until the job's * commit is published (`#pin()`). */ async #loadChain(start, overlay) { const passed = []; let installed = false; let at = start; try { while (at !== void 0) { if ("task" in at) { const id = at.task; let node = this.#live.get(id) ?? this.#settled.get(id); if (node === void 0 && overlay?.tasks.has(id) === true) break; if (node !== void 0 && !this.#unloaded.has(id)) break; if (node === void 0) { const record2 = await this.#session.lineStorage.task(id, this.#context); if (record2 === void 0) return; node = nodeOf(record2); if (record2.state.status === "terminal") { this.#settle(id, node); this.#pin(id); installed = true; } this.#scheduleReconcile(); } passed.push(id); at = parentOf(node); } else { let edge = this.#edge(at.conversation, overlay); if (edge === void 0) { const record2 = await this.#session.lineStorage.conversation(at.conversation, this.#context); edge = record2?.owner?.taskId ?? null; this.#setEdge(at.conversation, edge); this.#pin(conversationNode(at.conversation)); installed = true; this.#scheduleReconcile(); } at = edge === null ? void 0 : { task: edge }; } } } finally { if (passed.some((id) => this.#live.has(id)) || installed && this.#liveUnloaded()) { this.#unknownsChanged = true; this.#scheduleReconcile(); } } for (const id of passed) this.#unloaded.delete(id); } /** Whether a live task's chain is not loaded yet: one `#idle()` counts as inside every scope it may be in. */ #liveUnloaded() { for (const id of this.#unloaded) if (this.#live.has(id)) return true; return false; } #setEdge(conversationId, owner) { this.#edges.set(conversationId, owner); this.#relist(conversationNode(conversationId)); this.#dropIfUnneeded(conversationNode(conversationId)); } /** Know a terminal task a chain load read, so walks pass through it; its own chain is not loaded yet. */ #settle(id, node) { this.#settled.set(id, node); this.#unloaded.add(id); this.#relist(id); this.#dropIfUnneeded(id); } /** Queue an ended task or a conversation for the sweep when nothing is listed below it. */ #dropIfUnneeded(node) { if (typeof node !== "string" && this.#live.has(node)) return; if (this.#below.has(node)) return; this.#dropQueue.add(node); this.#scheduleSweep(); } /** * Sweep in a job of its own on the Session line, after the operation underway: a rejected commit, one with nothing to * write, and a read publish nothing, so the sweep at the end of `#observe` would not run for what they loaded. */ #scheduleSweep() { if (this.#sweepScheduled || this.#closing) return; this.#sweepScheduled = true; this.#session.readOnLine(async () => { this.#sweepScheduled = false; this.#sweep(); }).catch(() => { }); } /** * Drop the queued nodes that are still unneeded: not live, nothing listed below. Runs once a publication is fully * tracked, and as a job of its own on the line, never inside an operation: work a commit creates below a chain its * own callback loaded keeps that chain. A flat loop: unlisting already queued every node on the way up that lost its * last listed node. */ #sweep() { for (const node of this.#dropQueue) { if (this.#below.has(node) || this.#pins.has(node)) continue; if (typeof node === "string") { this.#edges.delete(conversationOf(node)); this.#roots.delete(conversationOf(node)); } else if (!this.#live.has(node)) { this.#settled.delete(node); this.#unloaded.delete(node); } } this.#dropQueue.clear(); } /** * Run `decide`, a decision region (group commit §6): synchronous, reading scheduler memory and recording the keys it * reads, staging nothing and changing no scheduler state. When a key it read has an unpublished writer, its result, * or its error, is dropped: once that writer is published, `refresh` reloads what the region needs and it runs * again. A waiting job holds the line, so unpublished writes only shrink, and the runs converge. What a region read * that had no unpublished writer gets none for the rest of the job, so its result stands while the job applies it. */ async #region(decide, refresh) { for (; ; ) { const keys = /* @__PURE__ */ new Set(); this.#reading = keys; let result; try { result = { value: decide() }; } catch (error) { result = { error }; } finally { this.#reading = void 0; } const gates = []; for (const key of keys) { const gate = this.#session.touch(key); if (gate !== void 0) gates.push(gate); } if (gates.length === 0) { if ("error" in result) throw result.error; return result.value; } await Promise.all(gates); await refresh?.(); } } /** Load the chains a region walks, again after it waited: what was published meanwhile may list new work. */ #reloadScopes = () => this.#loadScopes(false); /** In a decision region, note that it reads `key`; anywhere else, nothing. */ #record(key) { this.#reading?.add(key); } /** A live task's committed record, read by a decision region. */ #liveRecord(id) { this.#record(taskKey(id)); return this.#live.get(id); } /** * Keep `node`, which a chain load of the job on the line installed, until that job's commit is published or fails: * work it creates below the node needs it, and the sweep would drop it as unneeded before then. */ #pin(node) { this.#pins.set(node, (this.#pins.get(node) ?? 0) + 1); this.#session.onJobEnd(() => { const count = this.#pins.get(node) - 1; if (count > 0) { this.#pins.set(node, count); return; } this.#pins.delete(node); this.#dropIfUnneeded(node); }); } /** * Bring the scheduler's sets in line with a task's committed record. A task that ended may free the nearest live task * above it: one holding its outcome finalizes, an abort-marked one runs its abort handler; its waiters may run. */ #track(previous, record2) { const id = record2.id; const invocation = this.#invocations.get(id); if (invocation !== void 0) invocation.record = record2; if (record2.state.status === "terminal") { if (previous !== void 0 && !this.#closing) this.#countActive(-1, record2.kind); this.#resumed.delete(id); } else if (previous === void 0 && !this.#closing) { this.#countActive(1, record2.kind); } if (previous?.state.status === "waiting") for (const on of previous.state.on) deleteFrom(this.#waiters, on, id); if (record2.state.status === "terminal") { const node = nodeOf(record2); this.#live.delete(id); this.#sleeping.delete(id); this.#intent.delete(id); this.#runnable.delete(id); this.#finalizeChecks.delete(id); this.#settled.set(id, node); this.#release(node); for (const waiter of this.#waiters.get(id) ?? []) this.#runnable.add(waiter); this.#relist(id); this.#dropIfUnneeded(id); return; } this.#live.set(id, record2); if (previous === void 0) { if (!this.#known(parentNode(record2))) this.#unloaded.add(id); this.#relist(id); } if (cancellationIntent(record2)) this.#intent.add(id); else this.#intent.delete(id); if (record2.state.status === "completing") { this.#runnable.delete(id); this.#finalizeChecks.add(id); } else { this.#runnable.add(id); } if (record2.state.status === "waiting") for (const on of record2.state.on) addTo(this.#waiters, on, id); if (record2.state.status === "sleeping") this.#sleeping.add(id); else this.#sleeping.delete(id); } /** The nearest live task above ended ordinary work may be free now: one holding its outcome, or an aborting one. */ #release(node, overlay) { const above = this.#nearestLiveAbove(node, overlay); if (above?.state.status === "completing") this.#finalizeChecks.add(above.id); else if (above?.abortRequested === true) this.#runnable.add(above.id); return above; } /** * List `start` under its parent node in `#below` while it is a live task or has nodes listed below it, and unlist it * otherwise; then its parent, as far up as that changes. So `#below` holds exactly the tree around live work; a * parent that loses its last listed node is queued for the sweep. A loop, as ownership chains can be deeper than the * call stack. */ #relist(start) { let node = start; for (; ; ) { const needed = typeof node !== "string" && this.#live.has(node) || this.#below.has(node); let parent; if (typeof node === "string") { parent = this.#edges.get(conversationOf(node)); } else { const known = this.#live.get(node) ?? this.#settled.get(node); if (known === void 0) return; parent = parentNode(known); } if (parent === void 0) return; if (parent === null) { const root2 = conversationOf(node); if (needed) this.#roots.add(root2); else this.#roots.delete(root2); return; } if (this.#below.get(parent)?.has(node) === true === needed) return; if (needed) { const first = !this.#below.has(parent); addTo(this.#below, parent, node); if (!first) return; } else { deleteFrom(this.#below, parent, node); if (this.#below.has(parent)) return; this.#dropIfUnneeded(parent); } node = parent; } } /** * The nearest live task above ended ordinary work `node`, in the overlay's view: the one that work held, if any. Work * at or below a background task holds nothing above it. */ #nearestLiveAbove(node, overlay) { this.#record(INTENT_KEY); for (let current = node; !current.background; ) { const owner = current.owner ?? this.#edge(current.conversationId, overlay); if (owner === void 0 || owner === null) return void 0; const record2 = overlay?.tasks.get(owner) ?? this.#liveRecord(owner); if (record2 !== void 0 && record2.state.status !== "terminal") return record2; const above = record2 ?? this.#settled.get(owner); if (above === void 0) return void 0; current = above; } return void 0; } /** Whether the chain above `node` is loaded, without walking: known tasks outside `#unloaded` have a loaded chain. */ #known(node) { if (typeof node !== "string") { return (this.#live.has(node) || this.#settled.has(node)) && !this.#unloaded.has(node); } const owner = this.#edges.get(conversationOf(node)); return owner === null || owner !== void 0 && this.#known(owner); } /** Owner task of a conversation, `null` when ownerless, `undefined` while not loaded. */ #edge(id, overlay) { if (overlay?.edges.has(id) === true) return overlay.edges.get(id); this.#record(conversationKey(id)); return this.#edges.get(id); } #node(id, overlay) { return overlay?.tasks.get(id) ?? this.#live.get(id) ?? this.#settled.get(id); } /** Walk up from `start`: owner tasks and conversations, ending at an ownerless root or an edge not loaded yet. */ *#above(start, overlay) { let at = start; while (at !== void 0) { if ("task" in at) { const node = this.#node(at.task, overlay); if (node === void 0) { yield { unknown: true }; return; } yield { task: at.task, node }; at = parentOf(node); } else { yield { conversation: at.conversation }; const edge = this.#edge(at.conversation, overlay); if (edge === void 0) { yield { unknown: true }; return; } at = edge === null ? void 0 : { task: edge }; } } } /** * The tasks below `start` that a walk down reaches: those `#below` lists and, under an overlay, the commit's new tasks, * with its candidates in place of committed records. Background tasks are reached but not entered, unless * `crossBackground`, and neither are the tasks `enter` rejects. A loop, as chains can be deeper than the call stack. */ *#walkDown(start, options = {}) { const { overlay, crossBackground = false, enter } = options; const links = overlay === void 0 ? void 0 : this.#links(overlay); const pending = [start]; for (let at = pending.pop(); at !== void 0; at = pending.pop()) { this.#record(ownedKey(at)); for (const children2 of [this.#below.get(at), links?.get(at)]) { for (const child of children2 ?? []) { if (typeof child === "string") { pending.push(child); continue; } const record2 = overlay?.tasks.get(child) ?? this.#live.get(child); const node = record2 ?? this.#settled.get(child); if (node === void 0) continue; const live = record2 !== void 0 && record2.state.status !== "terminal" ? record2 : void 0; yield { id: child, node, live }; if ((crossBackground || !node.background) && (enter === void 0 || enter(child))) pending.push(child); } } } } /** * The links walks down need for the commit's new tasks: each one under its parent node, then each node above it that * `#below` does not list yet, until one it lists, a live task (always listed), or the chain's end. */ #links(overlay) { if (overlay.links !== void 0) return overlay.links; const links = /* @__PURE__ */ new Map(); for (const record2 of overlay.tasks.values()) { if (this.#live.has(record2.id) || this.#settled.has(record2.id)) continue; let child = record2.id; let parent = parentNode(record2); while (parent !== void 0 && parent !== null && links.get(parent)?.has(child) !== true) { addTo(links, parent, child); if (typeof parent !== "string" && (this.#live.has(parent) || overlay.tasks.has(parent))) break; let above; if (typeof parent === "string") { above = this.#edge(conversationOf(parent), overlay); } else { const fields = this.#settled.get(parent); above = fields === void 0 ? void 0 : parentNode(fields); } if (above !== void 0 && above !== null && this.#below.get(above)?.has(parent) === true) break; child = parent; parent = above; } } overlay.links = links; return links; } /** * The live ordinary owned work of `owner` (spec §5.5): the live non-background tasks below it that a walk down reaches * without entering a background task; with `first`, only the first. Candidates in `overlay` replace committed records; * the walk does not enter tasks in `done`, which have none. Owner chains must be loaded. */ #ownedLive(owner, options = {}) { const { overlay, first = false, done } = options; const found = []; const walk = this.#walkDown(owner, { ...overlay === void 0 ? {} : { overlay }, ...done === void 0 ? {} : { enter: (id) => !done.has(id) } }); for (const { id, node, live } of walk) { if (live === void 0 || node.background) continue; found.push(id); if (first) break; } return found; } #hasOwnedLive(owner, overlay, done) { const options = { first: true, ...overlay === void 0 ? {} : { overlay }, ...done === void 0 ? {} : { done } }; return this.#ownedLive(owner, options).length > 0; } /** * Whether ordinary traversal from `scope` reaches `start`: walking up reaches the scope's conversation, or an * ownerless one for `roots`, without crossing a background owner, unless `crossBackground`. `undefined` while an * edge is not loaded. */ #inScope(start, scope, crossBackground = false) { this.#record(INTENT_KEY); for (const step of this.#above(start)) { if ("unknown" in step) return void 0; if ("conversation" in step) { if ("conversation" in scope && step.conversation === scope.conversation) return true; } else if (step.node.background && !crossBackground) { return false; } } return "roots" in scope; } /** * Whether a live owner's cancellation intent reaches `start`: walking up finds an owner with intent before a * background owner without it. Terminal owners never cascade (spec §5.4). */ #belowCancelled(start) { return this.#cancellingOwner(start) !== void 0; } /** The nearest live owner above `start` whose cancellation intent reaches it, if any; see `#belowCancelled`. */ #cancellingOwner(start) { this.#record(INTENT_KEY); if (this.#intent.size === 0) return void 0; for (const step of this.#above(start)) { if ("unknown" in step) return void 0; if (!("task" in step)) continue; const live = this.#live.get(step.task); if (live !== void 0 && cancellationIntent(live)) return live; if (step.node.background) return void 0; } return void 0; } /** * Count a task in or out of `pi.durable.tasks.active`. Commit observers count at a commit's publication, which must not * throw, so a throwing adapter is reported. */ #countActive(delta, kind) { try { this.#activeMetric.add(delta, { task: kind }); } catch (error) { this.#report(error, TELEMETRY2); } } /** Fail the Session, which reports it, for a throw in one of the scheduler's own commits; ignored once closing. */ #failSession(error, context) { if (!this.#closing) this.#session.fail(error, context); } /** Close listener: runs synchronously once admission is sealed, before `join()`, or when the Session fails. */ #seal() { for (const record2 of this.#live.values()) this.#countActive(-1, record2.kind); this.#resumed.clear(); this.#closing = true; try { this.#unsubscribeRegistry(); } catch (error2) { this.#report(error2, REGISTRY); } const error = closedError(this.#session); this.#taskWaiters.rejectAll(error); this.#idleWaiters.rejectAll(error); this.#contexts.clear(); this.#idleOrder.clear(); this.#expiry?.disarm(); this.#expiry = void 0; this.#timer?.disarm(); this.#timer = void 0; } /** * Recompute the wake in a microtask the line never waits for, with no order relative to host commit listeners (one * already queued serves later changes too): arm the built-in timer, or tell `onWake` of a change. */ #scheduleWake() { if (this.#wakeScheduled || this.#closing || this.#readOnly) return; this.#wakeScheduled = true; queueMicrotask(() => { this.#wakeScheduled = false; if (this.#closing) return; const wake = this.wake(); if (this.#onWake === void 0) { this.#armTimer(this.#enabled ? wake.at : void 0); return; } const last = this.#lastWake; if (last !== void 0 && last.active === wake.active && last.at === wake.at) return; try { this.#onWake(wake); this.#lastWake = wake; } catch (error) { this.#report(error, ON_WAKE); } }); } /** * Keep the timer for the earliest sleep. It keeps the process alive, unlike the context expiry timer: a process with a * sleeping task has work left, such as a retry backoff. Rechecks the clock when it fires, so a wake before `until` * only arms it again. */ #armTimer(at) { if (this.#timer !== void 0 && this.#timer.at === at) return; this.#timer?.disarm(); this.#timer = void 0; if (at === void 0 || this.#closing) return; const disarm = this.#timers.foreground(() => { this.#timer = void 0; this.#wakeDue(); this.#kick(); }, at - this.#now()); this.#timer = { at, disarm }; } /** Make every sleeping task whose time has come a reservation candidate. */ #wakeDue() { for (const id of this.#sleeping) { const record2 = this.#live.get(id); if (record2 !== void 0 && !this.#notDue(record2)) this.#runnable.add(id); } } /** * The report info of a task's failure, with the extension that installed its definition: the code that failed is * that extension's. Built-in tasks have none. */ /** What a report about `task`'s own code says of it: the task, and the extension that installed its definition. */ #aboutTask(task) { let extension; if ("extension" in task) extension = task.extension; else { try { const snapshot = this.#registry.snapshot(); extension = installerOf(snapshot, snapshot.task(task.kind)); } catch { } } return { task: reportTask(task), ...optionalExtension(extension) }; } /** `settings.restartLimit`, or the default when the host's settings throw. */ #restartLimit() { try { return this.#settings().restartLimit; } catch (error) { this.#report(error, SETTINGS); return DEFAULT_RESTART_LIMIT; } } #notifyBlocked() { for (const listener of [...this.#blockedListeners]) { try { listener(); } catch (error) { this.#report(error, { kind: "listener_error" }); } } } /** A sleep whose `until` the clock has not reached; an abort mark ends a sleep. */ #notDue(record2) { const state = record2.state; return state.status === "sleeping" && !record2.abortRequested && state.until > this.#now(); } #kick() { this.#scheduleWake(); this.#dirty = true; if (this.#draining || !this.#enabled || this.#closing) return; this.#draining = true; queueMicrotask(() => void this.#drain()); } async #drain() { try { while (this.#dirty && this.#enabled && !this.#closing) { this.#dirty = false; for (const reservation of await this.#reserve()) this.#start(reservation); } } catch (error) { this.#failSession(error); } finally { this.#draining = false; if (this.#dirty) this.#kick(); } } /** * Reserve every eligible task in one commit; orphan abort-marked tasks no definition can take, unless abandoned after * a restart. The first pass after open only abort-marks the abandoned tasks, so later passes see the marks. */ async #reserve() { const reservations = []; const faulted = []; let abandoned = []; try { await this.#session.commitInternal(async (tx) => { if (!this.#enabled || this.#closing) return; if (this.#abandoned.size > 0) { const ids = [...this.#abandoned]; const unmarked = await this.#region(() => ids.map((id) => this.#liveRecord(id)).filter((record2) => record2 !== void 0 && !record2.abortRequested)); for (const record2 of unmarked) tx.setTask(withAbortMark(record2, "restart")); abandoned = ids; return; } await this.#loadScopes(false); this.#wakeDue(); const plan = await this.#region(() => this.#planReservation(), this.#reloadScopes); for (const id of plan.dropped) this.#runnable.delete(id); if (plan.cascade) { this.#cascadePending = true; this.#scheduleReconcile(); } let snapshot; for (const record2 of plan.ready) { const id = record2.id; const mode = record2.abortRequested ? "abort" : "run"; if (mode === "run" && this.#held) continue; const cutOff = record2.restarts !== void 0 && record2.restarts >= this.#restartLimit(); if (cutOff && (mode === "abort" || !this.#limitsRestarts(record2.kind))) { const message = mode === "abort" ? `Abort handler of task ${id} (${record2.kind}) was cut off ${record2.restarts} times` : `Task ${id} (${record2.kind}) was cut off ${record2.restarts} times without progress`; await this.#terminate(tx, record2, { status: "faulted", error: { message, detail: { reason: "restart_limit" } } }); faulted.push({ record: record2, message }); continue; } snapshot ??= this.#registry.snapshot(); const resolution = this.#resolve(record2, snapshot); if (resolution.kind === "blocked") { if (mode === "abort" && record2.abortReason === void 0) { await this.#terminate(tx, record2, { status: "orphaned", reason: resolution.reason }); } continue; } if (resolution.record !== record2 || record2.state.status !== "running") { tx.setTask(withState(resolution.record, { status: "running", checkpoint: resolution.record.state.checkpoint })); } this.#runnable.delete(id); const invocation = this.#createInvocation(record2, mode, installerOf(snapshot, resolution.task)); reservations.push({ invocation, task: resolution.task, snapshot }); } }, this.#context); } catch (error) { for (const { invocation } of reservations) { this.#invocations.delete(invocation.taskId); this.#runnable.add(invocation.taskId); invocation.finish(); } throw error; } for (const { record: record2, message } of faulted) { this.#report(new Error(message), { kind: "task_faulted", ...this.#aboutTask(recordTask(record2)), reason: "restart_limit" }); } for (const id of abandoned) this.#abandoned.delete(id); if (abandoned.length > 0) this.#dirty = true; return reservations; } /** * A reservation pass's decision region: the candidates it drops (gone, holding, waiting, asleep, or with an * invocation), whether one waits for a cascade, and the runnable records to resolve. Iterating the candidates is not * keyed: a pass that misses a task a pending commit makes runnable only delays it, as does skipping one with an * invocation without reading its record, or a waiter's check whether what it waits on is still live. */ #planReservation() { const dropped = []; const ready = []; let cascade = false; for (const id of this.#runnable) { if (this.#invocations.has(id)) { dropped.push(id); continue; } const record2 = this.#liveRecord(id); if (record2 === void 0 || record2.state.status === "completing" || this.#waitingOn(record2, true).length > 0 || this.#notDue(record2)) { dropped.push(id); continue; } if (!record2.abortRequested && !record2.background && this.#belowCancelled(parentOf(record2))) { cascade = true; continue; } ready.push(record2); } return { dropped, cascade, ready }; } /** * Live tasks a task waits for before its next invocation: its live ordinary owned work when abort-marked, since * abort runs bottom-up, otherwise the live part of the `on` of a wait. */ #waitingOn(record2, first = false) { if (record2.abortRequested) return this.#ownedLive(record2.id, { first }); if (record2.state.status !== "waiting") return []; if (!first) return record2.state.on.filter((id) => this.#live.has(id)); const live = record2.state.on.find((id) => this.#live.has(id)); return live === void 0 ? [] : [live]; } /** Resolve the record's definition by kind, migrating an older stored version. */ #resolve(record2, snapshot) { const fit = this.#fit(record2, snapshot.task(record2.kind)); if ("reason" in fit) return { kind: "blocked", reason: fit.reason }; if (!fit.migrates) return { kind: "ready", task: fit.task, record: record2 }; const definition = erased(fit.task); try { if (definition.migrate === void 0) throw missingMigration(record2, definition); const migrated = definition.migrate(record2.input, record2.state.checkpoint, record2.version); const state = { ...record2.state, checkpoint: copyJson(migrated.checkpoint) }; const migratedRecord = { ...record2, version: definition.version, input: copyJson(migrated.input), state }; return { kind: "ready", task: fit.task, record: migratedRecord }; } catch (error) { this.#failedMigrations.set(record2.id, { task: fit.task, error }); queueMicrotask(() => this.#notifyBlocked()); this.#scheduleWake(); const extension = installerOf(snapshot, fit.task); this.#report(error, { kind: "migration_failed", ...this.#aboutTask({ ...recordTask(record2), extension }) }); return { kind: "blocked", reason: "migration_failed" }; } } #fit(record2, task) { if (task === void 0) return { reason: "missing_task" }; const version = task.definition.version; if (version === record2.version) return { task, migrates: false }; if (version < record2.version) return { reason: "task_too_old" }; const failed = this.#failedMigrations.get(record2.id); if (failed?.task === task) return { reason: "migration_failed", error: failed.error }; return { task, migrates: true }; } /** Sizes of the ownership indexes, for tests that bound the scheduler's memory. */ get indexSizes() { let below = 0; for (const children2 of this.#below.values()) below += children2.size; return { live: this.#live.size, settled: this.#settled.size, edges: this.#edges.size, below, roots: this.#roots.size, unloaded: this.#unloaded.size, dropQueue: this.#dropQueue.size, pins: this.#pins.size }; } /** * The kept contexts' idle state, for tests (a diagnostic, computed on demand): how many are kept and how many are * idle, how many disagree with a full recompute (an idle period without `#idle()`, or `#idle()` without one; an * expiry order that differs from the idle periods, or is not sorted), and how many idle checks covered every scope. */ get idleState() { let mismatches = 0; let idle = 0; for (const [conversationId, kept] of this.#contexts) { if (kept.idleSince !== void 0) idle++; if (kept.idleSince !== void 0 !== this.#idle(conversationId)) mismatches++; if (this.#idleOrder.get(conversationId) !== kept.idleSince) mismatches++; } let previous = Number.NEGATIVE_INFINITY; for (const [conversationId, idleSince] of this.#idleOrder) { if (idleSince < previous || this.#contexts.get(conversationId)?.idleSince !== idleSince) mismatches++; previous = idleSince; } return { contexts: this.#contexts.size, idleContexts: idle, idleMismatches: mismatches, fullIdleChecks: this.#fullIdleChecks }; } /** * Scheduling state and every live task with its derived state, read on the Session line. Runs no task code: a * pending migration shows as `ready` with `migrates`, and only a migration the scheduler already tried, or one that * cannot exist, shows as failed. */ async inspect(snapshot) { await this.#published(); await this.#loadScopes(false); const tasks = []; for (const record2 of this.#live.values()) tasks.push({ record: record2, state: this.#inspectTask(record2, snapshot) }); const scheduling = this.#closing ? "closing" : this.#enabled && !this.#held ? "running" : "paused"; return { scheduling, tasks }; } #inspectTask(record2, snapshot) { if (this.#invocations.has(record2.id)) return { kind: "running" }; if (record2.state.status === "completing") return { kind: "completing" }; const on = this.#waitingOn(record2); if (on.length > 0) return { kind: "waiting", on }; if (this.#notDue(record2)) return { kind: "sleeping", until: record2.state.until }; const fit = this.#fit(record2, snapshot.task(record2.kind)); if ("reason" in fit) return { kind: "blocked", ...fit }; if (fit.migrates && fit.task.definition.migrate === void 0) { return { kind: "blocked", reason: "migration_failed", error: missingMigration(record2, erased(fit.task)) }; } return { kind: "pending", migrates: fit.migrates }; } #createInvocation(record2, mode, extension) { const controller = new AbortController(); const { promise: done, resolve: finish2 } = Promise.withResolvers(); const invocation = { taskId: record2.id, kind: record2.kind, extension, conversationId: record2.conversationId, mode, controller, links: record2.startedAt === void 0 ? record2.links : void 0, context: withAbortSignal(controller.signal, this.#context), record: record2, watches: /* @__PURE__ */ new Set(), timers: /* @__PURE__ */ new Set(), ended: false, done, finish: () => finish2() }; this.#invocations.set(record2.id, invocation); return invocation; } #start(reservation) { const invocation = reservation.invocation; void (async () => { try { if (invocation.mode === "run") await this.#run(reservation); else await this.#runAbort(reservation); } catch (error) { this.#report(error, { kind: "task_error", ...this.#aboutTask(invocation) }, invocation.context); } finally { this.#end(invocation); invocation.record = void 0; invocation.links = void 0; invocation.fault = void 0; invocation.finish(); this.#kick(); } })(); } /** Run phase handlers, each preceded by a step that decides on the line whether the invocation continues. */ async #run(reservation) { const invocation = reservation.invocation; const state = { task: reservation.task, snapshot: reservation.snapshot, reported: void 0 }; const phase = { snapshot: () => state.snapshot, task: () => state.task, agent: void 0 }; const runtime = this.#runtime(invocation, phase); const progress = (tx, current2) => this.#progress(tx, current2, state, invocation.extension, invocation.context); let current = await this.#step(invocation, (task) => this.#rules(task, void 0), progress); while (current !== void 0 && !this.#closing) { const task = current; current = await this.#phase(invocation, task, async () => { const checkpoint = task.state.checkpoint; phase.agent = void 0; let previous; try { await erased(state.task).phases[checkpoint.phase](task, runtime, invocation.context); previous = { checkpoint }; } catch (error) { previous = { checkpoint, failure: { error } }; } return this.#step(invocation, (next) => this.#rules(next, previous), progress); }); } } /** A phase owns its decision commit, not the next handler, a durable wait, or recovery. */ #phase(invocation, task, run2) { const parent = task.trace === void 0 ? this.#telemetry : this.#telemetry.propagation?.extract(task.trace) ?? this.#telemetry; const checkpoint = task.state.checkpoint; const attempt = checkpoint.attempt; const resumed = this.#resumed.delete(task.id); return createTypedSpanStarter(parent, [DURABLE_TELEMETRY_SCHEMA])("pi.durable.phase", { "task.id": task.id, "task.name": task.kind, phase: checkpoint.phase, kind: invocation.mode, resumed, ...attempt === void 0 ? {} : { attempt } }, async (span) => { if (span.propagation !== void 0 && invocation.links !== void 0) { for (const carrier of invocation.links) span.addLink({ context: span.propagation.extract(carrier) }); } const previous = invocation.context; invocation.context = withTelemetry(span, previous); const started = performance.now(); try { return await run2(); } finally { invocation.context = previous; const state = invocation.record.state; const terminal = invocation.fault ?? (state.status === "terminal" || state.status === "completing" ? state.outcome : void 0); const outcome = terminal !== void 0 ? terminal.status === "completed" ? "done" : terminal.status === "aborted" ? "aborted" : "faulted" : state.status === "waiting" || state.status === "sleeping" ? state.status : invocation.ended || this.#closing ? "aborted" : `next=${state.checkpoint.phase}`; span.setAttributes({ outcome }); if (terminal?.status === "faulted" || terminal?.status === "failed" || terminal?.status === "orphaned") { const message = terminal.status === "orphaned" ? terminal.reason : terminal.error.message; span.setStatus({ status: "error", error: { name: `task_${terminal.status}`, message: ownedSlice2(message, 0, 500) } }); } else if (outcome === "aborted") { span.setStatus({ status: "ok" }); } this.#durationMetric.record(performance.now() - started, { task: task.kind, phase: checkpoint.phase, outcome }, span); } }); } /** * Precedence rules for a run invocation, a decision region of its step. Rules 1 (terminal, `completing`, `waiting`, or * `sleeping`) and 2 (closing) are applied by `#step`. Progress (rule 5) is decided by `#progress()` once the decision * stands, as it takes a registry snapshot. */ #rules(current, previous) { if (current.abortRequested) return END; if (!current.background && this.#belowCancelled(parentOf(current))) return CASCADE; if (previous === void 0) return CONTINUE; if (previous.failure !== void 0) return { kind: "fault", error: previous.failure.error }; if (jsonEqual(current.state.checkpoint, previous.checkpoint)) { const message = `Task ${current.kind} phase ${previous.checkpoint.phase} returned without durable progress`; return { kind: "fault", error: new Error(message) }; } return PROGRESS; } /** * Rule 5, progress: refresh the snapshot; hand over to a replacement definition that can take the task, writing it * back as `pending`, or keep running under the old one, reported once per resolved definition. Returns whether the * invocation continues. */ #progress(tx, current, state, extension, context) { if (this.#held) { tx.setTask(withState(current, { status: "pending", checkpoint: current.state.checkpoint })); return false; } state.snapshot = this.#registry.snapshot(); const next = state.snapshot.task(current.kind); if (next === state.task) return true; if (next !== void 0 && canReserve(next, current)) { tx.setTask(withState(current, { status: "pending", checkpoint: current.state.checkpoint })); return false; } if (state.reported === void 0 || state.reported.task !== next) { state.reported = { task: next }; this.#report(new Error(`Task ${current.id} keeps running under its old ${current.kind} definition`), { kind: "task_outdated", ...this.#aboutTask({ ...recordTask(current), extension }), reason: outdatedReason(next, current) }, context); } return true; } /** Run the abort handler once; rules 1, 2, and 4 apply, and returning without an outcome faults. */ async #runAbort(reservation) { const invocation = reservation.invocation; const current = this.#live.get(invocation.taskId); if (current === void 0 || this.#closing) return; await this.#phase(invocation, current, async () => { let failure; try { const phase = { snapshot: () => reservation.snapshot, task: () => reservation.task, agent: void 0 }; const runtime = this.#runtime(invocation, phase); await erased(reservation.task).abort(current, runtime, invocation.context); } catch (error) { failure = { error }; } const message = `Abort handler of task ${invocation.taskId} returned without a terminal outcome`; await this.#step(invocation, () => ({ kind: "fault", error: failure?.error ?? new Error(message) })); }); } /** * One decision on the Session line, before a phase: a decision region (`#region()`) that reads the task's record. A * task that is no longer running (rule 1: terminal, `completing`, `waiting`, or `sleeping`) or a closing Harness (rule * 2) ends the invocation without a write; otherwise `plan` decides, and a decision that stands is applied: the * invocation continues, with `progress` for rule 5, or ends, with a fault written. Ending happens inside the callback, * before a fault's Harness cleanup. A rejected step, such as admission after close, also ends the invocation. Its * outcome goes to the invocation only, so it is a scheduler commit (`commitInternal()`). */ async #step(invocation, plan, progress) { let fault; try { const continued = await this.#session.commitInternal(async (tx) => { const decided = await this.#region(() => { const found = this.#liveRecord(invocation.taskId); if (found?.state.status !== "running" || this.#closing) return { plan: END }; const current2 = found; return { current: current2, plan: plan(current2) }; }); const { current } = decided; const step = decided.plan; if (step.kind === "continue") return current; if (step.kind === "progress" && progress(tx, current)) return current; this.#end(invocation); if (step.kind === "cascade") { this.#cascadePending = true; this.#scheduleReconcile(); } if (step.kind === "fault") { fault = { error: step.error }; const message = errorMessage(step.error); invocation.fault = { status: "faulted", error: { message } }; await this.#terminate(tx, current, invocation.fault); } return void 0; }, withoutAbortSignal(invocation.context)); if (fault !== void 0) this.#report(fault.error, { kind: "task_faulted", ...this.#aboutTask(invocation), reason: "threw" }, invocation.context); return continued; } catch (error) { this.#end(invocation); this.#failSession(error, invocation.context); return void 0; } } /** * Write an outcome the scheduler decided. While the task's ordinary owned work is live it holds as `completing` and * its Harness cleanup waits for the final commit (spec §5.5, rule 4); otherwise it is terminal with its cleanup. */ async #terminate(tx, record2, outcome) { await this.#loadScopes(false); if (await this.#region(() => this.#hasOwnedLive(record2.id, overlayOf(tx)), this.#reloadScopes)) { tx.setTask(withState(record2, { status: "completing", outcome })); return; } tx.setTask(withState(record2, { status: "terminal", outcome })); await this.#settleOutcome(tx, record2, outcome); } /** * Replace a running task's state with what it committed. A terminal state holds as `completing` while ordinary owned * work is live, judged on the commit's candidates, so work the same commit creates below the task counts. A wait is * validated first. */ async #commitState(tx, invocation, current, next) { if (next.status === "waiting") await this.#validateWait(tx, invocation, current, next.on, next.policy); if (next.status === "sleeping") validateSleep(invocation, current.id, next.until); if (next.status === "terminal") { const overlay = overlayOf(tx); await this.#loadScopes(false); for (const record2 of overlay.tasks.values()) await this.#loadChain(parentOf(record2), overlay); if (await this.#region(() => this.#hasOwnedLive(current.id, overlayOf(tx)), this.#reloadScopes)) { tx.setTask(withState(progressed(current), { status: "completing", outcome: next.outcome })); return; } } const same = jsonEqual(current.state, next); tx.setTask(withState(same ? current : progressed(current), next)); } /** * A wait names existing tasks, and `failFast` only tasks the waiter owns. It may not close a cycle in the wait-for * graph, whose edges run from each task to what it waits on and to its live ordinary owned work (spec §5.5); waiting * on itself or on an owner holding it are the shortest such cycles. An abort handler cannot wait. * * Invariant: the wait-for graph is acyclic. Only a wait adds an edge between existing tasks, as a new task has only * incoming edges, so checking each wait maintains it. */ async #validateWait(tx, invocation, current, on, policy) { if (invocation.mode === "abort") throw new Error(`Abort handler of task ${current.id} cannot wait`); await this.#loadScopes(false); const ended = /* @__PURE__ */ new Map(); for (; ; ) { const checked = await this.#region(() => this.#checkWait(current, on, policy, overlayOf(tx), ended), this.#reloadScopes); if (checked === void 0) return; if ("missing" in checked) { for (const id of checked.missing) ended.set(id, await this.#session.lineStorage.task(id, this.#context) ?? null); continue; } throw checked.error; } } /** * `#validateWait()`'s decision region: why the wait is refused, `undefined` when it may be stored, or the tasks in * `on` it needs from Storage first, when `ended` lacks them. */ #checkWait(current, on, policy, overlay, ended) { const targets = []; const missing = []; for (const id of on) { const member = overlay.tasks.get(id) ?? this.#liveRecord(id) ?? ended.get(id); if (member === void 0) { missing.push(id); continue; } if (member === null) return { error: new Error(`Task ${id} does not exist`) }; if (policy === "failFast" && member.owner !== current.id) { return { error: new Error(`Task ${current.id} can wait failFast only on tasks it owns; ${id} is not one`) }; } if (member.state.status !== "terminal" && member.owner !== current.id) targets.push(id); } if (missing.length > 0) return { missing }; if (targets.length === 0) return void 0; const cycle = this.#waitCycle(current.id, targets, overlay); if (cycle === void 0) return void 0; return { error: new Error(`Task ${current.id} cannot wait on ${cycle[0]}: ${[current.id, ...cycle].join(" -> ")} is a wait cycle`) }; } /** * The path from a member of `on` back to `task` in the wait-for graph, if the wait would close a cycle (spec §5.1). * An edge X -> Y means X cannot settle before Y: Y is in the `on` of waiting X that is not abort-marked, or Y is live * ordinary owned work of X, which holds X as `completing`. Each task is visited once and each subtree walked once, * so a check is bounded by the live work it reaches. Part of a decision region: it records every record it reads. * Owned work is found by walks down, which need the chains of every live task loaded. */ #waitCycle(task, on, overlay) { const from = /* @__PURE__ */ new Map(); const walked = /* @__PURE__ */ new Set(); const pending = []; const reach = (id, via) => { if (from.has(id)) return; from.set(id, via); pending.push(id); }; const path = (end) => { const steps = []; for (let at = end; at !== void 0; at = from.get(at)) steps.unshift(at); return steps; }; for (const id of on) reach(id, void 0); for (let id = pending.pop(); id !== void 0; id = pending.pop()) { if (id === task) return path(id); const record2 = overlay.tasks.get(id) ?? this.#liveRecord(id); if (record2 === void 0 || record2.state.status === "terminal") continue; if (record2.state.status === "waiting" && !record2.abortRequested) { for (const member of record2.state.on) reach(member, id); } if (walked.has(id)) continue; walked.add(id); const found = []; const walk = this.#walkDown(id, { overlay, enter: (below) => !walked.has(below) }); for (const { id: below, node, live } of walk) { if (live === void 0 || node.background) continue; found.push(below); reach(below, id); } for (const below of found) walked.add(below); } return void 0; } /** End an invocation: its runtime operations reject from now on, its signal aborts, its watches stop, and its task is free. */ #end(invocation) { if (invocation.ended) return; invocation.ended = true; if (this.#invocations.get(invocation.taskId) === invocation) { this.#invocations.delete(invocation.taskId); if (this.#live.has(invocation.taskId)) this.#runnable.add(invocation.taskId); } for (const watch of invocation.watches) void watch.stop(); for (const disarm of invocation.timers) disarm(); invocation.timers.clear(); invocation.controller.abort(endedError(invocation)); } /** No live non-background task in the scope; a task whose owner edges are not loaded yet counts as inside. */ #idle(conversationId) { const scope = conversationId === void 0 ? { roots: true } : { conversation: conversationId }; if (conversationId === void 0) this.#record(ROOTS_KEY); for (const id of this.#unloaded) { const record2 = this.#live.get(id); if (record2 !== void 0 && !record2.background && this.#inScope(parentOf(record2), scope) !== false) return false; } const starts = conversationId === void 0 ? this.#roots : [conversationId]; for (const start of starts) { for (const { node, live } of this.#walkDown(conversationNode(start))) { if (live !== void 0 && !node.background) return false; } } return true; } // ─── Invocation runtime ────────────────────────────────────────────────── #runtime(invocation, phase) { const agent = () => { if (invocation.ended) return Promise.reject(endedError(invocation)); if (phase.agent === void 0) { phase.agent = this.#agent(invocation.conversationId, phase.snapshot(), invocation.context); phase.agent.catch(() => { }); } return phase.agent; }; const hooks = { each: async (name, invoke) => { for (const { extension, handlers } of agentHooks(await agent(), phase.task().definition.name)) { const handler = handlers[name]; if (typeof handler !== "function") continue; try { await invoke(handler.bind(handlers)); } catch (error) { if (invocation.controller.signal.aborted) throw error; this.#report(error, { kind: "extension_error", extension, task: reportTask(invocation) }, invocation.context); } } } }; const settings = this.#settings; const models2 = this.#models; return { taskId: invocation.taskId, conversationId: invocation.conversationId, signal: invocation.controller.signal, get models() { return models2(); }, agent: (context) => invocation.ended ? Promise.reject(endedError(invocation)) : awaitWithContext(agent(), context), get settings() { return settings(); }, env: (context) => this.#read(invocation, () => this.#env(invocation.conversationId, context)), hooks, get registry() { return phase.snapshot(); }, commit: async (change, context, options) => { let checkpoint; let interrupted2; await this.#gated(invocation, async (tx, current) => { const next = await change(tx, current); if (next !== void 0) await this.#commitState(tx, invocation, current, next); if (next?.status === "running" && current.state.checkpoint.phase !== next.checkpoint.phase) { checkpoint = next.checkpoint.phase; } if (next?.status === "terminal") { const restarts = current.restarts ?? 0; const reason = next.outcome.status === "failed" ? interruptionOf(next.outcome.error) : void 0; if (reason !== void 0 && next.outcome.status === "failed") { interrupted2 = { message: next.outcome.error.message, reason, restarts }; } else { const message = endedByCrashesStates.get(next); if (message !== void 0) interrupted2 = { message, reason: "restart_limit", restarts }; } } }, context, options); if (interrupted2 !== void 0) { this.#report(new Error(interrupted2.message), { kind: "task_interrupted", ...this.#aboutTask(invocation), reason: interrupted2.reason, restarts: interrupted2.restarts }, invocation.context); } if (checkpoint !== void 0) { invocation.context.telemetry.addEvent("pi.durable.checkpoint", { phase: checkpoint }); } }, memo: ((name, ...rest) => { if (rest.length === 1) { return this.#read(invocation, async () => memoOf(this.#live.get(invocation.taskId), name)); } const candidate = rest[0]; return this.#gated(invocation, (tx, current) => { const winner = memoOf(current, name); if (winner !== void 0) return winner; tx.setTask({ ...current, memos: { ...current.memos, [name]: candidate } }); return candidate; }, rest[1]); }), sleepUntil: (until2, checkpoint, context) => this.#gated(invocation, (tx, current) => this.#commitState(tx, invocation, current, { status: "sleeping", checkpoint, until: until2 }), context), watchDoc: ((...args) => this.#watchDoc(invocation, args)), snapshot: ((...args) => this.#read(invocation, () => sessionMethod(this.#session, "snapshot")(...args))), snapshotAsOf: ((...args) => this.#read(invocation, () => sessionMethod(this.#session, "snapshotAsOf")(...args))), getTask: ((id, context) => this.#read(invocation, () => this.#session.readOnLine((storage2) => storage2.task(id, context)))), waitForTask: ((id, context) => this.#read(invocation, () => this.waitForTask(id, withAbortSignal(invocation.controller.signal, context)))), ownedTasks: (context) => this.#read(invocation, () => this.#session.readOnLine(async () => { if (invocation.ended) throw endedError(invocation); context.abortSignal?.throwIfAborted(); const owned = await this.#region(() => { this.#record(ownedKey(invocation.taskId)); const records = []; for (const node of this.#below.get(invocation.taskId) ?? []) { const record2 = typeof node === "string" ? void 0 : this.#liveRecord(node); if (record2?.owner === invocation.taskId) records.push(record2); } return records; }); return owned.sort((a, b) => a.id - b.id).map((record2) => copyJson(record2)); })), abortOwned: (id, context) => this.#read(invocation, async () => { const bound = withAbortSignal(invocation.controller.signal, context); const record2 = await this.#session.readOnLine((storage2) => storage2.task(id, bound)); if (record2?.owner !== invocation.taskId) { throw new Error(`Task ${id} is not owned by task ${invocation.taskId}`); } if (record2.state.status === "terminal") return; await this.abort(id, bound, true); await this.waitForTask(id, bound); }), outcomes: ((ids, context) => this.#read(invocation, () => this.#session.readOnLine(async (storage2) => { const outcomes = []; for (const id of ids) { const state = (await storage2.task(id, context))?.state; if (state?.status !== "terminal") throw new Error(`Task ${id} is not terminal`); outcomes.push(state.outcome); } return outcomes; }))), conversation: (id, context) => this.#read(invocation, () => this.#conversation(id, { signal: invocation.controller.signal, check: () => { if (invocation.ended) throw endedError(invocation); } }, context)), entry: ((...args) => { const [token, id, context] = args.length === 2 ? [void 0, args[0], args[1]] : [args[0], args[1], args[2]]; return this.#read(invocation, async () => { const kept = this.#contexts.get(invocation.conversationId); const entry2 = (kept === void 0 ? void 0 : rangeEntry(kept.range, id)) ?? (await this.#session.readOnLine((storage2) => storage2.entry(invocation.conversationId, id, context)))?.entry; return token === void 0 || entry2?.kind === token.kind ? entry2 : void 0; }); }), entries: (query, limit, cursor, context) => this.#read(invocation, () => this.#session.readOnLine((storage2) => storage2.scanEntries(conversationEntries(invocation.conversationId, query), limit, cursor, context))), context: (conversationId, context, options) => this.#read(invocation, async () => { const read2 = await readContextFrom(this.#session, this.#storage, conversationId, context, options?.at, this.#contexts.get(conversationId)?.range); const { view: view2 } = read2; const range = read2.range?.current === true && this.#session.knownLatestEntryId(conversationId) !== read2.range.bounds.tail ? { ...read2.range, current: false } : read2.range; const kept = this.#contexts.get(conversationId); if (range !== void 0 && !this.#closing && !invocation.ended && (kept === void 0 || kept.range.bounds.tail <= range.bounds.tail)) { if (!this.#idle(conversationId)) { this.#contexts.set(conversationId, { range, idleSince: void 0 }); this.#idleOrder.delete(conversationId); } else if (this.#contextRetentionMs() > 0) { const idleSince = kept?.idleSince ?? this.#now(); const expiresAt = kept?.idleSince !== void 0 && kept.expiresAt !== void 0 ? kept.expiresAt : idleSince + this.#contextRetentionMs(); this.#contexts.set(conversationId, { range, idleSince, expiresAt }); if (kept?.idleSince === void 0) this.#markIdle(conversationId, idleSince); this.#scheduleExpiry(); } } return view2; }), now: () => { if (invocation.ended) throw endedError(invocation); return this.#now(); }, setTimer: (callback, delayMs) => { if (invocation.ended) return () => { }; const disarm = this.#timers.foreground(() => { invocation.timers.delete(disarm); if (!invocation.ended) callback(); }, delayMs); invocation.timers.add(disarm); return () => { invocation.timers.delete(disarm); disarm(); }; }, report: (error, from) => { if (invocation.ended) throw endedError(invocation); const task = reportTask(invocation); this.#report(error, from === void 0 ? { kind: "task_error", ...this.#aboutTask(invocation) } : from.kind === "extension" ? { kind: "extension_error", task, ...optionalExtension(from.extension) } : { kind: "host_error", callback: from.callback, task }, invocation.context); } }; } /** * On the line: wait until every commit admitted before is published, for a decision that reads what cannot be keyed, * every live task (open, inspection) or every task a conversation abort reaches. */ async #published() { const gate = this.#session.touch(ALL_KEYS); if (gate !== void 0) await gate; } /** Run a committed-state read unless the invocation has ended. */ async #read(invocation, read2) { if (invocation.ended) throw endedError(invocation); return read2(); } /** Commit after rereading the task on the line and gating the invocation. */ #gated(invocation, change, context, options) { if (invocation.ended) return Promise.reject(endedError(invocation)); return this.#session.commitWith(async (tx) => { if (invocation.ended) throw endedError(invocation); if (this.#closing) throw closedError(this.#session); const current = await this.#region(() => { const found = this.#liveRecord(invocation.taskId); if (found === void 0) throw new Error(`Task ${invocation.taskId} is terminal`); if (found.state.status !== "running") throw new Error(`Task ${invocation.taskId} is ${found.state.status}`); if (invocation.mode === "run" && found.abortRequested) { throw new Error(`Task ${invocation.taskId} has a durable abort mark`); } return found; }); if (invocation.ended) throw endedError(invocation); return change(tx, current); }, context, { conversationId: invocation.conversationId, taskId: invocation.taskId }, options); } async #watchDoc(invocation, args) { if (invocation.ended) throw endedError(invocation); const watch = await sessionMethod(this.#session, "watchDoc")(...args); if (watch === void 0) return void 0; if (invocation.ended) { void watch.stop(); throw endedError(invocation); } invocation.watches.add(watch); void watch.closed.then(() => invocation.watches.delete(watch)); return watch; } }; var REGISTRY = { kind: "host_error", callback: "registry" }; var SETTINGS = { kind: "host_error", callback: "settings" }; var ON_WAKE = { kind: "host_error", callback: "onWake" }; var TELEMETRY2 = { kind: "host_error", callback: "telemetry" }; function installerOf(snapshot, task) { if (task === void 0) return void 0; return snapshot.installed().findLast((installed) => installed.tasks?.includes(task) === true)?.name; } function reportTask(task) { return { id: task.taskId, kind: task.kind, conversationId: task.conversationId }; } function recordTask(record2) { return { taskId: record2.id, kind: record2.kind, conversationId: record2.conversationId }; } var endedByCrashesStates = /* @__PURE__ */ new WeakMap(); function endedByCrashes(state, message) { endedByCrashesStates.set(state, message); return state; } function interruptionOf(error) { const detail = error.detail; if (typeof detail !== "object" || detail === null || !("reason" in detail)) return void 0; return detail.reason === "restart_limit" || detail.reason === "interrupted" ? detail.reason : void 0; } function withAbortMark(record2, reason) { const { abortReason: _reason, restarts: _restarts, ...rest } = record2; return { ...rest, abortRequested: true, ...reason === void 0 ? {} : { abortReason: reason } }; } function cancellationIntent(record2) { return record2.state.status !== "terminal" && (record2.abortRequested || failedOutcome(record2)); } function intentChanges(previous, next) { return (previous?.abortRequested ?? false) !== next.abortRequested || previous?.abortReason !== next.abortReason || (previous !== void 0 && failedOutcome(previous)) !== failedOutcome(next) || (previous !== void 0 && cancellationIntent(previous)) !== cancellationIntent(next); } function failedOutcome(record2) { const state = record2.state; return (state.status === "completing" || state.status === "terminal") && state.outcome.status !== "completed"; } function parentOf(node) { return node.owner !== void 0 ? { task: node.owner } : { conversation: node.conversationId }; } function nodeOf(record2) { return { conversationId: record2.conversationId, ...record2.owner === void 0 ? {} : { owner: record2.owner }, background: record2.background }; } function overlayOf(tx) { const tasks = new Map(tx.stagedTasks().map((record2) => [record2.id, record2])); const edges = new Map(tx.stagedConversations().map((record2) => [record2.id, record2.owner?.taskId ?? null])); return { tasks, edges }; } function addTo(map2, key, value2) { const values = map2.get(key); if (values === void 0) map2.set(key, /* @__PURE__ */ new Set([value2])); else values.add(value2); } function deleteFrom(map2, key, value2) { const values = map2.get(key); if (values === void 0) return; values.delete(value2); if (values.size === 0) map2.delete(key); } function memoOf(record2, name) { const memos = record2?.memos; return memos !== void 0 && Object.hasOwn(memos, name) ? memos[name] : void 0; } function sessionMethod(session, name) { return session[name].bind(session); } function missingMigration(record2, definition) { return new Error(`Task ${record2.kind} version ${definition.version} has no migration from ${record2.version}`); } function erased(task) { return task.definition; } function endedError(invocation) { return new Error(`Task ${invocation.taskId} invocation has ended`); } function progressed(record2) { if (record2.restarts === void 0) return record2; const { restarts: _restarts, ...rest } = record2; return rest; } function withState(record2, state) { if (state.status !== "terminal" && state.status !== "completing") return { ...record2, state }; const { memos: _memos, restarts: _restarts, ...rest } = record2; return { ...rest, state }; } function outdatedReason(task, record2) { if (task === void 0) return "missing_task"; return task.definition.version < record2.version ? "task_too_old" : "migration_failed"; } function canReserve(task, record2) { const definition = task.definition; return definition.version === record2.version || definition.version > record2.version && definition.migrate !== void 0; } function validateSleep(invocation, task, until2) { if (invocation.mode === "abort") throw new Error(`Abort handler of task ${task} cannot sleep`); if (typeof until2 !== "number" || !Number.isFinite(until2) || until2 < 0) { throw new Error(`Task ${task} cannot sleep until ${until2}: not a time in milliseconds`); } } function jsonEqual(left, right) { if (left === right) return true; if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false; if (Array.isArray(left) || Array.isArray(right)) { if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false; return left.every((value2, index) => jsonEqual(value2, right[index])); } const keys = Object.keys(left); if (keys.length !== Object.keys(right).length) return false; return keys.every((key) => Object.hasOwn(right, key) && jsonEqual(left[key], right[key])); } // node_modules/@earendil-works/chord/dist/output.js var NEWLINE = 10; var INVALID_OUTPUT = /[\x00-\x08\x0b-\x1f\ufff9-\ufffb]/g; var ESCAPE_SEQUENCE = /\x1b[\]PX^_][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]|\x1b[\x20-\x2f]*[\x30-\x7e]/g; var encoder3 = new TextEncoder(); var decoder3 = new TextDecoder("utf-8", { ignoreBOM: true }); function sanitizeOutput(text) { return text.replace(ESCAPE_SEQUENCE, "").replace(INVALID_OUTPUT, ""); } function boundOutput(text, limits) { const bytes2 = encoder3.encode(text); const [from, to] = limits.retain === "head" ? headRange(bytes2, limits) : tailRange(bytes2, limits); const kept = bytes2.subarray(from, to); return { text: kept.length === bytes2.length ? text : decoder3.decode(kept), bytes: kept.length, droppedBytes: bytes2.length - kept.length, droppedLines: lineCount(bytes2) - lineCount(kept) }; } function headRange(bytes2, limits) { if (limits.maxLines === 0 || limits.maxBytes === 0) return [0, 0]; let end = bytes2.length; let lines2 = 0; for (let index = bytes2.indexOf(NEWLINE); index !== -1; index = bytes2.indexOf(NEWLINE, index + 1)) { if (++lines2 === limits.maxLines) { end = index + 1; break; } } if (end > limits.maxBytes) { const newline = bytes2.lastIndexOf(NEWLINE, limits.maxBytes - 1); end = newline === -1 ? characterEnd(bytes2, limits.maxBytes) : newline + 1; } return [0, end]; } function tailRange(bytes2, limits) { if (limits.maxLines === 0 || limits.maxBytes === 0) return [bytes2.length, bytes2.length]; const last = bytes2[bytes2.length - 1] === NEWLINE ? bytes2.length - 2 : bytes2.length - 1; let start = 0; let lines2 = 1; for (let index = last < 0 ? -1 : bytes2.lastIndexOf(NEWLINE, last); index !== -1; ) { if (lines2 === limits.maxLines) { start = index + 1; break; } lines2++; index = index === 0 ? -1 : bytes2.lastIndexOf(NEWLINE, index - 1); } if (bytes2.length - start > limits.maxBytes) { const from = bytes2.length - limits.maxBytes; const newline = bytes2.indexOf(NEWLINE, from - 1); start = newline !== -1 && newline + 1 < bytes2.length ? newline + 1 : characterStart(bytes2, from); } return [start, bytes2.length]; } function characterEnd(bytes2, index) { let end = index; while (end > 0 && ((bytes2[end] ?? 0) & 192) === 128) end--; return end; } function characterStart(bytes2, index) { let start = index; while (start < bytes2.length && ((bytes2[start] ?? 0) & 192) === 128) start++; return start; } function lineCount(bytes2) { if (bytes2.length === 0) return 0; let newlines = 0; for (let index = bytes2.indexOf(NEWLINE); index !== -1; index = bytes2.indexOf(NEWLINE, index + 1)) newlines++; return newlines + (bytes2[bytes2.length - 1] === NEWLINE ? 0 : 1); } var OutputBuffer = class { #limits; /** * Decoder of byte chunks. A string chunk or a skip ends an incomplete character of earlier bytes (it becomes U+FFFD); * the next byte chunk then starts a new character. As in `StreamDecoder`, only a byte-order mark at the very start of * the output is dropped, never a U+FEFF later in it. */ #decoder = new TextDecoder("utf-8", { ignoreBOM: true }); #started = false; /** Stored chunks: for head the start of the stream, for tail a suffix that still contains the next window. */ #chunks = []; #storedBytes = 0; #storedNewlines = 0; #full = false; #totalBytes = 0; #totalNewlines = 0; #endsWithNewline = true; constructor(limits) { this.#limits = limits; } /** Bytes currently held; bounded by the limits plus one chunk. */ get storedBytes() { return this.#storedBytes; } /** * Accept a chunk; returns whether anything was accepted. `skipped` is output omitted right before the chunk, which * must be more than the tail window by at least one byte or line; only tail retention accepts it. */ push(chunk, skipped) { const pending = typeof chunk === "string" || skipped !== void 0 ? this.#decoder.decode() : ""; let text = typeof chunk === "string" ? chunk : this.#decoder.decode(chunk, { stream: true }); const first = !this.#started && pending === "" && skipped === void 0; if (pending !== "" || text !== "" || skipped !== void 0) this.#started = true; if (first && typeof chunk !== "string" && text.startsWith("\uFEFF")) text = text.slice(1); if (skipped === void 0) return this.#accept(pending + text); if (this.#limits.retain !== "tail") throw new Error("Skipped output requires tail retention"); this.#accept(pending); this.#skip(skipped); this.#accept(text); return true; } /** Count omitted output; nothing stored before it can be in the window once the text after it arrives. */ #skip(skipped) { if (skipped.bytes === 0) return; this.#totalBytes += skipped.bytes; this.#totalNewlines += skipped.newlines; this.#endsWithNewline = skipped.endsWithNewline; this.#chunks = []; this.#storedBytes = 0; this.#storedNewlines = 0; } /** Flush an incomplete trailing character as a replacement character; call when the stream ends. */ end() { this.#accept(this.#decoder.decode()); } #accept(text) { if (text.length === 0) return false; const bytes2 = utf8ByteLength(text); const newlines = countNewlines(text); this.#totalBytes += bytes2; this.#totalNewlines += newlines; this.#endsWithNewline = text.endsWith("\n"); if (this.#full) return true; this.#chunks.push({ text, bytes: bytes2, newlines }); this.#storedBytes += bytes2; this.#storedNewlines += newlines; if (this.#limits.retain === "head") { this.#full = this.#storedBytes > this.#limits.maxBytes || this.#storedNewlines >= this.#limits.maxLines; return true; } while (this.#chunks.length > 1) { const first = this.#chunks[0]; const bytesAfter = this.#storedBytes - first.bytes; const newlinesAfter = this.#storedNewlines - first.newlines; if (bytesAfter <= this.#limits.maxBytes + 1 && newlinesAfter <= this.#limits.maxLines + 1) break; this.#chunks.shift(); this.#storedBytes = bytesAfter; this.#storedNewlines = newlinesAfter; } return true; } /** Retained, sanitized output and what the limits dropped from the whole stream. */ snapshot() { const stored = this.#chunks.length === 1 ? this.#chunks[0].text : this.#chunks.map((chunk) => chunk.text).join(""); const kept = boundOutput(stored, this.#limits); const storedLines = lines(this.#storedNewlines, stored === "" || stored.endsWith("\n")); const keptLines = storedLines - kept.droppedLines; if (this.#limits.retain === "tail" || this.#chunks.length > 1) { const text = this.#limits.retain === "tail" ? tailMargin(stored, this.#limits) : stored; const bytes2 = this.#limits.retain === "tail" ? utf8ByteLength(text) : this.#storedBytes; this.#chunks = text === "" ? [] : [{ text, bytes: bytes2, newlines: countNewlines(text) }]; this.#storedBytes = bytes2; this.#storedNewlines = this.#chunks[0]?.newlines ?? 0; } return { text: sanitizeOutput(kept.text), droppedBytes: this.#totalBytes - kept.bytes, droppedLines: lines(this.#totalNewlines, this.#endsWithNewline) - keptLines }; } }; function tailMargin(text, limits) { const bytes2 = encoder3.encode(text); const byteStart = bytes2.length > limits.maxBytes ? characterEnd(bytes2, bytes2.length - limits.maxBytes - 1) : 0; let lineStart = 0; let newlines = 0; for (let index = bytes2.lastIndexOf(NEWLINE); index !== -1; index = bytes2.lastIndexOf(NEWLINE, index - 1)) { if (++newlines > limits.maxLines) { lineStart = index; break; } if (index === 0) break; } const start = Math.max(byteStart, lineStart); return start === 0 ? text : decoder3.decode(bytes2.subarray(start)); } function lines(newlines, terminated) { return newlines + (terminated ? 0 : 1); } var runtimeBuffer = globalThis.Buffer; var nonAsciiPattern = /[^\x00-\x7f]/; function utf8ByteLength(content) { if (runtimeBuffer) return runtimeBuffer.byteLength(content, "utf8"); const firstNonAscii = content.search(nonAsciiPattern); if (firstNonAscii === -1) return content.length; let bytes2 = firstNonAscii; for (let i = firstNonAscii; i < content.length; i++) { const code = content.charCodeAt(i); if (code <= 127) bytes2 += 1; else if (code <= 2047) bytes2 += 2; else if (code >= 55296 && code <= 56319 && i + 1 < content.length) { const next = content.charCodeAt(i + 1); if (next >= 56320 && next <= 57343) { bytes2 += 4; i++; } else bytes2 += 3; } else bytes2 += 3; } return bytes2; } function countNewlines(text) { let count = 0; for (let index = text.indexOf("\n"); index !== -1; index = text.indexOf("\n", index + 1)) count++; return count; } var PROGRESS_BYTES_PER_SECOND = 100 * 1024; var SYSTEM_CLOCK = { now: () => Date.now(), setTimer: (callback, delayMs) => { const timer = setTimeout(callback, delayMs); return () => clearTimeout(timer); } }; var Progress = class { #write; #onError; #minIntervalMs; #clock; #waiters = []; #timer; #inFlight; #nextAt = 0; #dirty = false; #stopped = false; constructor(write, onError, minIntervalMs, clock = SYSTEM_CLOCK) { this.#write = write; this.#onError = onError; this.#minIntervalMs = minIntervalMs; this.#clock = clock; } /** Schedule a commit. */ mark() { this.#dirty = true; this.#schedule(); } /** Schedule a commit; the promise settles with the commit that includes this change. */ markAndWait() { const waiter = Promise.withResolvers(); this.#waiters.push(waiter); this.mark(); return waiter.promise; } /** Stop committing and wait for the commit in flight; returns the waiters the final commit must settle. */ async stop() { this.#stopped = true; this.#timer?.(); this.#timer = void 0; await this.#inFlight; return this.#waiters.splice(0); } #schedule() { if (this.#stopped || this.#timer !== void 0 || this.#inFlight !== void 0) return; const wait = this.#nextAt - this.#clock.now(); if (wait <= 0) this.#flush(); else this.#timer = this.#clock.setTimer(() => { this.#timer = void 0; this.#schedule(); }, wait); } #flush() { if (this.#stopped || !this.#dirty) return; this.#dirty = false; const waiters = this.#waiters.splice(0); const started = this.#clock.now(); this.#inFlight = this.#write().then((bytes2) => { this.#nextAt = started + Math.max(this.#minIntervalMs, bytes2 * 1e3 / PROGRESS_BYTES_PER_SECOND); for (const waiter of waiters) waiter.resolve(); }, (error) => { this.#nextAt = started + this.#minIntervalMs; for (const waiter of waiters) waiter.reject(error); this.#onError(error); }).finally(() => { this.#inFlight = void 0; if (this.#dirty) this.#schedule(); }); } }; // node_modules/@earendil-works/pi-schema/dist/formats/typebox/date.js var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; function IsLeapYear(year) { return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); } function IsDate(value2) { const matches = DATE.exec(value2); if (!matches) return false; const year = +matches[1]; const month = +matches[2]; const day = +matches[3]; return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && IsLeapYear(year) ? 29 : DAYS[month]); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/time.js var TIME = /^(\d\d):(\d\d):(\d\d)(?:\.\d+)?(?:([Zz])|([+-])(\d\d):(\d\d))?$/; function IsTime(value2, strictTimeZone = true) { const matches = TIME.exec(value2); if (!matches) return false; if (strictTimeZone && !matches[4] && !matches[5]) return false; const hr = +matches[1]; const min = +matches[2]; const sec = +matches[3]; if (hr > 23 || min > 59 || sec > 60) return false; if (matches[5]) { const tzH2 = +matches[6]; const tzM2 = +matches[7]; if (tzH2 > 23 || tzM2 > 59) return false; } if (sec < 60) return true; const tzSign = matches[5] === "-" ? -1 : 1; const tzH = +(matches[6] || 0); const tzM = +(matches[7] || 0); const totalUtcMin = hr * 60 + min - tzSign * (tzH * 60 + tzM); return (totalUtcMin % 1440 + 1440) % 1440 === 1439; } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/date_time.js function IsDateTime(value2) { const dateTime = value2.split(/T/i); return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1]); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/duration.js var Duration = /^P((\d+Y(\d+M(\d+D)?)?|\d+M(\d+D)?|\d+D)(T(\d+H(\d+M(\d+S)?)?|\d+M(\d+S)?|\d+S))?|T(\d+H(\d+M(\d+S)?)?|\d+M(\d+S)?|\d+S)|\d+W)$/; function IsDuration(value2) { return Duration.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/email.js var Email = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]|\\[\x20-\x7e])*")@(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*|\[(?:IPv6:[a-f0-9:]+|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})\])$/i; function IsEmail(value2) { return Email.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idna/pattern/pattern.js var RE_RULE_HYPHEN_PLACEMENT = /^(?!-).*(? regexp.source).join("|"), "u"); var RE_PERMITTED_CATEGORY = new RegExp([ RE_LETTER, RE_EUROPEAN_SEPARATOR, RE_COMMON_SEPARATOR, RE_NUMBER_DECIMAL, RE_MARK_NONSPACING, RE_MARK_SPACING_COMBINING, RE_CONTEXTO_EXCEPTIONS, RE_PVALID_EXCEPTIONS ].map((regexp) => regexp.source).join("|"), "u"); // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idna/label/ascii.js function IsAsciiLabel(value2) { return RE_RULE_HYPHEN_PLACEMENT.test(value2) && RE_RULE_NOT_RESERVED_ACE.test(value2) && RE_ASCII_LDH.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idna/format/puny.js var PUNYCODE_BASE = 36; var PUNYCODE_TMIN = 1; var PUNYCODE_TMAX = 26; var PUNYCODE_SKEW = 38; var PUNYCODE_DAMP = 700; var PUNYCODE_INITIAL_BIAS = 72; var PUNYCODE_INITIAL_N = 128; function ThrowDontCare() { throw null; } function IsAcePrefixed(value2) { return value2.toLowerCase().startsWith("xn--"); } function Adapt(delta, numPoints, firstTime) { delta = firstTime ? Math.floor(delta / PUNYCODE_DAMP) : delta >> 1; delta += Math.floor(delta / numPoints); let k = 0; while (delta > (PUNYCODE_BASE - PUNYCODE_TMIN) * PUNYCODE_TMAX >> 1) { delta = Math.floor(delta / (PUNYCODE_BASE - PUNYCODE_TMIN)); k += PUNYCODE_BASE; } return k + Math.floor((PUNYCODE_BASE - PUNYCODE_TMIN + 1) * delta / (delta + PUNYCODE_SKEW)); } function Decode(value2) { const output = []; let n = PUNYCODE_INITIAL_N; let i = 0; let bias = PUNYCODE_INITIAL_BIAS; const delimIdx = value2.lastIndexOf("-"); if (delimIdx > 0) { for (let j = 0; j < delimIdx; j++) { const cp = value2.charCodeAt(j); if (cp >= 128) ThrowDontCare(); output.push(cp); } } let inIdx = delimIdx < 0 ? 0 : delimIdx + 1; while (inIdx < value2.length) { const oldi = i; let w = 1; let k = PUNYCODE_BASE; while (true) { if (inIdx >= value2.length) ThrowDontCare(); const ch = value2.charCodeAt(inIdx++); let digit; if (ch >= 97 && ch <= 122) digit = ch - 97; else if (ch >= 48 && ch <= 57) digit = ch - 48 + 26; else ThrowDontCare(); i += digit * w; const t = k <= bias ? PUNYCODE_TMIN : k >= bias + PUNYCODE_TMAX ? PUNYCODE_TMAX : k - bias; if (digit < t) break; w *= PUNYCODE_BASE - t; k += PUNYCODE_BASE; } const outLen = output.length + 1; bias = Adapt(i - oldi, outLen, oldi === 0); n += Math.floor(i / outLen); i %= outLen; output.splice(i, 0, n); i++; } return String.fromCodePoint(...output); } function DigitToChar(digit) { return digit < 26 ? String.fromCharCode(digit + 97) : String.fromCharCode(digit - 26 + 48); } var RE_REPLACE_NON_ASCII = new RegExp(RE_NON_ASCII.source, "gu"); function Encode(input) { const basic = input.replace(RE_REPLACE_NON_ASCII, ""); const basicLength = basic.length; const result = basicLength > 0 ? [basic, "-"] : []; const codePoints2 = Array.from(input, (char) => char.codePointAt(0)); let n = PUNYCODE_INITIAL_N; let delta = 0; let bias = PUNYCODE_INITIAL_BIAS; let handledCPCount = basicLength; while (handledCPCount < codePoints2.length) { let m = Infinity; for (const cp of codePoints2) { if (cp >= n && cp < m) m = cp; } delta += (m - n) * (handledCPCount + 1); n = m; for (const cp of codePoints2) { if (cp < n) delta++; if (cp === n) { let q = delta; for (let k = PUNYCODE_BASE; ; k += PUNYCODE_BASE) { const t = k <= bias ? PUNYCODE_TMIN : k >= bias + PUNYCODE_TMAX ? PUNYCODE_TMAX : k - bias; if (q < t) break; const digit = t + (q - t) % (PUNYCODE_BASE - t); result.push(DigitToChar(digit)); q = Math.floor((q - t) / (PUNYCODE_BASE - t)); } result.push(DigitToChar(q)); bias = Adapt(delta, handledCPCount + 1, handledCPCount === basicLength); delta = 0; handledCPCount++; } } delta++; n++; } return result.join(""); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idna/format/bidi.js var RE_RTL_ALLOWED = /^(?:R|AL|AN|EN|ES|CS|ET|ON|BN|NSM)$/; var RE_LTR_ALLOWED = /^(?:L|EN|ES|CS|ET|ON|BN|NSM)$/; var RE_RTL_CLASSES = /^(?:R|AL|AN)$/; function HasBidiChars(value2) { if (IsAcePrefixed(value2)) { try { return HasRightToLeftCharacters(Decode(value2.slice(4).toLowerCase())); } catch { return false; } } return HasRightToLeftCharacters(value2); } function GetBidiClass(codePoint) { const char = String.fromCodePoint(codePoint); return RE_EUROPEAN_NUMBER.test(char) ? "EN" : RE_ARABIC_INDIC_DIGIT.test(char) ? "AN" : ( // Pattern.RE_EUROPEAN_SEPARATOR.test(char) ? 'ES' : // (no-spec-coverage) // Pattern.RE_COMMON_SEPARATOR.test(char) ? 'CS' : // (no-spec-coverage) RE_MARK_NONSPACING.test(char) ? "NSM" : RE_SCRIPT_HEBREW.test(char) ? "R" : RE_SCRIPT_ARABIC_LETTER.test(char) ? "AL" : RE_LETTER.test(char) ? "L" : "ON" ); } function HasRightToLeftCharacters(value2) { for (const ch of value2) if (RE_RTL_CLASSES.test(GetBidiClass(ch.codePointAt(0)))) return true; return false; } function SatisfiesBidiRule(value2) { let isRtl = false; let allowed = RE_LTR_ALLOWED; let sawEN = false; let sawAN = false; let isFirst = true; for (const ch of value2) { const bidiClass = GetBidiClass(ch.codePointAt(0)); if (isFirst) { if (bidiClass !== "L" && bidiClass !== "R" && bidiClass !== "AL") return false; isRtl = bidiClass === "R" || bidiClass === "AL"; allowed = isRtl ? RE_RTL_ALLOWED : RE_LTR_ALLOWED; isFirst = false; } if (!allowed.test(bidiClass)) return false; if (bidiClass === "EN") sawEN = true; else if (bidiClass === "AN") sawAN = true; } if (isRtl && sawEN && sawAN) return false; return true; } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idna/label/unicode.js function ExceedsMaxALabelLength(value2) { return RE_NON_ASCII.test(value2) && Encode(value2).length + 4 > 63; } function HasInvalidHyphens(chars) { if (chars[0] === "-" || chars[chars.length - 1] === "-") return true; return chars.slice(2).join("").startsWith("--"); } function IsUnicodeLabel(value2) { if (ExceedsMaxALabelLength(value2)) return false; if (HasRightToLeftCharacters(value2) && !SatisfiesBidiRule(value2)) return false; const chars = [...value2]; const codePoints2 = chars.map((c) => c.codePointAt(0)); const length = codePoints2.length; if (HasInvalidHyphens(chars)) return false; if (RE_COMBINING_MARK.test(chars[0])) return false; let hasJapanese = false; for (let i = 0; i < length; i++) { const codePoint = codePoints2[i]; const char = chars[i]; if (RE_RFC5892_DISALLOWED.test(char)) return false; if (!RE_PERMITTED_CATEGORY.test(char)) return false; if (RE_SCRIPT_JAPANESE.test(char)) hasJapanese = true; const prev = codePoints2[i - 1], next = codePoints2[i + 1]; switch (codePoint) { case 183: if (prev !== 108 || next !== 108) return false; break; // MIDDLE DOT (Catalan) case 885: if (!next || !RE_SCRIPT_GREEK.test(chars[i + 1])) return false; break; // Greek KERAIA case 1523: case 1524: if (!prev || !RE_SCRIPT_HEBREW.test(chars[i - 1])) return false; break; // Hebrew GERESH case 8204: if (!prev || prev < 128 && !RE_VIRAMA.test(chars[i - 1])) return false; break; case 8205: if (!prev || !RE_VIRAMA.test(chars[i - 1])) return false; break; case 12539: break; } } if (value2.includes("\u30FB") && !hasJapanese) return false; return true; } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idna/label/puny.js function IsPunyLabel(value2) { if (!IsAcePrefixed(value2)) return false; try { const body = value2.slice(4).toLowerCase(); if (body.lastIndexOf("-") === 0) return false; const decoded = Decode(body); if (!RE_NON_ASCII.test(decoded)) return false; return IsUnicodeLabel(decoded); } catch { return false; } } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idna/hostname.js function IsValidLabelLength(value2) { return value2.length > 0 && value2.length <= 63; } function IsLabel(value2) { return IsValidLabelLength(value2) && (IsPunyLabel(value2) || IsAsciiLabel(value2)); } function IsHostname(value2) { if (value2.length === 0 || value2.length > 253) return false; if (value2.charCodeAt(value2.length - 1) === 46) return false; return value2.split(".").every((label) => IsLabel(label)); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idna/idn-hostname.js function IsValidLabelLength2(value2) { return value2.length > 0 && value2.length <= 63; } function IsLabel2(value2) { return IsValidLabelLength2(value2) && (IsPunyLabel(value2) || IsUnicodeLabel(value2)); } function NormalizeHostname(value2) { return value2.replace(/[\uff01-\uff5e]/g, (char) => String.fromCharCode(char.charCodeAt(0) - 65248)).normalize("NFC").replace(/[\u00ad\u034f\u180b-\u180d\u200b\ufe00-\ufe0f\u{e0100}-\u{e01ef}]/gu, "").replace(/[\u002E\u3002\uFF0E\uFF61]/g, "."); } function IsIdnHostname(value2) { if (value2.length === 0 || value2.includes(" ")) return false; const normalized = NormalizeHostname(value2); if (normalized.length > 253) return false; const labels = normalized.split("."); const hasBidiChars = labels.some((label) => HasBidiChars(label)); return labels.every((label) => IsLabel2(label) && (!hasBidiChars || SatisfiesBidiRule(label))); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/hostname.js function IsHostname2(value2) { return IsHostname(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/idn_email.js var IdnEmail = /^(?:[A-Za-z0-9!#$%&'*+/=?^_`{|}~\u{0080}-\u{10FFFF}-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~\u{0080}-\u{10FFFF}-]+)*|"(?:[^"\\]|\\.)*")@[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,62})(?^`{|}\\]/; var InvalidPercentEncoding = /%(?![0-9a-fA-F]{2})/; function NarrowIpvFuture(value2) { return value2.length < IpvFutureMatchMaxLength ? value2.replace(IpvFutureMatch, "[::1]") : value2; } function IsIri(value2) { if (InvalidIriChars.test(value2)) return false; if (InvalidPercentEncoding.test(value2)) return false; return URL.canParse(NarrowIpvFuture(value2)); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/iri_reference.js var InvalidIriChars2 = /[\x00-\x20\x7F\\]|%(?![0-9a-fA-F]{2})/; var MalformedScheme = /^[a-zA-Z][a-zA-Z0-9+\-.]*\/\//; function IsIriReference(value2) { return !InvalidIriChars2.test(value2) && !MalformedScheme.test(value2) && URL.canParse(value2, "http://example.com"); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/json_pointer.js var JsonPointer = /^(?:\/(?:[^~/]|~0|~1)*)*$/; function IsJsonPointer(value2) { return JsonPointer.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/json_pointer_uri_fragment.js var JsonPointerUriFragment = /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i; function IsJsonPointerUriFragment(value2) { return JsonPointerUriFragment.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/regex.js function IsRegex(value2) { try { new RegExp(value2, "u"); return true; } catch { return false; } } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/relative_json_pointer.js var RelativeJsonPointer = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/; function IsRelativeJsonPointer(value2) { return RelativeJsonPointer.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/uri.js var Uri = /^[a-z][a-z0-9+\-.]*:(?:\/\/(?:(?:[-a-z0-9._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:[\da-f]{1,4}:){6}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|::(?:[\da-f]{1,4}:){5}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:[\da-f]{1,4})?::(?:[\da-f]{1,4}:){4}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,1}[\da-f]{1,4})?::(?:[\da-f]{1,4}:){3}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,2}[\da-f]{1,4})?::(?:[\da-f]{1,4}:){2}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,3}[\da-f]{1,4})?::[\da-f]{1,4}:(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,4}[\da-f]{1,4})?::(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,5}[\da-f]{1,4})?::[\da-f]{1,4}|(?:(?:[\da-f]{1,4}:){0,6}[\da-f]{1,4})?::)|v[0-9a-f]+\.[-a-z0-9._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)|(?:[-a-z0-9._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[-a-z0-9._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[-a-z0-9._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; function IsUri(value2) { return Uri.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/uri_reference.js var UriReference = /^(?:[a-z][a-z0-9+\-.]*:(?:\/\/(?:(?:[-a-z0-9._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:[\da-f]{1,4}:){6}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|::(?:[\da-f]{1,4}:){5}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:[\da-f]{1,4})?::(?:[\da-f]{1,4}:){4}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,1}[\da-f]{1,4})?::(?:[\da-f]{1,4}:){3}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,2}[\da-f]{1,4})?::(?:[\da-f]{1,4}:){2}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,3}[\da-f]{1,4})?::[\da-f]{1,4}:(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,4}[\da-f]{1,4})?::(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,5}[\da-f]{1,4})?::[\da-f]{1,4}|(?:(?:[\da-f]{1,4}:){0,6}[\da-f]{1,4})?::)|v[0-9a-f]+\.[-a-z0-9._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)|(?:[-a-z0-9._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:\/\/(?:(?:[-a-z0-9._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:[\da-f]{1,4}:){6}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|::(?:[\da-f]{1,4}:){5}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:[\da-f]{1,4})?::(?:[\da-f]{1,4}:){4}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,1}[\da-f]{1,4})?::(?:[\da-f]{1,4}:){3}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,2}[\da-f]{1,4})?::(?:[\da-f]{1,4}:){2}(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,3}[\da-f]{1,4})?::[\da-f]{1,4}:(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,4}[\da-f]{1,4})?::(?:[\da-f]{1,4}:[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))|(?:(?:[\da-f]{1,4}:){0,5}[\da-f]{1,4})?::[\da-f]{1,4}|(?:(?:[\da-f]{1,4}:){0,6}[\da-f]{1,4})?::)|v[0-9a-f]+\.[-a-z0-9._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)|(?:[-a-z0-9._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[-a-z0-9._~!$&'()*+,;=@]|%[0-9a-f]{2})+(?:\/(?:[-a-z0-9._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?)(?:\?(?:[-a-z0-9._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[-a-z0-9._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; function IsUriReference(value2) { return UriReference.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/uri_template.js var UriTemplate = /^(?:(?:[^\x00-\x20"<>%\\^`{|}\x7f]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\.(?:[a-z0-9_]|%[0-9a-f]{2})+)*(?::[1-9]\d{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\.(?:[a-z0-9_]|%[0-9a-f]{2})+)*(?::[1-9]\d{0,3}|\*)?)*\})*$/i; function IsUriTemplate(value2) { return UriTemplate.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/url.js function IsUrl(value2) { return URL.canParse(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/typebox/uuid.js var Uuid = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; function IsUuid(value2) { return Uuid.test(value2); } // node_modules/@earendil-works/pi-schema/dist/formats/index.js var formats = Object.freeze({ date: IsDate, "date-time": IsDateTime, duration: IsDuration, email: IsEmail, hostname: IsHostname2, "idn-email": IsIdnEmail, "idn-hostname": IsIdnHostname2, ipv4: IsIPv4, ipv6: IsIPv6, iri: IsIri, "iri-reference": IsIriReference, "json-pointer": IsJsonPointer, "json-pointer-uri-fragment": IsJsonPointerUriFragment, regex: IsRegex, "relative-json-pointer": IsRelativeJsonPointer, time: IsTime, uri: IsUri, "uri-reference": IsUriReference, "uri-template": IsUriTemplate, url: IsUrl, uuid: IsUuid }); // node_modules/@earendil-works/pi-ai/dist/utils/validation.js var validatorCache = /* @__PURE__ */ new WeakMap(); function getValidator(schema) { const cached = validatorCache.get(schema); if (cached) { return cached; } const validator = compile(schema, { formats, coerce: true, unresolvedRefs: "reject" }); validatorCache.set(schema, validator); return validator; } function formatValidationPath(pointer2) { if (pointer2 === "") return "root"; return pointer2.slice(1).split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")).join("."); } function validateToolCall(tools, toolCall) { const tool = tools.find((t) => t.name === toolCall.name); if (!tool) { throw new Error(`Tool "${toolCall.name}" not found`); } return validateToolArguments(tool, toolCall); } function validateToolArguments(tool, toolCall) { if (toolCall.argumentsError) { throw new Error(`Invalid arguments for tool "${toolCall.name}": ${toolCall.argumentsError}`); } const result = getValidator(tool.parameters).parse(structuredClone(toolCall.arguments)); if (result.ok) { return result.value; } const errors = result.errors.map((error) => ` - ${formatValidationPath(error.path)}: ${error.message}`).join("\n"); const errorMessage2 = `Validation failed for tool "${toolCall.name}": ${errors} Received arguments: ${JSON.stringify(toolCall.arguments, null, 2)}`; throw new Error(errorMessage2); } // node_modules/@earendil-works/pi-durable/dist/truncate.js var DEFAULT_MAX_LINES = 2e3; var DEFAULT_MAX_BYTES = 50 * 1024; function splitLinesForCounting(content) { if (content.length === 0) return []; const lines2 = content.split("\n"); if (content.endsWith("\n")) lines2.pop(); return lines2; } function formatSize(bytes2) { if (bytes2 < 1024) { return `${bytes2}B`; } else if (bytes2 < 1024 * 1024) { return `${(bytes2 / 1024).toFixed(1)}KB`; } else { return `${(bytes2 / (1024 * 1024)).toFixed(1)}MB`; } } function truncateHeadOf(prefix, totals, options = {}) { const maxLines = options.maxLines ?? DEFAULT_MAX_LINES; const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES; const totalBytes = totals.bytes; const lines2 = splitLinesForCounting(prefix); const totalLines = totals.lines; if (totalLines <= maxLines && totalBytes <= maxBytes) { return { content: prefix, truncated: false, truncatedBy: null, totalLines, totalBytes, outputLines: totalLines, outputBytes: totalBytes, lastLinePartial: false, firstLineExceedsLimit: false, maxLines, maxBytes }; } const firstLineBytes = utf8ByteLength(lines2[0]); if (firstLineBytes > maxBytes) { return { content: "", truncated: true, truncatedBy: "bytes", totalLines, totalBytes, outputLines: 0, outputBytes: 0, lastLinePartial: false, firstLineExceedsLimit: true, maxLines, maxBytes }; } const outputLinesArr = []; let outputBytesCount = 0; let truncatedBy = "lines"; for (let i = 0; i < lines2.length && i < maxLines; i++) { const line = lines2[i]; const lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); if (outputBytesCount + lineBytes > maxBytes) { truncatedBy = "bytes"; break; } outputLinesArr.push(line); outputBytesCount += lineBytes; } if (truncatedBy !== "bytes") truncatedBy = outputLinesArr.length < totalLines ? "lines" : "bytes"; const outputContent = outputLinesArr.join("\n"); const finalOutputBytes = utf8ByteLength(outputContent); return { content: outputContent, truncated: true, truncatedBy, totalLines, totalBytes, outputLines: outputLinesArr.length, outputBytes: finalOutputBytes, lastLinePartial: false, firstLineExceedsLimit: false, maxLines, maxBytes }; } // node_modules/@earendil-works/pi-durable/dist/harness/usage.js var UsageDoc = defineDoc({ kind: "pi.usage", version: 1, scope: "conversation", history: "latest", fork: "initial", initial: () => ({ models: {}, tools: {} }), checkpointWhen: () => true }); async function recordUsage(tx, conversationId, bucket, key, usage) { const totals = (await tx.doc(UsageDoc, conversationId))[bucket]; const total = Object.hasOwn(totals, key) ? totals[key] : void 0; if (total === void 0) totals[key] = copyJson(usage, { omitUndefinedProperties: true }); else addUsage(total, usage); } function addUsage(total, usage) { total.input += usage.input; total.output += usage.output; total.cacheRead += usage.cacheRead; total.cacheWrite += usage.cacheWrite; total.totalTokens += usage.totalTokens; if (usage.cacheWrite1h !== void 0) total.cacheWrite1h = (total.cacheWrite1h ?? 0) + usage.cacheWrite1h; if (usage.reasoning !== void 0) total.reasoning = (total.reasoning ?? 0) + usage.reasoning; total.cost.input += usage.cost.input; total.cost.output += usage.cost.output; total.cost.cacheRead += usage.cost.cacheRead; total.cost.cacheWrite += usage.cost.cacheWrite; total.cost.total += usage.cost.total; } function addUsageState(sum, state) { for (const bucket of ["models", "tools"]) { for (const [key, usage] of Object.entries(state[bucket])) { const total = Object.hasOwn(sum[bucket], key) ? sum[bucket][key] : void 0; if (total !== void 0) addUsage(total, usage); else Object.defineProperty(sum[bucket], key, { value: structuredClone(usage), enumerable: true, writable: true }); } } } // node_modules/@earendil-works/pi-durable/dist/harness/tool.js var MADE_NESTED_CALLS = "pi.tool.madeNestedCalls"; var NestedCallDoc = defineDocFamily({ kind: "pi.tool.nested-call", version: 1, scope: "task", family: true, initial: () => ({}), // The document retires with its task. checkpointWhen: () => false }); var ToolTask = defineTask({ name: "pi.tool", version: 3, initial: () => ({ phase: "call" }), // Version 1 had only model-issued calls, without `kind`; version 3 added `unoffered`, absent in version 2 inputs. migrate: (input, checkpoint, fromVersion) => ({ input: fromVersion === 1 ? { kind: "model", ...input } : input, checkpoint }), phases: { call: async (task, runtime, context) => { const call2 = await readCall(runtime, task.input, context); if (cutOffTooOften(task, runtime)) return endCutOff(runtime, task, call2, context); const tool = isUnoffered(task.input) ? void 0 : await resolveTool(runtime, task.input, call2.name, context); if (tool === void 0) { const error = harnessError("tool_unavailable", `Tool ${call2.name} is not available`); return reject(runtime, task.input, call2, void 0, error, context); } const prepared = prepare(tool, call2.arguments); const checked = "error" in prepared ? prepared : validate(tool, call2, prepared.args); if ("error" in checked) return reject(runtime, task.input, call2, tool, invalid2(checked.error), context); let args = checked.args; let block; await runtime.hooks.each("beforeTool", async (hook2) => { if (block !== void 0) return; try { const decision = await hook2({ ...call2, arguments: args }, runtime, context); if (decision?.block !== void 0) block = decision.block; else if (decision?.arguments !== void 0) args = decision.arguments; } catch (error) { if (runtime.signal.aborted) throw error; block = errorMessage(error); } }); if (block !== void 0) { const blocked = harnessError("blocked", `Tool call blocked: ${block}`); return reject(runtime, task.input, call2, tool, blocked, context); } const validated = validate(tool, call2, args); if ("error" in validated) return reject(runtime, task.input, call2, tool, invalid2(validated.error), context); const final = copyJson(validated.args, { omitUndefinedProperties: true }); await runtime.commit(async (tx) => { const slot = toolSlot(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId); if (slot !== void 0) slot.status = "running"; if (slot !== void 0 && "parentTaskId" in slot) slot.arguments = final; return void 0; }, context, PROGRESS_COMMIT); await runtime.commit(() => { const intent = { phase: "execute", arguments: final, replay: tool.replay ?? "unsafe" }; return { status: "running", checkpoint: intent }; }, context); await run(runtime, task.input, call2, tool, final, context); }, /** Recovery after intent: rerun only when the stored and the current policy both say `safe`. */ execute: async (task, runtime, context) => { const { arguments: args, replay } = task.state.checkpoint; const call2 = await readCall(runtime, task.input, context); if (cutOffTooOften(task, runtime)) return endCutOff(runtime, task, call2, context); const tool = await resolveTool(runtime, task.input, call2.name, context); if (replay === "safe" && tool?.replay === "safe") { await runtime.commit(async (tx) => { const slot = toolSlot(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId); if (slot !== void 0) { clearProgress(slot); slot.status = "running"; if ("parentTaskId" in slot) slot.arguments = args; } return void 0; }, context); return run(runtime, task.input, call2, tool, args, context); } const message = `Tool ${call2.name} was interrupted and may have partially run`; const ending = { status: "failed", message }; await settleFromSlot(runtime, task.input, call2, { tool }, ending, "interrupted", message, context); } }, abort: async (task, runtime, context) => { const call2 = await readCall(runtime, task.input, context); const [code, message] = isUnoffered(task.input) ? ["tool_unavailable", `Tool ${call2.name} is not available`] : task.abortReason !== "restart" ? ["aborted", `Tool ${call2.name} was aborted`] : task.state.checkpoint.phase === "execute" ? ["interrupted", `Tool ${call2.name} was interrupted by a restart and may have partially run`] : ["abandoned", `Tool ${call2.name} was not started: its caller ended with a restart`]; const hooks = (task.restarts ?? 0) < runtime.settings.restartLimit - 1 ? { tool: await toolForLimits(runtime, task.input, call2.name, context) } : void 0; await settleFromSlot(runtime, task.input, call2, hooks, { status: "aborted" }, code, message, context); } }); async function endCutOff(runtime, task, call2, context) { const message = `Tool ${call2.name} was interrupted ${cutOffCount(task)}; it is not run again`; const ending = { status: "failed", message, reason: "restart_limit" }; const hooks = salvageable(task, runtime) ? { tool: await toolForLimits(runtime, task.input, call2.name, context) } : void 0; await settleFromSlot(runtime, task.input, call2, hooks, ending, "restart_limit", message, context); } async function reject(runtime, input, call2, tool, result, context) { const settled = await afterHarnessResult(runtime, input, call2, tool, result, void 0, context); await settle2(runtime, input, call2, COMPLETED, settled, context); } async function settleFromSlot(runtime, input, call2, hooks, ending, code, message, context) { const live = await runtime.snapshot(LiveDoc, runtime.conversationId, context); const { result, truncation } = fromSlot(live === void 0 ? void 0 : toolSlot(live, runtime.taskId), code, message); const settled = hooks !== void 0 ? await afterHarnessResult(runtime, input, call2, hooks.tool, result, truncation, context) : truncation === void 0 ? result : { ...result, diagnostics: [...result.diagnostics ?? [], truncation] }; await settle2(runtime, input, call2, ending, settled, context); } async function toolForLimits(runtime, input, name, context) { if (isUnoffered(input)) return void 0; return resolveTool(runtime, input, name, context).catch(() => void 0); } function isUnoffered(input) { return input.kind === "model" && input.unoffered === true; } async function resolveTool(runtime, input, name, context) { const agent = await runtime.agent(context); return (input.kind === "model" ? agent.tools : agent.callable).find((tool) => tool.name === name); } function outputLimits(tool) { return { maxBytes: tool?.outputLimits?.maxBytes ?? DEFAULT_MAX_BYTES, maxLines: tool?.outputLimits?.maxLines ?? DEFAULT_MAX_LINES, retain: tool?.outputLimits?.retain ?? "head" }; } async function readCall(runtime, input, context) { if (input.kind === "nested") { context.telemetry.setAttributes({ "tool.name": input.call.name, "tool.call_id": input.call.id }); return { ...input.call, parent: { taskId: input.parent, callId: input.parentCallId } }; } const entry2 = await runtime.entry(AssistantEntry, input.assistant, context); const message = entry2?.model?.[0]; const call2 = message?.role === "assistant" ? message.content.find((content) => content.type === "toolCall" && content.id === input.callId) : void 0; if (call2 === void 0) throw new Error(`Entry ${input.assistant} has no tool call ${input.callId}`); context.telemetry.setAttributes({ "tool.name": call2.name, "tool.call_id": call2.id }); return call2; } function prepare(tool, args) { if (tool.prepareArguments === void 0) return { args }; try { return { args: tool.prepareArguments(args) }; } catch (error) { return { error: errorMessage(error) }; } } function validate(tool, call2, args) { try { return { args: validateToolArguments(tool, { ...call2, arguments: args }) }; } catch (error) { return { error: errorMessage(error) }; } } function invalid2(message) { return harnessError("invalid_arguments", message); } async function run(runtime, input, call2, tool, args, context) { const limits = outputLimits(tool); const reported = { output: new OutputBuffer(limits), limits, diagnostics: [], details: void 0 }; const streams = input.kind === "model" || input.progress !== false; const progress = publishProgress(runtime, reported, streams, context); const admissions = []; const resumes = tool.replay === "safe"; let sequence3 = 0; let madeNestedMemo; let ended = false; const assertLive = () => { if (ended) throw new Error(`Tool call ${call2.id} has settled`); }; let env; const api = { taskId: runtime.taskId, conversationId: runtime.conversationId, callId: call2.id, registry: runtime.registry, agent: runtime.agent, get models() { return runtime.models; }, get env() { return env; }, now: () => runtime.now(), output: (chunk, skipped) => { assertLive(); if (reported.output.push(chunk, skipped)) progress.mark(); }, outputWindow: limits.retain === "tail" ? { maxBytes: limits.maxBytes, maxLines: limits.maxLines, minIntervalMs: runtime.settings.progress.outputIntervalMs, bytesPerSecond: PROGRESS_BYTES_PER_SECOND } : void 0, retainedOutput: () => { assertLive(); const retained = reported.output.snapshot(); return { text: retained.text, truncated: retained.droppedBytes > 0 }; }, diagnostic: (diagnostic) => { assertLive(); reported.diagnostics.push(copyJson(diagnostic, { omitUndefinedProperties: true })); progress.mark(); }, details: async (value2, detailsContext) => { assertLive(); detailsContext.abortSignal?.throwIfAborted(); reported.details = copyJson(value2, { omitUndefinedProperties: true }); const committed = progress.markAndWait(); committed.catch(() => { }); return awaitWithContext(committed, detailsContext); }, commit: async (change, commitContext, options) => { let result2; await runtime.commit(async (tx) => { result2 = await change(tx); return void 0; }, commitContext, options); return result2; }, memo: runtime.memo, createTask: async (task, input2, options, taskContext) => { const child = options.ownership.kind === "task" && !resumes ? { abandonOnRestart: true, ...options } : options; let id; await runtime.commit(async (tx) => { id = await tx.createTask(task, input2, child); return void 0; }, taskContext); return id; }, getTask: runtime.getTask, waitForTask: runtime.waitForTask, conversation: runtime.conversation, snapshot: runtime.snapshot, snapshotAsOf: runtime.snapshotAsOf, watchDoc: runtime.watchDoc, executeTool: async (name, toolArgs, callContext, options = {}) => { assertLive(); const key = options.key ?? String(++sequence3); if (options.key !== void 0) checkKey(options.key); madeNestedMemo ??= runtime.memo(MADE_NESTED_CALLS, true, callContext).catch((error) => { madeNestedMemo = void 0; throw error; }); const admission = madeNestedMemo.then(() => startNestedCall(runtime, call2.id, name, toolArgs, { key, progress: options.progress, abandonOnRestart: !resumes }, callContext)); admissions.push(admission); const id = await admission; const settled = await runtime.waitForTask(id, callContext); const stored = (await runtime.snapshot(NestedCallDoc, runtime.taskId, key, callContext))?.result; return stored === void 0 ? fallbackResult(id, name, settled.state.outcome) : copyJson(stored); } }; let result; let ending = COMPLETED; let durationMs; try { env = await runtime.env(context); const startedAt = performance.now(); try { result = await tool.execute(args, api, context); } finally { durationMs = Math.round(performance.now() - startedAt); } } catch (error) { if (runtime.signal.aborted) { ended = true; for (const waiter of await progress.stop()) waiter.reject(error); throw error; } result = { isError: true, diagnostics: [toolDiagnostic("tool_error", errorMessage(error))] }; ending = { status: "failed", message: `Tool ${call2.name} threw` }; } ended = true; await Promise.allSettled(admissions); reported.output.end(); const pending = await progress.stop(); try { const settled = await finalResult(runtime, input, call2, tool, result, reported, context); await settle2(runtime, input, call2, ending, settled, context, durationMs); } catch (error) { for (const waiter of pending) waiter.reject(error); throw error; } for (const waiter of pending) waiter.resolve(); } function progressClock(runtime) { let last = 0; return { now: () => { try { last = runtime.now(); } catch { } return last; }, setTimer: (callback, delayMs) => runtime.setTimer(callback, delayMs) }; } function publishProgress(runtime, reported, enabled, context) { let written = { text: "", details: void 0, diagnostics: 0 }; return new Progress(async () => { if (!enabled) return 0; const snapshot = reported.output.snapshot(); const current = { text: snapshot.text, details: reported.details, diagnostics: reported.diagnostics.length }; const added = reported.diagnostics.slice(written.diagnostics, current.diagnostics); const detailsChanged = current.details !== written.details; let bytes2 = 0; if (snapshot.text !== written.text) { const shared = snapshot.text.startsWith(written.text) ? written.text.length : overlap(written.text, snapshot.text, 65536); bytes2 += utf8ByteLength(snapshot.text.slice(shared)); } if (detailsChanged) bytes2 += utf8ByteLength(JSON.stringify(current.details ?? null)); if (added.length > 0) bytes2 += utf8ByteLength(JSON.stringify(added)); await runtime.commit(async (tx) => { const slot = toolSlot(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId); if (slot === void 0) return void 0; if ((slot.output ?? "") !== snapshot.text) slot.output = snapshot.text; if (snapshot.droppedBytes > 0) slot.droppedBytes = snapshot.droppedBytes; if (snapshot.droppedLines > 0) slot.droppedLines = snapshot.droppedLines; if (detailsChanged && current.details !== void 0) { assignJson(slot, "details", current.details); } if (added.length > 0) { if (slot.diagnostics === void 0) slot.diagnostics = []; for (const diagnostic of added) slot.diagnostics.push(diagnostic); } return void 0; }, context, PROGRESS_COMMIT); written = current; return bytes2; }, (error) => { if (!runtime.signal.aborted) runtime.report(error); }, runtime.settings.progress.outputIntervalMs, progressClock(runtime)); } async function finalResult(runtime, input, call2, tool, result, reported, context) { const harness2 = []; const retained = result.output === void 0 ? reported.output.snapshot() : void 0; const output = retained === void 0 ? result.output : retained.text === "" ? [] : [{ type: "text", text: retained.text }]; let final = { ...result, output, details: result.details === void 0 ? reported.details : result.details, diagnostics: [...reported.diagnostics, ...result.diagnostics ?? []] }; await runtime.hooks.each("afterTool", async (hook2) => { final = await hook2(call2, final, runtime, context) ?? final; }); if (retained !== void 0 && retained.droppedBytes > 0 && final.output === output) { harness2.push(truncated(retained, reported.limits.retain)); } const bounded = boundContent(final.output ?? [], reported.limits); if (bounded.droppedBytes > 0) harness2.push(truncated(bounded, reported.limits.retain)); const broken = structuredOutputError(tool, final); if (broken !== void 0) { const { structuredOutput: _dropped, ...rest } = final; if (input.kind === "nested") { final = { ...rest, isError: true }; harness2.push(toolDiagnostic("invalid_structured_output", broken)); } else { final = rest; const extension = await runtime.agent(context).then((agent) => toolExtension(agent, tool.name), () => void 0); runtime.report(new Error(broken), { kind: "extension", ...optionalExtension(extension) }); } } if (input.kind === "nested" && tool.structuredOutputSchema === void 0) { final = { ...final, structuredOutput: outputValue(bounded.content) }; } if (input.kind === "nested" && tool.structuredOutputSchema !== void 0 && final.isError === true && final.structuredOutput === void 0 && ![...final.diagnostics ?? [], ...harness2].some((diagnostic) => diagnostic.severity === "error")) { const text = bounded.content.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n"); if (text !== "") harness2.push(toolDiagnostic("tool_error", text)); } return { ...final, output: bounded.content, diagnostics: [...final.diagnostics ?? [], ...harness2] }; } async function afterHarnessResult(runtime, input, call2, tool, result, truncation, context) { let final = result; await runtime.hooks.each("afterTool", async (hook2) => { final = await hook2(call2, final, runtime, context) ?? final; }); const harness2 = []; if (truncation !== void 0 && final.output === result.output) harness2.push(truncation); const limits = outputLimits(tool); const bounded = boundContent(final.output ?? [], limits); if (bounded.droppedBytes > 0) harness2.push(truncated(bounded, limits.retain)); const { structuredOutput: _dropped, ...rest } = final; const value2 = input.kind === "nested" && final.isError !== true && tool?.structuredOutputSchema === void 0 ? { structuredOutput: outputValue(bounded.content) } : {}; return { ...rest, ...value2, output: bounded.content, diagnostics: [...rest.diagnostics ?? [], ...harness2] }; } var structuredValidators = /* @__PURE__ */ new WeakMap(); function structuredOutputError(tool, result) { const schema = tool.structuredOutputSchema; const value2 = result.structuredOutput; if (schema === void 0) { return value2 === void 0 ? void 0 : `Tool ${tool.name} returned structuredOutput but declares no structuredOutputSchema`; } if (value2 === void 0) { return result.isError === true ? void 0 : `Tool ${tool.name} returned no structuredOutput`; } let validator = structuredValidators.get(schema); if (validator === void 0) { validator = compile(schema, { formats, unresolvedRefs: "reject" }); structuredValidators.set(schema, validator); } if (validator.check(value2)) return void 0; const [first] = validator.errors(value2); const path = first?.path.slice(1).split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")).join(".") || "root"; return `Tool ${tool.name} returned structuredOutput that does not match its schema: ${path}: ${first?.message ?? "invalid"}`; } async function settle2(runtime, input, call2, ending, result, context, durationMs) { const nestedIds = (await runtime.ownedTasks(context)).filter((record2) => record2.kind === ToolTask.definition.name && record2.input.kind === "nested").map((record2) => record2.id); const madeNested = await runtime.memo(MADE_NESTED_CALLS, context) === true; await Promise.all(nestedIds.map((id) => runtime.abortOwned(id, context))); await runtime.commit(async (tx) => { const conversationId = runtime.conversationId; const live = await tx.doc(LiveDoc, conversationId); const slot = toolSlot(live, runtime.taskId); let settled; if (input.kind === "nested") { const nested = nestedResult(runtime.taskId, result, durationMs); if (nested.usage !== void 0) await recordUsage(tx, conversationId, "tools", call2.name, nested.usage); const state = await tx.doc(NestedCallDoc, input.parent, input.key, null); if (state.taskId !== runtime.taskId) throw new Error(`Nested call ${call2.id} is not in its caller's index`); state.result = nested; if (slot !== void 0 && "parentTaskId" in slot) { finishSlot(slot); slot.summary = summaryOf(nested, result.output ?? []); } settled = { kind: "nested" }; } else { const timing = { timestamp: runtime.now(), durationMs }; const entry2 = await appendToolResult(tx, conversationId, call2, result, timing); if (slot !== void 0 && !("parentTaskId" in slot)) { finishSlot(slot); slot.entry = entry2.id; } const control = result.control === void 0 || ending.status !== "completed" ? {} : { control: copyJson(result.control, { omitUndefinedProperties: true }) }; settled = { kind: "model", entryId: entry2.id, ...control }; } if (madeNested) removeNestedSlots(live, runtime.taskId); if (ending.status === "aborted") return { status: "terminal", outcome: { status: "aborted", result: settled } }; if (ending.status === "failed") { const error = { message: ending.message, ...ending.reason === void 0 ? {} : { detail: { reason: ending.reason } } }; return { status: "terminal", outcome: { status: "failed", error, result: settled } }; } return { status: "terminal", outcome: { status: "completed", result: settled } }; }, context); } function nestedResult(taskId, result, durationMs) { const nested = { taskId, structuredOutput: result.structuredOutput, isError: result.isError ?? false, details: result.details, diagnostics: result.diagnostics ?? [], usage: result.usage, durationMs }; return copyJson(nested, { omitUndefinedProperties: true }); } async function startNestedCall(runtime, parentCallId, name, args, options, context) { const key = options.key; const call2 = { type: "toolCall", id: `${parentCallId}/${key}`, name, arguments: copyJson(args, { omitUndefinedProperties: true }) }; let id; let replayed = false; await runtime.commit(async (tx) => { const state = await tx.doc(NestedCallDoc, runtime.taskId, key, null); const existing = state.taskId; if (existing !== void 0) { const input2 = (await tx.task(existing))?.input; const same = input2?.kind === "nested" && input2.call.name === name && jsonEqual2(input2.call.arguments, call2.arguments); if (!same) throw new Error(`Nested call ${call2.id} was already made with another tool or other arguments`); id = existing; replayed = state.result !== void 0; return void 0; } const input = { kind: "nested", parent: runtime.taskId, parentCallId, key, call: call2, ...options.progress === false ? { progress: false } : {} }; const created = await tx.createTask(ToolTask, input, { ownership: { kind: "task", taskId: runtime.taskId }, abandonOnRestart: options.abandonOnRestart }); state.taskId = created; const live = await tx.doc(LiveDoc, runtime.conversationId); live.nestedTools ??= []; const slot = { callId: call2.id, parentCallId, parentTaskId: runtime.taskId, name, taskId: created, arguments: call2.arguments, status: "pending" }; live.nestedTools.push(slot); id = created; return void 0; }, context); if (replayed) { context.telemetry.addEvent("pi.durable.replay", { "tool.name": name }); } return id; } function outputValue(output) { if (output.length === 0) return ""; if (output.length > 1) return output; const [only] = output; return only.type === "text" ? only.text : only; } function fallbackResult(taskId, name, outcome) { const message = outcome.status === "faulted" ? `Tool ${name} failed: ${outcome.error.message}` : outcome.status === "orphaned" ? `Tool ${name} could not resume: ${outcome.reason}` : `Tool ${name} ended without a result`; const error = harnessError(outcome.status, message); return { taskId, isError: true, diagnostics: error.diagnostics ?? [] }; } var COMPLETED = { status: "completed" }; function fromSlot(slot, code, message) { const droppedBytes = slot?.droppedBytes ?? 0; const result = { output: slot?.output === void 0 || slot.output === "" ? [] : [{ type: "text", text: slot.output }], isError: true, ...slot?.details === void 0 ? {} : { details: slot.details }, diagnostics: [...slot?.diagnostics ?? [], toolDiagnostic(code, message)] }; if (droppedBytes === 0) return { result }; return { result, truncation: truncated({ droppedBytes, droppedLines: slot?.droppedLines ?? 0 }) }; } function harnessError(code, message) { return { output: [], isError: true, diagnostics: [toolDiagnostic(code, message)] }; } function toolDiagnostic(code, message) { return { severity: "error", code, message }; } function truncated(dropped, retain) { const kept = retain === void 0 ? "" : ` to its ${retain === "head" ? "beginning" : "end"}`; return { severity: "warn", code: "truncated", message: `Output truncated${kept}: ${dropped.droppedLines} lines, ${dropped.droppedBytes} bytes dropped` }; } var MAX_SUMMARY_ERROR_CHARS = 500; function summaryOf(result, output) { const error = result.isError ? ownedSlice2(errorTextOf(result, output), 0, MAX_SUMMARY_ERROR_CHARS) : ""; return { isError: result.isError, ...result.durationMs === void 0 ? {} : { durationMs: result.durationMs }, ...result.usage === void 0 ? {} : { usage: result.usage }, ...error === "" ? {} : { error } }; } function errorTextOf(result, output) { const errors = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error"); if (errors.length > 0) return errors.map((diagnostic) => diagnostic.message).join("\n"); return output.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n"); } async function appendToolResult(tx, conversationId, call2, result, meta) { const { timestamp, durationMs } = meta; const diagnostics = [...result.diagnostics ?? []]; const content = [...result.output ?? []]; if (diagnostics.length > 0) content.push({ type: "text", text: renderDiagnostics(diagnostics) }); const message = { role: "toolResult", toolCallId: call2.id, toolName: call2.name, content, ...result.details === void 0 ? {} : { details: result.details }, ...result.usage === void 0 ? {} : { usage: result.usage }, isError: result.isError ?? false, ...durationMs === void 0 ? {} : { durationMs }, timestamp }; if (result.usage !== void 0) await recordUsage(tx, conversationId, "tools", call2.name, result.usage); return tx.appendEntry(ToolResultEntry, conversationId, { model: [message], data: { diagnostics } }); } function renderDiagnostics(diagnostics) { return ` ${diagnostics.map((diagnostic) => `[${diagnostic.severity}] ${diagnostic.message}`).join("\n")} `; } function boundContent(content, limits) { const texts = content.filter((item) => item.type === "text"); const bounded = boundOutput(texts.map((item) => item.text).join(""), limits); if (bounded.droppedBytes === 0) return { content, droppedBytes: 0, droppedLines: 0 }; const keep = limits.retain === "head" ? texts[0] : texts.at(-1); const result = []; for (const item of content) { if (item.type !== "text") result.push(item); else if (item === keep) result.push({ ...item, text: bounded.text }); } return { content: result, droppedBytes: bounded.droppedBytes, droppedLines: bounded.droppedLines }; } function checkKey(key) { if (key === "" || key.includes("/") || key === "__proto__" || /^[1-9][0-9]*$/.test(key)) { throw new Error(`Nested call key ${JSON.stringify(key)} must be non-empty, without "/", not "__proto__", and not a positive integer`); } } function jsonEqual2(a, b) { if (a === b) return true; if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false; if (Array.isArray(a) || Array.isArray(b)) { return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, i) => jsonEqual2(item, b[i])); } const keys = Object.keys(a); return keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && jsonEqual2(a[key], b[key])); } // node_modules/@earendil-works/pi-durable/dist/harness/generation.js var DEFAULT_POLL_AFTER_MS = 5e3; var GenerationTask = defineTask({ name: "pi.generation", version: 2, initial: () => ({ phase: "prepare", attempt: 1 }), // Version 2 leaves calls to tools not offered in a sequential round's `pending`, flagged `unoffered` in their slots. // A version 1 Harness would start their tool tasks unflagged, resolving the name, so it leaves version 2 blocked // instead. A version 1 checkpoint has no such calls and is kept as it is. migrate: (input, checkpoint) => ({ input, checkpoint }), phases: { /** * Render the system prompt and tool loadout and append the positional `pi.system` entries they need, then move to * `request`. The agent and settings resolved here are fixed for this request. Only the Harness writes model * context to a busy conversation, so the context read here is still current at the commit; display-only entries * may land meanwhile. */ prepare: async (task, runtime, context) => { if (cutOffTooOften(task, runtime)) return endCutOff2(runtime, task, context); const { conversationId } = runtime; const agent = await runtime.agent(context); const settings = runtime.settings; const { model: model2, thinkingLevel } = agent; const resolved = model2 === void 0 ? void 0 : runtime.models.getModel(model2.provider, model2.modelId); if (model2 === void 0 || resolved === void 0) return failNoModel(runtime, model2, context); const { attempt, compacted, overflow, threshold: blocked } = task.state.checkpoint; if (compacted !== void 0 && overflow !== void 0) { const [outcome] = await runtime.outcomes([compacted], context); if (outcome?.status !== "completed" || outcome.result.entryId === void 0) { return failModelError(runtime, overflow, context); } } if (compacted !== void 0 && blocked === true) { const [outcome] = await runtime.outcomes([compacted], context); if (outcome !== void 0 && outcome.status !== "completed") { return failModelError(runtime, compactionFailure(outcome), context); } } const view2 = await runtime.context(conversationId, context); const last = view2.messages.findLast((message) => message.role !== "system"); if (last?.role !== "user" && last?.role !== "toolResult") return failNothingToAnswer(runtime, context); const shown = replaySections(view2.messages); let env; try { env = await runtime.env(context); } catch (error) { if (context.abortSignal?.aborted) throw error; runtime.report(error, { kind: "host", callback: "env" }); } const input = { conversationId, agent, env, shown: Object.fromEntries(shown), read: runtime }; const desired = await renderSections(agent.sections, input, shown, (error, section2) => runtime.report(error, { kind: "extension", ...optionalExtension(sectionExtension(agent, section2.key)) }), context); const entries2 = planSystemEntries(view2, desired, agent.tools, runtime.now()); const threshold = compacted === void 0 ? thresholdCompaction(view2, entries2, resolved.contextWindow, compactionPolicy(settings, model2)) : void 0; if (threshold === "blocking") { await runtime.commit(async (tx) => { const child = await createCompaction(tx, conversationId, { reason: "threshold" }, runtime.taskId); const checkpoint = { phase: "prepare", attempt, compacted: child, threshold: true }; return { status: "waiting", checkpoint, on: [child], policy: "allSettled" }; }, context); return; } await runtime.commit(async (tx) => { let cutoff = await tx.latestEntryId(conversationId); for (const entry2 of entries2) cutoff = (await tx.appendEntry(SystemEntry, conversationId, entry2)).id; if (threshold === "background" && (await tx.doc(LiveDoc, conversationId)).compactions === void 0) { await createCompaction(tx, conversationId, { reason: "threshold" }); } const request2 = { attempt, ...compacted === void 0 ? {} : { compacted }, model: model2, thinkingLevel, streamOptions: settings.stream, cutoff }; return { status: "running", checkpoint: { phase: "request", ...request2 } }; }, context); }, request: async (task, runtime, context) => { if (cutOffTooOften(task, runtime)) return endCutOff2(runtime, task, context); if (task.restarts !== void 0) return interrupted(runtime, task.state.checkpoint, context); const { attempt, compacted, model: ref, thinkingLevel, streamOptions, cutoff } = task.state.checkpoint; const conversationId = runtime.conversationId; await runtime.commit(async (tx) => { const live = await tx.doc(LiveDoc, conversationId); await convertPartial(tx, live, conversationId); live.generation = { attempt }; return void 0; }, context, PROGRESS_COMMIT); const model2 = runtime.models.getModel(ref.provider, ref.modelId); if (model2 === void 0) return failNoModel(runtime, ref, context); const view2 = await runtime.context(conversationId, context, { at: cutoff }); let messages = view2.messages; await runtime.hooks.each("beforeRequest", async (hook2) => { const replaced = await hook2({ messages }, runtime, context); if (replaced !== void 0) messages = replaced.messages; }); const options = { ...streamOptions, signal: runtime.signal, telemetry: context.telemetry, sessionId: await ensureProviderSessionId(runtime, context), ...thinkingLevel === "off" ? {} : { reasoning: thinkingLevel }, onPayload: (payload) => providerPayload(runtime, model2, payload, context) }; const message = await streamResponse(runtime, model2, messages, options, attempt, context); const request2 = { attempt, compacted, model: ref, thinkingLevel, cutoff, messages: view2.messages }; await classify(runtime, request2, message, context); }, /** After the backoff's sleep; a run before `until` (an early wake, or a record of an earlier version) sleeps again. */ retry: async (task, runtime, context) => { const { attempt, compacted, until: until2, poll } = task.state.checkpoint; if (cutOffTooOften(task, runtime)) return endCutOff2(runtime, task, context); if (until2 > runtime.now()) return runtime.sleepUntil(until2, task.state.checkpoint, context); await runtime.commit(async (tx) => { const live = await tx.doc(LiveDoc, runtime.conversationId); const kept = compacted === void 0 ? {} : { compacted }; if (poll !== void 0) { const pollAt = runtime.now(); live.generation = { attempt: attempt + 1, deferred: { pollAt } }; return { status: "running", checkpoint: { phase: "poll", attempt: attempt + 1, ...kept, ...poll, pollAt } }; } live.generation = { attempt: attempt + 1 }; return { status: "running", checkpoint: { phase: "prepare", attempt: attempt + 1, ...kept } }; }, context); }, /** After the sleep until `pollAt`; a run before it (an early wake, or a record of an earlier version) sleeps again. */ poll: async (task, runtime, context) => { const { attempt, compacted, model: ref, thinkingLevel, cutoff, handle, pollAt } = task.state.checkpoint; if (cutOffTooOften(task, runtime)) return endCutOff2(runtime, task, context); if (task.restarts !== void 0) return interrupted(runtime, task.state.checkpoint, context); if (pollAt > runtime.now()) return runtime.sleepUntil(pollAt, task.state.checkpoint, context); const model2 = runtime.models.getModel(ref.provider, ref.modelId); if (model2 === void 0) return failNoModel(runtime, ref, context); const message = await runtime.models.fetchDeferred(model2, handle, { signal: runtime.signal, telemetry: context.telemetry }); const request2 = { attempt, compacted, model: ref, thinkingLevel, cutoff, pollAt }; await classify(runtime, request2, message, context); }, tools: async (task, runtime, context) => { if (cutOffTooOften(task, runtime)) return endCutOff2(runtime, task, context); const { assistant, tools, pending } = task.state.checkpoint; const [next, ...rest] = pending; if (next === void 0) return finishToolRound(runtime, assistant, tools, context); await runtime.commit(async (tx) => { const live = await tx.doc(LiveDoc, runtime.conversationId); const slot = live.tools?.find((slot2) => slot2.callId === next && slot2.taskId === void 0); const taskId = await createToolTask(tx, runtime, assistant, next, slot?.unoffered); if (slot !== void 0) slot.taskId = taskId; const checkpoint = { phase: "tools", assistant, tools: [...tools, taskId], pending: rest }; return { status: "waiting", checkpoint, on: [taskId], policy: "allSettled" }; }, context); }, /** * Ask the `afterRun` hooks for tasks and create them, as the generation's children, in the commit that moves to * `drain`; without any, end the run now. */ finish: async (task, runtime, context) => { const { run: run2 } = task.state.checkpoint; if (cutOffTooOften(task, runtime)) return endAnsweredCutOff(runtime, task, run2.answer, context); const prepared = []; await runtime.hooks.each("afterRun", async (hook2) => { const asked = await hook2(run2, runtime, context); if (asked !== void 0) prepareRunEndTasks(runtime, asked.tasks, prepared); }); if (prepared.length === 0) return endAnsweredRun(runtime, run2.answer, context); await runtime.commit(async (tx) => { const tasks = []; for (const { task: task2, input } of prepared) { tasks.push(await tx.createTask(task2, input, { ownership: { kind: "task", taskId: runtime.taskId } })); } const live = await tx.doc(LiveDoc, runtime.conversationId); if (live.run?.taskId === runtime.taskId && live.run.afterRun !== void 0) live.run.afterRun.tasks = tasks; const checkpoint = { phase: "drain", answer: run2.answer, tasks }; return { status: "waiting", checkpoint, on: tasks, policy: "allSettled" }; }, context); }, /** The after-run tasks are terminal, whatever their outcomes: end the run. */ drain: async (task, runtime, context) => { const { answer: answer2 } = task.state.checkpoint; if (cutOffTooOften(task, runtime)) return endAnsweredCutOff(runtime, task, answer2, context); return endAnsweredRun(runtime, answer2, context); } }, abort: async (task, runtime, context) => { const checkpoint = task.state.checkpoint; const polled = deferredOf(checkpoint); if (polled !== void 0) await cancelDeferred(runtime, polled, context); const conversationId = runtime.conversationId; const unstarted = checkpoint.phase === "tools" ? await readCalls(runtime, checkpoint.assistant, checkpoint.pending, context) : []; await runtime.commit(async (tx) => { const live = await tx.doc(LiveDoc, conversationId); await convertPartial(tx, live, conversationId); for (const call2 of unstarted) { const unoffered = live.tools?.find((slot) => slot.callId === call2.id)?.unoffered === true; const result = unoffered ? harnessError("tool_unavailable", `Tool ${call2.name} is not available`) : harnessError("aborted", `Tool ${call2.name} was aborted`); await appendToolResult(tx, conversationId, call2, result, { timestamp: runtime.now() }); } endRun(tx, live, runtime.taskId, { status: "unanswered", reason: "aborted" }); return { status: "terminal", outcome: { status: "aborted" } }; }, context); } }); async function readCalls(runtime, assistant, callIds, context) { const message = (await runtime.entry(AssistantEntry, assistant, context))?.model?.[0]; const calls = message?.role === "assistant" ? message.content.filter((content) => content.type === "toolCall") : []; return callIds.flatMap((id) => calls.filter((call2) => call2.id === id).slice(0, 1)); } function createToolTask(tx, runtime, assistant, callId, unoffered) { return tx.createTask(ToolTask, { kind: "model", assistant, callId, ...unoffered === void 0 ? {} : { unoffered } }, { ownership: { kind: "task", taskId: runtime.taskId } }); } function thresholdCompaction(view2, planned, contextWindow, policy) { if (!policy.enabled || contextWindow <= 0) return void 0; const tokens = estimateContext(view2, planned.flatMap((entry2) => entry2.model ?? [])); const blocking = contextWindow - policy.reserveTokens; const background = blocking - policy.backgroundTokens; const over = tokens > blocking ? "blocking" : policy.backgroundTokens > 0 && tokens > background ? "background" : void 0; if (over === void 0 || selectCut(view2, policy.keepRecentTokens) === void 0) return void 0; return over; } function compactionFailure(outcome) { switch (outcome.status) { case "failed": case "faulted": return `Compaction failed: ${outcome.error.message}`; case "orphaned": return `Compaction failed: ${outcome.reason}`; case "aborted": return "Compaction was aborted"; } } async function failModelError(runtime, text, context) { await runtime.commit(async (tx) => { const failure = await prepareFailure(tx, runtime); const live = await tx.doc(LiveDoc, runtime.conversationId); await endFailedRun(tx, runtime, live, failure, { status: "unanswered", reason: "model_error", detail: text }); return { status: "terminal", outcome: { status: "failed", error: { message: text, detail: { reason: "model_error" } } } }; }, context); } async function interrupted(runtime, checkpoint, context) { const { attempt, compacted } = checkpoint; const policy = runtime.settings.retry; const retry = policy.enabled && attempt <= policy.maxRetries; const until2 = retry ? runtime.now() + retryDelayMs(policy, attempt) : 0; const polled = checkpoint.phase === "poll" ? checkpoint : void 0; const text = polled === void 0 ? `The host stopped while the model was answering, on attempt ${attempt}` : `The host stopped while the deferred answer was fetched, on attempt ${attempt}`; if (!retry && polled !== void 0) await cancelDeferred(runtime, polled, context); await runtime.commit(async (tx) => { const failure = retry ? void 0 : await prepareFailure(tx, runtime); const live = await tx.doc(LiveDoc, runtime.conversationId); await convertPartial(tx, live, runtime.conversationId); if (failure === void 0) { live.generation = { attempt, retry: { at: until2, error: text } }; const poll = polled === void 0 ? {} : { poll: { model: polled.model, ...polled.thinkingLevel === void 0 ? {} : { thinkingLevel: polled.thinkingLevel }, cutoff: polled.cutoff, handle: polled.handle } }; const next = { phase: "retry", attempt, ...compacted === void 0 ? {} : { compacted }, until: until2, ...poll }; return { status: "sleeping", checkpoint: next, until: until2 }; } await endFailedRun(tx, runtime, live, failure, { status: "unanswered", reason: "interrupted", detail: text }); return { status: "terminal", outcome: { status: "failed", error: { message: text, detail: { reason: "interrupted" } } } }; }, context); } async function endCutOff2(runtime, task, context) { const checkpoint = task.state.checkpoint; const text = `The generation was interrupted ${cutOffCount(task)} in its ${checkpoint.phase} phase`; const salvage = salvageable(task, runtime); const polled = deferredOf(checkpoint); if (salvage && polled !== void 0) await cancelDeferred(runtime, polled, context); await runtime.commit(async (tx) => { const failure = await prepareFailure(tx, runtime); const live = await tx.doc(LiveDoc, runtime.conversationId); if (salvage) await convertPartial(tx, live, runtime.conversationId); await endFailedRun(tx, runtime, live, failure, { status: "unanswered", reason: "restart_limit", detail: text }); return { status: "terminal", outcome: { status: "failed", error: { message: text, detail: { reason: "restart_limit" } } } }; }, context); } function deferredOf(checkpoint) { if (checkpoint.phase === "poll") return checkpoint; return checkpoint.phase === "retry" ? checkpoint.poll : void 0; } async function cancelDeferred(runtime, checkpoint, context) { const model2 = runtime.models.getModel(checkpoint.model.provider, checkpoint.model.modelId); if (model2 === void 0) return; try { await runtime.models.cancelDeferred(model2, checkpoint.handle, { signal: runtime.signal, telemetry: context.telemetry }); } catch (error) { runtime.report(error, { kind: "host", callback: "models" }); } } async function failNoModel(runtime, ref, context) { const message = ref === void 0 ? "No model is configured" : `Model ${ref.provider}/${ref.modelId} is not available`; await runtime.commit(async (tx) => { const failure = await prepareFailure(tx, runtime); const live = await tx.doc(LiveDoc, runtime.conversationId); await endFailedRun(tx, runtime, live, failure, { status: "unanswered", reason: "no_model", detail: message, settlement: { status: "unanswered", reason: "no_model" } }); return { status: "terminal", outcome: { status: "failed", error: { message, detail: { reason: "no_model" } } } }; }, context); } async function failNothingToAnswer(runtime, context) { const message = "The conversation has nothing to answer"; await runtime.commit(async (tx) => { const failure = await prepareFailure(tx, runtime); const live = await tx.doc(LiveDoc, runtime.conversationId); await endFailedRun(tx, runtime, live, failure, { status: "unanswered", reason: "nothing_to_answer" }); const error = { message, detail: { reason: "nothing_to_answer" } }; return { status: "terminal", outcome: { status: "failed", error } }; }, context); } async function prepareFailure(tx, runtime) { const settings = runtime.settings; const policy = settings.afterFailedRun; if (policy !== "continue") return { policy }; return { policy, boundary: await prepareBoundary(tx, runtime.conversationId, settings, "final", runtime.now()) }; } async function endFailedRun(tx, runtime, live, failure, end) { const conversationId = runtime.conversationId; endRun(tx, live, runtime.taskId, end); if (failure.policy === "withdraw") await withdrawQueuedInputs(tx, conversationId, "run_failed"); if (failure.boundary === void 0) return; const { users, first } = await applyBoundary(tx, failure.boundary, "final", runtime.now()); if (users.length > 0) await startRun(tx, conversationId, live, { inputs: users, first }, { keepFailure: true }); } async function convertPartial(tx, live, conversationId) { const partial = live.generation?.message; if (partial === void 0) return; const message = copyJson(partial); await appendAssistant(tx, conversationId, { ...message, stopReason: "aborted" }); } async function providerPayload(runtime, model2, payload, context) { let current = payload; await runtime.hooks.each("beforeProviderRequest", async (hook2) => { const replaced = await hook2({ model: model2, payload: current }, runtime, context); if (replaced !== void 0) current = replaced.payload; }); return current; } async function streamResponse(runtime, model2, messages, options, attempt, context) { const interval = runtime.settings.progress.partialIntervalMs; let pending; let timer; let inFlight; let stopped = false; const changes = new PartialChanges(); const flush = () => { timer = void 0; const partial = pending; pending = void 0; if (partial === void 0 || stopped) return; inFlight = (async () => { const message = copyJson(partial, { omitUndefinedProperties: true }); const taken = changes.take(message); try { await runtime.commit(async (tx) => { const live = await tx.doc(LiveDoc, runtime.conversationId); live.generation ??= { attempt }; changes.commit(live.generation, message, taken); return void 0; }, context, PROGRESS_COMMIT); } catch (error) { changes.restore(taken); throw error; } })().catch((error) => { if (!runtime.signal.aborted) runtime.report(error); }).finally(() => { inFlight = void 0; if (pending !== void 0 && !stopped) timer = runtime.setTimer(flush, interval); }); }; try { const events = runtime.models.streamSimple(model2, { messages: [...messages] }, options); for await (const event of events) { if (event.type === "done" || event.type === "error" || event.partial.content.length === 0) continue; changes.observe(event); pending = event.partial; if (timer === void 0 && inFlight === void 0) timer = runtime.setTimer(flush, interval); } return await events.result(); } finally { stopped = true; timer?.(); await inFlight; } } async function classify(runtime, request2, message, context) { runtime.signal.throwIfAborted(); const conversationId = runtime.conversationId; const { attempt, compacted, model: ref, thinkingLevel, cutoff } = request2; if (thinkingLevel !== void 0) message.thinkingLevel = thinkingLevel; if (message.stopReason === "deferred" && message.deferred !== void 0) { const handle = message.deferred; const pollAt = Math.max(runtime.now() + (handle.pollAfterMs ?? DEFAULT_POLL_AFTER_MS), request2.pollAt === void 0 ? Number.NEGATIVE_INFINITY : request2.pollAt + 1); await runtime.commit(async (tx) => { (await tx.doc(LiveDoc, conversationId)).generation = { attempt, deferred: { pollAt } }; const checkpoint = { phase: "poll", attempt, ...compacted === void 0 ? {} : { compacted }, model: ref, ...thinkingLevel === void 0 ? {} : { thinkingLevel }, cutoff, handle, pollAt }; return { status: "sleeping", checkpoint, until: pollAt }; }, context); return; } await runtime.hooks.each("afterResponse", (hook2) => hook2(message, runtime, context)); const calls = message.content.filter((content) => content.type === "toolCall"); if (message.stopReason === "toolUse" && calls.length > 0) { return startToolRound(runtime, request2, message, calls, context); } if (message.stopReason === "stop" || message.stopReason === "length" || message.stopReason === "toolUse") { return answer(runtime, message, context); } const settings = runtime.settings; const overflow = message.stopReason === "error" && isContextOverflow(message); const compaction = compactionPolicy(settings, ref); if (overflow && compacted === void 0 && compaction.enabled) { const view2 = await runtime.context(conversationId, context, { at: cutoff }); if (selectCut(view2, compaction.keepRecentTokens) !== void 0) { const text = message.errorMessage ?? "Context overflow"; await runtime.commit(async (tx) => { const live = await tx.doc(LiveDoc, conversationId); await appendAssistant(tx, conversationId, message); delete live.generation; const child = await createCompaction(tx, conversationId, { reason: "overflow" }, runtime.taskId); const checkpoint = { phase: "prepare", attempt, compacted: child, overflow: text }; return { status: "waiting", checkpoint, on: [child], policy: "allSettled" }; }, context); return; } } const policy = settings.retry; const retry = message.stopReason === "error" && !overflow && isRetryableAssistantError(message) && policy.enabled && attempt <= policy.maxRetries; const until2 = retry ? runtime.now() + retryDelayMs(policy, attempt) : 0; await runtime.commit(async (tx) => { const failure = retry ? void 0 : await prepareFailure(tx, runtime); const live = await tx.doc(LiveDoc, conversationId); await appendAssistant(tx, conversationId, message); if (failure === void 0) { live.generation = { attempt, retry: { at: until2, error: message.errorMessage ?? "" } }; const checkpoint = { phase: "retry", attempt, ...compacted === void 0 ? {} : { compacted }, until: until2 }; return { status: "sleeping", checkpoint, until: until2 }; } const text = message.errorMessage ?? `Model response ended with stop reason ${message.stopReason}`; await endFailedRun(tx, runtime, live, failure, { status: "unanswered", reason: "model_error", detail: text }); return { status: "terminal", outcome: { status: "failed", error: { message: text, detail: { reason: "model_error" } } } }; }, context); } async function answer(runtime, message, context) { let continuation; await runtime.hooks.each("onYield", async (hook2) => { if (continuation !== void 0) return; continuation = (await hook2(message, runtime, context))?.continue; }); const afterRun = await asksAfterRun(runtime, context); const conversationId = runtime.conversationId; await runtime.commit(async (tx) => { const boundary = await prepareBoundary(tx, conversationId, runtime.settings, "final", runtime.now()); const live = await tx.doc(LiveDoc, conversationId); const entry2 = await appendAssistant(tx, conversationId, message); const result = { status: "terminal", outcome: { status: "completed", result: { entryId: entry2.id } } }; const continues = takesContinuation(continuation, boundary) ? continuation : void 0; if (continues === void 0 && afterRun) { const held2 = holdRun(tx, live, runtime, { reason: "answer", answer: entry2.id, last: entry2.id }); if (held2 !== void 0) return held2; } const { users, first } = await applyBoundary(tx, boundary, "final", runtime.now()); if (continues !== void 0) { const user = { role: "user", content: continues, timestamp: runtime.now() }; await tx.appendEntry(UserEntry, conversationId, { model: [user] }); if (live.run?.taskId === runtime.taskId) live.run.turns = 0; handOver(live, runtime.taskId, await createGeneration(tx, conversationId)); delete live.generation; return result; } endRun(tx, live, runtime.taskId, { status: "done", answer: entry2.id }); if (users.length > 0) await startRun(tx, conversationId, live, { inputs: users, first }); return result; }, context); } async function asksAfterRun(runtime, context) { const hooks = agentHooks(await runtime.agent(context), GenerationTask.definition.name); return hooks.some(({ handlers }) => typeof handlers.afterRun === "function"); } function holdRun(tx, live, runtime, end) { const run2 = live.run; if (run2?.taskId !== runtime.taskId) return void 0; for (const id of run2.inputs) tx.settleSubmission(id, { status: "done", answer: end.answer }); run2.inputs = []; run2.afterRun = {}; delete live.generation; delete live.tools; delete live.nestedTools; const summary = { ...end, first: run2.first ?? end.answer }; return { status: "running", checkpoint: { phase: "finish", run: summary } }; } function prepareRunEndTasks(runtime, requests, prepared) { const rejected = []; for (const request2 of requests) { try { const name = request2.task.definition.name; if (name.startsWith("pi.")) throw new Error(`An afterRun hook cannot start the built-in task ${name}`); const installed = runtime.registry.task(name); if (installed === void 0) throw new Error(`Task ${name} is not installed`); const input = copyJson(request2.input, { omitUndefinedProperties: true }); const definition = installed.definition; const checkpoint = copyJson(definition.initial(input), { omitUndefinedProperties: true }); if (!Object.hasOwn(definition.phases, checkpoint.phase)) { throw new Error(`Task ${name} starts at the unknown phase ${String(checkpoint.phase)}`); } prepared.push({ task: { definition: { ...definition, initial: () => checkpoint } }, input }); } catch (error) { rejected.push(error); } } if (rejected.length === 1) throw rejected[0]; if (rejected.length > 1) throw new AggregateError(rejected, `${rejected.length} afterRun task requests were rejected`); } async function endAnsweredRun(runtime, answer2, context) { const conversationId = runtime.conversationId; await runtime.commit(async (tx) => { const boundary = await prepareBoundary(tx, conversationId, runtime.settings, "final", runtime.now()); const live = await tx.doc(LiveDoc, conversationId); const { users, first } = await applyBoundary(tx, boundary, "final", runtime.now()); endRun(tx, live, runtime.taskId, { status: "done", answer: answer2 }); if (users.length > 0) await startRun(tx, conversationId, live, { inputs: users, first }); return { status: "terminal", outcome: { status: "completed", result: { entryId: answer2 } } }; }, context); } async function endAnsweredCutOff(runtime, task, answer2, context) { const text = `The generation was interrupted ${cutOffCount(task)} in its ${task.state.checkpoint.phase} phase`; await runtime.commit(async (tx) => { const live = await tx.doc(LiveDoc, runtime.conversationId); endRun(tx, live, runtime.taskId, { status: "done", answer: answer2 }); const completed = { status: "terminal", outcome: { status: "completed", result: { entryId: answer2 } } }; return endedByCrashes(completed, text); }, context); } function takesContinuation(continuation, boundary) { const items = boundary.inbox.items; return continuation !== void 0 && !items.some((item) => item.mode === "steer" || item.mode === "followUp") && !hasQueuedReset(items); } async function startToolRound(runtime, request2, message, calls, context) { const conversationId = runtime.conversationId; const messages = request2.messages ?? (await runtime.context(conversationId, context, { at: request2.cutoff })).messages; const offered = new Set(getCurrentTools(messages).map((tool) => tool.name)); const tools = (await runtime.agent(context)).tools; const sequential = runtime.settings.toolExecution === "sequential" || calls.some((call2) => offered.has(call2.name) && tools.find((tool) => tool.name === call2.name)?.executionMode === "sequential"); await runtime.commit(async (tx) => { const live = await tx.doc(LiveDoc, conversationId); const entry2 = await appendAssistant(tx, conversationId, message); const slots = []; const tools2 = []; const pending = []; for (const call2 of calls) { const unoffered = offered.has(call2.name) ? {} : { unoffered: true }; if (sequential && tools2.length > 0) { pending.push(call2.id); slots.push({ callId: call2.id, name: call2.name, status: "pending", ...unoffered }); continue; } const taskId = await createToolTask(tx, runtime, entry2.id, call2.id, unoffered.unoffered); tools2.push(taskId); slots.push({ callId: call2.id, name: call2.name, taskId, status: "pending", ...unoffered }); } delete live.generation; live.tools = slots; if (live.run?.taskId === runtime.taskId) { live.run.turns += 1; live.run.first ??= entry2.id; } const checkpoint = { phase: "tools", assistant: entry2.id, tools: tools2, pending }; return { status: "waiting", checkpoint, on: tools2, policy: "allSettled" }; }, context); } async function finishToolRound(runtime, assistant, tools, context) { const conversationId = runtime.conversationId; const controls = /* @__PURE__ */ new Map(); const outcomes = await runtime.outcomes(tools, context); tools.forEach((id, index) => { const outcome = outcomes[index]; controls.set(id, outcome.status === "completed" && outcome.result.kind !== "nested" ? outcome.result.control : void 0); }); const slots = (await runtime.snapshot(LiveDoc, conversationId, context))?.tools ?? []; const results = slots.flatMap((slot) => slot.entry === void 0 ? [] : [slot.entry]); await runtime.hooks.each("afterTools", (hook2) => hook2(assistant, results, runtime, context)); const ordered = slots.map((slot) => slot.taskId === void 0 ? void 0 : controls.get(slot.taskId)); const terminate = slots.length > 0 && ordered.every((control) => control?.terminate === true); const added = ordered.flatMap((control) => control?.addTools ?? []); const handoff = ordered.findLast((control) => control?.handoff !== void 0)?.handoff; const maxTurns = (await runtime.agent(context)).maxTurns; const afterRun = terminate && await asksAfterRun(runtime, context); await runtime.commit(async (tx) => { const boundary = await prepareBoundary(tx, conversationId, runtime.settings, terminate || handoff !== void 0 ? "final" : "postTools", runtime.now()); if (added.length > 0) await addTools(tx, conversationId, added); const live = await tx.doc(LiveDoc, conversationId); const now = runtime.now(); let last = results.reduce((newest, id) => id > newest ? id : newest, assistant); if (handoff !== void 0) { const message = { role: "user", content: handoff, timestamp: now }; const entry2 = await tx.appendEntry(ResetEntry, conversationId, { head: "self", model: [message] }); boundary.head = entry2.id; last = entry2.id; } const turns = live.run?.taskId === runtime.taskId ? live.run.turns : 0; const held2 = afterRun ? holdRun(tx, live, runtime, { reason: "terminate", answer: assistant, last }) : void 0; if (held2 !== void 0) return held2; if (terminate) { const { users, first } = await applyBoundary(tx, boundary, "final", now); endRun(tx, live, runtime.taskId, { status: "done", answer: assistant }); if (users.length > 0) await startRun(tx, conversationId, live, { inputs: users, first }); } else if (maxTurns !== void 0 && turns >= maxTurns) { const policy = runtime.settings.afterFailedRun; const failure = { policy, ...policy === "continue" ? { boundary } : {} }; await endFailedRun(tx, runtime, live, failure, { status: "unanswered", reason: "turn_limit", detail: turns === 1 ? "1 turn" : `${turns} turns` }); } else { const { users, first, reset } = await applyBoundary(tx, boundary, "postTools", now); if (reset) { endRun(tx, live, runtime.taskId, { status: "unanswered", reason: "reset" }); if (users.length > 0) { await startRun(tx, conversationId, live, { inputs: users, first }, { keepFailure: true }); } } else { delete live.tools; delete live.nestedTools; if (live.run?.taskId === runtime.taskId) live.run.inputs.push(...users); handOver(live, runtime.taskId, await createGeneration(tx, conversationId)); } } return { status: "terminal", outcome: { status: "completed", result: { entryId: assistant } } }; }, context); } async function appendAssistant(tx, conversationId, message) { await recordUsage(tx, conversationId, "models", `${message.provider}/${message.model}`, message.usage); return tx.appendEntry(AssistantEntry, conversationId, { model: [message] }); } async function startRun(tx, conversationId, live, run2, options) { const { inputs, first } = run2; const taskId = await createGeneration(tx, conversationId, { submissionIds: inputs }); live.run = { taskId, inputs, turns: 0, ...first === void 0 ? {} : { first } }; if (options?.keepFailure !== true) delete live.runFailure; } function createGeneration(tx, conversationId, telemetry = {}) { return tx.createTask(GenerationTask, {}, { ownership: { kind: "conversation" }, conversationId, ...telemetry }); } function handOver(live, from, to) { if (live.run?.taskId === from) live.run.taskId = to; } // node_modules/@earendil-works/pi-durable/dist/harness/live.js var LiveDoc = defineDoc({ kind: "pi.live", version: 3, scope: "conversation", history: "latest", fork: "initial", initial: () => ({}), // Version 1 named a compaction's trigger `reason`, in `compactions` and `compactionFailure`. Versions 1 and 2 (as // builds before `run.turns` stored it) may lack `run.turns`: a run in flight across the upgrade counts its turns // from 0. Version 2 already names the trigger `trigger`, so renaming leaves it alone. migrate: (value2) => { const live = value2; const { run: run2, compactions, compactionFailure: compactionFailure2 } = live; return { ...value2, ...run2 === void 0 ? {} : { run: { ...run2, turns: typeof run2.turns === "number" ? run2.turns : 0 } }, ...compactions === void 0 ? {} : { compactions: compactions.map(renameReason) }, ...compactionFailure2 === void 0 ? {} : { compactionFailure: renameReason(compactionFailure2) } }; }, // REMINDER: a complete base whenever nothing runs (spec §8.2): no generation and no running tool slot. That holds // while idle, in the commit handing a generation over to its tool round, and between tools, so the delta chain // spans at most one generation or the overlapping execution of one round's tools. A slot holds output only while // running, so every base is small; a nested call's result lives in its caller's task documents, never here. Do not // add a delta-count bound; the tool output benchmark checks this rule. checkpointWhen: (value2) => value2.generation === void 0 && !(value2.tools ?? []).some((slot) => slot.status === "running") && !(value2.nestedTools ?? []).some((slot) => slot.status === "running") }); function renameReason(value2) { return Object.fromEntries(Object.entries(value2).map(([key, field]) => [key === "reason" ? "trigger" : key, field])); } var PROGRESS_COMMIT = { persistence: { kind: "deferred" } }; var RUN_TASK_KINDS = /* @__PURE__ */ new Set(["pi.generation"]); var TOOL_TASK_KIND = "pi.tool"; var COMPACTION_TASK_KIND = "pi.compaction"; var LIMITS_RESTARTS = /* @__PURE__ */ new Set([...RUN_TASK_KINDS, TOOL_TASK_KIND, COMPACTION_TASK_KIND]); function cutOffTooOften(task, runtime) { return task.restarts !== void 0 && task.restarts >= runtime.settings.restartLimit; } function salvageable(task, runtime) { return (task.restarts ?? 0) <= runtime.settings.restartLimit; } function cutOffCount(task) { const restarts = task.restarts ?? 0; return restarts === 1 ? "once" : `${restarts} times in a row`; } function endRun(tx, live, taskId, end) { if (live.run?.taskId === taskId) { const inputs = [...live.run.inputs]; const { first } = live.run; if (end.status === "done") { for (const id of inputs) tx.settleSubmission(id, { status: "done", answer: end.answer }); } else if (live.run.afterRun === void 0) { const { reason, detail } = end; const settlement = end.settlement ?? { status: "unanswered", reason, ...detail === void 0 ? {} : { detail } }; for (const id of inputs) tx.settleSubmission(id, settlement); live.runFailure = { taskId, inputs, reason, ...detail === void 0 ? {} : { detail }, ...first === void 0 ? {} : { first } }; } delete live.run; } delete live.generation; delete live.tools; delete live.nestedTools; } function addCompactionStatus(live, status) { live.compactions ??= []; live.compactions.push(status); } function compactionStatus(live, taskId) { return live.compactions?.find((status) => status.taskId === taskId); } function removeCompactionStatus(live, taskId) { const statuses = live.compactions; if (statuses === void 0) return; const index = statuses.findIndex((status) => status.taskId === taskId); if (index >= 0) statuses.splice(index, 1); if (statuses.length === 0) delete live.compactions; } function failCompactionStatus(live, taskId, error) { const status = compactionStatus(live, taskId); if (status !== void 0) live.compactionFailure = { taskId: status.taskId, trigger: status.trigger, error }; removeCompactionStatus(live, taskId); } function toolSlot(live, taskId) { const slot = live.tools?.find((each) => each.taskId === taskId); if (slot !== void 0 || live.nestedTools === void 0) return slot; const nested = live.nestedTools; let low = 0; let high = nested.length - 1; while (low <= high) { const middle = low + high >>> 1; const candidate = nested[middle]; if (candidate.taskId === taskId) return candidate; if (candidate.taskId < taskId) low = middle + 1; else high = middle - 1; } return void 0; } function finishSlot(slot) { slot.status = "done"; clearProgress(slot); } function removeNestedSlots(live, taskId) { const nested = live.nestedTools; if (nested === void 0) return; const removed = /* @__PURE__ */ new Set([taskId]); for (const slot of nested) if (removed.has(slot.parentTaskId)) removed.add(slot.taskId); for (let index = nested.length - 1; index >= 0; index--) { if (removed.has(nested[index].parentTaskId)) nested.splice(index, 1); } if (nested.length === 0) delete live.nestedTools; } function clearProgress(slot) { delete slot.output; delete slot.droppedBytes; delete slot.droppedLines; delete slot.details; delete slot.diagnostics; } async function settleSchedulerOutcome(tx, record2, outcome) { if (record2.kind === TOOL_TASK_KIND) { const live2 = await tx.doc(LiveDoc, record2.conversationId); const slot = toolSlot(live2, record2.id); if (slot !== void 0) finishSlot(slot); removeNestedSlots(live2, record2.id); return; } const detail = outcome.status === "faulted" ? outcome.error.detail : void 0; const cutOff = typeof detail === "object" && detail !== null && "reason" in detail && detail.reason === "restart_limit"; if (record2.kind === COMPACTION_TASK_KIND) { const live2 = await tx.doc(LiveDoc, record2.conversationId); if (cutOff) removeCompactionStatus(live2, record2.id); else failCompactionStatus(live2, record2.id, outcome.status === "faulted" ? outcome.error.message : outcome.reason); return; } if (!RUN_TASK_KINDS.has(record2.kind)) return; const live = await tx.doc(LiveDoc, record2.conversationId); if (live.run?.taskId !== record2.id) return; if (!cutOff) await convertPartial(tx, live, record2.conversationId); endRun(tx, live, record2.id, outcome.status === "faulted" ? { status: "unanswered", reason: cutOff ? "aborted" : "faulted", detail: outcome.error.message } : { status: "unanswered", reason: "orphaned", detail: outcome.reason, settlement: { status: "unanswered", reason: outcome.reason } }); } // node_modules/@earendil-works/pi-durable/dist/harness/submissions.js var Submissions = class { #telemetry; #session; #now; /** Read at each admission, on the Session line. */ #queueModes; /** Enable task scheduling; submitting or waiting asks for progress. */ #resume; #waiters = new Waiters(); #closed = false; constructor(session, now, queueModes, resume, telemetry = NOOP_TELEMETRY_CONTEXT) { this.#telemetry = telemetry; this.#session = session; this.#now = now; this.#queueModes = queueModes; this.#resume = resume; session.followCommits((publication) => this.#observe(publication)); session.subscribeClose(() => { this.#closed = true; this.#waiters.rejectAll(closedError(this.#session)); }); } /** Admit a submission in one commit; see `admitSubmission()`. */ async submit(conversationId, draft, context) { const { id } = await this.#traced(conversationId, draft, context, (span) => this.#session.commitWith((tx) => admitSubmission(tx, conversationId, draft, this.#now(), this.#queueModes(), span), withTelemetry(span, context))); return new SubmissionHandle(id, this); } /** * Admit a submission inside the caller's commit `tx`, with the rules of `submit()`. The admission is stored or lost * with that commit. It must come before the commit's first table write, another admission's included. */ async admit(tx, conversationId, draft) { if (!this.#session.ownsTransaction(tx)) throw new Error("The transaction belongs to another Session"); if (tx.hasTableWrite) throw new ReadAfterWrite("submit(tx, draft)"); const admission = await this.#traced(conversationId, draft, tx.context, (span) => admitSubmission(tx, conversationId, draft, this.#now(), this.#queueModes(), span)); return admission.id; } /** Run one admission in its `pi.durable.submit` span, asking for progress first, as the run it starts needs it. */ #traced(conversationId, draft, context, admit) { this.#resume(); const startsRun = draft.type !== "write"; const parent = startsRun ? this.#telemetry : context.telemetry; return createTypedSpanStarter(parent, [DURABLE_TELEMETRY_SCHEMA])("pi.durable.submit", { "submission.type": draft.type, "conversation.id": conversationId }, async (span) => { if (startsRun) span.addLink({ context: context.telemetry }); try { const admission = await admit(span); span.setAttributes({ "submission.id": admission.id, result: admission.status }); return admission; } catch (error) { span.setStatus({ status: "error" }); throw error; } }); } /** Handle for an existing submission, or `undefined`. */ async get(id, context) { const record2 = await this.#session.readOnLine((storage2) => storage2.submission(id, context)); return record2 === void 0 ? void 0 : new SubmissionHandle(record2.id, this); } async status(id, context) { const record2 = await this.#session.readOnLine((storage2) => storage2.submission(id, context)); if (record2 === void 0) throw new Error(`Submission ${id} does not exist`); return record2; } async wait(id, context) { this.#resume(); const found = await this.#session.readOnLine(async (storage2) => { const record2 = await storage2.submission(id, context); if (record2 === void 0) throw new Error(`Submission ${id} does not exist`); if (isSettled(record2)) return { promise: Promise.resolve(record2) }; if (this.#closed) throw closedError(this.#session); return { promise: this.#waiters.add(id, context) }; }); return found.promise; } /** Withdraw a queued submission and remove its inbox item; placed inputs and settled submissions are reported. */ abort(id, context, conversationId) { return this.#session.commitWith(async (tx) => { const record2 = await tx.submission(id); if (record2 === void 0 || conversationId !== void 0 && record2.conversationId !== conversationId) { return "not_found"; } if (record2.status === "queued") { tx.settleSubmission(id, { status: "unanswered", reason: "aborted" }); await removeInboxItem(tx, record2.conversationId, id); return "aborted"; } return record2.status === "placed" ? "already_placed" : "settled"; }, context); } #observe(publication) { if (publication.kind === "memory") return; for (const change of publication.changes) { if (change.type !== "submission" || !isSettled(change.value)) continue; this.#waiters.resolve(change.value.id, change.value); } } }; var SubmissionHandle = class { id; #submissions; constructor(id, submissions) { this.id = id; this.#submissions = submissions; } status(context) { return this.#submissions.status(this.id, context); } wait(context) { return this.#submissions.wait(this.id, context); } async abort(context) { const result = await this.#submissions.abort(this.id, context); if (result === "not_found") throw new Error(`Submission ${this.id} does not exist`); return result; } }; function isSettled(record2) { return record2.status === "done" || record2.status === "unanswered"; } function checkInputEntry(entry2) { const { kind } = entry2; if (typeof kind !== "string" || kind.length === 0 || kind.startsWith("pi.")) { throw new TypeError(`An input entry's kind must be a non-empty string not starting with "pi.", got ${String(kind)}`); } } async function admitSubmission(tx, conversationId, draft, now, queueModes, span) { if (draft.type === "input" && draft.entry !== void 0) checkInputEntry(draft.entry); if (draft.requestId !== void 0) { const existing = await tx.submissionByRequest(conversationId, draft.requestId); if (existing !== void 0) { if (existing.type !== draft.type) { throw new Error(`Request ${draft.requestId} already identifies a submission of type ${existing.type}`); } return { id: existing.id, status: existing.status === "unanswered" ? "done" : existing.status }; } } const fields = { conversationId, requestId: draft.requestId, trace: span?.propagation?.inject(span) }; if (draft.type === "write" && isDisplayOnly(draft.entry) && !hasQueuedReset((await tx.doc(InboxDoc, conversationId)).items)) { const entry3 = await tx.appendEntry(conversationId, draft.entry); const write = { ...fields, type: "write", status: "done", entry: entry3.id }; return { id: (await tx.createSubmission(write)).id, status: "done" }; } const live = await tx.doc(LiveDoc, conversationId); const busy = live.run !== void 0; if (draft.type === "input" && draft.whenBusy === "steer" && live.run !== void 0 && span?.propagation !== void 0) { const run2 = await tx.task(live.run.taskId); if (run2?.trace !== void 0) span.addLink({ context: span.propagation.extract(run2.trace) }); } if (busy && draft.type === "input" && draft.whenBusy === "reject") throw new ConversationBusy(conversationId); if (!busy && draft.type === "input" && draft.whenIdle === "reject") throw new ConversationIdle(conversationId); if (draft.type === "continue") { if (busy) throw new ConversationBusy(conversationId); const boundary2 = await prepareBoundary(tx, conversationId, queueModes, "final", now); const { id: id2 } = await tx.createSubmission({ ...fields, type: "continue", status: "placed" }); const { users, first } = await applyBoundary(tx, boundary2, "final", now); await startRun(tx, conversationId, live, { inputs: [...users, id2], first: live.runFailure?.first ?? first }); return { id: id2, status: "placed" }; } const boundary = busy ? void 0 : await prepareBoundary(tx, conversationId, queueModes, "final", now, draft.type === "input" ? inputDraft(draft.content, draft.entry, now) : void 0); if (boundary === void 0 || boundary.inbox.items.length > 0) { const { id: id2 } = await tx.createSubmission({ ...fields, type: draft.type, status: "queued" }); const value2 = copyJson(draft.type === "write" ? draft.entry : draft.content, { omitUndefinedProperties: true }); const items = (boundary?.inbox ?? await tx.doc(InboxDoc, conversationId)).items; if (draft.type === "write") items.push({ id: id2, mode: "write", entry: value2 }); else { const mode = draft.whenBusy === "steer" ? "steer" : "followUp"; const content = value2; const entry3 = draft.entry === void 0 ? {} : { entry: pickInputEntry(draft.entry) }; items.push({ id: id2, mode, content, ...entry3 }); } if (boundary === void 0) return { id: id2, status: "queued" }; const { users, first } = await applyBoundary(tx, boundary, "final", now); if (users.length > 0) await startRun(tx, conversationId, live, { inputs: users, first }); return { id: id2, status: draft.type === "write" ? "done" : users.at(-1) === id2 ? "placed" : "queued" }; } if (draft.type === "write") { if (isStale(boundary, draft.entry)) { const stale = { status: "unanswered", reason: "stale" }; return { id: (await tx.createSubmission({ ...fields, type: "write", ...stale })).id, status: "done" }; } const entry3 = await tx.appendEntry(conversationId, draft.entry); const write = { ...fields, type: "write", status: "done", entry: entry3.id }; return { id: (await tx.createSubmission(write)).id, status: "done" }; } for (const before of boundary.entries) await tx.appendEntry(conversationId, before); const entry2 = await appendInput(tx, conversationId, draft.content, draft.entry, now); const input = { ...fields, type: "input", status: "placed", entry: entry2.id }; const { id } = await tx.createSubmission(input); await startRun(tx, conversationId, live, { inputs: [id], first: entry2.id }); return { id, status: "placed" }; } // node_modules/@earendil-works/pi-durable/dist/harness/compaction.js var TOOL_RESULT_MAX_CHARS = 2e3; var SUMMARY_PREFIX = "The conversation history before this point was compacted into the following summary:\n\n\n"; var SUMMARY_SUFFIX = "\n"; var SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI assistant, then produce a structured summary following the exact format specified. Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`; var SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work. If the conversation starts with an earlier summary, preserve its information and fold the newer messages into it. Use this EXACT format: ## Goal [What is the user trying to accomplish? Can be multiple items if the session covers different tasks.] ## Constraints & Preferences - [Any constraints, preferences, or requirements mentioned by user] - [Or "(none)" if none were mentioned] ## Progress ### Done - [x] [Completed tasks/changes] ### In Progress - [ ] [Current work] ### Blocked - [Issues preventing progress, if any] ## Key Decisions - **[Decision]**: [Brief rationale] ## Next Steps 1. [Ordered list of what should happen next] ## Critical Context - [Any data, examples, or references needed to continue] - [Or "(none)" if not applicable] Keep each section concise. Preserve exact file paths, function names, and error messages.`; var CompactionTask = defineTask({ name: "pi.compaction", version: 1, initial: () => ({ phase: "select" }), phases: { select: async (task, runtime, context) => { if (cutOffTooOften(task, runtime)) return failCutOff(runtime, task, context); const { conversationId } = runtime; const agent = await runtime.agent(context); const settings = runtime.settings; const ref = agent.model; const model2 = ref === void 0 ? void 0 : runtime.models.getModel(ref.provider, ref.modelId); if (ref === void 0 || model2 === void 0) return failNoModel2(runtime, ref, context); const policy = compactionPolicy(settings, ref); const view2 = await runtime.context(conversationId, context); const cut = selectCut(view2, policy.keepRecentTokens); if (cut === void 0) return complete(runtime, context); const firstKept = view2.entries[cut].id; const { reason, instructions } = task.input; const compaction = { reason, entries: view2.entries.slice(0, cut), messages: summarizedMessages(view2, cut), firstKept, ...instructions === void 0 ? {} : { instructions } }; let decision; await runtime.hooks.each("beforeCompact", async (hook2) => { if (decision === void 0) decision = await hook2(compaction, runtime, context); }); if (decision !== void 0 && "decline" in decision) return complete(runtime, context); if (decision !== void 0) return place(runtime, firstKept, decision.summary, context); const request2 = { attempt: 1, model: ref, thinkingLevel: agent.thinkingLevel, streamOptions: settings.stream, maxTokens: Math.min(Math.floor(0.8 * policy.reserveTokens), model2.maxTokens > 0 ? model2.maxTokens : Number.POSITIVE_INFINITY), tail: view2.entries.reduce((tail, entry2) => entry2.id > tail ? entry2.id : tail, firstKept), firstKept }; await runtime.commit(() => ({ status: "running", checkpoint: { phase: "summarize", ...request2 } }), context); }, summarize: async (task, runtime, context) => { if (cutOffTooOften(task, runtime)) return failCutOff(runtime, task, context); const { phase: _, ...request2 } = task.state.checkpoint; const { model: ref, thinkingLevel, streamOptions, maxTokens, tail, firstKept, attempt } = request2; const model2 = runtime.models.getModel(ref.provider, ref.modelId); if (model2 === void 0) return failNoModel2(runtime, ref, context); const view2 = await runtime.context(runtime.conversationId, context, { at: tail }); const cut = view2.entries.findIndex((entry2) => entry2.id === firstKept); const now = runtime.now(); const messages = [ { role: "system", content: SUMMARIZATION_SYSTEM_PROMPT, timestamp: now }, { role: "user", content: [{ type: "text", text: summaryPrompt(summarizedMessages(view2, cut), task.input.instructions) }], timestamp: now } ]; const { deferred: _deferred, ...forwarded } = streamOptions; const options = { ...forwarded, cacheRetention: "none", maxTokens, signal: runtime.signal, telemetry: context.telemetry, sessionId: await ensureProviderSessionId(runtime, context), ...thinkingLevel === "off" ? {} : { reasoning: thinkingLevel } }; const message = await runtime.models.completeSimple(model2, { messages }, options); runtime.signal.throwIfAborted(); const summary = summaryText(message); const policy = runtime.settings.retry; const retry = message.stopReason === "error" && isRetryableAssistantError(message) && policy.enabled && attempt <= policy.maxRetries; const until2 = retry ? runtime.now() + retryDelayMs(policy, attempt) : 0; await runtime.commit(async (tx, current) => { await recordUsage(tx, runtime.conversationId, "models", `${message.provider}/${message.model}`, message.usage); const live = await tx.doc(LiveDoc, runtime.conversationId); if (summary !== void 0) return placeSummary(tx, runtime, current, live, firstKept, summary); if (retry) { const status = compactionStatus(live, runtime.taskId); if (status !== void 0) status.retry = { at: until2, error: message.errorMessage ?? "" }; const checkpoint = { phase: "retry", ...request2, until: until2 }; return { status: "sleeping", checkpoint, until: until2 }; } const text = summaryFailure(message); failCompactionStatus(live, runtime.taskId, text); return { status: "terminal", outcome: { status: "failed", error: { message: text, detail: { reason: "model_error" } } } }; }, context); }, /** After the backoff's sleep; a run before `until` (an early wake, or a record of an earlier version) sleeps again. */ retry: async (task, runtime, context) => { const { phase: _, until: until2, ...request2 } = task.state.checkpoint; if (cutOffTooOften(task, runtime)) return failCutOff(runtime, task, context); if (until2 > runtime.now()) return runtime.sleepUntil(until2, task.state.checkpoint, context); const attempt = request2.attempt + 1; await runtime.commit(async (tx) => { const status = compactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId); if (status !== void 0) { status.attempt = attempt; delete status.retry; } return { status: "running", checkpoint: { phase: "summarize", ...request2, attempt } }; }, context); } }, abort: async (_task, runtime, context) => { await runtime.commit(async (tx) => { removeCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId); return { status: "terminal", outcome: { status: "aborted" } }; }, context); } }); async function createCompaction(tx, conversationId, input, owner) { const ownership = owner === void 0 ? { kind: "conversation" } : { kind: "task", taskId: owner }; const background = owner === void 0 && input.reason !== "manual"; const taskId = await tx.createTask(CompactionTask, input, { ownership, conversationId, background }); const status = { taskId, trigger: input.reason, blocking: owner !== void 0, attempt: 1 }; addCompactionStatus(await tx.doc(LiveDoc, conversationId), status); return taskId; } function compactionPolicy(settings, model2) { const { modelOverrides, ...policy } = settings.compaction; const override = modelOverrides?.[`${model2.provider}/${model2.modelId}`]; return { enabled: override?.enabled ?? policy.enabled, reserveTokens: override?.reserveTokens ?? policy.reserveTokens, keepRecentTokens: override?.keepRecentTokens ?? policy.keepRecentTokens, backgroundTokens: override?.backgroundTokens ?? policy.backgroundTokens }; } function selectCut(view2, keepRecentTokens) { const { contributions } = view2; const start = view2.head === void 0 ? 0 : 1; const candidates = []; for (let index = start; index < contributions.length; index++) { if (isCandidate(contributions, index)) candidates.push(index); } let kept = 0; let cut; for (let index = contributions.length - 1; index >= start; index--) { for (const message of contributions[index]) kept += estimateMessageTokens(message); if (kept < keepRecentTokens) continue; cut = candidates.find((candidate) => candidate >= index) ?? candidates.at(-1); break; } if (cut === void 0) return void 0; for (let index = start; index < cut; index++) if (contributions[index].length > 0) return cut; return void 0; } function isCandidate(contributions, index) { const first = contributions[index][0]; if (first?.role === "assistant") return true; if (first?.role !== "user") return false; let calls = /* @__PURE__ */ new Set(); for (let before = index - 1; before >= 0; before--) { const assistant = contributions[before].findLast((message) => message.role === "assistant"); if (assistant === void 0) continue; calls = new Set(assistant.content.flatMap((content) => content.type === "toolCall" ? [content.id] : [])); break; } if (calls.size === 0) return true; for (let after = index; after < contributions.length; after++) { for (const [position, message] of contributions[after].entries()) { if (message.role === "assistant" && (after > index || position > 0)) return true; if (message.role === "toolResult" && calls.has(message.toolCallId)) return false; } } return true; } function summarizedMessages(view2, cut) { return orderToolResults(view2.contributions.slice(0, cut).flat()); } function estimateContext(view2, extra) { let measured; const after = view2.head?.id ?? Number.NEGATIVE_INFINITY; for (let index = view2.entries.length - 1; index >= 0 && measured === void 0; index--) { if (view2.entries[index].id <= after) continue; measured = view2.contributions[index].findLast((message) => message.role === "assistant" && calculateContextTokens(message.usage) > 0); } const from = measured === void 0 ? 0 : view2.messages.lastIndexOf(measured) + 1; let tokens = measured === void 0 ? 0 : calculateContextTokens(measured.usage); for (const message of view2.messages.slice(from)) tokens += estimateMessageTokens(message); for (const message of extra) tokens += estimateMessageTokens(message); return tokens; } function summaryText(message) { if (message.stopReason !== "stop" || message.content.some((content) => content.type === "toolCall")) return void 0; const text = message.content.flatMap((content) => content.type === "text" ? [content.text] : []).join("\n").trim(); return text.length === 0 ? void 0 : text; } function summaryFailure(message) { if (message.stopReason === "error" || message.stopReason === "aborted") { return `Summarization failed: ${message.errorMessage ?? message.stopReason}`; } if (message.stopReason === "length") return "Summarization hit the token limit; the summary is incomplete"; if (message.content.some((content) => content.type === "toolCall")) return "Summarization attempted to call a tool"; return "Summarization produced no text"; } function summaryPrompt(messages, instructions) { const focus = instructions === void 0 ? "" : ` Additional focus: ${instructions}`; return ` ${serializeConversation(messages)} ${SUMMARIZATION_PROMPT}${focus}`; } function serializeConversation(messages) { const parts = []; for (const message of messages) { if (message.role === "user") { const text = contentText2(message.content); if (text.length > 0) parts.push(`[User]: ${text}`); } else if (message.role === "assistant") { const text = message.content.flatMap((content) => content.type === "text" ? [content.text] : []); const calls = message.content.flatMap((content) => content.type === "toolCall" ? [ `${content.name}(${Object.entries(content.arguments).map(([key, value2]) => `${key}=${JSON.stringify(value2)}`).join(", ")})` ] : []); if (text.length > 0) parts.push(`[Assistant]: ${text.join("\n")}`); if (calls.length > 0) parts.push(`[Assistant tool calls]: ${calls.join("; ")}`); } else if (message.role === "toolResult") { const text = contentText2(message.content); if (text.length > 0) parts.push(`[Tool result]: ${truncate(text, TOOL_RESULT_MAX_CHARS)}`); } } return parts.join("\n\n"); } function contentText2(content) { if (typeof content === "string") return content; return content.flatMap((block) => block.type === "text" && block.text !== void 0 ? [block.text] : []).join("\n"); } function truncate(text, maxChars) { if (text.length <= maxChars) return text; return `${text.slice(0, maxChars)} [... ${text.length - maxChars} more characters truncated]`; } async function place(runtime, firstKept, summary, context) { await runtime.commit(async (tx, current) => placeSummary(tx, runtime, current, await tx.doc(LiveDoc, runtime.conversationId), firstKept, summary), context); } async function placeSummary(tx, runtime, current, live, firstKept, summary) { removeCompactionStatus(live, runtime.taskId); delete live.compactionFailure; const text = `${SUMMARY_PREFIX}${summary}${SUMMARY_SUFFIX}`; const entry2 = { kind: CompactionEntry.kind, head: firstKept, model: [{ role: "user", content: [{ type: "text", text }], timestamp: runtime.now() }], data: { reason: current.input.reason } }; const result = current.owner === void 0 ? { submissionId: (await admitSubmission(tx, runtime.conversationId, { type: "write", requestId: `compaction:${runtime.taskId}`, entry: entry2 }, runtime.now(), runtime.settings)).id } : { entryId: (await tx.appendEntry(runtime.conversationId, entry2)).id }; return { status: "terminal", outcome: { status: "completed", result } }; } async function complete(runtime, context) { await runtime.commit(async (tx) => { removeCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId); return { status: "terminal", outcome: { status: "completed", result: {} } }; }, context); } async function failCutOff(runtime, task, context) { const message = `Compaction was interrupted ${cutOffCount(task)}`; await runtime.commit(async (tx) => { failCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId, message); return { status: "terminal", outcome: { status: "failed", error: { message, detail: { reason: "restart_limit" } } } }; }, context); } async function failNoModel2(runtime, ref, context) { const message = ref === void 0 ? "No model is configured" : `Model ${ref.provider}/${ref.modelId} is not available`; await runtime.commit(async (tx) => { failCompactionStatus(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId, message); return { status: "terminal", outcome: { status: "failed", error: { message, detail: { reason: "no_model" } } } }; }, context); } // node_modules/@earendil-works/pi-durable/dist/harness/define.js function defineExtension(extension) { return extension; } function defineTool(tool) { return tool; } function section(key, render, options) { return options?.tag === void 0 ? { key, render } : { key, render, tag: options.tag }; } function hook(task, handlers) { return { task: task.definition.name, handlers }; } function runEndTask(task, input) { return { task, input }; } function wrapTool(tool, wrapper) { return { tool: tool.name, wrap: wrapper }; } function wrapSection(key, wrapper) { return { section: key, wrap: wrapper }; } // node_modules/@earendil-works/pi-durable/dist/harness/registry.js var SECTION_KEY = /^[a-z][a-z0-9_-]*$/; var BUILTIN_TASKS = [GenerationTask, ToolTask, CompactionTask]; var RegistryState = class { #extensions; #byName; #tasks; constructor(extensions) { this.#extensions = extensions; this.#byName = new Map(extensions.map((extension) => [extension.name, extension])); const tasks = /* @__PURE__ */ new Map(); for (const task of BUILTIN_TASKS) tasks.set(task.definition.name, task); for (const extension of extensions) { for (const task of extension.tasks ?? []) { const name = task.definition.name; if (tasks.has(name)) throw new Error(`Task ${name} of extension ${extension.name} is already installed`); tasks.set(name, task); } } this.#tasks = tasks; } installed() { return this.#extensions; } extension(name) { return this.#byName.get(name); } tools() { return this.#extensions.flatMap((extension) => (extension.tools ?? []).map((tool) => ({ extension, tool }))); } sections() { return this.#extensions.flatMap((extension) => (extension.sections ?? []).map((section2) => ({ extension, section: section2 }))); } tasks() { return [...this.#tasks.values()]; } task(name) { return this.#tasks.get(name); } }; var RegistryImpl = class { #current = new RegistryState([]); #listeners = /* @__PURE__ */ new Set(); snapshot() { return this.#current; } subscribe(listener) { this.#listeners.add(listener); return () => this.#listeners.delete(listener); } install(extension) { validateExtension(extension); const current = this.#current.installed(); const index = current.findIndex((installed) => installed.name === extension.name); const next = index < 0 ? [...current, extension] : current.map((installed, at) => at === index ? extension : installed); this.#publish(next); } uninstall(extension) { const current = this.#current.installed(); if (!current.some((installed) => installed.name === extension.name)) return; this.#publish(current.filter((installed) => installed.name !== extension.name)); } /** Build and validate the next state, which throws on a task name collision, then publish it synchronously. */ #publish(extensions) { this.#current = new RegistryState(extensions); for (const listener of [...this.#listeners]) listener(); } }; function validateExtension(extension) { const tools = /* @__PURE__ */ new Set(); for (const tool of extension.tools ?? []) { if (tools.has(tool.name)) throw new Error(`Extension ${extension.name} has two tools named ${tool.name}`); tools.add(tool.name); } const sections = /* @__PURE__ */ new Set(); for (const { key } of extension.sections ?? []) { if (!SECTION_KEY.test(key)) throw new TypeError(`Section key ${JSON.stringify(key)} must match ${SECTION_KEY}`); if (key === INSTRUCTIONS_KEY) throw new Error(`Section key ${key} is reserved for the agent's instructions`); if (sections.has(key)) throw new Error(`Extension ${extension.name} has two sections with key ${key}`); sections.add(key); } } function createRegistry() { return new RegistryImpl(); } // node_modules/@earendil-works/pi-durable/dist/harness/task-graph.js var LIVE_STATUSES2 = ["pending", "running", "waiting", "sleeping", "completing"]; var SCAN_PAGE_SIZE4 = 256; var TaskGraphView = class { #session; #blocking; #mount; #closed = false; constructor(session, blocking) { this.#session = session; this.#blocking = blocking; session.followCommits((publication, context) => { if (this.#mount === void 0 || publication.kind === "memory") return; advance(this.#mount, publication, context, blocking.blocked, (error) => session.report(error)); }); blocking.subscribeBlocked(() => this.#refreshBlocked()); session.subscribeClose(() => { this.#closed = true; const ends = closeEnds(this.#session.failure); for (const observer of [...this.#mount?.observers ?? []]) { contained(() => observer.end(closeEndOf(observer, ends)), (error) => session.report(error)); } this.#mount = void 0; }); } /** * Attach a synchronous observer of the graph (`Harness.observeTaskGraph()`), behind `guardObserver()`; `context` * governs only the attachment. */ async observe(observer, context) { let value2; const attached = await this.#attach((current, release) => { value2 = current; return guardObserver(observer, release, (error) => this.#session.report(error)); }, context); const signal = context.abortSignal; if (signal?.aborted) { attached.detach(); throw signal.reason; } return observation(value2, attached.observer); } /** A serialized watch of the graph, an observer of its mount; aborting `context` ends it `aborted`. */ async watch(context) { const { observer } = await this.#attach((value2, release) => new Watch(value2, release, (error) => this.#session.report(error)), context); const signal = context.abortSignal; if (signal?.aborted) { observer.abort(); throw signal.reason; } if (signal !== void 0) observer.observeAbort(signal); return observer; } /** Register an observer created from the current revision, atomically on the Session line. */ #attach(create, context) { return this.#session.readOnLine(async (storage2) => { const mount = this.#mount ?? { ...await this.#build(storage2, context), observers: /* @__PURE__ */ new Set() }; const detach = () => { mount.observers.delete(observer); if (mount.observers.size === 0 && this.#mount === mount) this.#mount = void 0; }; const observer = create(mount.value, detach); if (this.#closed) throw closedError(this.#session); context.abortSignal?.throwIfAborted(); this.#mount = mount; mount.observers.add(observer); return { observer, detach }; }); } /** On the Session line: derive every node's blocked reason again, and publish the ones that changed. */ #refreshBlocked() { if (this.#mount === void 0 || this.#closed) return; this.#session.readOnLine(async () => { const mount = this.#mount; if (mount === void 0) return; const ops3 = []; for (const [key, record2] of mount.records) { const blocked = this.#blocking.blocked(record2); if (blocked === mount.value.tasks[key]?.blocked) continue; ops3.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]); } publish(mount, ops3, BACKGROUND_CONTEXT, (error) => this.#session.report(error)); }).catch(() => { }); } /** Read the graph on the line, through the line's `storage`. */ async #build(storage2, context) { const tasks = {}; const live = /* @__PURE__ */ new Map(); const records = []; for (const status of LIVE_STATUSES2) { records.push(...await scanAll((cursor) => storage2.scanTasks({ status }, SCAN_PAGE_SIZE4, cursor, context))); } records.sort((a, b) => a.id - b.id); for (const record2 of records) { const owned = await scanAll((cursor) => storage2.scanConversations({ ownerTaskId: record2.id }, SCAN_PAGE_SIZE4, cursor, context)); const conversations = owned.map((conversation) => conversation.id).sort((a, b) => a - b); const subject = blockedSubject(record2); tasks[String(record2.id)] = nodeOf2(record2, conversations, this.#blocking.blocked(subject)); live.set(String(record2.id), subject); } return { value: { tasks }, records: live }; } }; function advance(mount, publication, context, blocked, report2) { const ops3 = []; const changed = /* @__PURE__ */ new Map(); const node = (key) => changed.has(key) ? changed.get(key) : mount.value.tasks[key]; for (const change of publication.changes) { if (change.type !== "task") continue; const record2 = change.value; const key = String(record2.id); const previous = node(key); if (record2.state.status === "terminal") { mount.records.delete(key); if (previous === void 0) continue; ops3.push(["d", ["tasks", key]]); changed.set(key, void 0); continue; } const subject = blockedSubject(record2); mount.records.set(key, subject); const next = nodeOf2(record2, previous?.conversations ?? [], blocked(subject)); if (previous !== void 0 && JSON.stringify(next) === JSON.stringify(previous)) continue; ops3.push(["s", ["tasks", key], next]); changed.set(key, next); } const created = /* @__PURE__ */ new Map(); for (const change of publication.changes) { if (change.type !== "conversation" || change.value.owner === void 0) continue; const key = String(change.value.owner.taskId); if (node(key) !== void 0) created.set(key, [...created.get(key) ?? [], change.value.id]); } for (const [key, ids] of created) { const conversations = [...node(key).conversations, ...ids].sort((a, b) => a - b); ops3.push(["s", ["tasks", key, "conversations"], conversations]); } publish(mount, ops3, context, report2); } function publish(mount, ops3, context, report2) { if (ops3.length === 0) return; mount.value = applyImmutable(mount.value, ops3); const frameContext = withoutAbortSignal(context); const call2 = { kind: "value", value: mount.value, ops: ops3, retired: false }; for (const observer of [...mount.observers]) contained(() => observer.advance(call2, frameContext), report2); } function nodeOf2(record2, conversations, blocked) { return { id: record2.id, kind: record2.kind, conversationId: record2.conversationId, ...record2.owner === void 0 ? {} : { owner: record2.owner }, background: record2.background, abortRequested: record2.abortRequested, state: stateOf(record2), conversations, ...blocked === void 0 ? {} : { blocked } }; } function stateOf(record2) { const state = record2.state; switch (state.status) { case "pending": case "running": return { status: state.status, phase: phaseOf(state.checkpoint) }; case "waiting": return { status: "waiting", phase: phaseOf(state.checkpoint), on: [...state.on], policy: state.policy }; case "sleeping": return { status: "sleeping", phase: phaseOf(state.checkpoint), until: state.until }; // Terminal records never reach here: they leave the graph. case "completing": case "terminal": return { status: "completing", outcome: state.outcome.status }; } } function phaseOf(checkpoint) { return checkpoint.phase; } // node_modules/@earendil-works/pi-durable/dist/harness/view.js var DEFAULT_WINDOW_MIN = 50; var MOUNTED = [ AgentDoc, LiveDoc, InboxDoc, ProviderDoc, UsageDoc ]; var MOUNTED_KINDS = new Set(MOUNTED.map((token) => token.definition.kind)); var SCAN_PAGE_SIZE5 = 256; var VIEWS = /* @__PURE__ */ new WeakMap(); function resolveWindow(window) { const min = window?.min ?? DEFAULT_WINDOW_MIN; if (!Number.isSafeInteger(min) || min < 1) throw new RangeError(`Window min must be an integer >= 1, got ${min}`); const max = window?.max ?? 2 * min; if (!Number.isSafeInteger(max) || max < min) { throw new RangeError(`Window max must be an integer >= min (${min}), got ${max}`); } const from = window?.from; if (from !== void 0 && !Number.isSafeInteger(from)) { throw new RangeError(`Window from must be an entry ID, got ${from}`); } return { from, min, max }; } var ConversationViews = class { #session; #mounts = /* @__PURE__ */ new Map(); #closed = false; constructor(session) { this.#session = session; VIEWS.set(session, this); session.followCommits((publication, context) => { for (const [id, mount] of this.#mounts) advance2(id, mount, publication, context, (error) => this.report(error)); }); session.subscribeClose(() => { this.#closed = true; const ends = closeEnds(this.#session.failure); for (const mount of this.#mounts.values()) for (const windowed of [...mount.observers]) { contained(() => windowed.observer.end(closeEndOf(windowed.observer, ends)), (error) => this.report(error)); } this.#mounts.clear(); }); } /** Report an observer's listener error through the Harness. */ report(error) { this.#session.report(error); } /** * Attach a synchronous observer of the view (`Harness.observeConversation()`), behind `guardObserver()`; `context` * governs only the attachment. */ async observe(id, observer, context, window) { let value2; const attached = await this.attach(id, window, (current, release) => { value2 = current; return guardObserver(observer, release, (error) => this.report(error)); }, context); const signal = context.abortSignal; if (signal?.aborted) { attached.detach(); throw signal.reason; } return observation(value2, attached.observer); } /** A serialized watch of the view, an observer of its mount; aborting `context` ends it `aborted`. */ async watch(id, context, window) { const { observer } = await this.attach(id, window, (value2, release) => new Watch(value2, release, (error) => this.#session.report(error)), context); const signal = context.abortSignal; if (signal?.aborted) { observer.abort(); throw signal.reason; } if (signal !== void 0) observer.observeAbort(signal); return observer; } /** * Register an observer created from its window's first revision, atomically on the Session line: it sees every later * publication and nothing earlier. `release` drops it, and the mount with its last observer. */ async attach(id, window, create, context) { const bounds = resolveWindow(window); return this.#session.readOnLine(async (storage2) => { const mount = this.#mounts.get(id) ?? await this.#build(storage2, id, context); const { entries: entries2, before } = await loadWindow(storage2, id, bounds, context); const value2 = view(mount.conversation, entries2, before, mount.docs); let windowed; const detach = () => { if (windowed !== void 0) mount.observers.delete(windowed); if (mount.observers.size === 0 && this.#mounts.get(id) === mount) this.#mounts.delete(id); }; const observer = create(value2, detach); if (this.#closed) throw closedError(this.#session); context.abortSignal?.throwIfAborted(); windowed = { min: bounds.min, max: bounds.max, value: value2, observer }; this.#mounts.set(id, mount); mount.observers.add(windowed); return { observer, detach }; }); } /** Hydrate a mount on the line, through the line's `storage`. */ async #build(storage2, id, context) { const conversation = await storage2.conversation(id, context); if (conversation === void 0) throw new Error(`Conversation ${id} does not exist`); const docs = {}; const incarnations = /* @__PURE__ */ new Map(); for (const token of MOUNTED) { const loaded = await this.#session.conversationDocumentOnLine(token, id, context); if (loaded === void 0) continue; docs[token.definition.kind] = loaded.value; incarnations.set(token.definition.kind, { id: loaded.record.id, version: loaded.version }); } return { conversation, docs, incarnations, observers: /* @__PURE__ */ new Set() }; } }; function view(conversation, entries2, before, docs) { return before === void 0 ? { conversation, entries: entries2, docs } : { conversation, entries: entries2, before, docs }; } async function loadWindow(storage2, conversationId, bounds, context) { const { from, min, max } = bounds; if (from !== void 0) { const since = await scanUpTo(storage2, { conversationId, minEntryId: from, order: "ascending" }, max + 1, context); if (since.length <= max) { const query = { conversationId, maxEntryId: idFromNumber(from - 1) }; const previous = (await storage2.scanEntries(query, 1, void 0, context)).items[0]; return { entries: since, before: previous?.id }; } } const newest = await scanUpTo(storage2, { conversationId }, min + 1, context); const before = newest.length > min ? newest.pop().id : void 0; return { entries: newest.reverse(), before }; } async function scanUpTo(storage2, query, limit, context) { const entries2 = []; let cursor; do { const page3 = await storage2.scanEntries(query, Math.min(SCAN_PAGE_SIZE5, limit - entries2.length), cursor, context); entries2.push(...page3.items); cursor = page3.next; } while (cursor !== void 0 && entries2.length < limit); return entries2; } function advance2(id, mount, publication, context, report2) { const docOps = []; const appended = []; for (const change of publication.changes) { if (change.type === "entry" && change.value.conversationId === id) { appended.push(change.value); continue; } if (change.type !== "document" || change.conversationId !== id) continue; const kind = change.record.kind; if (!MOUNTED_KINDS.has(kind) || change.record.key !== void 0) continue; const path = ["docs", kind]; const mounted = mount.incarnations.get(kind); if (change.kind === "retire") { if (mounted?.id !== change.record.id) continue; mount.incarnations.delete(kind); docOps.push(["d", path]); } else if (mounted?.id === change.record.id && mounted.version === change.version) { for (const op2 of change.ops) docOps.push(prefixed(op2, path)); } else { mount.incarnations.set(kind, { id: change.record.id, version: change.version }); docOps.push(["s", path, change.value]); } } if (docOps.length === 0 && appended.length === 0) return; if (docOps.length > 0) mount.docs = applyImmutable({ docs: mount.docs }, docOps).docs; const frameContext = withoutAbortSignal(context); const inserted = appended; for (const windowed of [...mount.observers]) { const previous = windowed.value; let entries2 = previous.entries; let before = previous.before; let ops3 = docOps; if (appended.length > 0) { ops3 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]]; entries2 = entries2.concat(appended); if (entries2.length > windowed.max) { const drop = entries2.length - windowed.min; before = entries2[drop - 1].id; entries2 = entries2.slice(drop); ops3.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]); } } windowed.value = view(mount.conversation, entries2, before, mount.docs); const call2 = { kind: "value", value: windowed.value, ops: ops3, retired: false }; contained(() => windowed.observer.advance(call2, frameContext), report2); } } function prefixed(op2, prefix) { const at = (path) => [...prefix, ...path]; switch (op2[0]) { case "r": return ["s", prefix, op2[1]]; case "p": return ["p", at(op2[1]), op2[2], op2[3], op2[4]]; case "m": return ["m", at(op2[1]), op2[2]]; case "s": return ["s", at(op2[1]), op2[2]]; case "d": return ["d", at(op2[1])]; case "a": return ["a", at(op2[1]), op2[2]]; case "t": return ["t", at(op2[1]), op2[2]]; } } // node_modules/@earendil-works/pi-durable/dist/harness/harness.js var SCAN_PAGE_SIZE6 = 256; var SCHEDULER_INDEX_SIZES = /* @__PURE__ */ Symbol.for("pi-durable.schedulerIndexSizes"); var SCHEDULER_IDLE_STATE = /* @__PURE__ */ Symbol.for("pi-durable.schedulerIdleState"); var ConversationImpl = class { id; #host; constructor(id, host2) { this.id = id; this.#host = host2; } agent(context) { return this.#host.harness.resolveAgent(this.id, void 0, context); } configure(change, context) { return this.#host.harness.commitWith((tx) => configure(tx, this.id, change), context); } submit(first, second) { if ("type" in first) return this.#host.submissions.submit(this.id, first, second); return this.#host.submissions.admit(first, this.id, second); } async compact(instructions, context) { this.#host.tasks.resume(); const input = { reason: "manual", ...instructions === void 0 ? {} : { instructions } }; return this.#host.harness.commitWith((tx) => createCompaction(tx, this.id, input), context); } async reset(handoff, context) { const model2 = handoff === void 0 ? {} : { model: [{ role: "user", content: handoff, timestamp: this.#host.now() }] }; const entry2 = { kind: ResetEntry.kind, head: "self", ...model2 }; await this.#host.submissions.submit(this.id, { type: "write", entry: entry2 }, context); } commit(change, context, options) { return this.#host.harness.commitWith(change, context, { conversationId: this.id }, options); } context(context, options) { return readContext(this.#host.harness, this.#host.storage, this.id, context, options?.at); } entries(query, limit, cursor, context) { const scan2 = conversationEntries(this.id, query); return this.#host.harness.readOnLine((storage2) => storage2.scanEntries(scan2, limit, cursor, context)); } fork(at, options, context) { return this.#host.create({ kind: "fork", parentId: this.id, at, ownership: options.ownership }, options, context); } async abort(context, options) { this.#host.tasks.resume(); return this.#host.tasks.abortConversation(this.id, options?.background === true, context); } async waitForIdle(context) { this.#host.tasks.resume(); return this.#host.tasks.waitForIdle(this.id, context); } watch(context, window) { return this.#host.views.watch(this.id, context, window); } }; var DEFAULT_HARNESS_IDENTITY = { attribution: PI_ATTRIBUTION }; function currentModels(models2, identity) { const merged = mergeClientIdentity(DEFAULT_HARNESS_IDENTITY, identity) ?? DEFAULT_HARNESS_IDENTITY; if (typeof models2 === "function") return () => withClientIdentity(models2(), merged); const wrapped = withClientIdentity(models2, merged); return () => wrapped; } function currentSettings(settings) { return typeof settings === "function" ? settings : () => settings; } var HarnessImpl = class extends SessionImpl { #options; #hostSettings; #report; #host; #tasks; #submissions; #taskGraph; constructor(rawStorage, options, context) { const report2 = safeReport(options.onReport, options.telemetry ?? NOOP_TELEMETRY_CONTEXT); const now = safeNow(options.now, report2); const hostSettings = currentSettings(options.settings); super(rawStorage, now, () => hostSettings()?.documentRetentionMs, options.setTimer, options.readOnly === true, () => hostSettings()?.progress?.persistWithinMs); const storage2 = this.storage; this.#options = options; this.#report = report2; this.#hostSettings = hostSettings; const settings = () => resolveSettings(hostSettings()); this.#tasks = new TaskScheduler({ telemetry: options.telemetry, session: this, storage: storage2, registry: options.registry, models: currentModels(options.models, options.identity), agent: (id, snapshot, callContext) => this.resolveAgent(id, snapshot, callContext), settings, env: (id, callContext) => this.buildEnv(id, callContext), now, onWake: options.onWake, timers: hostTimers(options.setTimer, (error) => report2(error, { kind: "host_error", callback: "setTimer" })), writeAtClose: (writes) => this.writeAtClose(writes), report: this.#report, limitsRestarts: (kind) => LIMITS_RESTARTS.has(kind), settleOutcome: settleSchedulerOutcome, withdrawInputs: withdrawQueuedInputs, conversation: async (id, binding, callContext) => { const record2 = await this.readOnLine((line) => line.conversation(id, callContext)); return record2 === void 0 ? void 0 : boundConversation(id, binding, this.#submissions, this.#tasks); }, context: withoutAbortSignal(context), readOnly: options.readOnly === true }); this.#submissions = new Submissions(this, now, settings, () => this.#tasks.resume(), options.telemetry); this.#taskGraph = new TaskGraphView(this, { blocked: (record2) => this.#tasks.blockedReason(record2), subscribeBlocked: (listener) => this.#tasks.subscribeBlocked(listener) }); this.#host = { harness: this, storage: storage2, tasks: this.#tasks, submissions: this.#submissions, views: new ConversationViews(this), now, create: (target, createOptions, context2) => this.#create(target, createOptions, context2) }; } async beforeInput(tx, conversationId, input, context) { const agent = resolveAgent(await tx.doc(AgentDoc, conversationId), this.#options.registry.snapshot(), resolveSettings(this.#hostSettings()), (error, extension) => this.#report(error, { kind: "extension_error", conversationId, extension }, context)); const entries2 = []; for (const { handlers } of agentHooks(agent, "pi.generation")) { const result = await handlers.beforeInput?.(input, { conversationId, tx }, context); entries2.push(...result?.entries ?? []); } return entries2; } /** Resolve a conversation's committed `pi.agent` against `snapshot`, or the current one, and the current settings. */ async resolveAgent(id, snapshot, context) { const registry2 = snapshot ?? this.#options.registry.snapshot(); const state = await this.snapshot(AgentDoc, id, context); return resolveAgent(state, registry2, resolveSettings(this.#hostSettings()), (error, extension) => this.#report(error, { kind: "extension_error", conversationId: id, extension }, context)); } /** Build a conversation's environment from its current `cwd`; `undefined` without an `env` option. */ async buildEnv(id, context) { const build = this.#options.env; if (build === void 0) return void 0; const cwd = (await this.snapshot(AgentDoc, id, context))?.cwd; return build({ conversationId: id, ...cwd === void 0 ? {} : { cwd }, read: this }, context); } /** Reconcile surviving `running` tasks to `pending`; part of open. */ openTasks(context) { return this.#tasks.open(context); } resume() { this.assertUsable(); this.#tasks.resume(); } hold() { this.#tasks.hold(); } release() { this.#tasks.release(); } wake() { this.assertUsable(); return this.#tasks.wake(); } getTask(id, context) { return this.readOnLine((storage2) => storage2.task(id, context)); } /** Internal, for tests: sizes of the scheduler's ownership indexes. */ get [SCHEDULER_INDEX_SIZES]() { return this.#tasks.indexSizes; } /** Internal, for tests: the kept contexts' idle state and its disagreements with a full recompute. */ get [SCHEDULER_IDLE_STATE]() { return this.#tasks.idleState; } inspect(context) { return this.readOnLine(async (storage2) => { const { scheduling, tasks } = await this.#tasks.inspect(this.#options.registry.snapshot()); const scan2 = (status) => scanAll((cursor) => storage2.scanSubmissions({ status }, SCAN_PAGE_SIZE6, cursor, context)); const submissions = [...await scan2("queued"), ...await scan2("placed")].sort((a, b) => a.id - b.id); return { scheduling, tasks, submissions }; }); } submission(id, context) { return this.#submissions.get(id, context); } abortSubmission(id, context, conversationId) { return this.#submissions.abort(id, context, conversationId); } abortTask(id, context) { return this.#tasks.abort(id, context); } async waitForTask(id, context) { this.#tasks.resume(); return this.#tasks.waitForTask(id, context); } async waitForIdle(context) { this.#tasks.resume(); return this.#tasks.waitForIdle(void 0, context); } /** Sum every conversation's committed `pi.usage`. Each document is read at its own point; totals only grow. */ async usage(context) { const conversations = await this.readOnLine((storage2) => scanAll((cursor) => storage2.scanConversations({}, SCAN_PAGE_SIZE6, cursor, context))); const total = UsageDoc.definition.initial(); for (const { id } of conversations) { const state = await this.snapshot(UsageDoc, id, context); if (state !== void 0) addUsageState(total, state); } return total; } watchTaskGraph(context) { return this.#taskGraph.watch(context); } async observeTaskGraph(observer, context) { this.assertUsable(); return this.#taskGraph.observe(observer, context); } async observeConversation(id, observer, context, window) { this.assertUsable(); return this.#host.views.observe(id, observer, context, window); } root(context, options) { return this.#create({ kind: "root" }, options ?? {}, context); } async conversation(id, context) { this.assertUsable(); const record2 = await this.readOnLine((storage2) => storage2.conversation(id, context)); return record2 === void 0 ? void 0 : new ConversationImpl(record2.id, this.#host); } createConversation(options, context) { return this.#create({ kind: "independent", ownership: options.ownership }, options, context); } report(error, info = { kind: "listener_error" }, context) { this.#report(error, info, context); } /** The ownership memberships a commit's task changes write, from the scheduler's index. */ membershipKeys(tx) { return this.#tasks.membershipKeys(tx); } /** Signal and join task invocations after the close listeners and before Storage closes; writes no task outcome. */ beforeClose() { return this.#tasks.join(); } async #create(target, options, context) { this.assertUsable(); const id = await this.commitWith(async (tx) => { if (target.kind === "root" && await tx.conversation(ROOT_CONVERSATION_ID) !== void 0) { return ROOT_CONVERSATION_ID; } const record2 = target.kind === "root" ? await tx.createRootConversation() : target.kind === "fork" ? await tx.forkConversation(target.parentId, target.at, { ownership: target.ownership }) : await tx.createConversation({ ownership: target.ownership }); if (options.agent !== void 0) await configure(tx, record2.id, options.agent); if (options.init !== void 0) await options.init(tx, record2.id); return record2.id; }, context); return new ConversationImpl(id, this.#host); } /** * The built-in creation hook, in every commit that creates or forks a conversation: empty `pi.live`, `pi.inbox`, and * `pi.usage`, a fresh `pi.provider`, the conversation's `pi.agent` (see `createAgent()`), then * `HarnessOptions.conversationCreated`. */ async conversationCreated(tx, record2) { await tx.doc(LiveDoc, record2.id); await tx.doc(InboxDoc, record2.id); await tx.doc(UsageDoc, record2.id); await tx.doc(ProviderDoc, record2.id); await createAgent(tx, record2); await this.#options.conversationCreated?.(tx, record2); } }; function safeReport(onReport, telemetry) { const reports2 = createTypedMetrics(telemetry.metrics ?? NOOP_TELEMETRY_METRICS, [DURABLE_TELEMETRY_SCHEMA]).counter("pi.durable.reports"); return (error, info, context) => { if (onReport !== void 0) { contained(() => onReport({ ...info, error }), () => { }); } const attributes = { type: info.kind }; switch (info.kind) { case "task_faulted": case "task_interrupted": case "task_outdated": attributes.reason = info.reason; attributes.extension = info.extension; attributes["task.id"] = info.task.id; break; case "migration_failed": case "task_error": case "extension_error": attributes.extension = info.extension; attributes["task.id"] = info.task?.id; break; case "host_error": attributes["task.id"] = info.task?.id; break; } try { reports2.add(1, { type: info.kind }); context?.telemetry.addEvent("pi.durable.report", attributes); } catch { } }; } function safeNow(now, report2) { if (now === void 0) return Date.now; let reported = false; return () => { try { return now(); } catch (error) { if (!reported) { reported = true; report2(error, { kind: "host_error", callback: "now" }); } return Date.now(); } }; } function boundConversation(id, binding, submissions, tasks) { const bind2 = (context) => withAbortSignal(binding.signal, context); const bound = (operation) => { return async (context) => { binding.check(); return operation(bind2(context)); }; }; async function submit2(first, second) { binding.check(); if (!("type" in first)) { binding.signal.throwIfAborted(); return submissions.admit(first, id, second); } const submission2 = await submissions.submit(id, first, bind2(second)); return { id: submission2.id, status: bound((callContext) => submission2.status(callContext)), wait: bound((callContext) => submission2.wait(callContext)), abort: bound((callContext) => submission2.abort(callContext)) }; } return { id, submit: submit2, abort: async (context, options) => { binding.check(); return tasks.abortConversation(id, options?.background === true, bind2(context)); }, waitForIdle: bound((callContext) => tasks.waitForIdle(id, callContext)) }; } var Harness = { /** Open a Harness over storage. The registry may keep changing while the Harness runs. */ async open(storage2, options, context) { context.abortSignal?.throwIfAborted(); const snapshot = options.registry.snapshot(); const missing = BUILTIN_TASKS.filter((task) => snapshot.task(task.definition.name) === void 0); if (missing.length > 0) { const names2 = missing.map((task) => task.definition.name).join(", "); throw new Error(`Registry lacks built-in tasks ${names2}; create it with createRegistry()`); } const harness2 = new HarnessImpl(storage2, options, context); try { await harness2.openTasks(context); } catch (error) { await harness2.close(withoutAbortSignal(context)).catch(() => { }); throw error; } return harness2; } }; // node_modules/@earendil-works/pi-durable/dist/harness/registry-reader.js function registryReader(value2, context) { return { snapshot: () => value2.value, subscribe: (listener) => { const observation2 = value2.observe({ advance: (call2) => { if (call2.kind === "value") listener(); }, end: () => { } }, context); return () => observation2.detach(); } }; } // node_modules/@earendil-works/pi-durable/dist/harness/transcript.js var EARLIER = Object.freeze({ kind: "earlier" }); var ConversationTranscript = class { #load; #pageSize; /** Every entry held, in ID order, as items. */ #entries = []; #ids = /* @__PURE__ */ new Set(); /** Gaps in ID order; each is an immutable item, replaced when it shrinks. */ #gaps = []; /** Whether anything was merged yet. */ #started = false; /** Exclusive lower bound of the known range: entries at or below it are not loaded; `-Infinity` at the start. */ #low = Number.NEGATIVE_INFINITY; /** Inclusive upper bound of the known range: the newest entry, or a later empty window's `before`. */ #tail = Number.NEGATIVE_INFINITY; #items = []; #last; #view; #earlier; #filling = /* @__PURE__ */ new Map(); constructor(load, options) { const pageSize = options?.pageSize ?? 50; if (!Number.isSafeInteger(pageSize) || pageSize < 1) { throw new RangeError(`Page size must be an integer >= 1, got ${pageSize}`); } this.#load = load; this.#pageSize = pageSize; } /** * The ID the transcript reaches: its newest entry, or the `before` of a later window that was empty. Undefined before * the first merge and for an empty transcript. */ get newest() { return this.#tail === Number.NEGATIVE_INFINITY ? void 0 : idFromNumber(this.#tail); } /** * The window that resumes this transcript after a drop: `{ from }` right after `newest`, so a new watch returns only * what was missed when it fits its window, otherwise its newest entries and a gap; `{}` for an empty transcript; * undefined before the first merge. `conversation.watch(context, transcript.resume)`. */ get resume() { if (!this.#started) return void 0; const newest = this.newest; return newest === void 0 ? {} : { from: idFromNumber(newest + 1) }; } /** Take one value of the view: its window's entries join the transcript. */ merge(view2) { this.#last = view2; const { entries: entries2, before } = view2; const hi = entries2.at(-1)?.id ?? before ?? Number.NEGATIVE_INFINITY; this.#cover(before ?? Number.NEGATIVE_INFINITY, hi, entries2); return this.#result(); } /** Load the page before the oldest entry held. Without anything earlier, returns the current view. */ loadEarlier(context) { const last = this.#last; if (last === void 0) return Promise.reject(new Error("ConversationTranscript: merge a view first")); if (this.#low === Number.NEGATIVE_INFINITY) return Promise.resolve(this.#result()); if (this.#earlier !== void 0) return this.#earlier; const low = this.#low; const loading = Promise.resolve().then(async () => { const query = { maxEntryId: idFromNumber(low), order: "descending" }; const page3 = await this.#load(query, this.#pageSize, context); const items = [...page3.items].reverse(); const oldest = items[0]; if (page3.next === void 0 || oldest === void 0) this.#cover(Number.NEGATIVE_INFINITY, low, items); else this.#cover(oldest.id - 1, low, items); return this.#result(); }); this.#earlier = loading; const settled = () => { if (this.#earlier === loading) this.#earlier = void 0; }; loading.then(settled, settled); return loading; } /** * Load the next page of `gap`, a gap item of `items`, shrinking it from its start, or removing it once nothing is * missing. A gap that has shrunk since loads the gap it became; one that is no longer there returns the current view. */ fillGap(gap, context) { const current = this.#gaps.find((each) => each.after === gap.after && each.upTo === gap.upTo) ?? this.#gaps.find((each) => each.after < gap.upTo && gap.after < each.upTo); if (current === void 0) { const last = this.#last; if (last === void 0) return Promise.reject(new Error("ConversationTranscript: merge a view first")); return Promise.resolve(this.#result()); } const key = `${current.after}:${current.upTo}`; const running = this.#filling.get(key); if (running !== void 0) return running; const filling = Promise.resolve().then(async () => { const query = { minEntryId: idFromNumber(current.after + 1), maxEntryId: idFromNumber(current.upTo - 1), order: "ascending" }; const page3 = await this.#load(query, this.#pageSize, context); const newest = page3.items.at(-1); if (page3.next === void 0 || newest === void 0) this.#cover(current.after, current.upTo - 1, page3.items); else this.#cover(current.after, newest.id, page3.items); return this.#result(); }); this.#filling.set(key, filling); const settled = () => { if (this.#filling.get(key) === filling) this.#filling.delete(key); }; filling.then(settled, settled); return filling; } /** * Record that every entry with an ID in (`lo`, `hi`] is known, `added` being those entries in ID order: hold the new * ones, shrink the gaps they fill, extend the known range, and open a gap where the range does not continue it. */ #cover(lo, hi, added) { const fresh = added.filter((entry2) => !this.#ids.has(entry2.id)); for (const entry2 of fresh) this.#ids.add(entry2.id); const held2 = this.#entries; const appended = fresh.length === 0 || held2.length === 0 || fresh[0].id > held2.at(-1).entry.id; const items = fresh.map((entry2) => ({ kind: "entry", entry: entry2 })); if (items.length > 0) this.#entries = appended ? held2.concat(items) : mergeById(held2, items); let rebuild = false; if (!this.#started || held2.length === 0 && this.#gaps.length === 0 && lo >= this.#tail) { rebuild = !this.#started || lo !== this.#low; this.#started = true; this.#low = lo; this.#tail = Math.max(this.#tail, hi); } else if (lo > this.#tail) { const upTo = added[0]?.id ?? lo + 1; this.#gaps = [...this.#gaps, gapOf(this.#tail, upTo)]; this.#tail = hi; rebuild = true; } else if (hi !== Number.NEGATIVE_INFINITY && hi < this.#low) { const upTo = held2.find((item) => item.entry.id > hi)?.entry.id ?? this.#low + 1; this.#gaps = [gapOf(hi, upTo), ...this.#gaps]; this.#low = lo; rebuild = true; } else { const gaps = this.#subtract(lo, hi); if (gaps !== this.#gaps) { this.#gaps = gaps; rebuild = true; } if (lo < this.#low) { this.#low = lo; rebuild = true; } if (hi > this.#tail) this.#tail = hi; } if (rebuild || !appended) this.#items = this.#build(); else if (items.length > 0) this.#items = this.#items.concat(items); } /** The gaps without (`lo`, `hi`]; the same array when none changes, as when the range is empty. */ #subtract(lo, hi) { if (hi <= lo) return this.#gaps; let changed = false; const gaps = []; for (const gap of this.#gaps) { if (hi <= gap.after || lo + 1 >= gap.upTo) { gaps.push(gap); continue; } changed = true; if (lo > gap.after) { const firstKnown = this.#entries.find((item) => item.entry.id > lo)?.entry.id ?? lo + 1; gaps.push(gapOf(gap.after, Math.min(gap.upTo, firstKnown))); } if (hi + 1 < gap.upTo) gaps.push(gapOf(hi, gap.upTo)); } return changed ? gaps : this.#gaps; } /** Items in ID order: "earlier" until the start is known, then entries with each gap before the entry after it. */ #build() { const items = this.#low === Number.NEGATIVE_INFINITY ? [] : [EARLIER]; const gaps = this.#gaps; let next = 0; for (const item of this.#entries) { while (next < gaps.length && gaps[next].after < item.entry.id) items.push(gaps[next++]); items.push(item); } while (next < gaps.length) items.push(gaps[next++]); return items; } /** The view with the current items; the previous one when nothing changed. */ #result() { const last = this.#last; const previous = this.#view; if (previous !== void 0 && previous.items === this.#items && previous.docs === last.docs && previous.conversation === last.conversation) { return previous; } this.#view = { conversation: last.conversation, docs: last.docs, items: this.#items }; return this.#view; } }; function gapOf(after, upTo) { return Object.freeze({ kind: "gap", after: idFromNumber(after), upTo: idFromNumber(upTo) }); } function mergeById(left, right) { const merged = []; let i = 0; let j = 0; while (i < left.length && j < right.length) { merged.push(left[i].entry.id < right[j].entry.id ? left[i++] : right[j++]); } while (i < left.length) merged.push(left[i++]); while (j < right.length) merged.push(right[j++]); return merged; } // node_modules/@earendil-works/pi-durable/dist/storage/scan.js function scanStart(requested, cursor, fallback) { if (requested !== void 0 && requested !== "ascending" && requested !== "descending") { throw new StorageRequestError(`Invalid scan order: ${String(requested)}`); } if (cursor === void 0) return { order: requested ?? fallback, after: void 0 }; const { after, order: stored } = cursor; if (typeof after !== "number" || !Number.isSafeInteger(after)) throw new StorageRequestError("Invalid storage cursor"); if (stored !== void 0 && stored !== "ascending" && stored !== "descending") { throw new StorageRequestError("Invalid storage cursor"); } const order = stored ?? fallback; if (requested !== void 0 && requested !== order) { throw new StorageRequestError(`The cursor continues a ${order} scan; the query asks for ${requested}`); } return { order, after }; } function nextCursor(id, order) { return { after: id, order }; } // node_modules/@earendil-works/pi-durable/dist/storage/memory.js function* documentDeltaBatches(id, version, revisions, start) { for (let index = start; index < revisions.length; index++) { const revision = revisions[index]; if (revision.kind !== "delta" || revision.version !== version) { throw new Error(`Document ${id} crosses a stored version boundary without a base`); } yield revision.ops; } } var clone = (value2) => { if (value2 === null || typeof value2 !== "object") return value2; if (Array.isArray(value2)) return value2.map((item) => clone(item)); const source = value2; const nullPrototype = Object.getPrototypeOf(value2) === null; const result = nullPrototype ? /* @__PURE__ */ Object.create(null) : {}; for (const key of Object.keys(source)) { const copied = clone(source[key]); if (!nullPrototype && key in result) { Object.defineProperty(result, key, { value: copied, writable: true, enumerable: true, configurable: true }); } else { result[key] = copied; } } return result; }; var freeze3 = (value2) => { if (value2 === null || typeof value2 !== "object" || Object.isFrozen(value2)) return value2; for (const child of Object.values(value2)) freeze3(child); return Object.freeze(value2); }; var cursorId = (cursor) => { const after = cursor?.after; if (after === void 0) return void 0; if (typeof after !== "number" || !Number.isSafeInteger(after)) throw new StorageRequestError("Invalid storage cursor"); return idFromNumber(after); }; var lowerBound2 = (ids, target) => { let low = 0; let high = ids.length; while (low < high) { const middle = low + high >>> 1; if (ids[middle] < target) low = middle + 1; else high = middle; } return low; }; var upperBound = (ids, target) => { let low = 0; let high = ids.length; while (low < high) { const middle = low + high >>> 1; if (ids[middle] <= target) low = middle + 1; else high = middle; } return low; }; var insertSorted = (ids, id) => { if (ids.length === 0 || ids[ids.length - 1] < id) ids.push(id); else ids.splice(lowerBound2(ids, id), 0, id); }; var removeSorted = (ids, id) => { const index = lowerBound2(ids, id); if (ids[index] === id) ids.splice(index, 1); }; var insertMapId = (index, key, id) => { let ids = index.get(key); if (ids === void 0) { ids = []; index.set(key, ids); } insertSorted(ids, id); }; var scopeKey2 = (scope) => { switch (scope.kind) { case "session": return JSON.stringify(["session"]); case "conversation": return JSON.stringify(["conversation", scope.conversationId]); case "task": return JSON.stringify(["task", scope.taskId]); } }; var addressKey = (address) => JSON.stringify([ address.kind, scopeKey2(address.scope), address.key === void 0 ? ["singleton"] : ["family", address.key] ]); var recordAddressKey = (record2) => addressKey({ kind: record2.kind, scope: record2.scope, key: record2.key }); var isAliveAt = (record2, at) => { if (at === "current") return record2.retiredAt === void 0; return record2.createdAt <= at && (record2.retiredAt === void 0 || at < record2.retiredAt); }; var isCurrentOnly = (record2) => record2.scope.kind !== "conversation" || record2.history === "latest"; var tableContaining = (state, id) => state.recordTypes.get(id); var page = (values, limit, order = "ascending") => { const items = values.slice(0, limit); if (values.length <= limit) return { items: clone(items) }; return { items: clone(items), next: nextCursor(items.at(-1).id, order) }; }; function* scanIndexes(ids, start) { if (start.order === "ascending") { const first = start.after === void 0 ? 0 : upperBound(ids, start.after); for (let index = first; index < ids.length; index++) yield index; } else { const end = start.after === void 0 ? ids.length : lowerBound2(ids, start.after); for (let index = end - 1; index >= 0; index--) yield index; } } var MemoryStorage = class { state = { recordTypes: /* @__PURE__ */ new Map(), conversations: /* @__PURE__ */ new Map(), conversationIds: [], conversationIdsByOwnerConversation: /* @__PURE__ */ new Map(), conversationIdsByOwnerTask: /* @__PURE__ */ new Map(), entries: /* @__PURE__ */ new Map(), entryIds: /* @__PURE__ */ new Map(), headEntryIds: /* @__PURE__ */ new Map(), entryCommitSeqs: /* @__PURE__ */ new Map(), tasks: /* @__PURE__ */ new Map(), taskIds: [], taskIdsByStatus: { pending: [], running: [], waiting: [], sleeping: [], completing: [], terminal: [] }, submissions: /* @__PURE__ */ new Map(), submissionIds: [], submissionIdsByStatus: { queued: [], placed: [], done: [], unanswered: [] }, submissionIdsByRequest: /* @__PURE__ */ new Map(), documents: /* @__PURE__ */ new Map(), documentAddresses: /* @__PURE__ */ new Map(), documentIdsByScope: /* @__PURE__ */ new Map() }; nextId = 2; nextSeq = 1; closed = false; /** * Apply `batches` one by one, each checked against the state the earlier ones left (`prepareCommit()`, which only * checks and detaches, so a batch it refuses changed nothing); stops at the first one refused. Memory is durable at * once; the receipts follow the last batch. */ async commit(batches, receive, _context) { this.assertOpen(); const seqs = []; let refused; try { for (const writes of batches) { let prepared; try { prepared = this.prepareCommit(writes); } catch (error) { refused = { error }; break; } seqs.push(prepared.apply()); } } finally { for (const [index, seq] of seqs.entries()) receive(index, seq); } return refused === void 0 ? { stored: seqs.length } : { stored: seqs.length, refused: refused.error }; } /** Validate and detach one commit without changing observable state. */ prepareCommit(writes, seq = seqFromNumber(this.nextSeq)) { this.assertOpen(); if (!Number.isSafeInteger(seq) || seq < this.nextSeq) { throw new Error(`Commit sequence ${seq} does not strictly increase`); } const detachedWrites = freeze3(this.resolveDocumentCopies(writes.map((write) => clone(write)))); this.checkGlobalIds(detachedWrites); const documentActions = this.prepareDocumentActions(detachedWrites); this.checkDocumentActions(documentActions); let applied = false; return { seq, writes: detachedWrites, apply: () => { if (!applied) { applied = true; this.applyPreparedCommit(detachedWrites, documentActions, seq); } return seq; } }; } resolveDocumentCopies(writes) { if (!writes.some((write) => write.type === "document.copy")) return writes; const changedDocumentIds = /* @__PURE__ */ new Set(); for (const write of writes) { if (write.type === "document.create" || write.type === "document.copy") { changedDocumentIds.add(write.record.id); } else if (write.type === "document.change" || write.type === "document.retire") { changedDocumentIds.add(write.id); } } return writes.map((write) => { if (write.type !== "document.copy") return write; try { if (changedDocumentIds.has(write.source.id)) { throw new Error(`Fork source document ${write.source.id} is changed in the copy batch`); } const stored = this.materializeDocument(write.source.id, write.source.at); if (stored === void 0) throw new Error(`Fork source document ${write.source.id} cannot be read`); if (stored.record.scope.kind !== "conversation" || write.record.scope.kind !== "conversation" || stored.record.kind !== write.record.kind || stored.record.key !== write.record.key || stored.record.history !== write.record.history || stored.record.fork !== write.record.fork) { throw new Error(`Fork source document ${write.source.id} does not match the copied record`); } return { type: "document.create", record: write.record, content: { kind: "base", version: stored.version, value: stored.value } }; } catch (error) { throw new Error(`Document copy ${write.record.id} was rejected`, { cause: error }); } }); } applyPreparedCommit(prepared, documentActions, seq) { for (const write of prepared) { switch (write.type) { case "conversation": { this.state.recordTypes.set(write.value.id, "conversation"); this.state.conversations.set(write.value.id, write.value); insertSorted(this.state.conversationIds, write.value.id); const owner = write.value.owner; if (owner !== void 0) { insertMapId(this.state.conversationIdsByOwnerConversation, owner.conversationId, write.value.id); insertMapId(this.state.conversationIdsByOwnerTask, owner.taskId, write.value.id); } this.nextId = Math.max(this.nextId, write.value.id + 1); break; } case "entry": { this.state.recordTypes.set(write.value.id, "entry"); this.state.entries.set(write.value.id, write.value); this.state.entryCommitSeqs.set(write.value.id, seq); let ids = this.state.entryIds.get(write.value.conversationId); if (ids === void 0) { ids = []; this.state.entryIds.set(write.value.conversationId, ids); } insertSorted(ids, write.value.id); if (write.value.head !== void 0) { let headIds = this.state.headEntryIds.get(write.value.conversationId); if (headIds === void 0) { headIds = []; this.state.headEntryIds.set(write.value.conversationId, headIds); } insertSorted(headIds, write.value.id); } this.nextId = Math.max(this.nextId, write.value.id + 1); break; } case "task": { this.state.recordTypes.set(write.value.id, "task"); const previous = this.state.tasks.get(write.value.id); if (previous === void 0) { insertSorted(this.state.taskIds, write.value.id); insertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id); } else if (previous.state.status !== write.value.state.status) { removeSorted(this.state.taskIdsByStatus[previous.state.status], write.value.id); insertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id); } this.state.tasks.set(write.value.id, write.value); this.nextId = Math.max(this.nextId, write.value.id + 1); break; } case "submission": { this.state.recordTypes.set(write.value.id, "submission"); const previous = this.state.submissions.get(write.value.id); if (previous === void 0) { insertSorted(this.state.submissionIds, write.value.id); insertSorted(this.state.submissionIdsByStatus[write.value.status], write.value.id); } else if (previous.status !== write.value.status) { removeSorted(this.state.submissionIdsByStatus[previous.status], write.value.id); insertSorted(this.state.submissionIdsByStatus[write.value.status], write.value.id); } if (previous?.requestId !== void 0) { const previousRequests = this.state.submissionIdsByRequest.get(previous.conversationId); if (previousRequests?.get(previous.requestId) === write.value.id) { previousRequests.delete(previous.requestId); if (previousRequests.size === 0) this.state.submissionIdsByRequest.delete(previous.conversationId); } } this.state.submissions.set(write.value.id, write.value); if (write.value.requestId !== void 0) { let requests = this.state.submissionIdsByRequest.get(write.value.conversationId); if (requests === void 0) { requests = /* @__PURE__ */ new Map(); this.state.submissionIdsByRequest.set(write.value.conversationId, requests); } requests.set(write.value.requestId, write.value.id); } this.nextId = Math.max(this.nextId, write.value.id + 1); break; } case "document.copy": throw new Error("Prepared document copy was not resolved"); case "document.create": case "document.change": case "document.retire": break; } } this.applyDocumentActions(documentActions, seq); this.nextSeq = seq + 1; return seq; } async mintId() { this.assertOpen(); if (!Number.isSafeInteger(this.nextId)) throw new Error("ID space is exhausted"); return idFromNumber(this.nextId++); } async conversation(id, _context) { this.assertOpen(); const value2 = this.state.conversations.get(id); return value2 === void 0 ? void 0 : clone(value2); } async scanConversations(query, limit, cursor, _context) { this.assertOpen(); const ids = query.ownerTaskId !== void 0 ? this.state.conversationIdsByOwnerTask.get(query.ownerTaskId) ?? [] : query.ownerConversationId !== void 0 ? this.state.conversationIdsByOwnerConversation.get(query.ownerConversationId) ?? [] : this.state.conversationIds; const start = scanStart(query.order, cursor, "ascending"); const values = []; for (const index of scanIndexes(ids, start)) { if (values.length > limit) break; const value2 = this.state.conversations.get(ids[index]); if (query.ownerConversationId !== void 0 && value2.owner?.conversationId !== query.ownerConversationId) { continue; } values.push(value2); } return page(values, limit, start.order); } async entry(idOrConversationId, idOrContext, context) { this.assertOpen(); if (context === void 0) { const id2 = idFromNumber(idOrConversationId); const entry3 = this.state.entries.get(id2); if (entry3 === void 0) return void 0; return { entry: clone(entry3), commitSeq: this.state.entryCommitSeqs.get(id2) }; } if (typeof idOrContext !== "number") throw new StorageRequestError("Storage.entry() requires an entry ID"); const conversationId = idFromNumber(idOrConversationId); const id = idFromNumber(idOrContext); const entry2 = this.visibleEntries(conversationId, id, id).next().value; if (entry2 === void 0) return void 0; return { entry: clone(entry2), commitSeq: this.state.entryCommitSeqs.get(id) }; } async findLatestHeadMarker(conversationId, atOrBeforeEntryId, _context) { this.assertOpen(); if (!this.state.conversations.has(conversationId)) { throw new StorageRequestError(`Unknown conversation: ${conversationId}`); } let currentId = conversationId; let upperEntryId = atOrBeforeEntryId ?? Number.POSITIVE_INFINITY; while (true) { const ids = this.state.headEntryIds.get(currentId) ?? []; const index = upperBound(ids, upperEntryId) - 1; if (index >= 0) { const entry2 = this.state.entries.get(ids[index]); return clone({ ...entry2, head: entry2.head }); } const conversation = this.state.conversations.get(currentId); if (conversation.parent === void 0) return void 0; upperEntryId = Math.min(upperEntryId, conversation.parent.at); currentId = conversation.parent.conversationId; } } async scanEntries(query, limit, cursor, _context) { this.assertOpen(); const { order, after } = scanStart(query.order, cursor, "descending"); let minEntryId = query.minEntryId; let maxEntryId = query.maxEntryId; if (after !== void 0 && order === "descending") maxEntryId = Math.min(maxEntryId ?? after, after - 1); if (after !== void 0 && order === "ascending") minEntryId = Math.max(minEntryId ?? after, after + 1); const visible = []; const entries2 = order === "descending" ? this.visibleEntries(query.conversationId, minEntryId, maxEntryId) : this.visibleEntriesAscending(query.conversationId, minEntryId, maxEntryId); for (const entry2 of entries2) { visible.push(entry2); if (visible.length > limit) break; } return page(visible, limit, order); } async task(id, _context) { this.assertOpen(); const value2 = this.state.tasks.get(id); return value2 === void 0 ? void 0 : clone(value2); } async scanTasks(query, limit, cursor, _context) { this.assertOpen(); const start = scanStart(query.order, cursor, "ascending"); const ids = query.status === void 0 ? this.state.taskIds : this.state.taskIdsByStatus[query.status]; const values = []; for (const index of scanIndexes(ids, start)) { if (values.length > limit) break; const value2 = this.state.tasks.get(ids[index]); if (query.conversationId !== void 0 && value2.conversationId !== query.conversationId) continue; if (query.kind !== void 0 && value2.kind !== query.kind) continue; if (query.abortRequested !== void 0 && value2.abortRequested !== query.abortRequested) continue; if (query.background !== void 0 && value2.background !== query.background) continue; values.push(value2); } return page(values, limit, start.order); } async submission(id, _context) { this.assertOpen(); const value2 = this.state.submissions.get(id); return value2 === void 0 ? void 0 : clone(value2); } async scanSubmissions(query, limit, cursor, _context) { this.assertOpen(); const start = scanStart(query.order, cursor, "ascending"); const ids = query.status === void 0 ? this.state.submissionIds : this.state.submissionIdsByStatus[query.status]; const values = []; for (const index of scanIndexes(ids, start)) { if (values.length > limit) break; const value2 = this.state.submissions.get(ids[index]); if (query.conversationId !== void 0 && value2.conversationId !== query.conversationId) continue; values.push(value2); } return page(values, limit, start.order); } async submissionByRequest(conversationId, requestId, _context) { this.assertOpen(); const id = this.state.submissionIdsByRequest.get(conversationId)?.get(requestId); if (id === void 0) return void 0; return clone(this.state.submissions.get(id)); } async findDocument(address, at, _context) { this.assertOpen(); const index = this.state.documentAddresses.get(addressKey(address)); if (at === "current") { return index?.currentId === void 0 ? void 0 : clone(this.state.documents.get(index.currentId).record); } for (const id of index?.ids ?? []) { const record2 = this.state.documents.get(id).record; if (isAliveAt(record2, at)) return clone(record2); } return void 0; } async document(id, at, _context) { this.assertOpen(); const stored = this.materializeDocument(id, at); return stored === void 0 ? void 0 : { record: clone(stored.record), version: stored.version, value: clone(stored.value), deltasSinceBase: stored.deltasSinceBase }; } async scanDocuments(query, limit, cursor, _context) { this.assertOpen(); const ids = this.state.documentIdsByScope.get(scopeKey2(query.scope)) ?? []; const after = cursorId(cursor); const start = after === void 0 ? 0 : upperBound(ids, after); const values = []; for (let index = start; index < ids.length && values.length <= limit; index++) { const record2 = this.state.documents.get(ids[index]).record; if (query.kind !== void 0 && record2.kind !== query.kind) continue; if (isAliveAt(record2, query.at)) values.push(record2); } return page(values, limit); } async close(_context) { this.closed = true; } materializeDocument(id, at) { const stored = this.state.documents.get(id); if (stored === void 0) return void 0; if (at !== "current" && isCurrentOnly(stored.record)) { throw new StorageRequestError(`Document ${id} does not retain historical content`); } if (!isAliveAt(stored.record, at)) return void 0; const revisions = at === "current" ? stored.revisions : stored.revisions.filter((revision) => revision.seq <= at); let baseIndex = revisions.length - 1; while (baseIndex >= 0 && revisions[baseIndex].kind !== "base") baseIndex--; const base = revisions[baseIndex]; if (base?.kind !== "base") throw new Error(`Document ${id} is missing a required base`); const value2 = applyImmutableBatches(base.value, documentDeltaBatches(id, base.version, revisions, baseIndex + 1)); return { record: stored.record, version: base.version, value: value2, deltasSinceBase: revisions.length - baseIndex - 1 }; } *visibleEntries(conversationId, minEntryId = Number.NEGATIVE_INFINITY, maxEntryId = Number.POSITIVE_INFINITY) { if (!this.state.conversations.has(conversationId)) { throw new StorageRequestError(`Unknown conversation: ${conversationId}`); } let currentId = conversationId; let upperEntryId = maxEntryId; while (true) { const ids = this.state.entryIds.get(currentId) ?? []; for (let index = upperBound(ids, upperEntryId) - 1; index >= 0; index--) { const id = ids[index]; if (id < minEntryId) break; yield this.state.entries.get(id); } const conversation = this.state.conversations.get(currentId); if (conversation.parent === void 0) break; upperEntryId = Math.min(upperEntryId, conversation.parent.at); if (upperEntryId < minEntryId) break; currentId = conversation.parent.conversationId; } } /** Visible entries oldest first: the fork chain's segments from the root conversation forward. */ *visibleEntriesAscending(conversationId, minEntryId = Number.NEGATIVE_INFINITY, maxEntryId = Number.POSITIVE_INFINITY) { if (!this.state.conversations.has(conversationId)) { throw new StorageRequestError(`Unknown conversation: ${conversationId}`); } const segments = []; let currentId = conversationId; let upperEntryId = maxEntryId; while (true) { segments.push({ conversationId: currentId, upper: upperEntryId }); const conversation = this.state.conversations.get(currentId); if (conversation.parent === void 0) break; upperEntryId = Math.min(upperEntryId, conversation.parent.at); if (upperEntryId < minEntryId) break; currentId = conversation.parent.conversationId; } for (const segment of segments.reverse()) { const ids = this.state.entryIds.get(segment.conversationId) ?? []; for (let index = lowerBound2(ids, minEntryId); index < ids.length; index++) { const id = ids[index]; if (id > segment.upper) break; yield this.state.entries.get(id); } } } checkGlobalIds(writes) { const claimed = /* @__PURE__ */ new Map(); for (const write of writes) { if (write.type === "document.change" || write.type === "document.retire") continue; const document = write.type === "document.create" || write.type === "document.copy"; const table = document ? "document" : write.type; const id = document ? write.record.id : write.value.id; const existing = tableContaining(this.state, id); const earlier = claimed.get(id); if (table === "conversation" || table === "entry" || table === "document") { if (existing !== void 0) throw new Error(`ID ${id} already belongs to ${existing}`); if (earlier !== void 0) throw new Error(`ID ${id} is written more than once`); } else { if (existing !== void 0 && existing !== table) { throw new Error(`ID ${id} already belongs to ${existing}`); } if (earlier !== void 0 && earlier !== table) throw new Error(`ID ${id} is written as two record types`); } claimed.set(id, table); } } prepareDocumentActions(writes) { const actions = /* @__PURE__ */ new Map(); for (const write of writes) { if (write.type !== "document.create" && write.type !== "document.change" && write.type !== "document.retire") { continue; } const id = write.type === "document.create" ? write.record.id : write.id; let action = actions.get(id); if (action === void 0) { action = { retire: false }; actions.set(id, action); } switch (write.type) { case "document.create": if (action.create !== void 0 || action.content !== void 0) { throw new Error(`Document ${id} has more than one content command`); } action.create = write.record; action.content = write.content; break; case "document.change": if (action.content !== void 0) throw new Error(`Document ${id} has more than one content command`); action.content = write.content; break; case "document.retire": if (action.retire) throw new Error(`Document ${id} is retired more than once`); action.retire = true; break; } } return actions; } checkDocumentActions(actions) { const liveCounts = /* @__PURE__ */ new Map(); for (const [id, action] of actions) { const existing = this.state.documents.get(id); if (action.create === void 0 && existing === void 0) throw new Error(`Unknown document: ${id}`); if (action.create !== void 0 && existing !== void 0) throw new Error(`Document ${id} already exists`); if (existing?.record.retiredAt !== void 0) throw new Error(`Document ${id} is retired`); const previous = existing?.revisions.at(-1); if (action.content?.kind === "delta") { if (previous === void 0) throw new Error(`Document ${id} delta has no base`); if (previous.version !== action.content.version) { throw new Error(`Document ${id} version transition requires a base`); } } const key = action.create === void 0 ? recordAddressKey(existing.record) : recordAddressKey(action.create); const currentId = this.state.documentAddresses.get(key)?.currentId; let live = liveCounts.get(key); if (live === void 0) live = currentId === void 0 ? 0 : 1; if (action.retire && currentId === id) live--; if (action.create !== void 0 && !action.retire) live++; liveCounts.set(key, live); } for (const live of liveCounts.values()) { if (live > 1) throw new Error(`Document address already has a current incarnation`); } } applyDocumentActions(actions, seq) { for (const [id, action] of actions) { let stored = this.state.documents.get(id); if (action.create !== void 0) { const record2 = { ...action.create, createdAt: seq, ...action.retire ? { retiredAt: seq } : {} }; stored = { record: record2, revisions: [{ ...action.content, seq }] }; this.state.recordTypes.set(id, "document"); this.state.documents.set(id, stored); const key = recordAddressKey(record2); let address = this.state.documentAddresses.get(key); if (address === void 0) { address = { ids: [] }; this.state.documentAddresses.set(key, address); } insertSorted(address.ids, id); let scopeIds = this.state.documentIdsByScope.get(scopeKey2(record2.scope)); if (scopeIds === void 0) { scopeIds = []; this.state.documentIdsByScope.set(scopeKey2(record2.scope), scopeIds); } insertSorted(scopeIds, id); this.nextId = Math.max(this.nextId, id + 1); } else if (action.content !== void 0) { const revision = { ...action.content, seq }; if (revision.kind === "base" && isCurrentOnly(stored.record)) stored.revisions = [revision]; else stored.revisions.push(revision); } if (action.retire && action.create === void 0) stored.record = { ...stored.record, retiredAt: seq }; if (action.retire && isCurrentOnly(stored.record)) stored.revisions = []; if (action.create !== void 0 || action.retire) { const address = this.state.documentAddresses.get(recordAddressKey(stored.record)); if (action.retire && address.currentId === id) delete address.currentId; if (action.create !== void 0 && !action.retire) address.currentId = id; } } } assertOpen() { if (this.closed) throw new Error("MemoryStorage is closed"); } }; // node_modules/@earendil-works/pi-durable/dist/tools/index.js var tools_exports = {}; __export(tools_exports, { CodingTools: () => CodingTools, DEFAULT_IMAGE_LIMITS: () => DEFAULT_IMAGE_LIMITS, createBashTool: () => createBashTool, createCodingTools: () => createCodingTools, createEditTool: () => createEditTool, createPowerShellTool: () => createPowerShellTool, createReadTool: () => createReadTool, createWriteTool: () => createWriteTool }); // node_modules/@earendil-works/pi-durable/dist/tools/env.js function requireEnv(api) { if (api.env === void 0) throw new Error("No execution environment is configured"); return api.env; } // node_modules/@earendil-works/pi-durable/dist/tools/bash.js var MAX_TIMEOUT_SECONDS = 2147483647 / 1e3; var bashSchema = obj({ command: str({ description: "Bash command to execute" }), timeout: opt(num({ description: "Timeout in seconds (optional, no default timeout)" })) }, { open: true }); var powershellSchema = obj({ command: str({ description: "PowerShell command to execute" }), timeout: opt(num({ description: "Timeout in seconds (optional, no default timeout)" })) }, { open: true }); var shellStructuredOutputSchema = obj({ output: str({ description: "Combined stdout and stderr: the retained tail, as the model sees it" }), truncated: bool({ description: "Whether earlier output was dropped" }), fullOutputPath: opt(str({ description: "File with the complete output, when it was spilled" })), exitCode: num() }, { open: true }); function validateTimeout(timeout) { if (timeout === void 0) return; if (!Number.isFinite(timeout) || timeout <= 0) { throw new Error("Invalid timeout: must be a finite number of seconds"); } if (timeout > MAX_TIMEOUT_SECONDS) { throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`); } } async function prepareExecution(command, options, api, context) { const execution = { command: options?.commandPrefix ? `${options.commandPrefix} ${command}` : command, cwd: requireEnv(api).cwd, env: {}, inheritEnv: true }; await options?.prepare?.(execution, api, context); return execution; } async function runCommand(commands, execution, timeout, api, context) { const env = requireEnv(api); let result; for (const command of commands) { result = await env.exec(command, { cwd: execution.cwd, env: execution.env, inheritEnv: execution.inheritEnv, ...timeout === void 0 ? {} : { timeout }, onOutput: (text, _context, info) => api.output(text, info.skipped), spill: { afterBytes: DEFAULT_MAX_BYTES, afterLines: DEFAULT_MAX_LINES }, // An environment may then omit output outside the retained tail and report the omission. ...api.outputWindow === void 0 ? {} : { window: api.outputWindow } }, context); if (result.ok || result.error.code !== "spawn_error") break; } if (result === void 0) throw new Error("No command to run"); const spillPath = result.ok ? result.value.spillPath : result.error.spillPath; if (spillPath !== void 0) { api.diagnostic({ severity: "info", code: "full_output", message: `Full output: ${spillPath}` }); } if (!result.ok) { if (result.error.code === "aborted" && context.abortSignal?.aborted) throw result.error; if (result.error.code === "timeout") throw new Error(`Command timed out after ${timeout} seconds`); if (result.error.code === "aborted") throw new Error("Command aborted"); throw result.error; } const exitCode = result.value.exitCode; const retained = api.retainedOutput(); const structuredOutput = { output: retained.text, truncated: retained.truncated, ...spillPath === void 0 ? {} : { fullOutputPath: spillPath }, exitCode }; if (exitCode === 0) return { structuredOutput }; const exited = { severity: "error", code: "exit_code", message: `Command exited with code ${exitCode}` }; return { structuredOutput, isError: true, diagnostics: [exited] }; } function createBashTool(options) { return defineTool({ name: "bash", description: `Execute a bash command in the current working directory. Returns combined stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`, parameters: bashSchema, structuredOutputSchema: shellStructuredOutputSchema, outputLimits: { retain: "tail" }, async execute(args, api, context) { validateTimeout(args.timeout); const execution = await prepareExecution(args.command, options, api, context); return runCommand([execution.command], execution, args.timeout, api, context); } }); } var UTF8_OUTPUT = "try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}"; var POWERSHELL_ARGS = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command"]; function createPowerShellTool(options) { const programs = options?.programs ?? ["pwsh", "powershell"]; return defineTool({ name: "powershell", description: `Execute a PowerShell command in the current working directory. Returns combined stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`, parameters: powershellSchema, structuredOutputSchema: shellStructuredOutputSchema, outputLimits: { retain: "tail" }, async execute(args, api, context) { validateTimeout(args.timeout); const execution = await prepareExecution(args.command, options, api, context); const script = `${UTF8_OUTPUT} ${execution.command}`; const commands = programs.map((program) => [program, ...POWERSHELL_ARGS, script]); return runCommand(commands, execution, args.timeout, api, context); } }); } // node_modules/diff/libesm/diff/base.js var Diff = class { diff(oldStr, newStr, options = {}) { let callback; if (typeof options === "function") { callback = options; options = {}; } else if ("callback" in options) { callback = options.callback; } const oldString = this.castInput(oldStr, options); const newString = this.castInput(newStr, options); const oldTokens = this.removeEmpty(this.tokenize(oldString, options)); const newTokens = this.removeEmpty(this.tokenize(newString, options)); return this.diffWithOptionsObj(oldTokens, newTokens, options, callback); } diffWithOptionsObj(oldTokens, newTokens, options, callback) { var _a; const done = (value2) => { value2 = this.postProcess(value2, options); if (callback) { setTimeout(function() { callback(value2); }, 0); return void 0; } else { return value2; } }; const newLen = newTokens.length, oldLen = oldTokens.length; let editLength = 1; let maxEditLength = newLen + oldLen; if (options.maxEditLength != null) { maxEditLength = Math.min(maxEditLength, options.maxEditLength); } const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity; const abortAfterTimestamp = Date.now() + maxExecutionTime; const bestPath = [{ oldPos: -1, lastComponent: void 0 }]; let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options); if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens)); } let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity; const execEditLength = () => { for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) { let basePath; const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1]; if (removePath) { bestPath[diagonalPath - 1] = void 0; } let canAdd = false; if (addPath) { const addPathNewPos = addPath.oldPos - diagonalPath; canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen; } const canRemove = removePath && removePath.oldPos + 1 < oldLen; if (!canAdd && !canRemove) { bestPath[diagonalPath] = void 0; continue; } if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) { basePath = this.addToPath(addPath, true, false, 0, options); } else { basePath = this.addToPath(removePath, false, true, 1, options); } newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options); if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true; } else { bestPath[diagonalPath] = basePath; if (basePath.oldPos + 1 >= oldLen) { maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1); } if (newPos + 1 >= newLen) { minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1); } } } editLength++; }; if (callback) { (function exec2() { setTimeout(function() { if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) { return callback(void 0); } if (!execEditLength()) { exec2(); } }, 0); })(); } else { while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) { const ret = execEditLength(); if (ret) { return ret; } } } } addToPath(path, added, removed, oldPosInc, options) { const last = path.lastComponent; if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { return { oldPos: path.oldPos + oldPosInc, lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent } }; } else { return { oldPos: path.oldPos + oldPosInc, lastComponent: { count: 1, added, removed, previousComponent: last } }; } } extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) { const newLen = newTokens.length, oldLen = oldTokens.length; let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0; while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) { newPos++; oldPos++; commonCount++; if (options.oneChangePerToken) { basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false }; } } if (commonCount && !options.oneChangePerToken) { basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false }; } basePath.oldPos = oldPos; return newPos; } equals(left, right, options) { if (options.comparator) { return options.comparator(left, right); } else { return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase(); } } removeEmpty(array) { const ret = []; for (let i = 0; i < array.length; i++) { if (array[i]) { ret.push(array[i]); } } return ret; } // eslint-disable-next-line @typescript-eslint/no-unused-vars castInput(value2, options) { return value2; } // eslint-disable-next-line @typescript-eslint/no-unused-vars tokenize(value2, options) { return Array.from(value2); } join(chars) { return chars.join(""); } postProcess(changeObjects, options) { return changeObjects; } get useLongestToken() { return false; } buildValues(lastComponent, newTokens, oldTokens) { const components = []; let nextComponent; while (lastComponent) { components.push(lastComponent); nextComponent = lastComponent.previousComponent; delete lastComponent.previousComponent; lastComponent = nextComponent; } components.reverse(); const componentLen = components.length; let componentPos = 0, newPos = 0, oldPos = 0; for (; componentPos < componentLen; componentPos++) { const component = components[componentPos]; if (!component.removed) { if (!component.added && this.useLongestToken) { let value2 = newTokens.slice(newPos, newPos + component.count); value2 = value2.map(function(value3, i) { const oldValue = oldTokens[oldPos + i]; return oldValue.length > value3.length ? oldValue : value3; }); component.value = this.join(value2); } else { component.value = this.join(newTokens.slice(newPos, newPos + component.count)); } newPos += component.count; if (!component.added) { oldPos += component.count; } } else { component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count)); oldPos += component.count; } } return components; } }; // node_modules/diff/libesm/diff/line.js var LineDiff = class extends Diff { constructor() { super(...arguments); this.tokenize = tokenize; } equals(left, right, options) { if (options.ignoreWhitespace) { if (!options.newlineIsToken || !left.includes("\n")) { left = left.trim(); } if (!options.newlineIsToken || !right.includes("\n")) { right = right.trim(); } } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { if (left.endsWith("\n")) { left = left.slice(0, -1); } if (right.endsWith("\n")) { right = right.slice(0, -1); } } return super.equals(left, right, options); } }; var lineDiff = new LineDiff(); function diffLines(oldStr, newStr, options) { return lineDiff.diff(oldStr, newStr, options); } function tokenize(value2, options) { if (options.stripTrailingCr) { value2 = value2.replace(/\r\n/g, "\n"); } const retLines = [], linesAndNewlines = value2.split(/(\n|\r\n)/); if (!linesAndNewlines[linesAndNewlines.length - 1]) { linesAndNewlines.pop(); } for (let i = 0; i < linesAndNewlines.length; i++) { const line = linesAndNewlines[i]; if (i % 2 && !options.newlineIsToken) { retLines[retLines.length - 1] += line; } else { retLines.push(line); } } return retLines; } // node_modules/diff/libesm/patch/create.js var INCLUDE_HEADERS = { includeIndex: true, includeUnderline: true, includeFileHeaders: true }; var FILE_HEADERS_ONLY = { includeIndex: false, includeUnderline: false, includeFileHeaders: true }; function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { let optionsObj; if (!options) { optionsObj = {}; } else if (typeof options === "function") { optionsObj = { callback: options }; } else { optionsObj = options; } if (typeof optionsObj.context === "undefined") { optionsObj.context = 4; } const context = optionsObj.context; if (optionsObj.newlineIsToken) { throw new Error("newlineIsToken may not be used with patch-generation functions, only with diffing functions"); } if (!optionsObj.callback) { return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj)); } else { const { callback } = optionsObj; diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => { const patch = diffLinesResultToPatch(diff); callback(patch); } })); } function diffLinesResultToPatch(diff) { if (!diff) { return; } diff.push({ value: "", lines: [] }); function contextLines(lines2) { return lines2.map(function(entry2) { return " " + entry2; }); } const hunks = []; let oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1; for (let i = 0; i < diff.length; i++) { const current = diff[i], lines2 = current.lines || splitLines(current.value); current.lines = lines2; if (current.added || current.removed) { if (!oldRangeStart) { const prev = diff[i - 1]; oldRangeStart = oldLine; newRangeStart = newLine; if (prev) { curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : []; oldRangeStart -= curRange.length; newRangeStart -= curRange.length; } } for (const line of lines2) { curRange.push((current.added ? "+" : "-") + line); } if (current.added) { newLine += lines2.length; } else { oldLine += lines2.length; } } else { if (oldRangeStart) { if (lines2.length <= context * 2 && i < diff.length - 2) { for (const line of contextLines(lines2)) { curRange.push(line); } } else { const contextSize = Math.min(lines2.length, context); for (const line of contextLines(lines2.slice(0, contextSize))) { curRange.push(line); } const hunk = { oldStart: oldRangeStart, oldLines: oldLine - oldRangeStart + contextSize, newStart: newRangeStart, newLines: newLine - newRangeStart + contextSize, lines: curRange }; hunks.push(hunk); oldRangeStart = 0; newRangeStart = 0; curRange = []; } } oldLine += lines2.length; newLine += lines2.length; } } for (const hunk of hunks) { for (let i = 0; i < hunk.lines.length; i++) { if (hunk.lines[i].endsWith("\n")) { hunk.lines[i] = hunk.lines[i].slice(0, -1); } else { hunk.lines.splice(i + 1, 0, "\\ No newline at end of file"); i++; } } } return { oldFileName, newFileName, oldHeader, newHeader, hunks }; } } function formatPatch(patch, headerOptions) { if (!headerOptions) { headerOptions = INCLUDE_HEADERS; } if (Array.isArray(patch)) { if (patch.length > 1 && !headerOptions.includeFileHeaders) { throw new Error("Cannot omit file headers on a multi-file patch. (The result would be unparseable; how would a tool trying to apply the patch know which changes are to which file?)"); } return patch.map((p) => formatPatch(p, headerOptions)).join("\n"); } const ret = []; if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName) { ret.push("Index: " + patch.oldFileName); } if (headerOptions.includeUnderline) { ret.push("==================================================================="); } if (headerOptions.includeFileHeaders) { ret.push("--- " + patch.oldFileName + (typeof patch.oldHeader === "undefined" ? "" : " " + patch.oldHeader)); ret.push("+++ " + patch.newFileName + (typeof patch.newHeader === "undefined" ? "" : " " + patch.newHeader)); } for (let i = 0; i < patch.hunks.length; i++) { const hunk = patch.hunks[i]; if (hunk.oldLines === 0) { hunk.oldStart -= 1; } if (hunk.newLines === 0) { hunk.newStart -= 1; } ret.push("@@ -" + hunk.oldStart + "," + hunk.oldLines + " +" + hunk.newStart + "," + hunk.newLines + " @@"); for (const line of hunk.lines) { ret.push(line); } } return ret.join("\n") + "\n"; } function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { if (typeof options === "function") { options = { callback: options }; } if (!(options === null || options === void 0 ? void 0 : options.callback)) { const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); if (!patchObj) { return; } return formatPatch(patchObj, options === null || options === void 0 ? void 0 : options.headerOptions); } else { const { callback } = options; structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options), { callback: (patchObj) => { if (!patchObj) { callback(void 0); } else { callback(formatPatch(patchObj, options.headerOptions)); } } })); } } function splitLines(text) { const hasTrailingNl = text.endsWith("\n"); const result = text.split("\n").map((line) => line + "\n"); if (hasTrailingNl) { result.pop(); } else { result.push(result.pop().slice(0, -1)); } return result; } // node_modules/@earendil-works/pi-durable/dist/tools/edit-diff.js function detectLineEnding(content) { const crlfIdx = content.indexOf("\r\n"); const lfIdx = content.indexOf("\n"); if (lfIdx === -1) return "\n"; if (crlfIdx === -1) return "\n"; return crlfIdx < lfIdx ? "\r\n" : "\n"; } function normalizeToLF(text) { return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); } function restoreLineEndings(text, ending) { return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text; } function normalizeForFuzzyMatch(text) { return text.normalize("NFKC").split("\n").map((line) => line.trimEnd()).join("\n").replace(/[\u2018\u2019\u201A\u201B]/g, "'").replace(/[\u201C\u201D\u201E\u201F]/g, '"').replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, "-").replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " "); } function splitLinesWithEndings(content) { return content.match(/[^\n]*\n|[^\n]+/g) ?? []; } function getLineSpans(content) { let offset = 0; return splitLinesWithEndings(content).map((line) => { const span = { start: offset, end: offset + line.length }; offset = span.end; return span; }); } function getReplacementLineRange(lines2, replacement) { const replacementStart = replacement.matchIndex; const replacementEnd = replacement.matchIndex + replacement.matchLength; let startLine = -1; for (let i = 0; i < lines2.length; i++) { const line = lines2[i]; if (replacementStart >= line.start && replacementStart < line.end) { startLine = i; break; } } if (startLine === -1) { throw new Error("Replacement range is outside the base content."); } let endLine = startLine; while (endLine < lines2.length && lines2[endLine].end < replacementEnd) { endLine++; } if (endLine >= lines2.length) { throw new Error("Replacement range is outside the base content."); } return { startLine, endLine: endLine + 1 }; } function applyReplacements(content, replacements, offset = 0) { let result = content; for (let i = replacements.length - 1; i >= 0; i--) { const replacement = replacements[i]; const matchIndex = replacement.matchIndex - offset; result = result.substring(0, matchIndex) + replacement.newText + result.substring(matchIndex + replacement.matchLength); } return result; } function applyReplacementsPreservingUnchangedLines(originalContent, baseContent, replacements) { const originalLines = splitLinesWithEndings(originalContent); const baseLines = getLineSpans(baseContent); if (originalLines.length !== baseLines.length) { throw new Error("Cannot preserve unchanged lines because the base content has a different line count."); } const groups = []; const sortedReplacements = [...replacements].sort((a, b) => a.matchIndex - b.matchIndex); for (const replacement of sortedReplacements) { const range = getReplacementLineRange(baseLines, replacement); const current = groups[groups.length - 1]; if (current && range.startLine < current.endLine) { current.endLine = Math.max(current.endLine, range.endLine); current.replacements.push(replacement); continue; } groups.push({ ...range, replacements: [replacement] }); } let originalLineIndex = 0; let result = ""; for (const group of groups) { result += originalLines.slice(originalLineIndex, group.startLine).join(""); const groupStartOffset = baseLines[group.startLine].start; const groupEndOffset = baseLines[group.endLine - 1].end; result += applyReplacements(baseContent.slice(groupStartOffset, groupEndOffset), group.replacements, groupStartOffset); originalLineIndex = group.endLine; } result += originalLines.slice(originalLineIndex).join(""); return result; } function fuzzyFindText(content, oldText) { const exactIndex = content.indexOf(oldText); if (exactIndex !== -1) { return { found: true, index: exactIndex, matchLength: oldText.length, usedFuzzyMatch: false, contentForReplacement: content }; } const fuzzyContent = normalizeForFuzzyMatch(content); const fuzzyOldText = normalizeForFuzzyMatch(oldText); const fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText); if (fuzzyIndex === -1) { return { found: false, index: -1, matchLength: 0, usedFuzzyMatch: false, contentForReplacement: content }; } return { found: true, index: fuzzyIndex, matchLength: fuzzyOldText.length, usedFuzzyMatch: true, contentForReplacement: fuzzyContent }; } function stripBom(content) { return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content }; } function countOccurrences(content, oldText) { const fuzzyContent = normalizeForFuzzyMatch(content); const fuzzyOldText = normalizeForFuzzyMatch(oldText); return fuzzyContent.split(fuzzyOldText).length - 1; } function getNotFoundError(path, editIndex, totalEdits) { if (totalEdits === 1) { return new Error(`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`); } return new Error(`Could not find edits[${editIndex}] in ${path}. The oldText must match exactly including all whitespace and newlines.`); } function getDuplicateError(path, editIndex, totalEdits, occurrences) { if (totalEdits === 1) { return new Error(`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`); } return new Error(`Found ${occurrences} occurrences of edits[${editIndex}] in ${path}. Each oldText must be unique. Please provide more context to make it unique.`); } function getEmptyOldTextError(path, editIndex, totalEdits) { if (totalEdits === 1) { return new Error(`oldText must not be empty in ${path}.`); } return new Error(`edits[${editIndex}].oldText must not be empty in ${path}.`); } function getNoChangeError(path, totalEdits) { if (totalEdits === 1) { return new Error(`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`); } return new Error(`No changes made to ${path}. The replacements produced identical content.`); } function applyEditsToNormalizedContent(normalizedContent, edits, path) { const normalizedEdits = edits.map((edit) => ({ oldText: normalizeToLF(edit.oldText), newText: normalizeToLF(edit.newText) })); for (let i = 0; i < normalizedEdits.length; i++) { if (normalizedEdits[i].oldText.length === 0) { throw getEmptyOldTextError(path, i, normalizedEdits.length); } } const initialMatches = normalizedEdits.map((edit) => fuzzyFindText(normalizedContent, edit.oldText)); const usedFuzzyMatch = initialMatches.some((match) => match.usedFuzzyMatch); const replacementBaseContent = usedFuzzyMatch ? normalizeForFuzzyMatch(normalizedContent) : normalizedContent; const matchedEdits = []; for (let i = 0; i < normalizedEdits.length; i++) { const edit = normalizedEdits[i]; const matchResult = fuzzyFindText(replacementBaseContent, edit.oldText); if (!matchResult.found) { throw getNotFoundError(path, i, normalizedEdits.length); } const occurrences = countOccurrences(replacementBaseContent, edit.oldText); if (occurrences > 1) { throw getDuplicateError(path, i, normalizedEdits.length, occurrences); } matchedEdits.push({ editIndex: i, matchIndex: matchResult.index, matchLength: matchResult.matchLength, newText: edit.newText }); } matchedEdits.sort((a, b) => a.matchIndex - b.matchIndex); for (let i = 1; i < matchedEdits.length; i++) { const previous = matchedEdits[i - 1]; const current = matchedEdits[i]; if (previous.matchIndex + previous.matchLength > current.matchIndex) { throw new Error(`edits[${previous.editIndex}] and edits[${current.editIndex}] overlap in ${path}. Merge them into one edit or target disjoint regions.`); } } const baseContent = normalizedContent; const newContent = usedFuzzyMatch ? applyReplacementsPreservingUnchangedLines(normalizedContent, replacementBaseContent, matchedEdits) : applyReplacements(replacementBaseContent, matchedEdits); if (baseContent === newContent) { throw getNoChangeError(path, normalizedEdits.length); } return { baseContent, newContent }; } function generateUnifiedPatch(path, oldContent, newContent, contextLines = 4) { return createTwoFilesPatch(path, path, oldContent, newContent, void 0, void 0, { context: contextLines, headerOptions: FILE_HEADERS_ONLY }); } function generateDiffString(oldContent, newContent, contextLines = 4) { const parts = diffLines(oldContent, newContent); const output = []; const oldLines = oldContent.split("\n"); const newLines = newContent.split("\n"); const maxLineNum = Math.max(oldLines.length, newLines.length); const lineNumWidth = String(maxLineNum).length; let oldLineNum = 1; let newLineNum = 1; let lastWasChange = false; let firstChangedLine; for (let i = 0; i < parts.length; i++) { const part = parts[i]; const raw = part.value.split("\n"); if (raw[raw.length - 1] === "") { raw.pop(); } if (part.added || part.removed) { if (firstChangedLine === void 0) { firstChangedLine = newLineNum; } for (const line of raw) { if (part.added) { const lineNum = String(newLineNum).padStart(lineNumWidth, " "); output.push(`+${lineNum} ${line}`); newLineNum++; } else { const lineNum = String(oldLineNum).padStart(lineNumWidth, " "); output.push(`-${lineNum} ${line}`); oldLineNum++; } } lastWasChange = true; } else { const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed); const hasLeadingChange = lastWasChange; const hasTrailingChange = nextPartIsChange; if (hasLeadingChange && hasTrailingChange) { if (raw.length <= contextLines * 2) { for (const line of raw) { const lineNum = String(oldLineNum).padStart(lineNumWidth, " "); output.push(` ${lineNum} ${line}`); oldLineNum++; newLineNum++; } } else { const leadingLines = raw.slice(0, contextLines); const trailingLines = raw.slice(raw.length - contextLines); const skippedLines = raw.length - leadingLines.length - trailingLines.length; for (const line of leadingLines) { const lineNum = String(oldLineNum).padStart(lineNumWidth, " "); output.push(` ${lineNum} ${line}`); oldLineNum++; newLineNum++; } output.push(` ${"".padStart(lineNumWidth, " ")} ...`); oldLineNum += skippedLines; newLineNum += skippedLines; for (const line of trailingLines) { const lineNum = String(oldLineNum).padStart(lineNumWidth, " "); output.push(` ${lineNum} ${line}`); oldLineNum++; newLineNum++; } } } else if (hasLeadingChange) { const shownLines = raw.slice(0, contextLines); const skippedLines = raw.length - shownLines.length; for (const line of shownLines) { const lineNum = String(oldLineNum).padStart(lineNumWidth, " "); output.push(` ${lineNum} ${line}`); oldLineNum++; newLineNum++; } if (skippedLines > 0) { output.push(` ${"".padStart(lineNumWidth, " ")} ...`); oldLineNum += skippedLines; newLineNum += skippedLines; } } else if (hasTrailingChange) { const skippedLines = Math.max(0, raw.length - contextLines); if (skippedLines > 0) { output.push(` ${"".padStart(lineNumWidth, " ")} ...`); oldLineNum += skippedLines; newLineNum += skippedLines; } for (const line of raw.slice(skippedLines)) { const lineNum = String(oldLineNum).padStart(lineNumWidth, " "); output.push(` ${lineNum} ${line}`); oldLineNum++; newLineNum++; } } else { oldLineNum += raw.length; newLineNum += raw.length; } lastWasChange = false; } } return { diff: output.join("\n"), firstChangedLine }; } // node_modules/@earendil-works/pi-durable/dist/env/decode.js function rangeDecoder() { return new TextDecoder("utf-8", { ignoreBOM: true }); } function startsWithBom(firstBytes) { return firstBytes[0] === 239 && firstBytes[1] === 187 && firstBytes[2] === 191; } // node_modules/@earendil-works/pi-durable/dist/env/index.js function getOrThrow(result) { if (!result.ok) throw result.error; return result.value; } // node_modules/@earendil-works/pi-durable/dist/tools/file-mutation-queue.js var queues = /* @__PURE__ */ new Map(); async function mutationKey(env, path, context) { const absolutePath = getOrThrow(await env.absolutePath(path, context)); return `${env.id}\0${await canonical(env, absolutePath, context)}`; } async function canonical(env, absolutePath, context) { const result = await env.canonicalPath(absolutePath, context); if (result.ok) return result.value; if (result.error.code === "not_supported") return absolutePath; if (result.error.code !== "not_found") throw result.error; const parent = getOrThrow(await env.joinPath([absolutePath, ".."], context)); if (parent === absolutePath || !absolutePath.startsWith(parent)) return absolutePath; const name = absolutePath.slice(parent.length + (/[/\\]$/.test(parent) ? 0 : 1)); return getOrThrow(await env.joinPath([await canonical(env, parent, context), name], context)); } async function withFileMutationQueue(env, path, fn, context) { const key = await mutationKey(env, path, context); const previous = queues.get(key) ?? Promise.resolve(); let release = () => { }; const done = new Promise((resolve2) => { release = resolve2; }); const tail = previous.then(() => done); queues.set(key, tail); await previous; try { return await fn(); } finally { release(); if (queues.get(key) === tail) queues.delete(key); } } // node_modules/@earendil-works/pi-durable/dist/tools/path-utils.js var UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g; var NARROW_NO_BREAK_SPACE = "\u202F"; function normalizeToolPath(path) { const normalized = path.replace(UNICODE_SPACES, " "); return normalized.startsWith("@") ? normalized.slice(1) : normalized; } async function resolveToolPath(env, path, context) { return getOrThrow(await env.absolutePath(normalizeToolPath(path), context)); } async function resolveReadToolPath(env, path, context) { const resolved = await resolveToolPath(env, path, context); const variants = [ resolved, resolved.replace(/ (AM|PM)\./gi, `${NARROW_NO_BREAK_SPACE}$1.`), resolved.normalize("NFD"), resolved.replace(/'/g, "\u2019"), resolved.normalize("NFD").replace(/'/g, "\u2019") ]; for (const variant of new Set(variants)) { if (getOrThrow(await env.exists(variant, context))) return variant; } return resolved; } // node_modules/@earendil-works/pi-durable/dist/tools/edit.js var replaceEditSchema = obj({ oldText: str({ description: "Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call." }), newText: str({ description: "Replacement text for this targeted edit." }) }, { open: true }); var editSchema = obj({ path: str({ description: "Path to the file to edit (relative or absolute)" }), edits: arr(replaceEditSchema, { description: "One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead." }) }, { open: true }); function isSingleEditInput(value2) { if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return false; const edit = value2; return typeof edit.oldText === "string" && typeof edit.newText === "string"; } function prepareEditArguments(input) { if (!input || typeof input !== "object" || Array.isArray(input)) return input; const args = { ...input }; if (typeof args.edits === "string") { try { const parsed = JSON.parse(args.edits); if (Array.isArray(parsed)) { args.edits = parsed; } else if (isSingleEditInput(parsed)) { args.edits = [parsed]; } } catch { } } else if (isSingleEditInput(args.edits)) { args.edits = [args.edits]; } const legacy = args; if (typeof legacy.oldText !== "string" || typeof legacy.newText !== "string") return legacy; const edits = Array.isArray(legacy.edits) ? [...legacy.edits] : []; edits.push({ oldText: legacy.oldText, newText: legacy.newText }); const { oldText: _oldText, newText: _newText, ...rest } = legacy; return { ...rest, edits }; } function validateEditInput(input) { if (!Array.isArray(input.edits) || input.edits.length === 0) { throw new Error("Edit tool input is invalid. edits must contain at least one replacement."); } return { path: input.path, edits: input.edits }; } function editAccessError(path, error) { return new Error(`Could not edit file: ${path}. Error code: ${error.code}.`, { cause: error }); } function createEditTool() { return defineTool({ name: "edit", description: "Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.", parameters: editSchema, prepareArguments: prepareEditArguments, async execute(args, api, context) { const { path, edits } = validateEditInput(args); const env = requireEnv(api); const absolutePath = await resolveToolPath(env, path, context); return withFileMutationQueue(env, absolutePath, async () => { if (context.abortSignal?.aborted) throw new Error("Operation aborted"); const info = await env.fileInfo(absolutePath, context); if (!info.ok) throw editAccessError(path, info.error); if (info.value.kind !== "file" && info.value.kind !== "symlink") { throw new Error(`Could not edit file: ${path}. Path is not a file.`); } const readResult = await env.readTextFile(absolutePath, context); if (!readResult.ok) throw editAccessError(path, readResult.error); if (context.abortSignal?.aborted) throw new Error("Operation aborted"); const { bom, text: content } = stripBom(readResult.value); const originalEnding = detectLineEnding(content); const normalizedContent = normalizeToLF(content); const { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path); if (context.abortSignal?.aborted) throw new Error("Operation aborted"); const finalContent = bom + restoreLineEndings(newContent, originalEnding); const writeResult = await env.writeFile(absolutePath, finalContent, context); if (!writeResult.ok) throw editAccessError(path, writeResult.error); if (context.abortSignal?.aborted) throw new Error("Operation aborted"); const diffResult = generateDiffString(baseContent, newContent); const details = { diff: diffResult.diff, patch: generateUnifiedPatch(path, baseContent, newContent), ...diffResult.firstChangedLine === void 0 ? {} : { firstChangedLine: diffResult.firstChangedLine } }; return { output: [{ type: "text", text: `Successfully replaced ${edits.length} block(s) in ${path}.` }], details }; }, context); } }); } // node_modules/@earendil-works/pi-durable/dist/tools/image.js var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10]; var HEADER_BYTES = 32; var BLOCK_BYTES = 64 * 1024; async function detectSupportedImageMimeTypeOf(source) { const header = await source.read(0, HEADER_BYTES); if (!startsWith(header, PNG_SIGNATURE)) return detectSupportedImageMimeType(header); return isPng(header) && !await isAnimatedPngOf(source) ? "image/png" : void 0; } async function isAnimatedPngOf(source) { let block = new Uint8Array(0); let blockStart = 0; const bytesAt = async (offset2, length) => { if (offset2 < blockStart || offset2 + length > blockStart + block.length) { blockStart = offset2; block = await source.read(offset2, BLOCK_BYTES); } return block.subarray(offset2 - blockStart, offset2 - blockStart + length); }; let offset = PNG_SIGNATURE.length; while (offset + 8 <= source.size) { const chunkHeader = await bytesAt(offset, 8); const chunkLength = readUint32BE(chunkHeader, 0); if (startsWithAscii(chunkHeader, 4, "acTL")) return true; if (startsWithAscii(chunkHeader, 4, "IDAT")) return false; const nextOffset = offset + 8 + chunkLength + 4; if (nextOffset <= offset || nextOffset > source.size) return false; offset = nextOffset; } return false; } function detectSupportedImageMimeType(buffer) { if (startsWith(buffer, [255, 216, 255])) return buffer[3] === 247 ? void 0 : "image/jpeg"; if (startsWith(buffer, PNG_SIGNATURE)) return isPng(buffer) && !isAnimatedPng(buffer) ? "image/png" : void 0; if (startsWithAscii(buffer, 0, "GIF87a") || startsWithAscii(buffer, 0, "GIF89a")) return "image/gif"; if (startsWithAscii(buffer, 0, "RIFF") && startsWithAscii(buffer, 8, "WEBP")) return "image/webp"; if (startsWithAscii(buffer, 0, "BM") && isBmp(buffer)) return "image/bmp"; return void 0; } function isPng(buffer) { return buffer.length >= 16 && readUint32BE(buffer, PNG_SIGNATURE.length) === 13 && startsWithAscii(buffer, 12, "IHDR"); } function isAnimatedPng(buffer) { let offset = PNG_SIGNATURE.length; while (offset + 8 <= buffer.length) { const chunkLength = readUint32BE(buffer, offset); const chunkTypeOffset = offset + 4; if (startsWithAscii(buffer, chunkTypeOffset, "acTL")) return true; if (startsWithAscii(buffer, chunkTypeOffset, "IDAT")) return false; const nextOffset = offset + 8 + chunkLength + 4; if (nextOffset <= offset || nextOffset > buffer.length) return false; offset = nextOffset; } return false; } function imageDimensions(bytes2, mimeType) { switch (mimeType) { case "image/png": return bytes2.length < 24 ? void 0 : { width: readUint32BE(bytes2, 16), height: readUint32BE(bytes2, 20) }; case "image/gif": return bytes2.length < 10 ? void 0 : { width: readUint16LE(bytes2, 6), height: readUint16LE(bytes2, 8) }; case "image/webp": if (bytes2.length < 30) return void 0; if (startsWithAscii(bytes2, 12, "VP8 ")) return { width: readUint16LE(bytes2, 26) & 16383, height: readUint16LE(bytes2, 28) & 16383 }; if (startsWithAscii(bytes2, 12, "VP8L")) { const bits = readUint32LE(bytes2, 21); return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 }; } if (startsWithAscii(bytes2, 12, "VP8X")) { const uint24 = (offset) => readUint16LE(bytes2, offset) + ((bytes2[offset + 2] ?? 0) << 16); return { width: uint24(24) + 1, height: uint24(27) + 1 }; } return void 0; case "image/jpeg": for (let offset = 2; offset + 9 <= bytes2.length; ) { if (bytes2[offset] !== 255) return void 0; const marker = bytes2[offset + 1]; if (marker === 255) { offset++; continue; } if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) { return { width: readUint16BE(bytes2, offset + 7), height: readUint16BE(bytes2, offset + 5) }; } offset += 2 + readUint16BE(bytes2, offset + 2); } return void 0; default: return void 0; } } function isBmp(buffer) { if (buffer.length < 26) return false; const declaredFileSize = readUint32LE(buffer, 2); const pixelDataOffset = readUint32LE(buffer, 10); const dibHeaderSize = readUint32LE(buffer, 14); if (declaredFileSize !== 0 && declaredFileSize < 26) return false; if (pixelDataOffset < 14 + dibHeaderSize) return false; if (declaredFileSize !== 0 && pixelDataOffset >= declaredFileSize) return false; let colorPlanes; let bitsPerPixel; if (dibHeaderSize === 12) { colorPlanes = readUint16LE(buffer, 22); bitsPerPixel = readUint16LE(buffer, 24); } else if (dibHeaderSize >= 40 && dibHeaderSize <= 124) { if (buffer.length < 30) return false; colorPlanes = readUint16LE(buffer, 26); bitsPerPixel = readUint16LE(buffer, 28); } else { return false; } return colorPlanes === 1 && [1, 4, 8, 16, 24, 32].includes(bitsPerPixel); } function readUint16LE(buffer, offset) { return (buffer[offset] ?? 0) + ((buffer[offset + 1] ?? 0) << 8); } function readUint16BE(buffer, offset) { return ((buffer[offset] ?? 0) << 8) + (buffer[offset + 1] ?? 0); } function readUint32BE(buffer, offset) { return (buffer[offset] ?? 0) * 16777216 + ((buffer[offset + 1] ?? 0) << 16) + ((buffer[offset + 2] ?? 0) << 8) + (buffer[offset + 3] ?? 0); } function readUint32LE(buffer, offset) { return (buffer[offset] ?? 0) + ((buffer[offset + 1] ?? 0) << 8) + ((buffer[offset + 2] ?? 0) << 16) + (buffer[offset + 3] ?? 0) * 16777216; } function startsWith(buffer, bytes2) { if (buffer.length < bytes2.length) return false; return bytes2.every((byte, index) => buffer[index] === byte); } function startsWithAscii(buffer, offset, text) { if (buffer.length < offset + text.length) return false; for (let index = 0; index < text.length; index++) { if (buffer[offset + index] !== text.charCodeAt(index)) return false; } return true; } // node_modules/@earendil-works/pi-durable/dist/tools/image-processor.js var DEFAULT_IMAGE_LIMITS = { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4.5 * 1024 * 1024 }; var INLINE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]); function toBase64(bytes2) { const parts = []; for (let i = 0; i < bytes2.length; i += 8192) { parts.push(String.fromCharCode.apply(null, bytes2.subarray(i, i + 8192))); } return btoa(parts.join("")); } function base64Length(byteLength) { return Math.ceil(byteLength / 3) * 4; } // node_modules/@earendil-works/pi-durable/dist/tools/read.js var readSchema = obj({ path: str({ description: "Path to the file to read (relative or absolute)" }), offset: opt(num({ description: "Line number to start reading from (1-indexed)" })), limit: opt(num({ description: "Maximum number of lines to read" })) }, { open: true }); var readStructuredOutputSchema = union([ str(), obj({ type: lit("image"), data: str(), mimeType: str() }, { open: true }) ]); var READ_CHUNK = 64 * 1024; function sliceIndex(value2) { return Number.isNaN(value2) ? 0 : Math.trunc(value2); } async function readHead(reader, start, end, skipBom, context) { const decoder5 = rangeDecoder(); let text = ""; let newlines = 0; for (let position = skipBom && start === 0 ? 3 : start; position < end; ) { const bytes2 = getOrThrow(await reader.read(position, Math.min(READ_CHUNK, end - position), context)); if (bytes2.length === 0) break; position += bytes2.length; const decoded = decoder5.decode(bytes2, { stream: true }); text += decoded; for (let index = decoded.indexOf("\n"); index !== -1; index = decoded.indexOf("\n", index + 1)) newlines++; if (newlines >= DEFAULT_MAX_LINES || utf8ByteLength(text) > DEFAULT_MAX_BYTES + 1) return text; } return text + decoder5.decode(); } function createReadTool(options = {}) { const formats2 = options.images === void 0 ? "jpg, png, gif, webp" : "jpg, png, gif, webp, bmp"; return defineTool({ name: "read", description: `Read the contents of a file. Supports text files and images (${formats2}). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`, parameters: readSchema, structuredOutputSchema: readStructuredOutputSchema, async execute(args, api, context) { const { path, offset, limit } = args; const env = requireEnv(api); const absolutePath = await resolveReadToolPath(env, path, context); const reader = getOrThrow(await env.openBinaryReader(absolutePath, void 0, context)); try { for (let attempt = 0; ; attempt++) { const before = getOrThrow(await reader.info(context)); const mimeType = await detectSupportedImageMimeTypeOf({ size: before.size, read: async (position, length) => getOrThrow(await reader.read(position, length, context)) }); const result = mimeType === void 0 ? await readText(reader, path, offset, limit, context) : await readImage(reader, before, path, mimeType, options.images, await imageModel(api, context), context); const after = getOrThrow(await reader.info(context)); const unchanged = after.size === before.size && after.mtimeMs === before.mtimeMs; if (unchanged || mimeType === void 0 && after.size > before.size) return result; if (attempt === 1) throw new Error(`${path} changed while it was read`); } } finally { await reader.close(context); } } }); } async function readImage(reader, info, path, mimeType, processor, { vision, limits }, context) { let image; const notes2 = []; if (processor !== void 0) { const bytes2 = getOrThrow(await reader.read(0, info.size, context)); const prepared = await processor.prepare(bytes2, mimeType, limits); if (prepared === void 0) return imageError(`${path} is an image (${mimeType}) that cannot be prepared for the model`); image = prepared; if (prepared.convertedFrom !== void 0) notes2.push(`Converted from ${prepared.convertedFrom} to ${prepared.mimeType}.`); if (prepared.resized !== void 0) { const { from, to } = prepared.resized; const scale = (from.width / to.width).toFixed(2); notes2.push(`Resized from ${from.width}x${from.height} to ${to.width}x${to.height}. Multiply coordinates by ${scale} to map them to the original.`); } } else { if (!INLINE_IMAGE_TYPES.has(mimeType)) { return imageError(`${path} is an image (${mimeType}) that needs converting, and no image processor is configured`); } const unshrinkable = "and no image processor is configured to shrink it"; if (base64Length(info.size) > limits.maxBytes) { return imageError(`${path} is an image (${mimeType}) of ${formatSize(info.size)}, too large to send, ${unshrinkable}`); } const bytes2 = getOrThrow(await reader.read(0, info.size, context)); const size = imageDimensions(bytes2, mimeType); if (size === void 0) return imageError(`${path} is an image (${mimeType}) whose size cannot be read`); if (size.width > limits.maxWidth || size.height > limits.maxHeight) { return imageError(`${path} is an image (${mimeType}) of ${size.width}x${size.height}, larger than ${limits.maxWidth}x${limits.maxHeight}, ${unshrinkable}`); } image = { data: toBase64(bytes2), mimeType }; } if (!vision) notes2.push("The current model does not support images; it sees a placeholder instead."); const diagnostics = [ { severity: "info", code: "image", message: [`Read image file [${image.mimeType}].`, ...notes2].join(" ") } ]; const block = { type: "image", data: image.data, mimeType: image.mimeType }; return { output: [block], structuredOutput: block, diagnostics }; } async function imageModel(api, context) { const ref = (await api.agent(context)).model; const model2 = ref === void 0 ? void 0 : api.models.getModel(ref.provider, ref.modelId); const resize = model2?.inputLimits?.images?.resize; return { vision: model2?.input.includes("image") ?? true, limits: { maxWidth: resize?.maxWidth ?? DEFAULT_IMAGE_LIMITS.maxWidth, maxHeight: resize?.maxHeight ?? DEFAULT_IMAGE_LIMITS.maxHeight, maxBytes: resize?.maxBytes ?? DEFAULT_IMAGE_LIMITS.maxBytes } }; } function imageError(message) { return { output: [], isError: true, diagnostics: [{ severity: "error", code: "unsupported_image", message }] }; } async function readText(reader, path, offset, limit, context) { const startLine = offset ? Math.max(0, offset - 1) : 0; const startLineDisplay = startLine + 1; const sliceStart = sliceIndex(startLine); const scanStart2 = Number.isSafeInteger(sliceStart) ? sliceStart : 0; const requestedEnd = limit === void 0 ? void 0 : Math.max(scanStart2 + 1, sliceIndex(startLine + limit)); const scanEnd = requestedEnd !== void 0 && Number.isSafeInteger(requestedEnd) ? requestedEnd : void 0; const scanOf = async (endLine) => getOrThrow(await reader.scanLines({ startLine: scanStart2, ...endLine === void 0 ? {} : { endLine } }, context)); let scan2 = await scanOf(scanEnd); const totalFileLines = scan2.newlines + 1; if (startLine >= totalFileLines) { throw new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`); } let userLimitedLines; let selectedLineCount = totalFileLines - sliceStart; if (limit !== void 0) { const endLine = Math.min(startLine + limit, totalFileLines); userLimitedLines = endLine - startLine; const relativeEnd = sliceIndex(endLine); const sliceEnd = relativeEnd < 0 ? Math.max(totalFileLines + relativeEnd, 0) : relativeEnd; selectedLineCount = Math.max(0, sliceEnd - sliceStart); if (selectedLineCount > 0 && relativeEnd < 0) scan2 = await scanOf(sliceEnd); } const empty = selectedLineCount === 0; const endsWithNewline = !empty && scan2.lastLineStart === scan2.end && scan2.lastLineStart > scan2.start; const totals = { lines: empty || scan2.selectedBytes === 0 ? 0 : selectedLineCount - (endsWithNewline ? 1 : 0), bytes: empty ? 0 : scan2.selectedBytes }; const firstBytes = getOrThrow(await reader.read(0, 3, context)); const head = empty ? "" : await readHead(reader, scan2.start, scan2.end, startsWithBom(firstBytes), context); const { content: headText, ...truncation } = truncateHeadOf(head, totals); const diagnostics = []; let outputText = headText; let details; if (truncation.firstLineExceedsLimit) { const integral = Number.isInteger(startLine); const lineBytes = new TextEncoder().encode(integral ? head.split("\n")[0] ?? "" : ""); const lineSize = integral ? scan2.firstLineBytes : 0; const end = characterEnd(lineBytes, DEFAULT_MAX_BYTES); outputText = new TextDecoder().decode(lineBytes.subarray(0, end)); diagnostics.push({ severity: "warn", code: "truncated", message: `Line ${startLineDisplay} is ${formatSize(lineSize)}, exceeds the ${formatSize(DEFAULT_MAX_BYTES)} limit; showing its first ${formatSize(end)}. Use bash: sed -n '${startLineDisplay}p' ${path} | tail -c +${end + 1}` }); details = { truncation: { ...truncation, outputBytes: end, outputLines: 1 } }; } else if (truncation.truncated) { const endLineDisplay = startLineDisplay + truncation.outputLines - 1; const nextOffset = endLineDisplay + 1; const limitText = truncation.truncatedBy === "lines" ? "" : ` (${formatSize(DEFAULT_MAX_BYTES)} limit)`; diagnostics.push({ severity: "info", code: "truncated", message: `Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}${limitText}. Use offset=${nextOffset} to continue.` }); details = { truncation }; } else if (userLimitedLines !== void 0 && startLine + userLimitedLines < totalFileLines) { const remaining = totalFileLines - (startLine + userLimitedLines); const nextOffset = startLine + userLimitedLines + 1; diagnostics.push({ severity: "info", message: `${remaining} more lines in file. Use offset=${nextOffset} to continue.` }); } return { output: outputText === "" ? [] : [{ type: "text", text: outputText }], structuredOutput: outputText, ...details === void 0 ? {} : { details }, diagnostics }; } // node_modules/@earendil-works/pi-durable/dist/tools/write.js var writeSchema = obj({ path: str({ description: "Path to the file to write (relative or absolute)" }), content: str({ description: "Content to write to the file" }) }, { open: true }); function createWriteTool() { return defineTool({ name: "write", description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.", parameters: writeSchema, async execute(args, api, context) { const { path, content } = args; const env = requireEnv(api); const absolutePath = await resolveToolPath(env, path, context); return withFileMutationQueue(env, absolutePath, async () => { if (context.abortSignal?.aborted) throw new Error("Operation aborted"); getOrThrow(await env.writeFile(absolutePath, content, context)); if (context.abortSignal?.aborted) throw new Error("Operation aborted"); return { output: [{ type: "text", text: `Successfully wrote to ${path}` }] }; }, context); } }); } // node_modules/@earendil-works/pi-durable/dist/tools/index.js function createCodingTools(options = {}) { return defineExtension({ name: "coding-tools", tools: [createReadTool(options), createWriteTool(), createEditTool(), createBashTool()] }); } var CodingTools = createCodingTools(); // node_modules/@earendil-works/pi-ai/dist/index.js var dist_exports4 = {}; __export(dist_exports4, { AI_TELEMETRY_SCHEMA: () => AI_TELEMETRY_SCHEMA, AssistantMessageEventStream: () => AssistantMessageEventStream, DEFAULT_MAX_AGENT_RETRY_DELAY_MS: () => DEFAULT_MAX_AGENT_RETRY_DELAY_MS, EventStream: () => EventStream, InMemoryCredentialStore: () => InMemoryCredentialStore, InMemoryModelsStore: () => InMemoryModelsStore, ModelsError: () => ModelsError, PI_AI_VERSION: () => PI_AI_VERSION, PI_ATTRIBUTION: () => PI_ATTRIBUTION, StreamingJsonParser: () => StreamingJsonParser, StringEnum: () => StringEnum, ToolCallArgumentsStream: () => ToolCallArgumentsStream, appendAssistantMessageDiagnostic: () => appendAssistantMessageDiagnostic, calculateCost: () => calculateCost, clampThinkingLevel: () => clampThinkingLevel, cleanupSessionResources: () => cleanupSessionResources, collapseSystemMessages: () => collapseSystemMessages, contentText: () => contentText, createAssistantMessageDiagnostic: () => createAssistantMessageDiagnostic, createAssistantMessageEventStream: () => createAssistantMessageEventStream, createFauxCore: () => createFauxCore, createInitialSystemMessage: () => createInitialSystemMessage, createModels: () => createModels, createProvider: () => createProvider, declarationsEqual: () => declarationsEqual, defaultProviderAuthContext: () => defaultProviderAuthContext, envApiKeyAuth: () => envApiKeyAuth, extractDiagnosticError: () => extractDiagnosticError, fauxAssistantMessage: () => fauxAssistantMessage, fauxProvider: () => fauxProvider, fauxText: () => fauxText, fauxThinking: () => fauxThinking, fauxToolCall: () => fauxToolCall, formatThrownValue: () => formatThrownValue, formatUserAgent: () => formatUserAgent, getAttributionHeaders: () => getAttributionHeaders, getClientVersion: () => getClientVersion, getCurrentSystemMessage: () => getCurrentSystemMessage, getCurrentSystemPrompt: () => getCurrentSystemPrompt, getCurrentTools: () => getCurrentTools, getDeclaredTools: () => getDeclaredTools, getIdentityHeaders: () => getIdentityHeaders, getInitialSystemMessage: () => getInitialSystemMessage, getModelType: () => getModelType, getOpenCodeSessionHeaders: () => getOpenCodeSessionHeaders, getOverflowPatterns: () => getOverflowPatterns, getSupportedThinkingLevels: () => getSupportedThinkingLevels, getSystemMessageText: () => getSystemMessageText, getToolStateChanges: () => getToolStateChanges, hasApi: () => hasApi, hasNonAdditiveToolChanges: () => hasNonAdditiveToolChanges, hasToolRedefinitions: () => hasToolRedefinitions, isContextOverflow: () => isContextOverflow, isModelType: () => isModelType, isRecoverableLength: () => isRecoverableLength, isRetryableAssistantError: () => isRetryableAssistantError, lazyApi: () => lazyApi, lazyOAuth: () => lazyOAuth, lazyStream: () => lazyStream, mergeClientIdentity: () => mergeClientIdentity, modelsAreEqual: () => modelsAreEqual, normalizeContext: () => normalizeContext, parseJsonWithRepair: () => parseJsonWithRepair, parseStreamingJson: () => parseStreamingJson, parseToolCallArguments: () => parseToolCallArguments, registerSessionResourceCleanup: () => registerSessionResourceCleanup, renderSystemMessageUpdate: () => renderSystemMessageUpdate, repairJson: () => repairJson, resolveTranscript: () => resolveTranscript, resolveTranscriptTools: () => resolveTranscriptTools, retryAssistantCall: () => retryAssistantCall, retryDelayMs: () => retryDelayMs, toToolDeclaration: () => toToolDeclaration, uuidv7: () => uuidv7, validateToolArguments: () => validateToolArguments, validateToolCall: () => validateToolCall, withClientIdentity: () => withClientIdentity, withoutInitialSystemMessage: () => withoutInitialSystemMessage }); // node_modules/@earendil-works/pi-ai/dist/auth/helpers.js function envApiKeyAuth(name, envVars) { return { name, login: async (interaction) => { interaction.signal.throwIfAborted(); const key = await interaction.prompt({ type: "secret", message: `Enter ${name}` }); interaction.signal.throwIfAborted(); return { type: "api_key", key }; }, resolve: async ({ ctx, credential, signal }) => { signal.throwIfAborted(); if (credential?.key) { return { auth: { apiKey: credential.key }, env: credential.env, source: "stored credential" }; } for (const envVar of envVars) { const value2 = await ctx.env(envVar); signal.throwIfAborted(); if (value2) return { auth: { apiKey: value2 }, source: envVar }; } return void 0; } }; } function lazyOAuth(input) { let promise; const loaded = () => { promise ??= input.load(); return promise; }; return { name: input.name, isSubscription: input.isSubscription, loginLabel: input.loginLabel, login: async (interaction, options) => (await loaded()).login(interaction, options), refresh: async (credential, signal) => (await loaded()).refresh(credential, signal), toAuth: async (credential) => (await loaded()).toAuth(credential) }; } // node_modules/@earendil-works/pi-ai/dist/session-resources.js var sessionResourceCleanups = /* @__PURE__ */ new Set(); function registerSessionResourceCleanup(cleanup) { sessionResourceCleanups.add(cleanup); return () => { sessionResourceCleanups.delete(cleanup); }; } function cleanupSessionResources(sessionId) { const errors = []; for (const cleanup of sessionResourceCleanups) { try { cleanup(sessionId); } catch (error) { errors.push(error); } } if (errors.length > 0) { throw new AggregateError(errors, "Failed to cleanup session resources"); } } // node_modules/@earendil-works/pi-ai/dist/utils/string-enum.js function StringEnum(values, options) { return { type: "string", enum: values, ...options?.description && { description: options.description }, ...options?.default && { default: options.default } }; } // events.ts var events_exports = {}; __export(events_exports, { EVENT_FEED: () => EVENT_FEED, EVENT_FEED_SIZE: () => EVENT_FEED_SIZE, EventFeed: () => EventFeed, EventLog: () => EventLog, NoticeEvent: () => NoticeEvent, defineEvent: () => defineEvent, emit: () => emit, eventLog: () => eventLog }); function defineEvent(name, options = {}) { const kind = `event.${name}`; return { name, kind, version: options.version ?? 1, is: (event) => event?.kind === kind }; } var NoticeEvent = defineEvent("sausage.notice"); var EVENT_FEED = "sausage.event-feed"; var EVENT_FEED_SIZE = 100; var EventLog = defineDoc({ kind: "sausage.events", version: 1, scope: "conversation", history: "latest", fork: "initial", checkpointWhen: () => false, initial: () => ({ cursors: {} }) }); var EventFeed = defineDoc({ kind: EVENT_FEED, version: 1, scope: "conversation", history: "latest", fork: "initial", checkpointWhen: () => false, initial: () => ({ events: [] }) }); var record = (entry2) => structuredClone({ id: entry2.id, name: entry2.kind.slice(6), kind: entry2.kind, ...entry2.data }); async function emit(tx, event, data) { const index = await tx.doc(EventLog, ROOT_CONVERSATION_ID); if (index.conversation === void 0) index.conversation = (await tx.createConversation({ ownership: { kind: "ownerless" } })).id; const entry2 = await tx.appendEntry(index.conversation, { kind: event.kind, data: { v: event.version, at: (/* @__PURE__ */ new Date()).toISOString(), data } }); const emitted = record(entry2); const feed = (await tx.doc(EventFeed, ROOT_CONVERSATION_ID)).events; feed.push(emitted); if (feed.length > EVENT_FEED_SIZE) feed.splice(0, feed.length - EVENT_FEED_SIZE); return emitted; } async function scan(tx, log, after, visit) { let cursor; do { const page3 = await tx.scanEntries({ conversationId: log, minEntryId: after + 1, order: "ascending" }, 256, cursor); for (const entry2 of page3.items) if (visit(entry2)) return; cursor = page3.next; } while (cursor); } function eventLog(harness2) { const reactors = /* @__PURE__ */ new Map(); function drain2(name) { const state = reactors.get(name); state.chain = state.chain.then(async () => { for (; ; ) { const active = state.active; if (!active) return; let attempted; try { const handled = await harness2().commit(async (tx) => { const index = await tx.doc(EventLog, ROOT_CONVERSATION_ID); if (index.cursors[name] === void 0 && active.from === "now") { const newest = index.conversation === void 0 ? void 0 : (await tx.scanEntries({ conversationId: index.conversation }, 1)).items[0]; index.cursors[name] = newest?.id ?? 0; return false; } if (index.conversation === void 0) return false; const after = index.cursors[name] ?? 0; let pending, last = after; await scan(tx, index.conversation, after, (entry2) => { last = entry2.id; if (active.kinds.has(entry2.kind)) pending = entry2; return pending !== void 0; }); if (pending) { attempted = pending.id; await active.handler(tx, record(pending)); } if (last !== after) index.cursors[name] = last; return pending !== void 0; }, BACKGROUND_CONTEXT); state.failure = void 0; if (!handled) return; } catch (error) { state.failure = { error: String(error), at: (/* @__PURE__ */ new Date()).toISOString(), entryId: attempted }; return; } } }); } return { emit, reactor(name, events, handler, options = {}) { const active = { kinds: new Set(events.map((event) => event.kind)), handler, from: options.from ?? "start" }; if (!reactors.has(name)) reactors.set(name, { kinds: active.kinds, chain: Promise.resolve() }); const state = reactors.get(name); let detach; const stop2 = () => { detach?.(); detach = void 0; if (state.active === active) state.active = void 0; return state.chain; }; return { start: async () => { await stop2(); state.active = active; state.kinds = active.kinds; const observation2 = harness2().observeCommits({ advance: (publication) => { if (publication.changes.some((change) => change.type === "entry" && active.kinds.has(change.value.kind))) drain2(name); }, end: () => { } }); detach = () => observation2.detach(); drain2(name); }, stop: stop2 }; }, eventLog: { recent, reactors: statuses } }; async function recent(options = {}) { const log = (await harness2().snapshot(EventLog, ROOT_CONVERSATION_ID, BACKGROUND_CONTEXT))?.conversation; if (log === void 0) return []; const kinds = options.events && new Set(options.events.map((event) => event.kind)); const limit = options.limit ?? 20; return harness2().commit(async (tx) => { const found = []; let cursor; do { const page3 = await tx.scanEntries({ conversationId: log, ...options.since === void 0 ? {} : { minEntryId: options.since + 1 }, ...options.before === void 0 ? {} : { maxEntryId: options.before - 1 }, order: options.order ?? "descending" }, 256, cursor); for (const entry2 of page3.items) { if (!(kinds ? kinds.has(entry2.kind) : entry2.kind.startsWith("event."))) continue; found.push(record(entry2)); if (found.length >= limit) return found; } cursor = page3.next; } while (cursor); return found; }, BACKGROUND_CONTEXT); } async function statuses() { const index = await harness2().snapshot(EventLog, ROOT_CONVERSATION_ID, BACKGROUND_CONTEXT); return harness2().commit(async (tx) => { const list = []; for (const [name, state] of reactors) { const cursor = index?.cursors[name] ?? 0; let lag = 0; if (index?.conversation !== void 0) await scan(tx, index.conversation, cursor, (entry2) => { if (state.kinds.has(entry2.kind)) lag++; return false; }); list.push({ name, events: [...state.kinds].map((kind) => kind.slice(6)), cursor, lag, running: state.active !== void 0, ...state.failure ? { failure: state.failure } : {} }); } return list; }, BACKGROUND_CONTEXT); } } // environment.ts var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool) => ({ ...tool, execute: (args, api, context) => tool.execute(args, { ...api, env: api.env.forTool(api.callId, tool.name) }, context) })) }; var Started = defineDoc({ kind: "board.started", version: 1, checkpointWhen: () => false, scope: "conversation", history: "latest", fork: "initial", initial: () => ({}) }); function conversationChains(harness2, context) { const cache = /* @__PURE__ */ new Map(); const chain = (id) => { let result = cache.get(id); if (!result) { result = (async () => { const record2 = await harness2.commit((tx) => tx.conversation(id), context); const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId; const starter = (await harness2.snapshot(Started, id, context))?.by; return [.../* @__PURE__ */ new Set([ String(id), ...up === void 0 ? [] : await chain(up), ...starter === void 0 ? [] : await chain(starter) ])]; })(); cache.set(id, result); } return result; }; return chain; } var ops = Deno.core.ops; // extensions.ts var ops2 = Deno.core.ops; var encoder4 = new TextEncoder(); var decoder4 = new TextDecoder(); var ExtensionState = defineDoc({ kind: "sausage.extensions", version: 1, scope: "session", checkpointWhen: () => false, initial: () => ({ good: null, current: null, error: null }) }); var Metadata = defineDoc({ kind: "sausage.metadata", version: 1, scope: "conversation", history: "latest", fork: "current", initial: () => ({ label: "", title: "" }), checkpointWhen: () => false }); async function file(kind, path, bytes2 = []) { const result = await ops2.op_file(kind, path, bytes2); if (!result.ok) throw Object.assign(new Error(result.error.message), result.error); return result.value; } var fs = { read: async (path) => decoder4.decode(new Uint8Array(await file("read", path))), write: (path, text) => file("write", path, Array.from(encoder4.encode(text))), append: (path, text) => file("append", path, Array.from(encoder4.encode(text))), list: (path) => file("list", path), stat: (path) => file("stat", path), lstat: (path) => file("lstat", path), mkdir: (path) => file("mkdir", path), remove: (path) => file("remove", path), rename: (from, to) => file("rename", from, Array.from(encoder4.encode(to))) }; async function exec(command, options) { options.signal?.throwIfAborted(); const tag = options.extension ? { id: options.toolCall ?? `extension:${options.extension}:${options.task ?? crypto.randomUUID()}`, pid: 0, program: `extension:${options.extension}`, conversations: options.conversations ?? [] } : void 0; const rid = ops2.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag); const abort = () => Deno.core.close(rid); options.signal?.addEventListener("abort", abort, { once: true }); const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() }; const result = { code: 0, stdout: "", stderr: "" }; try { for (; ; ) { const chunk = await ops2.op_exec_next(rid); if (!chunk.stream) { if (chunk.error) throw new Error(chunk.error); result.code = chunk.exitCode; for (const stream of ["stdout", "stderr"]) { const text2 = decoders[stream].decode(); result[stream] += text2; if (text2) options.onOutput?.(text2, stream); } return result; } const text = decoders[chunk.stream].decode(new Uint8Array(chunk.bytes), { stream: true }); result[chunk.stream] += text; options.onOutput?.(text, chunk.stream); } } finally { options.signal?.removeEventListener("abort", abort); } } async function git(cwd, ...args) { const result = await exec(["git", ...args], { cwd, timeout: 30 }); if (result.code) throw new Error(result.stderr || `git exited ${result.code}`); return result.stdout.trim(); } function registerModules() { const modules = { "@earendil-works/sausage-events": events_exports, "@earendil-works/pi-schema": dist_exports, "@earendil-works/pi-durable": dist_exports3, "@earendil-works/pi-durable/tools": tools_exports, "@earendil-works/pi-ai": dist_exports4, "@earendil-works/pi-ai/models": models_exports, "@earendil-works/chord": dist_exports2, "@earendil-works/chord/context": context_exports }; globalThis.__sausage = { modules }; ops2.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)]))); } async function loadExtensions(harness2, registry2, storage2, models2, context, builtins = []) { registerModules(); const eventService = eventLog(() => harness2); const chain = conversationChains(harness2, context); const repo = "/src/sausage"; let providers = /* @__PURE__ */ new Map(); const resources = /* @__PURE__ */ new Map(); let facets = []; let state = { ...await harness2.snapshot(ExtensionState, context) ?? { good: null, current: null, error: null } }; let settings = {}; let active = true; let queue2 = Promise.resolve(); const publish2 = async () => { await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context); ops2.op_extension_status(JSON.stringify(state)); await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? ""); }; const dispose = async (entries2) => { for (const facet of entries2) for (const cleanup of facet.close) { try { await cleanup(); } catch (error) { ops2.op_output(`Extension cleanup: ${String(error)} `); } } }; const scope = (name, staged) => { let opened = false; const notices = /* @__PURE__ */ new Set(); const open = [async () => { opened = true; }], close = [async () => { opened = false; }]; const handle = (api) => new Proxy({}, { get: (_, key) => { if (typeof key === "symbol" || key === "then") return void 0; const value2 = providers.get(api.name); if (!value2) throw new Error(`No provider for ${api.name}`); const member = value2[key]; return typeof member === "function" ? (...args) => { const current = providers.get(api.name); if (!current) throw new Error(`No provider for ${api.name}`); return Reflect.apply(current[key], current, args); } : member; } }); const host2 = { harness: () => harness2, storage: storage2, models: () => models2, settings: () => settings, metadata: Metadata, fs, exec: (command, options) => exec(command, { ...options, extension: name }), git, chain, implement: (api, value2) => { staged.set(api.name, value2); }, need: handle, want: (api) => providers.has(api.name) ? handle(api) : void 0, resource: (key, create) => { const id = `${name}/${key}`; if (!resources.has(id)) resources.set(id, create()); return resources.get(id); }, emit: eventService.emit, eventLog: eventService.eventLog, react: (key, tokens, handler, options) => { const reactor = eventService.reactor(`${name}/${key}`, tokens, handler, options); open.push(reactor.start); close.push(reactor.stop); if (opened) void reactor.start(); }, onOpen: (cb) => open.push(cb), onClose: (cb) => close.push(cb), notice: (message) => { state.notice = `${name}: ${message}`; ops2.op_extension_status(JSON.stringify(state)); ops2.op_output(`${name}: ${message} `); if (!notices.has(message)) { notices.add(message); void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops2.op_output(`Recording a notice failed: ${String(error)} `)); } } }; return { host: host2, open, close }; }; const load = async (sha) => { const next = []; const staged = /* @__PURE__ */ new Map(); try { const snapshot = `/run/sausage/extensions/${sha}`; for (const path of ["/run/sausage", "/run/sausage/extensions"]) { try { await fs.mkdir(path); } catch (error) { if (error.code !== "already_exists") throw error; } } try { await fs.stat(snapshot); } catch { await git(repo, "worktree", "prune"); await git(repo, "worktree", "add", "--detach", snapshot, sha); } const modules = []; for (const entry2 of await fs.list(`${snapshot}/extensions`).catch((error) => { if (error.code === "not_found") return []; throw error; })) { if (entry2.kind !== "directory") continue; const path = `${snapshot}/extensions/${entry2.name}/server.ts`; try { await fs.stat(path); } catch (error) { if (error.code === "not_found") continue; throw error; } const module = await import( /* @vite-ignore */ `file://${path}` ); modules.push({ name: entry2.name, module }); } modules.sort((a, b) => (a.module.order ?? 0) - (b.module.order ?? 0) || a.name.localeCompare(b.name)); for (const { name, module } of modules) { const facet = scope(name, staged); next.push(facet); facet.actions = Object.fromEntries((module.userActions ?? []).filter((key) => typeof module.actions?.[key] === "function").map((key) => [key, module.actions[key]])); facet.extension = typeof module.default === "function" ? await module.default(facet.host) : module.default; if (!facet.extension || facet.extension.name !== name) throw new Error(`Extension name must match ${name}`); } const probe2 = createRegistry(); for (const extension of [...builtins, ...next.map((f) => f.extension)]) probe2.install(extension); for (const old2 of facets) registry2.uninstall(old2.extension); for (const extension of [...builtins, ...next.map((f) => f.extension)]) registry2.install(extension); const old = facets; facets = next; providers = staged; await dispose(old); for (const facet of next) for (const cb of facet.open) { try { await cb(); } catch (error) { facet.host.notice(`onOpen: ${String(error)}`); } } state.good = sha; return null; } catch (error) { await dispose(next); return String(error); } }; const reload = async () => { let tip; try { tip = await git(repo, "rev-parse", "refs/heads/main"); } catch (error) { state.error = String(error); ops2.op_extension_status(JSON.stringify(state)); return; } try { settings = JSON.parse(await fs.read(`${repo}/settings.json`)); } catch (error) { if (error.code === "not_found") settings = {}; else { state.notice = `Settings: ${String(error)}`; ops2.op_extension_status(JSON.stringify(state)); ops2.op_output(`${state.notice} `); } } if (state.current === tip && facets.length) return; state.current = tip; state.error = await load(tip); if (state.error && !facets.length && state.good && state.good !== tip) { const failed = state.error; const fallback = await load(state.good); state.error = fallback ? `${failed} Last good: ${fallback}` : failed; } await publish2(); }; const enqueue2 = () => queue2 = queue2.then(reload); const rid = ops2.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]); await enqueue2(); const pending = (async () => { while (active) { await ops2.op_watch_next(rid); if (active) await enqueue2(); } })(); return { reload: enqueue2, state: () => ({ ...state }), async invoke(name, key, args, signal, conversationId) { const facet = facets.find((f) => f.extension.name === name); const action = facet?.actions?.[key]; if (!action) throw Object.assign(new Error("Unknown user action"), { status: 404 }); return action(args, { host: facet.host, signal, conversationId, source: "ui" }); }, close: async () => { active = false; Deno.core.close(rid); await pending; await queue2; await dispose(facets); } }; } // node_modules/@earendil-works/chord/dist/sqlite/schema.js function validateSqliteSchemaPrefix(prefix) { if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) { throw new Error(`Invalid SQLite schema prefix ${JSON.stringify(prefix)}`); } if (prefix.toLowerCase().startsWith("sqlite_")) { throw new Error(`SQLite schema prefix ${JSON.stringify(prefix)} is reserved`); } } // node_modules/@earendil-works/chord/dist/sqlite/migrations.js async function applySqliteMigrations(database, migrations, options) { const { table } = options; validateSqliteSchemaPrefix(table); for (let index = 0; index < migrations.length; index++) { if (migrations[index]?.version !== index + 1) { throw new Error("SQLite migrations must have contiguous versions starting at 1"); } } await database.transaction(async (transaction) => { await transaction.exec(`CREATE TABLE IF NOT EXISTS ${table} ( singleton INTEGER PRIMARY KEY CHECK (singleton = 1), version INTEGER NOT NULL CHECK (version >= 0) ) STRICT`); await transaction.run(`INSERT OR IGNORE INTO ${table} (singleton, version) VALUES (1, 0)`); const row = await transaction.get(`SELECT version FROM ${table} WHERE singleton = 1`); if (row === void 0) throw new Error("SQLite schema metadata is missing"); const currentVersion = migrations.at(-1)?.version ?? 0; if (row.version > currentVersion) { throw new Error(`SQLite schema version ${row.version} is newer than supported version ${currentVersion}`); } for (const migration of migrations) { if (migration.version <= row.version) continue; for (const statement of migration.statements) await transaction.exec(statement); await transaction.run(`UPDATE ${table} SET version = ? WHERE singleton = 1`, migration.version); } }); } // node_modules/@earendil-works/pi-durable/dist/storage/sqlite/schema.js var TABLE_NAMES = { durableSchema: "durable_schema", durableMetadata: "durable_metadata", recordIds: "record_ids", conversations: "conversations", entries: "entries", tasks: "tasks", submissions: "submissions", documents: "documents", documentRevisions: "document_revisions" }; var INDEX_NAMES = { conversationsByOwnerConversation: "conversations_by_owner_conversation", conversationsByOwnerTask: "conversations_by_owner_task", entriesByConversation: "entries_by_conversation", entryHeadsByConversation: "entry_heads_by_conversation", tasksByStatus: "tasks_by_status", tasksByConversation: "tasks_by_conversation", tasksByKind: "tasks_by_kind", tasksByAbortRequested: "tasks_by_abort_requested", tasksByBackground: "tasks_by_background", submissionsByRequest: "submissions_by_request", submissionsByConversation: "submissions_by_conversation", submissionsByStatus: "submissions_by_status", documentsByAddress: "documents_by_address", documentsByScope: "documents_by_scope", documentsByScopeKind: "documents_by_scope_kind", documentRevisionsByKind: "document_revisions_by_kind" }; function prefixIdentifiers(names2, prefix) { return Object.fromEntries(Object.entries(names2).map(([key, name]) => [key, `${prefix}${name}`])); } function createSqliteSchema(prefix) { if (prefix !== void 0) validateSqliteSchemaPrefix(prefix); const actualPrefix = prefix ?? ""; return { tables: prefixIdentifiers(TABLE_NAMES, actualPrefix), indexes: prefixIdentifiers(INDEX_NAMES, actualPrefix) }; } var DEFAULT_SQLITE_SCHEMA = createSqliteSchema(); // node_modules/@earendil-works/pi-durable/dist/storage/sqlite/migrations.js function createSqliteMigrations(schema) { const { tables, indexes } = schema; const initialSchema = [ `CREATE TABLE ${tables.durableMetadata} ( singleton INTEGER PRIMARY KEY CHECK (singleton = 1), next_id TEXT NOT NULL, next_seq INTEGER NOT NULL ) STRICT`, `INSERT INTO ${tables.durableMetadata} (singleton, next_id, next_seq) VALUES (1, '2', 1)`, `CREATE TABLE ${tables.recordIds} ( id INTEGER PRIMARY KEY, record_type TEXT NOT NULL CHECK (record_type IN ('conversation', 'entry', 'task', 'submission', 'document')) ) STRICT`, `CREATE TABLE ${tables.conversations} ( id INTEGER PRIMARY KEY, owner_conversation_id INTEGER, owner_task_id INTEGER, record TEXT NOT NULL CHECK (json_valid(record)) ) STRICT`, `CREATE INDEX ${indexes.conversationsByOwnerConversation} ON ${tables.conversations} (owner_conversation_id, id)`, `CREATE INDEX ${indexes.conversationsByOwnerTask} ON ${tables.conversations} (owner_task_id, id)`, `CREATE TABLE ${tables.entries} ( id INTEGER PRIMARY KEY, conversation_id INTEGER NOT NULL, head INTEGER, commit_seq INTEGER NOT NULL, record TEXT NOT NULL CHECK (json_valid(record)) ) STRICT`, `CREATE INDEX ${indexes.entriesByConversation} ON ${tables.entries} (conversation_id, id DESC)`, `CREATE INDEX ${indexes.entryHeadsByConversation} ON ${tables.entries} (conversation_id, id DESC) WHERE head IS NOT NULL`, `CREATE TABLE ${tables.tasks} ( id INTEGER PRIMARY KEY, conversation_id INTEGER NOT NULL, kind TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'waiting', 'completing', 'terminal')), abort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)), background INTEGER NOT NULL CHECK (background IN (0, 1)), record TEXT NOT NULL CHECK (json_valid(record)) ) STRICT`, `CREATE INDEX ${indexes.tasksByStatus} ON ${tables.tasks} (status, id)`, `CREATE INDEX ${indexes.tasksByConversation} ON ${tables.tasks} (conversation_id, id)`, `CREATE INDEX ${indexes.tasksByKind} ON ${tables.tasks} (kind, id)`, `CREATE INDEX ${indexes.tasksByAbortRequested} ON ${tables.tasks} (abort_requested, id)`, `CREATE INDEX ${indexes.tasksByBackground} ON ${tables.tasks} (background, id)`, `CREATE TABLE ${tables.submissions} ( id INTEGER PRIMARY KEY, conversation_id INTEGER NOT NULL, request_id TEXT, status TEXT NOT NULL CHECK (status IN ('queued', 'placed', 'done', 'unanswered')), record TEXT NOT NULL CHECK (json_valid(record)) ) STRICT`, `CREATE INDEX ${indexes.submissionsByRequest} ON ${tables.submissions} (conversation_id, request_id)`, `CREATE INDEX ${indexes.submissionsByConversation} ON ${tables.submissions} (conversation_id, id)`, `CREATE INDEX ${indexes.submissionsByStatus} ON ${tables.submissions} (status, id)`, `CREATE TABLE ${tables.documents} ( id INTEGER PRIMARY KEY, kind TEXT NOT NULL, family INTEGER NOT NULL CHECK (family IN (0, 1)), key_value TEXT NOT NULL, scope_kind TEXT NOT NULL CHECK (scope_kind IN ('session', 'conversation', 'task')), owner_id INTEGER NOT NULL, created_at INTEGER NOT NULL, retired_at INTEGER, record TEXT NOT NULL CHECK (json_valid(record)) ) STRICT`, `CREATE INDEX ${indexes.documentsByAddress} ON ${tables.documents} (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at)`, `CREATE INDEX ${indexes.documentsByScope} ON ${tables.documents} (scope_kind, owner_id, id)`, `CREATE INDEX ${indexes.documentsByScopeKind} ON ${tables.documents} (scope_kind, owner_id, kind, id)`, `CREATE TABLE ${tables.documentRevisions} ( document_id INTEGER NOT NULL, seq INTEGER NOT NULL, kind TEXT NOT NULL CHECK (kind IN ('base', 'delta')), version INTEGER NOT NULL, content TEXT NOT NULL CHECK (json_valid(content)), PRIMARY KEY (document_id, seq) ) STRICT`, `CREATE INDEX ${indexes.documentRevisionsByKind} ON ${tables.documentRevisions} (document_id, kind, seq DESC)` ]; const rebuiltTasks = `${tables.tasks}_v2`; const sleepingStatus = [ `CREATE TABLE ${rebuiltTasks} ( id INTEGER PRIMARY KEY, conversation_id INTEGER NOT NULL, kind TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'waiting', 'sleeping', 'completing', 'terminal')), abort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)), background INTEGER NOT NULL CHECK (background IN (0, 1)), record TEXT NOT NULL CHECK (json_valid(record)) ) STRICT`, `INSERT INTO ${rebuiltTasks} (id, conversation_id, kind, status, abort_requested, background, record) SELECT id, conversation_id, kind, status, abort_requested, background, record FROM ${tables.tasks}`, `DROP TABLE ${tables.tasks}`, `ALTER TABLE ${rebuiltTasks} RENAME TO ${tables.tasks}`, `CREATE INDEX ${indexes.tasksByStatus} ON ${tables.tasks} (status, id)`, `CREATE INDEX ${indexes.tasksByConversation} ON ${tables.tasks} (conversation_id, id)`, `CREATE INDEX ${indexes.tasksByKind} ON ${tables.tasks} (kind, id)`, `CREATE INDEX ${indexes.tasksByAbortRequested} ON ${tables.tasks} (abort_requested, id)`, `CREATE INDEX ${indexes.tasksByBackground} ON ${tables.tasks} (background, id)` ]; return [ { version: 1, statements: initialSchema }, { version: 2, statements: sleepingStatus } ]; } var SQLITE_MIGRATIONS = createSqliteMigrations(DEFAULT_SQLITE_SCHEMA); var CURRENT_SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.at(-1)?.version ?? 0; async function applySqliteMigrationsForSchema(database, schema, migrations = createSqliteMigrations(schema)) { await applySqliteMigrations(database, migrations, { table: schema.tables.durableSchema }); } // node_modules/@earendil-works/pi-durable/dist/storage/sqlite/storage.js var parseJson = (value2) => JSON.parse(value2); var encodeJson = (value2) => JSON.stringify(value2); var encodeIndexedString = (value2) => JSON.stringify(value2); var cursorId2 = (cursor) => { const after = cursor?.after; if (after === void 0) return void 0; if (typeof after !== "number" || !Number.isSafeInteger(after)) throw new StorageRequestError("Invalid storage cursor"); return idFromNumber(after); }; var page2 = (values, limit, order = "ascending") => { const items = values.slice(0, limit); if (values.length <= limit) return { items }; return { items, next: nextCursor(items.at(-1).id, order) }; }; var scanSql = (start) => start.order === "ascending" ? { clause: "id > ?", param: start.after ?? -1, direction: "ASC" } : { clause: "id < ?", param: start.after ?? Number.MAX_SAFE_INTEGER, direction: "DESC" }; var scopeColumns = (scope) => { switch (scope.kind) { case "session": return { scopeKind: "session", ownerId: 0 }; case "conversation": return { scopeKind: "conversation", ownerId: scope.conversationId }; case "task": return { scopeKind: "task", ownerId: scope.taskId }; } }; var addressParts = (address) => { const scope = scopeColumns(address.scope); return { kind: encodeIndexedString(address.kind), ...scope, family: address.key === void 0 ? 0 : 1, keyValue: encodeIndexedString(address.key ?? "") }; }; var addressKey2 = (address) => { const parts = addressParts(address); return JSON.stringify([parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue]); }; var isAliveAt2 = (record2, at) => { if (at === "current") return record2.retiredAt === void 0; return record2.createdAt <= at && (record2.retiredAt === void 0 || at < record2.retiredAt); }; var isCurrentOnly2 = (record2) => record2.scope.kind !== "conversation" || record2.history === "latest"; var writeId = (write) => { switch (write.type) { case "conversation": case "entry": case "task": case "submission": return write.value.id; case "document.create": case "document.copy": return write.record.id; case "document.change": case "document.retire": return void 0; } }; function watchCalls(executor, calls) { const watched = async (call2) => { try { return await call2(); } catch (error) { calls.failed = true; throw error; } }; return { exec: (sql) => watched(() => executor.exec(sql)), run: (sql, ...params) => watched(() => executor.run(sql, ...params)), runCounted: (sql, ...params) => watched(() => executor.runCounted(sql, ...params)), get: (sql, ...params) => watched(() => executor.get(sql, ...params)), all: (sql, ...params) => watched(() => executor.all(sql, ...params)) }; } var SqliteStorage = class _SqliteStorage { db; schema; nextId; closed = false; closing; admittedReads = 0; readsDrained; constructor(db, schema, nextId) { this.db = db; this.schema = schema; this.nextId = nextId; } /** Initialize storage over an owned SQLite database facade. */ static async open(db, options = {}) { try { const schema = createSqliteSchema(options.prefix); await applySqliteMigrationsForSchema(db, schema); const metadata = await db.get(`SELECT next_id, next_seq FROM ${schema.tables.durableMetadata} WHERE singleton = 1`); if (metadata === void 0) throw new Error("Durable SQLite metadata is missing"); return new _SqliteStorage(db, schema, Number(metadata.next_id)); } catch (error) { try { await db.close(); } catch { } throw error; } } /** * Commit `batches` in one SQL transaction, each with its own seq. Each batch is checked before it writes anything, * against the state the earlier ones left, and the first one refused ends the batch: the transaction commits the ones * before it. No savepoints, which Durable Objects do not allow: the checks run before the writes instead. */ async commit(batches, receive, _context) { this.assertOpen(); const seqs = []; let refused; let candidateNextId = this.nextId; await this.db.transaction(async (transaction) => { const metadata = await this.readMetadata(transaction); let next = metadata.next_seq; const calls = { failed: false }; const checked = watchCalls(transaction, calls); for (const writes of batches) { let documentActions; calls.failed = false; try { documentActions = this.prepareDocumentActions(writes); await this.checkBatch(checked, writes, documentActions); } catch (error) { if (calls.failed) throw error; refused = { error }; break; } const seq = seqFromNumber(next++); await this.applyBatch(transaction, writes, documentActions, seq); seqs.push(seq); candidateNextId = Math.max(candidateNextId, this.candidateNextId(writes)); } if (seqs.length > 0) { await this.writeMetadata(transaction, Math.max(Number(metadata.next_id), candidateNextId), next); } }); this.nextId = Math.max(this.nextId, candidateNextId); for (const [index, seq] of seqs.entries()) receive(index, seq); return refused === void 0 ? { stored: seqs.length } : { stored: seqs.length, refused: refused.error }; } async readMetadata(executor) { const metadata = await executor.get(`SELECT next_id, next_seq FROM ${this.schema.tables.durableMetadata} WHERE singleton = 1`); if (metadata === void 0) throw new Error("Durable SQLite metadata is missing"); return metadata; } async writeMetadata(executor, nextId, nextSeq) { await executor.run(`UPDATE ${this.schema.tables.durableMetadata} SET next_id = ?, next_seq = ? WHERE singleton = 1`, String(nextId), nextSeq); } /** Durable's own checks of one batch, copy resolution included, before it writes anything. */ async checkBatch(executor, writes, documentActions) { await this.checkGlobalIds(executor, writes); await this.checkDocumentActions(executor, documentActions); await this.resolveCopies(executor, documentActions); } async applyBatch(executor, writes, documentActions, seq) { for (const write of writes) await this.applyTableWrite(executor, write, seq); await this.applyDocumentActions(executor, documentActions, seq); } async mintId() { this.assertOpen(); if (!Number.isSafeInteger(this.nextId)) throw new Error("ID space is exhausted"); return idFromNumber(this.nextId++); } async conversation(id, _context) { this.assertOpen(); const row = await this.db.get(`SELECT record FROM ${this.schema.tables.conversations} WHERE id = ?`, id); return row === void 0 ? void 0 : parseJson(row.record); } async scanConversations(query, limit, cursor, _context) { this.assertOpen(); const start = scanStart(query.order, cursor, "ascending"); const scan2 = scanSql(start); const clauses = [scan2.clause]; const params = [scan2.param]; if (query.ownerConversationId !== void 0) { clauses.push("owner_conversation_id = ?"); params.push(query.ownerConversationId); } if (query.ownerTaskId !== void 0) { clauses.push("owner_task_id = ?"); params.push(query.ownerTaskId); } params.push(limit + 1); const rows = await this.db.all(`SELECT record FROM ${this.schema.tables.conversations} WHERE ${clauses.join(" AND ")} ORDER BY id ${scan2.direction} LIMIT ?`, ...params); return page2(rows.map((row) => parseJson(row.record)), limit, start.order); } entry(idOrConversationId, idOrContext, context) { return this.admitRead(() => this.readEntry(idOrConversationId, idOrContext, context)); } findLatestHeadMarker(conversationId, atOrBeforeEntryId, _context) { return this.admitRead(() => this.readLatestHeadMarker(conversationId, atOrBeforeEntryId)); } scanEntries(query, limit, cursor, _context) { return this.admitRead(() => this.readEntries(query, limit, cursor)); } async readEntry(idOrConversationId, idOrContext, context) { const id = context === void 0 ? idFromNumber(idOrConversationId) : typeof idOrContext === "number" ? idFromNumber(idOrContext) : void 0; if (id === void 0) throw new StorageRequestError("Storage.entry() requires an entry ID"); let conversation; if (context !== void 0) { const conversationId = idFromNumber(idOrConversationId); conversation = await this.readConversation(conversationId); if (conversation === void 0) throw new StorageRequestError(`Unknown conversation: ${conversationId}`); } const row = await this.db.get(`SELECT record, commit_seq FROM ${this.schema.tables.entries} WHERE id = ?`, id); if (row === void 0) return void 0; const entry2 = parseJson(row.record); if (conversation !== void 0) { let upperEntryId = Number.POSITIVE_INFINITY; while (conversation.id !== entry2.conversationId) { if (conversation.parent === void 0) return void 0; upperEntryId = Math.min(upperEntryId, conversation.parent.at); conversation = await this.readConversation(conversation.parent.conversationId); } if (entry2.id > upperEntryId) return void 0; } return { entry: entry2, commitSeq: seqFromNumber(row.commit_seq) }; } async readLatestHeadMarker(conversationId, atOrBeforeEntryId) { let conversation = await this.readConversation(conversationId); if (conversation === void 0) throw new StorageRequestError(`Unknown conversation: ${conversationId}`); let upper = atOrBeforeEntryId; while (true) { const row = upper === void 0 ? await this.db.get(`SELECT record FROM ${this.schema.tables.entries} WHERE conversation_id = ? AND head IS NOT NULL ORDER BY id DESC LIMIT 1`, conversation.id) : await this.db.get(`SELECT record FROM ${this.schema.tables.entries} WHERE conversation_id = ? AND head IS NOT NULL AND id <= ? ORDER BY id DESC LIMIT 1`, conversation.id, upper); if (row !== void 0) return parseJson(row.record); if (conversation.parent === void 0) return void 0; upper = upper === void 0 ? conversation.parent.at : Math.min(upper, conversation.parent.at); conversation = await this.readConversation(conversation.parent.conversationId); } } async readEntries(query, limit, cursor) { const { order, after } = scanStart(query.order, cursor, "descending"); if (order === "ascending") return this.readEntriesAscending(query, limit, after); let conversation = await this.readConversation(query.conversationId); if (conversation === void 0) throw new StorageRequestError(`Unknown conversation: ${query.conversationId}`); let upper = query.maxEntryId; if (after !== void 0) upper = Math.min(upper ?? Number.MAX_SAFE_INTEGER, after - 1); const values = []; while (true) { const clauses = ["conversation_id = ?"]; const params = [conversation.id]; if (query.minEntryId !== void 0) { clauses.push("id >= ?"); params.push(query.minEntryId); } if (upper !== void 0) { clauses.push("id <= ?"); params.push(upper); } params.push(limit + 1 - values.length); const rows = await this.db.all(`SELECT record FROM ${this.schema.tables.entries} WHERE ${clauses.join(" AND ")} ORDER BY id DESC LIMIT ?`, ...params); values.push(...rows.map((row) => parseJson(row.record))); if (values.length > limit || conversation.parent === void 0) break; upper = upper === void 0 ? conversation.parent.at : Math.min(upper, conversation.parent.at); if (query.minEntryId !== void 0 && upper < query.minEntryId) break; conversation = await this.readConversation(conversation.parent.conversationId); } return page2(values, limit, "descending"); } /** Oldest first: the fork chain's segments from the root conversation forward, each up to its fork point. */ async readEntriesAscending(query, limit, after) { const segments = []; let conversation = await this.readConversation(query.conversationId); if (conversation === void 0) throw new StorageRequestError(`Unknown conversation: ${query.conversationId}`); let upper = query.maxEntryId; while (true) { segments.push({ conversationId: conversation.id, upper }); if (conversation.parent === void 0) break; upper = upper === void 0 ? conversation.parent.at : Math.min(upper, conversation.parent.at); if (query.minEntryId !== void 0 && upper < query.minEntryId) break; conversation = await this.readConversation(conversation.parent.conversationId); } let lower = query.minEntryId; if (after !== void 0) lower = Math.max(lower ?? after + 1, after + 1); const values = []; for (const segment of segments.reverse()) { const clauses = ["conversation_id = ?"]; const params = [segment.conversationId]; if (lower !== void 0) { clauses.push("id >= ?"); params.push(lower); } if (segment.upper !== void 0) { clauses.push("id <= ?"); params.push(segment.upper); } params.push(limit + 1 - values.length); const rows = await this.db.all(`SELECT record FROM ${this.schema.tables.entries} WHERE ${clauses.join(" AND ")} ORDER BY id ASC LIMIT ?`, ...params); values.push(...rows.map((row) => parseJson(row.record))); if (values.length > limit) break; } return page2(values, limit, "ascending"); } async task(id, _context) { this.assertOpen(); const row = await this.db.get(`SELECT record FROM ${this.schema.tables.tasks} WHERE id = ?`, id); return row === void 0 ? void 0 : parseJson(row.record); } async scanTasks(query, limit, cursor, _context) { this.assertOpen(); const start = scanStart(query.order, cursor, "ascending"); const scan2 = scanSql(start); const clauses = [scan2.clause]; const params = [scan2.param]; if (query.conversationId !== void 0) { clauses.push("conversation_id = ?"); params.push(query.conversationId); } if (query.kind !== void 0) { clauses.push("kind = ?"); params.push(encodeIndexedString(query.kind)); } if (query.status !== void 0) { clauses.push("status = ?"); params.push(query.status); } if (query.abortRequested !== void 0) { clauses.push("abort_requested = ?"); params.push(query.abortRequested ? 1 : 0); } if (query.background !== void 0) { clauses.push("background = ?"); params.push(query.background ? 1 : 0); } params.push(limit + 1); const rows = await this.db.all(`SELECT record FROM ${this.schema.tables.tasks} WHERE ${clauses.join(" AND ")} ORDER BY id ${scan2.direction} LIMIT ?`, ...params); return page2(rows.map((row) => parseJson(row.record)), limit, start.order); } async submission(id, _context) { this.assertOpen(); const row = await this.db.get(`SELECT record FROM ${this.schema.tables.submissions} WHERE id = ?`, id); return row === void 0 ? void 0 : parseJson(row.record); } async scanSubmissions(query, limit, cursor, _context) { this.assertOpen(); const start = scanStart(query.order, cursor, "ascending"); const scan2 = scanSql(start); const clauses = [scan2.clause]; const params = [scan2.param]; if (query.conversationId !== void 0) { clauses.push("conversation_id = ?"); params.push(query.conversationId); } if (query.status !== void 0) { clauses.push("status = ?"); params.push(query.status); } params.push(limit + 1); const rows = await this.db.all(`SELECT record FROM ${this.schema.tables.submissions} WHERE ${clauses.join(" AND ")} ORDER BY id ${scan2.direction} LIMIT ?`, ...params); return page2(rows.map((row) => parseJson(row.record)), limit, start.order); } async submissionByRequest(conversationId, requestId, _context) { this.assertOpen(); const row = await this.db.get(`SELECT record FROM ${this.schema.tables.submissions} WHERE conversation_id = ? AND request_id = ?`, conversationId, encodeIndexedString(requestId)); return row === void 0 ? void 0 : parseJson(row.record); } async findDocument(address, at, _context) { this.assertOpen(); const parts = addressParts(address); const sql = at === "current" ? `SELECT record FROM ${this.schema.tables.documents} WHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ? AND retired_at IS NULL ORDER BY created_at DESC LIMIT 1` : `SELECT record FROM ${this.schema.tables.documents} WHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ? AND created_at <= ? AND (retired_at IS NULL OR retired_at > ?) ORDER BY created_at DESC LIMIT 1`; const params = [parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue]; if (at !== "current") params.push(at, at); const row = await this.db.get(sql, ...params); return row === void 0 ? void 0 : parseJson(row.record); } async document(id, at, _context) { this.assertOpen(); return this.db.transaction((transaction) => this.materializeDocument(transaction, id, at)); } async scanDocuments(query, limit, cursor, _context) { this.assertOpen(); const scope = scopeColumns(query.scope); const clauses = ["scope_kind = ?", "owner_id = ?", "id > ?"]; const params = [scope.scopeKind, scope.ownerId, cursorId2(cursor) ?? -1]; if (query.kind !== void 0) { clauses.push("kind = ?"); params.push(encodeIndexedString(query.kind)); } if (query.at === "current") { clauses.push("retired_at IS NULL"); } else { clauses.push("created_at <= ?", "(retired_at IS NULL OR retired_at > ?)"); params.push(query.at, query.at); } params.push(limit + 1); const rows = await this.db.all(`SELECT record FROM ${this.schema.tables.documents} WHERE ${clauses.join(" AND ")} ORDER BY id LIMIT ?`, ...params); return page2(rows.map((row) => parseJson(row.record)), limit); } close(_context) { if (this.closing === void 0) { this.closed = true; this.closing = this.closeDatabase(); } return this.closing; } async closeDatabase() { if (this.admittedReads > 0) { await new Promise((resolve2) => { this.readsDrained = resolve2; }); } await this.db.close(); } /** * Run a read that issues several queries. Close waits for admitted reads, so their later queries never reach a * closed database. Single-query reads and transactions are already ordered before close by the database. */ async admitRead(read2) { this.assertOpen(); this.admittedReads++; try { return await read2(); } finally { if (--this.admittedReads === 0) this.readsDrained?.(); } } async readConversation(id) { const row = await this.db.get(`SELECT record FROM ${this.schema.tables.conversations} WHERE id = ?`, id); return row === void 0 ? void 0 : parseJson(row.record); } async materializeDocument(executor, id, at) { const row = await executor.get(`SELECT record FROM ${this.schema.tables.documents} WHERE id = ?`, id); if (row === void 0) return void 0; const record2 = parseJson(row.record); if (at !== "current" && isCurrentOnly2(record2)) { throw new StorageRequestError(`Document ${id} does not retain historical content`); } if (!isAliveAt2(record2, at)) return void 0; const upper = at === "current" ? Number.MAX_SAFE_INTEGER : at; const base = await executor.get(`SELECT seq, kind, version, content FROM ${this.schema.tables.documentRevisions} WHERE document_id = ? AND kind = 'base' AND seq <= ? ORDER BY seq DESC LIMIT 1`, id, upper); if (base === void 0) throw new Error(`Document ${id} is missing a required base`); let value2 = parseJson(base.content); const tail = await executor.all(`SELECT seq, kind, version, content FROM ${this.schema.tables.documentRevisions} WHERE document_id = ? AND seq > ? AND seq <= ? ORDER BY seq`, id, base.seq, upper); for (const revision of tail) { if (revision.kind !== "delta" || revision.version !== base.version) { throw new Error(`Document ${id} crosses a stored version boundary without a base`); } value2 = apply(value2, parseJson(revision.content)); } return { record: record2, version: base.version, value: value2, deltasSinceBase: tail.length }; } candidateNextId(writes) { let nextId = this.nextId; for (const write of writes) { const id = writeId(write); if (id !== void 0) nextId = Math.max(nextId, id + 1); } return nextId; } async checkGlobalIds(executor, writes) { const claimed = /* @__PURE__ */ new Map(); for (const write of writes) { if (write.type === "document.change" || write.type === "document.retire") continue; const document = write.type === "document.create" || write.type === "document.copy"; const table = document ? "document" : write.type; const id = document ? write.record.id : write.value.id; const existing = (await executor.get(`SELECT record_type FROM ${this.schema.tables.recordIds} WHERE id = ?`, id))?.record_type; const earlier = claimed.get(id); if (table === "conversation" || table === "entry" || table === "document") { if (existing !== void 0) throw new Error(`ID ${id} already belongs to ${existing}`); if (earlier !== void 0) throw new Error(`ID ${id} is written more than once`); } else { if (existing !== void 0 && existing !== table) throw new Error(`ID ${id} already belongs to ${existing}`); if (earlier !== void 0 && earlier !== table) throw new Error(`ID ${id} is written as two record types`); } claimed.set(id, table); } } prepareDocumentActions(writes) { const actions = /* @__PURE__ */ new Map(); for (const write of writes) { if (write.type !== "document.create" && write.type !== "document.copy" && write.type !== "document.change" && write.type !== "document.retire") { continue; } const id = write.type === "document.create" || write.type === "document.copy" ? write.record.id : write.id; let action = actions.get(id); if (action === void 0) { action = { retire: false }; actions.set(id, action); } switch (write.type) { case "document.create": if (action.create !== void 0 || action.content !== void 0 || action.copy !== void 0) { throw new Error(`Document ${id} has more than one content command`); } action.create = write.record; action.content = write.content; break; case "document.copy": if (action.create !== void 0 || action.content !== void 0 || action.copy !== void 0) { throw new Error(`Document ${id} has more than one content command`); } action.create = write.record; action.copy = write.source; break; case "document.change": if (action.content !== void 0 || action.copy !== void 0) { throw new Error(`Document ${id} has more than one content command`); } action.content = write.content; break; case "document.retire": if (action.retire) throw new Error(`Document ${id} is retired more than once`); action.retire = true; break; } } return actions; } async checkDocumentActions(executor, actions) { const liveCounts = /* @__PURE__ */ new Map(); for (const [id, action] of actions) { if (action.copy !== void 0 && actions.has(action.copy.id)) { throw new Error(`Document copy ${id} source is changed in the copy batch`); } const row = await executor.get(`SELECT record FROM ${this.schema.tables.documents} WHERE id = ?`, id); const existing = row === void 0 ? void 0 : parseJson(row.record); if (action.create === void 0 && existing === void 0) throw new Error(`Unknown document: ${id}`); if (action.create !== void 0 && existing !== void 0) throw new Error(`Document ${id} already exists`); if (existing?.retiredAt !== void 0) throw new Error(`Document ${id} is retired`); if (action.content?.kind === "delta") { const previous = await executor.get(`SELECT version FROM ${this.schema.tables.documentRevisions} WHERE document_id = ? ORDER BY seq DESC LIMIT 1`, id); if (previous === void 0) throw new Error(`Document ${id} delta has no base`); if (previous.version !== action.content.version) { throw new Error(`Document ${id} version transition requires a base`); } } const record2 = action.create ?? existing; const key = addressKey2(record2); let live = liveCounts.get(key); if (live === void 0) live = await this.currentDocumentId(executor, record2) === void 0 ? 0 : 1; if (action.retire && existing !== void 0) live--; if (action.create !== void 0 && !action.retire) live++; liveCounts.set(key, live); } for (const live of liveCounts.values()) { if (live > 1) throw new Error("Document address already has a current incarnation"); } } async currentDocumentId(executor, address) { const parts = addressParts(address); const id = (await executor.get(`SELECT id FROM ${this.schema.tables.documents} WHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ? AND retired_at IS NULL LIMIT 1`, parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue))?.id; return id === void 0 ? void 0 : idFromNumber(id); } async applyTableWrite(executor, write, seq) { switch (write.type) { case "conversation": await this.claimId(executor, write.value.id, "conversation"); await executor.run(`INSERT INTO ${this.schema.tables.conversations} (id, owner_conversation_id, owner_task_id, record) VALUES (?, ?, ?, ?)`, write.value.id, write.value.owner?.conversationId ?? null, write.value.owner?.taskId ?? null, encodeJson(write.value)); break; case "entry": await this.claimId(executor, write.value.id, "entry"); await executor.run(`INSERT INTO ${this.schema.tables.entries} (id, conversation_id, head, commit_seq, record) VALUES (?, ?, ?, ?, ?)`, write.value.id, write.value.conversationId, write.value.head ?? null, seq, encodeJson(write.value)); break; case "task": await this.claimId(executor, write.value.id, "task"); await executor.run(`INSERT INTO ${this.schema.tables.tasks} (id, conversation_id, kind, status, abort_requested, background, record) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id, kind = excluded.kind, status = excluded.status, abort_requested = excluded.abort_requested, background = excluded.background, record = excluded.record`, write.value.id, write.value.conversationId, encodeIndexedString(write.value.kind), write.value.state.status, write.value.abortRequested ? 1 : 0, write.value.background ? 1 : 0, encodeJson(write.value)); break; case "submission": await this.claimId(executor, write.value.id, "submission"); await executor.run(`INSERT INTO ${this.schema.tables.submissions} (id, conversation_id, request_id, status, record) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id, request_id = excluded.request_id, status = excluded.status, record = excluded.record`, write.value.id, write.value.conversationId, write.value.requestId === void 0 ? null : encodeIndexedString(write.value.requestId), write.value.status, encodeJson(write.value)); break; case "document.create": case "document.copy": case "document.change": case "document.retire": break; } } async claimId(executor, id, table) { await executor.run(`INSERT OR IGNORE INTO ${this.schema.tables.recordIds} (id, record_type) VALUES (?, ?)`, id, table); } /** Resolve each copy to a base of its source's state before this batch, which may not change the source. */ async resolveCopies(executor, actions) { for (const [id, action] of actions) { if (action.copy === void 0) continue; try { const stored = await this.materializeDocument(executor, action.copy.id, action.copy.at); if (stored === void 0) throw new Error(`Fork source document ${action.copy.id} cannot be read`); const create = action.create; if (stored.record.scope.kind !== "conversation" || create.scope.kind !== "conversation" || stored.record.kind !== create.kind || stored.record.key !== create.key || stored.record.history !== create.history || stored.record.fork !== create.fork) { throw new Error(`Fork source document ${action.copy.id} does not match the copied record`); } action.content = { kind: "base", version: stored.version, value: stored.value }; } catch (error) { throw new Error(`Document copy ${id} was rejected`, { cause: error }); } } } async applyDocumentActions(executor, actions, seq) { for (const [id, action] of actions) { const content = action.content; let record2; if (action.create !== void 0) { record2 = { ...action.create, createdAt: seq, ...action.retire ? { retiredAt: seq } : {} }; const parts = addressParts(record2); await this.claimId(executor, id, "document"); await executor.run(`INSERT INTO ${this.schema.tables.documents} (id, kind, family, key_value, scope_kind, owner_id, created_at, retired_at, record) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, id, parts.kind, parts.family, parts.keyValue, parts.scopeKind, parts.ownerId, seq, action.retire ? seq : null, encodeJson(record2)); } else { const row = await executor.get(`SELECT record FROM ${this.schema.tables.documents} WHERE id = ?`, id); record2 = parseJson(row.record); } if (content !== void 0) { if (content.kind === "base" && isCurrentOnly2(record2)) { await executor.run(`DELETE FROM ${this.schema.tables.documentRevisions} WHERE document_id = ?`, id); } const encodedContent = content.kind === "base" ? encodeJson(content.value) : encodeJson(content.ops); await executor.run(`INSERT INTO ${this.schema.tables.documentRevisions} (document_id, seq, kind, version, content) VALUES (?, ?, ?, ?, ?)`, id, seq, content.kind, content.version, encodedContent); } if (action.retire) { if (action.create === void 0) { record2 = { ...record2, retiredAt: seq }; await executor.run(`UPDATE ${this.schema.tables.documents} SET retired_at = ?, record = ? WHERE id = ?`, seq, encodeJson(record2), id); } if (isCurrentOnly2(record2)) { await executor.run(`DELETE FROM ${this.schema.tables.documentRevisions} WHERE document_id = ?`, id); } } } } assertOpen() { if (this.closed) throw new Error("SqliteStorage is closed"); } }; // sqlite.ts var op = (kind, sql = "", params = []) => Deno.core.ops.op_sql(kind, sql, params.map((p) => p instanceof Uint8Array ? Array.from(p) : typeof p === "bigint" ? Number(p) : p)); async function openStorage(path) { op("open", path); let tail = Promise.resolve(); const queue2 = (call2) => { const result = tail.then(call2); tail = result.then(() => { }, () => { }); return result; }; const executor = { exec: async (sql) => { op("exec", sql); }, run: async (sql, ...params) => { op("run", sql, params); }, runCounted: async (sql, ...params) => op("run", sql, params).changes, get: async (sql, ...params) => (await executor.all(sql, ...params))[0], all: async (sql, ...params) => op("all", sql, params).map((row) => Object.fromEntries(Object.entries(row).map(([key, value2]) => [key, Array.isArray(value2) ? new Uint8Array(value2) : value2]))) }; const database = Object.fromEntries(Object.entries(executor).map(([name, method2]) => [name, (...args) => queue2(() => method2(...args))])); database.transaction = (callback) => queue2(async () => { op("exec", "BEGIN IMMEDIATE"); try { const result = await callback(executor); op("exec", "COMMIT"); return result; } catch (error) { op("exec", "ROLLBACK"); throw error; } }); database.close = () => queue2(async () => { op("close"); }); return SqliteStorage.open(database); } // workspaces-test.ts var assert = (value2, message) => { if (!value2) throw new Error(message); }; var models = createModels(); var faux = fauxProvider({ tokensPerSecond: 1e5 }); models.setProvider(faux.provider); var registry = createRegistry(); var storage = await openStorage(globalThis.storagePath); var harness = await Harness.open(storage, { models, registry, settings: { get extensions() { return registry.snapshot().installed(); } } }, BACKGROUND_CONTEXT); var root = await harness.root(BACKGROUND_CONTEXT, { agent: { model: { provider: "faux", modelId: "faux-1" }, cwd: "/project" } }); if (globalThis.workspacesMode === "kill") { const install = registry.install.bind(registry); registry.install = (extension) => install(extension.name !== "workspaces" ? extension : { ...extension, tasks: extension.tasks.map((task) => task.definition.name !== "workspaces.lander" ? task : { definition: { ...task.definition, phases: { ...task.definition.phases, land: async (record2, runtime, ctx) => { const commit = runtime.commit.bind(runtime); await taskDefinition(record2, runtime, ctx); async function taskDefinition(record3, rt, c) { await task.definition.phases.land(record3, { ...rt, commit: async (...args) => { await commit(...args); const index = await harness.snapshot(globalThis.workspaceService.doc, root.id, BACKGROUND_CONTEXT); if (index.queue.some((entry2) => entry2.landed)) { await fs.write("/push-ready", "yes"); await awaitWithContext(new Promise(() => { }), c); } } }, c); } } } } }) }); } var probe = defineExtension({ name: "guest", tools: [defineTool({ name: "implement", description: "Write and commit in the prepared checkout", parameters: obj({ name: str() }), replay: "safe", execute: async (args, api, ctx) => { const cwd = (await api.agent(ctx)).cwd; assert(cwd.startsWith("/worktrees/"), "First tool needs isolated cwd"); assert((await fs.stat(`${cwd}/base.txt`)).kind === "file", "Checkout must exist before first tool"); const workspace = await globalThis.workspaceService.get(api.conversationId, ctx); assert(workspace.state === "ready", "Preparation must finish"); await fs.write(`${cwd}/${args.name}`, "feature"); for (const args2 of [["add", "."], ["-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "--no-gpg-sign", "-m", "feature"]]) await globalThis.workspaceHost.git(cwd, ...args2); return { output: [{ type: "text", text: "committed" }] }; } })] }); var route = (transcript) => { faux.appendResponses([route]); const last = transcript.messages.findLast((message) => message.role !== "system"); const text = typeof last.content === "string" ? last.content : last.content.filter((part) => part.type === "text").map((part) => part.text).join(""); if (last.role === "user" && text.startsWith("CALL ")) { const { tool, args } = JSON.parse(text.slice(5)); return fauxAssistantMessage([fauxToolCall(tool, args)], { stopReason: "toolUse" }); } if (last.role === "user" && text === "IMPLEMENT") return fauxAssistantMessage([fauxToolCall("implement", { name: "feature.txt" })], { stopReason: "toolUse" }); if (last.role === "user" && text === "BROKEN") return fauxAssistantMessage([fauxToolCall("implement", { name: "broken.txt" })], { stopReason: "toolUse" }); return fauxAssistantMessage("Done"); }; faux.setResponses([route]); var loader = await loadExtensions(harness, registry, storage, models, BACKGROUND_CONTEXT, [probe]); assert(!loader.state().error, `Load: ${loader.state().error}`); var service = globalThis.workspaceService; var host = globalThis.workspaceHost; var call = async (tool, args) => { await (await root.submit({ type: "input", content: `CALL ${JSON.stringify({ tool, args })}` }, BACKGROUND_CONTEXT)).wait(BACKGROUND_CONTEXT); const result = (await root.context(BACKGROUND_CONTEXT)).entries.filter((entry2) => entry2.kind === "pi.tool-result").at(-1)?.model?.[0]; assert(!result?.isError, `Tool error: ${JSON.stringify(result)}`); return result; }; var submit = async (id) => { const { Started: Started2 } = await import(`file:///run/sausage/extensions/${await fs.read("/src/sausage/.git/sausage-last-good")}/extensions/messages/addresses.ts`); await harness.commit(async (tx) => { (await tx.doc(Started2, id)).by = root.id; }, BACKGROUND_CONTEXT); const child = await harness.conversation(id, BACKGROUND_CONTEXT); await (await child.submit({ type: "input", content: `CALL ${JSON.stringify({ tool: "workspace", args: { action: "submit", divergence: "None" } })}` }, BACKGROUND_CONTEXT)).wait(BACKGROUND_CONTEXT); const result = (await child.context(BACKGROUND_CONTEXT)).entries.filter((entry2) => entry2.kind === "pi.tool-result").at(-1)?.model?.[0]; assert(!result?.isError, `Workspace tool: ${JSON.stringify(result)}`); return JSON.parse(result.content.filter((part) => part.type === "text").map((part) => part.text).join("")); }; var children = async () => { const { Subagents } = await import(`file:///run/sausage/extensions/${await fs.read("/src/sausage/.git/sausage-last-good")}/extensions/subagents/api.ts`); return Object.values((await harness.snapshot(Subagents, root.id, BACKGROUND_CONTEXT)).agents); }; var waitQueue = async () => { const deadline = Date.now() + 3e4; while ((await harness.snapshot(service.doc, root.id, BACKGROUND_CONTEXT)).queue.length) { if (Date.now() > deadline) throw new Error(`Lander deadline: ${JSON.stringify(await harness.snapshot(service.doc, root.id, BACKGROUND_CONTEXT))}`); await new Promise((r) => setTimeout(r, 0)); } await harness.waitForIdle(BACKGROUND_CONTEXT); }; var reports = async (id) => (await (await harness.conversation(id, BACKGROUND_CONTEXT)).context(BACKGROUND_CONTEXT)).entries.filter((entry2) => entry2.kind === "workspaces.lander-report"); var notes = async () => { const { Messages } = await import(`file:///run/sausage/extensions/${await fs.read("/src/sausage/.git/sausage-last-good")}/extensions/messages/api.ts`); const deadline = Date.now() + 3e4; for (; ; ) { const notes2 = (await harness.snapshot(Messages, root.id, BACKGROUND_CONTEXT)).messages.filter((message) => message.text.startsWith("[lander] Submission") && message.text.includes(" landed as ")); if (!notes2.some((note) => note.state === "sent")) return notes2; assert(Date.now() < deadline, `Landing note delivery: ${JSON.stringify(notes2)}`); await new Promise((r) => setTimeout(r, 0)); } }; harness.resume(); try { if (globalThis.workspacesMode === "resume") { await waitQueue(); const index = await harness.snapshot(service.doc, root.id, BACKGROUND_CONTEXT); const result = Object.values(index.results)[0]; assert(result?.outcome === "landed", `Recovery result ${JSON.stringify(index)}`); const [child] = await children(); const report2 = await reports(child.conversationId); assert(report2.length === 1, "Restart must report once"); assert(report2[0].data.sha === result.sha, "Report must retain landed.sha"); const delivered = await notes(); assert(delivered.length === 1 && delivered[0].state === "delivered" && delivered[0].text.includes(result.sha), `Restart must notify the starter once with landed.sha: ${JSON.stringify(delivered)}`); assert(await host.git("/remote.git", "rev-parse", "main") === result.sha, `Push must retain landed.sha: ${JSON.stringify(result)}`); assert((await host.git("/project", "log", "--format=%s")).split("\n").filter((line) => line.startsWith("Merge from")).length === 1, "Restart must not merge again"); assert((await host.eventLog.recent({ events: [{ kind: "event.workspaces.landed" }] })).length === 1, "Restart must emit once"); } else { await call("subagent", { action: "start", agent: "worker", task: globalThis.workspacesMode === "normal" ? "BROKEN" : "IMPLEMENT" }); if (globalThis.workspacesMode === "normal") { const [broken] = await children(); const before = globalThis.workspaceExecutions.length; assert(!await service.unavailable(broken.conversationId, BACKGROUND_CONTEXT), "Ready VFS gate"); assert(globalThis.workspaceExecutions.length === before, "Ready gate must not spawn Git"); const checkout = await service.get(broken.conversationId, BACKGROUND_CONTEXT); await fs.rename(`${checkout.path}/.git`, `${checkout.path}/.git.saved`); assert((await service.unavailable(broken.conversationId, BACKGROUND_CONTEXT)).includes("missing or unregistered"), "Gate must reject missing registration"); const blocked = await harness.conversation(broken.conversationId, BACKGROUND_CONTEXT); await (await blocked.submit({ type: "input", content: `CALL ${JSON.stringify({ tool: "implement", args: { name: "guard.txt" } })}` }, BACKGROUND_CONTEXT)).wait(BACKGROUND_CONTEXT); const blockedResult = (await blocked.context(BACKGROUND_CONTEXT)).entries.filter((entry2) => entry2.kind === "pi.tool-result").at(-1)?.model?.[0]; assert(blockedResult.isError && JSON.stringify(blockedResult).includes("missing or unregistered"), "beforeTool must block the guest tool"); assert(!await fs.stat(`${checkout.path}/guard.txt`).then(() => true, (error) => { if (error.code === "not_found") return false; throw error; }), "Blocked tool must not write"); await fs.rename(`${checkout.path}/.git.saved`, `${checkout.path}/.git`); assert(globalThis.workspaceExecutions.length === before, "Failed VFS gate must not spawn Git"); const failed = await submit(broken.conversationId); await waitQueue(); const result2 = (await harness.snapshot(service.doc, root.id, BACKGROUND_CONTEXT)).results[failed.id]; assert(result2.outcome === "failed" && result2.reason.includes("Check sh"), `Check must fail: ${JSON.stringify(result2)}`); assert((await reports(broken.conversationId)).length === 1, "Failed check must return to submitter"); const failedWorkspace = await service.get(broken.conversationId, BACKGROUND_CONTEXT); assert((await service.release([broken.conversationId], BACKGROUND_CONTEXT)).length === 1, "Release must retain unlanded work"); await call("subagent", { action: "discard", id: broken.conversationId }); assert(await host.git("/project", "rev-parse", failedWorkspace.branch), "Discard must retain its branch"); await call("subagent", { action: "start", agent: "worker", task: "IMPLEMENT" }); const child = (await children()).at(-1); await call("subagent", { action: "land", id: child.conversationId }); assert(await fs.read("/project/feature.txt") === "feature", "Parent fast-forward"); assert((await service.get(child.conversationId, BACKGROUND_CONTEXT)).state === "removed", "Land must release checkout"); await call("subagent", { action: "start", agent: "worker", task: "BROKEN" }); } const submitter = (await children()).at(-1).conversationId; const submission2 = await submit(submitter); if (globalThis.workspacesMode === "kill") await new Promise(() => { }); await waitQueue(); const result = (await harness.snapshot(service.doc, root.id, BACKGROUND_CONTEXT)).results[submission2.id]; assert(result.outcome === "landed", `Landing failed: ${JSON.stringify(result)}`); assert(await host.git("/remote.git", "rev-parse", "main") === result.sha, `Local bare push: ${JSON.stringify(result)}`); assert((await reports(submitter)).length === 1, "Submission report"); const delivered = await notes(); assert(delivered.length === 1 && delivered[0].state === "delivered" && delivered[0].from === submitter && delivered[0].to === root.id && delivered[0].reply === false && delivered[0].text.includes(result.sha), `Landing note must use the messages API: ${JSON.stringify(delivered)}`); const commands = globalThis.workspaceExecutions.filter((entry2) => typeof entry2.command === "string" || entry2.command[1] === "fetch" || entry2.command[1] === "push"); const push = commands.findLast((entry2) => entry2.command[1] === "push"); assert(push, "Real push must run"); assert(JSON.stringify(push.options.conversations) === JSON.stringify([String(submitter), String(root.id)]), "Push must use the exact submitter chain"); for (const entry2 of commands) { assert(entry2.options.task !== void 0, "Lander exec needs its task tag"); if (typeof entry2.command === "string") assert(entry2.options.timeout === 600, "Checks need a bounded default timeout"); assert(entry2.options.conversations[1] === String(root.id), "Lander exec needs the submitter ancestor"); assert(entry2.options.conversations[0] !== String(root.id), "Lander exec needs the submitter first"); } } } finally { await loader.close(); await harness.close(BACKGROUND_CONTEXT); }