@loomwork/ui-hydration 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +398 -2
- package/dist/cache.d.ts +71 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +157 -0
- package/dist/cache.js.map +1 -0
- package/dist/chatSource.d.ts +105 -0
- package/dist/chatSource.d.ts.map +1 -0
- package/dist/chatSource.js +259 -0
- package/dist/chatSource.js.map +1 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +36 -0
- package/dist/client.js.map +1 -0
- package/dist/contentRefs.d.ts +78 -0
- package/dist/contentRefs.d.ts.map +1 -0
- package/dist/contentRefs.js +129 -0
- package/dist/contentRefs.js.map +1 -0
- package/dist/controller.d.ts +228 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +454 -0
- package/dist/controller.js.map +1 -0
- package/dist/dataProvider.d.ts +53 -0
- package/dist/dataProvider.d.ts.map +1 -0
- package/dist/dataProvider.js +77 -0
- package/dist/dataProvider.js.map +1 -0
- package/dist/esm/cache.js +151 -0
- package/dist/esm/cache.js.map +1 -0
- package/dist/esm/chatSource.js +254 -0
- package/dist/esm/chatSource.js.map +1 -0
- package/dist/esm/client.js +15 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/contentRefs.js +119 -0
- package/dist/esm/contentRefs.js.map +1 -0
- package/dist/esm/controller.js +449 -0
- package/dist/esm/controller.js.map +1 -0
- package/dist/esm/dataProvider.js +72 -0
- package/dist/esm/dataProvider.js.map +1 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/invoker.js +2 -0
- package/dist/esm/invoker.js.map +1 -0
- package/dist/esm/invokers.js +310 -0
- package/dist/esm/invokers.js.map +1 -0
- package/dist/esm/loomEffectProvider.js +96 -0
- package/dist/esm/loomEffectProvider.js.map +1 -0
- package/dist/esm/params.js +207 -0
- package/dist/esm/params.js.map +1 -0
- package/dist/esm/path.js +24 -0
- package/dist/esm/path.js.map +1 -0
- package/dist/esm/plan.js +190 -0
- package/dist/esm/plan.js.map +1 -0
- package/dist/esm/projection.js +230 -0
- package/dist/esm/projection.js.map +1 -0
- package/dist/esm/provider.js +416 -0
- package/dist/esm/provider.js.map +1 -0
- package/dist/esm/repair.js +53 -0
- package/dist/esm/repair.js.map +1 -0
- package/dist/esm/reshape.js +156 -0
- package/dist/esm/reshape.js.map +1 -0
- package/dist/esm/restricted.js +54 -0
- package/dist/esm/restricted.js.map +1 -0
- package/dist/esm/scheduler.js +247 -0
- package/dist/esm/scheduler.js.map +1 -0
- package/dist/esm/select.js +18 -0
- package/dist/esm/select.js.map +1 -0
- package/dist/esm/shape.js +34 -0
- package/dist/esm/shape.js.map +1 -0
- package/dist/esm/split/client.js +309 -0
- package/dist/esm/split/client.js.map +1 -0
- package/dist/esm/split/index.js +3 -0
- package/dist/esm/split/index.js.map +1 -0
- package/dist/esm/split/server.js +488 -0
- package/dist/esm/split/server.js.map +1 -0
- package/dist/esm/split/shared.js +13 -0
- package/dist/esm/split/shared.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/invoker.d.ts +10 -0
- package/dist/invoker.d.ts.map +1 -0
- package/dist/invoker.js +3 -0
- package/dist/invoker.js.map +1 -0
- package/dist/invokers.d.ts +36 -0
- package/dist/invokers.d.ts.map +1 -0
- package/dist/invokers.js +315 -0
- package/dist/invokers.js.map +1 -0
- package/dist/loomEffectProvider.d.ts +67 -0
- package/dist/loomEffectProvider.d.ts.map +1 -0
- package/dist/loomEffectProvider.js +101 -0
- package/dist/loomEffectProvider.js.map +1 -0
- package/dist/params.d.ts +103 -0
- package/dist/params.d.ts.map +1 -0
- package/dist/params.js +221 -0
- package/dist/params.js.map +1 -0
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +28 -0
- package/dist/path.js.map +1 -0
- package/dist/plan.d.ts +56 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +199 -0
- package/dist/plan.js.map +1 -0
- package/dist/projection.d.ts +122 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/projection.js +238 -0
- package/dist/projection.js.map +1 -0
- package/dist/provider.d.ts +152 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +421 -0
- package/dist/provider.js.map +1 -0
- package/dist/repair.d.ts +18 -0
- package/dist/repair.d.ts.map +1 -0
- package/dist/repair.js +57 -0
- package/dist/repair.js.map +1 -0
- package/dist/reshape.d.ts +32 -0
- package/dist/reshape.d.ts.map +1 -0
- package/dist/reshape.js +164 -0
- package/dist/reshape.js.map +1 -0
- package/dist/restricted.d.ts +21 -0
- package/dist/restricted.d.ts.map +1 -0
- package/dist/restricted.js +61 -0
- package/dist/restricted.js.map +1 -0
- package/dist/scheduler.d.ts +82 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +254 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/select.d.ts +34 -0
- package/dist/select.d.ts.map +1 -0
- package/dist/select.js +21 -0
- package/dist/select.js.map +1 -0
- package/dist/shape.d.ts +3 -0
- package/dist/shape.d.ts.map +1 -0
- package/dist/shape.js +37 -0
- package/dist/shape.js.map +1 -0
- package/dist/split/client.d.ts +132 -0
- package/dist/split/client.d.ts.map +1 -0
- package/dist/split/client.js +313 -0
- package/dist/split/client.js.map +1 -0
- package/dist/split/index.d.ts +5 -0
- package/dist/split/index.d.ts.map +1 -0
- package/dist/split/index.js +14 -0
- package/dist/split/index.js.map +1 -0
- package/dist/split/server.d.ts +160 -0
- package/dist/split/server.d.ts.map +1 -0
- package/dist/split/server.js +496 -0
- package/dist/split/server.js.map +1 -0
- package/dist/split/shared.d.ts +6 -0
- package/dist/split/shared.d.ts.map +1 -0
- package/dist/split/shared.js +19 -0
- package/dist/split/shared.js.map +1 -0
- package/package.json +77 -3
- package/src/cache.ts +211 -0
- package/src/chatSource.ts +374 -0
- package/src/client.ts +46 -0
- package/src/contentRefs.ts +195 -0
- package/src/controller.ts +708 -0
- package/src/dataProvider.ts +141 -0
- package/src/index.ts +86 -0
- package/src/invoker.ts +14 -0
- package/src/invokers.ts +348 -0
- package/src/loomEffectProvider.ts +136 -0
- package/src/params.ts +314 -0
- package/src/path.ts +26 -0
- package/src/plan.ts +197 -0
- package/src/projection.ts +297 -0
- package/src/provider.ts +657 -0
- package/src/repair.ts +70 -0
- package/src/reshape.ts +193 -0
- package/src/restricted.ts +69 -0
- package/src/scheduler.ts +284 -0
- package/src/select.ts +59 -0
- package/src/shape.ts +39 -0
- package/src/split/client.ts +449 -0
- package/src/split/index.ts +18 -0
- package/src/split/server.ts +746 -0
- package/src/split/shared.ts +18 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CASsB;AARpB,qHAAA,uBAAuB,OAAA;AACvB,0HAAA,4BAA4B,OAAA;AAC5B,iHAAA,mBAAmB,OAAA;AACnB,4HAAA,8BAA8B,OAAA;AAC9B,iHAAA,mBAAmB,OAAA;AACnB,4GAAA,cAAc,OAAA;AACd,iHAAA,mBAAmB,OAAA;AACnB,8GAAA,gBAAgB,OAAA;AAUlB,uCAAsE;AAA7D,wGAAA,YAAY,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,yGAAA,aAAa,OAAA;AACjD,qCAA+E;AAAtE,0GAAA,eAAe,OAAA;AAAE,yGAAA,cAAc,OAAA;AAAE,4GAAA,iBAAiB,OAAA;AAE3D,mCAA2D;AAAlD,4GAAA,kBAAkB,OAAA;AAAE,qGAAA,WAAW,OAAA;AACxC,6CAAsD;AAA7C,oHAAA,qBAAqB,OAAA;AAO9B,+CAKwB;AAJtB,sHAAA,sBAAsB,OAAA;AACtB,4HAAA,4BAA4B,OAAA;AAC5B,mHAAA,mBAAmB,OAAA;AACnB,2GAAA,WAAW,OAAA;AAGb,2DAM8B;AAL5B,sHAAA,gBAAgB,OAAA;AAChB,mHAAA,aAAa,OAAA;AACb,yHAAA,mBAAmB,OAAA;AACnB,+GAAA,SAAS,OAAA;AACT,wHAAA,kBAAkB,OAAA;AAOpB,uCAA6C;AAApC,2GAAA,eAAe,OAAA;AACxB,+BAAkE;AAAzD,sGAAA,cAAc,OAAA;AAAE,qGAAA,aAAa,OAAA;AAAE,iGAAA,SAAS,OAAA;AAEjD,iCAAoE;AAA3D,kHAAA,yBAAyB,OAAA;AAAE,uGAAA,cAAc,OAAA;AAGlD,2CAAmD;AAA1C,iHAAA,mBAAmB,OAAA;AAE5B,uCASuB;AARrB,0GAAA,iBAAiB,OAAA;AACjB,qHAAA,4BAA4B,OAAA;AAC5B,iHAAA,wBAAwB,OAAA;AACxB,2GAAA,kBAAkB,OAAA;AAClB,wGAAA,eAAe,OAAA;AACf,sGAAA,aAAa,OAAA;AACb,oGAAA,WAAW,OAAA;AACX,qGAAA,YAAY,OAAA;AAad,mCAA2D;AAAlD,yGAAA,eAAe,OAAA;AAAE,wGAAA,cAAc,OAAA;AAExC,2CAAgE;AAAvD,iHAAA,mBAAmB,OAAA;AAAE,yGAAA,WAAW,OAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TransientInvokeOptions } from "@loomwork/effect-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The one thing hydration needs from loom's runtime: a one-shot, non-durable effect call.
|
|
4
|
+
* `EffectRuntime` satisfies this structurally (ADR 0066 `invokeTransient`); tests use a fake.
|
|
5
|
+
* Keeping it an interface is what makes the read path provably log-free — there is no `ctx`.
|
|
6
|
+
*/
|
|
7
|
+
export interface TransientInvoker {
|
|
8
|
+
invokeTransient<TIn = unknown, TOut = unknown>(toolId: string, input: TIn, opts?: TransientInvokeOptions): Promise<TOut>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=invoker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoker.d.ts","sourceRoot":"","sources":["../src/invoker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,eAAe,CAAC,GAAG,GAAG,OAAO,EAAE,IAAI,GAAG,OAAO,EAC3C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,GAAG,EACV,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB"}
|
package/dist/invoker.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoker.js","sourceRoot":"","sources":["../src/invoker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TransientInvoker } from "./invoker";
|
|
2
|
+
/**
|
|
3
|
+
* At most `max` calls in flight through this invoker, across every caller. A queued call whose
|
|
4
|
+
* signal aborts leaves the queue and rejects `"aborted"` without ever reaching `inner`. FIFO: a
|
|
5
|
+
* finished call hands its slot straight to the oldest waiter.
|
|
6
|
+
*/
|
|
7
|
+
export declare function limitInvoker(inner: TransientInvoker, max: number): TransientInvoker;
|
|
8
|
+
/**
|
|
9
|
+
* Every call through this invoker is ALSO cancelled, synchronously, when `signal` aborts — the
|
|
10
|
+
* owner's lifetime (a controller's `dispose`), independent of any caller's own signal, which still
|
|
11
|
+
* reaches `inner` too. Once `signal` has aborted, new calls reject `"aborted"` without reaching
|
|
12
|
+
* `inner`. Leak-free on a long-lived `signal`: a call holds its listener only until it settles.
|
|
13
|
+
*/
|
|
14
|
+
export declare function signalInvoker(inner: TransientInvoker, signal: AbortSignal): TransientInvoker;
|
|
15
|
+
/**
|
|
16
|
+
* One call per identical MCP step for this invoker's lifetime (a PASS): same tool, same resolved
|
|
17
|
+
* arguments, same schema pin, same call options — the canonical JSON of `[principal, every other
|
|
18
|
+
* option except signal, mcp.invoke's input]`, so key order does not matter. A joiner is
|
|
19
|
+
* served straight from the shared promise — it never calls `inner` itself, so it never passes
|
|
20
|
+
* through whatever per-call authorizer `inner` enforces. That is safe ONLY because every
|
|
21
|
+
* authorization input is part of the key: the principal, and also the stamped execution identity
|
|
22
|
+
* (`as` — an authorizer may grant by workflow), the `label` (which names the transient
|
|
23
|
+
* execution when `as` is absent) and anything else in the options. Every joiner
|
|
24
|
+
* sharing one entry was, by construction, asking with the exact inputs the leader's own (sole)
|
|
25
|
+
* call to `inner` was authorized for; an option that is not plain JSON passes through, uncached. Settled results stay (a step in a later wave is still
|
|
26
|
+
* served) and failures are shared. Other effects (a sandbox reshape) always run, uncached.
|
|
27
|
+
*
|
|
28
|
+
* Cancellation is ref-counted, not leader-owned: `inner` is called once, through an internal
|
|
29
|
+
* `AbortController` this function owns — never a caller's own signal directly — so any ONE
|
|
30
|
+
* caller's abort only ever settles that caller's OWN returned promise (a race against the shared
|
|
31
|
+
* promise). The upstream call itself is cancelled, and the entry forgotten, only once every live
|
|
32
|
+
* caller has aborted AND the call had not already settled; a caller that arrives after a real
|
|
33
|
+
* cancellation finds no entry and starts a fresh call.
|
|
34
|
+
*/
|
|
35
|
+
export declare function memoInvoker(inner: TransientInvoker): TransientInvoker;
|
|
36
|
+
//# sourceMappingURL=invokers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invokers.d.ts","sourceRoot":"","sources":["../src/invokers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAwBlD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,gBAAgB,CA0DnF;AAWD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAG,gBAAgB,CAwB5F;AAyDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CA2IrE"}
|
package/dist/invokers.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.limitInvoker = limitInvoker;
|
|
4
|
+
exports.signalInvoker = signalInvoker;
|
|
5
|
+
exports.memoInvoker = memoInvoker;
|
|
6
|
+
const core_1 = require("@loomwork/core");
|
|
7
|
+
const plugin_mcp_1 = require("@loomwork/plugin-mcp");
|
|
8
|
+
// ── Invoker wrappers: a call cap, an owner's lifetime signal, an in-pass step memo ─
|
|
9
|
+
// All are TransientInvokers wrapping a TransientInvoker, so they compose with each other and
|
|
10
|
+
// with loom's EffectRuntime without either knowing the other exists. The in-process controller
|
|
11
|
+
// builds ONE `limitInvoker` (the cap is controller-wide: it bounds every call it makes), ONE
|
|
12
|
+
// `signalInvoker` over it bound to its own lifetime (so `dispose` cancels every upstream call at
|
|
13
|
+
// once), and one `memoInvoker` PER PASS in front of both (a memo hit takes no slot). The split
|
|
14
|
+
// server (`serveHandles`, split.ts) composes a memo over a cap the same way, once per batch.
|
|
15
|
+
// None ever returns a promise it did not get from the inner invoker except to wait for a slot,
|
|
16
|
+
// so they add no settle latency on a free slot.
|
|
17
|
+
/**
|
|
18
|
+
* The rejection for a call abandoned because its signal aborted. Named `AbortError` (the
|
|
19
|
+
* platform's own name for a cancelled operation) so a consumer classifies it by `name` — a
|
|
20
|
+
* planner's decider must propagate a cancellation, never retry it or record it as a failed
|
|
21
|
+
* plan. A plain `Error("aborted")` is indistinguishable from a provider failure. Message kept.
|
|
22
|
+
*/
|
|
23
|
+
function abortError() {
|
|
24
|
+
const e = new Error("aborted");
|
|
25
|
+
e.name = "AbortError";
|
|
26
|
+
return e;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* At most `max` calls in flight through this invoker, across every caller. A queued call whose
|
|
30
|
+
* signal aborts leaves the queue and rejects `"aborted"` without ever reaching `inner`. FIFO: a
|
|
31
|
+
* finished call hands its slot straight to the oldest waiter.
|
|
32
|
+
*/
|
|
33
|
+
function limitInvoker(inner, max) {
|
|
34
|
+
if (!Number.isInteger(max) || max < 1)
|
|
35
|
+
throw new RangeError(`limitInvoker: the cap must be a positive integer, got ${String(max)}`);
|
|
36
|
+
let active = 0;
|
|
37
|
+
// Insertion-ordered: the first entry is the oldest waiter; add/delete are O(1).
|
|
38
|
+
const waiting = new Set();
|
|
39
|
+
const release = () => {
|
|
40
|
+
const next = waiting.values().next();
|
|
41
|
+
if (next.done === true) {
|
|
42
|
+
active -= 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
waiting.delete(next.value);
|
|
46
|
+
next.value(); // the slot passes to the waiter: `active` is unchanged
|
|
47
|
+
};
|
|
48
|
+
const acquire = (signal) => {
|
|
49
|
+
// Checked before capacity: an already-aborted caller must never take a slot (or reach
|
|
50
|
+
// `inner`) just because one happened to be free — that slot belongs to the next live caller.
|
|
51
|
+
if (signal?.aborted === true)
|
|
52
|
+
return Promise.reject(abortError());
|
|
53
|
+
if (active < max) {
|
|
54
|
+
active += 1;
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const onAbort = () => {
|
|
59
|
+
waiting.delete(grant);
|
|
60
|
+
reject(abortError());
|
|
61
|
+
};
|
|
62
|
+
const grant = () => {
|
|
63
|
+
signal?.removeEventListener("abort", onAbort);
|
|
64
|
+
resolve();
|
|
65
|
+
};
|
|
66
|
+
waiting.add(grant);
|
|
67
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const run = (call) => {
|
|
71
|
+
let p;
|
|
72
|
+
try {
|
|
73
|
+
p = call();
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
release();
|
|
77
|
+
return Promise.reject(e);
|
|
78
|
+
}
|
|
79
|
+
p.then(release, release);
|
|
80
|
+
return p;
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
invokeTransient(toolId, input, opts) {
|
|
84
|
+
const call = () => inner.invokeTransient(toolId, input, opts);
|
|
85
|
+
const slot = acquire(opts?.signal);
|
|
86
|
+
return slot === undefined ? run(call) : slot.then(() => run(call));
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** `call()`, with a synchronous throw turned into the rejected promise the contract promises. */
|
|
91
|
+
function callSafely(call) {
|
|
92
|
+
try {
|
|
93
|
+
return call();
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
return Promise.reject(e);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Every call through this invoker is ALSO cancelled, synchronously, when `signal` aborts — the
|
|
101
|
+
* owner's lifetime (a controller's `dispose`), independent of any caller's own signal, which still
|
|
102
|
+
* reaches `inner` too. Once `signal` has aborted, new calls reject `"aborted"` without reaching
|
|
103
|
+
* `inner`. Leak-free on a long-lived `signal`: a call holds its listener only until it settles.
|
|
104
|
+
*/
|
|
105
|
+
function signalInvoker(inner, signal) {
|
|
106
|
+
return {
|
|
107
|
+
invokeTransient(toolId, input, opts) {
|
|
108
|
+
const own = opts?.signal;
|
|
109
|
+
if (signal.aborted || own?.aborted === true)
|
|
110
|
+
return Promise.reject(abortError());
|
|
111
|
+
const ac = new AbortController();
|
|
112
|
+
const abort = () => ac.abort();
|
|
113
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
114
|
+
own?.addEventListener("abort", abort, { once: true });
|
|
115
|
+
const release = () => {
|
|
116
|
+
signal.removeEventListener("abort", abort);
|
|
117
|
+
own?.removeEventListener("abort", abort);
|
|
118
|
+
};
|
|
119
|
+
const p = callSafely(() => inner.invokeTransient(toolId, input, { ...opts, signal: ac.signal }));
|
|
120
|
+
p.then(release, release);
|
|
121
|
+
return p;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The shape `canonicalJson` actually understands — null/string/finite-number/boolean, and arrays
|
|
127
|
+
* or plain objects (`Object.prototype` or `null` as the prototype) built from those, recursively.
|
|
128
|
+
* `canonicalJson` does not THROW on a `Date`, `Map`, `Set` or a class instance — it silently reads
|
|
129
|
+
* `Object.keys()` on it, which for most of those is `[]`, so two calls with DIFFERENT such
|
|
130
|
+
* arguments would canonicalize to the same `"{}"` and collide on one memo key. Reject those before
|
|
131
|
+
* a key is ever computed, not after: `memoInvoker` must never be the reason a value collapses.
|
|
132
|
+
*
|
|
133
|
+
* `seen` makes this cycle-safe: a hostile or accidental circular reference is walked at most once
|
|
134
|
+
* per object (the second visit reports "not plain" rather than recursing forever), so a cyclic
|
|
135
|
+
* argument bypasses the memo instead of throwing `RangeError: Maximum call stack size exceeded`
|
|
136
|
+
* out of `invokeTransient`. Sharing one `seen` set across a whole call's plainness check is
|
|
137
|
+
* deliberately conservative: a repeated (non-cyclic) reference is also flagged "not plain" and
|
|
138
|
+
* bypasses sharing rather than being memoized — safe, since bypass just means no caching, never a
|
|
139
|
+
* wrong answer.
|
|
140
|
+
*/
|
|
141
|
+
function isPlainJsonValue(value, seen = new WeakSet()) {
|
|
142
|
+
if (value === null || value === undefined)
|
|
143
|
+
return true;
|
|
144
|
+
const t = typeof value;
|
|
145
|
+
if (t === "string" || t === "boolean")
|
|
146
|
+
return true;
|
|
147
|
+
if (t === "number")
|
|
148
|
+
return Number.isFinite(value);
|
|
149
|
+
if (t !== "object")
|
|
150
|
+
return false; // function, bigint, symbol
|
|
151
|
+
const obj = value;
|
|
152
|
+
if (seen.has(obj))
|
|
153
|
+
return false;
|
|
154
|
+
seen.add(obj);
|
|
155
|
+
if (Array.isArray(value))
|
|
156
|
+
return value.every((v) => isPlainJsonValue(v, seen));
|
|
157
|
+
const proto = Object.getPrototypeOf(obj);
|
|
158
|
+
if (proto !== Object.prototype && proto !== null)
|
|
159
|
+
return false;
|
|
160
|
+
return Object.values(value).every((v) => isPlainJsonValue(v, seen));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* One call per identical MCP step for this invoker's lifetime (a PASS): same tool, same resolved
|
|
164
|
+
* arguments, same schema pin, same call options — the canonical JSON of `[principal, every other
|
|
165
|
+
* option except signal, mcp.invoke's input]`, so key order does not matter. A joiner is
|
|
166
|
+
* served straight from the shared promise — it never calls `inner` itself, so it never passes
|
|
167
|
+
* through whatever per-call authorizer `inner` enforces. That is safe ONLY because every
|
|
168
|
+
* authorization input is part of the key: the principal, and also the stamped execution identity
|
|
169
|
+
* (`as` — an authorizer may grant by workflow), the `label` (which names the transient
|
|
170
|
+
* execution when `as` is absent) and anything else in the options. Every joiner
|
|
171
|
+
* sharing one entry was, by construction, asking with the exact inputs the leader's own (sole)
|
|
172
|
+
* call to `inner` was authorized for; an option that is not plain JSON passes through, uncached. Settled results stay (a step in a later wave is still
|
|
173
|
+
* served) and failures are shared. Other effects (a sandbox reshape) always run, uncached.
|
|
174
|
+
*
|
|
175
|
+
* Cancellation is ref-counted, not leader-owned: `inner` is called once, through an internal
|
|
176
|
+
* `AbortController` this function owns — never a caller's own signal directly — so any ONE
|
|
177
|
+
* caller's abort only ever settles that caller's OWN returned promise (a race against the shared
|
|
178
|
+
* promise). The upstream call itself is cancelled, and the entry forgotten, only once every live
|
|
179
|
+
* caller has aborted AND the call had not already settled; a caller that arrives after a real
|
|
180
|
+
* cancellation finds no entry and starts a fresh call.
|
|
181
|
+
*/
|
|
182
|
+
function memoInvoker(inner) {
|
|
183
|
+
const memo = new Map();
|
|
184
|
+
const onCallerAbort = (key, hit) => {
|
|
185
|
+
hit.live -= 1;
|
|
186
|
+
if (hit.live > 0)
|
|
187
|
+
return;
|
|
188
|
+
// Deferred one microtask: `hit.settled` is set by a handler attached at entry-creation time,
|
|
189
|
+
// which — because it was attached first — runs before this check for any settlement the memo
|
|
190
|
+
// can observe, even when the abort lands in the very same synchronous turn as `hit.promise`
|
|
191
|
+
// settling. Checking `hit.settled` synchronously right here would miss exactly that race.
|
|
192
|
+
// "The memo can observe" is `hit.promise` — `inner`'s promise — not the raw upstream: behind
|
|
193
|
+
// a queued `limitInvoker` (or any wrapper that settles a few hops after its own inner call),
|
|
194
|
+
// an all-abort landing after the RAW upstream settled but before `hit.promise` does still
|
|
195
|
+
// cancels and evicts, so a later caller re-fetches. A wasted call, never a wrong answer.
|
|
196
|
+
//
|
|
197
|
+
// Both conditions below are RE-READ at that later point, not captured now, because either can
|
|
198
|
+
// change in the gap between "live just dropped to 0" and "the microtask actually runs": a
|
|
199
|
+
// fresh caller with no signal of its own can join in the very same synchronous turn (bumping
|
|
200
|
+
// `live` back above 0 before this ever executes), and the upstream can settle in that same
|
|
201
|
+
// window (flipping `settled` true). A snapshot taken at decrement time would race both ways —
|
|
202
|
+
// this is why the check happens here, once, right before the one place that acts on it.
|
|
203
|
+
//
|
|
204
|
+
// Invariant: the controller is aborted, and the entry deleted, AT MOST ONCE per entry — and
|
|
205
|
+
// only when, at the moment this specific check runs, `settled` is still false AND `live` is
|
|
206
|
+
// still <= 0 (no one re-joined). Either condition failing means someone still needs this call
|
|
207
|
+
// to keep going (or already has its answer), so it is left alone.
|
|
208
|
+
queueMicrotask(() => {
|
|
209
|
+
if (hit.settled || hit.live > 0)
|
|
210
|
+
return;
|
|
211
|
+
// Forget the entry BEFORE aborting: `abort()` runs `inner`'s listeners synchronously, and a
|
|
212
|
+
// caller re-entering with the same key from one of them must start a fresh call, not join
|
|
213
|
+
// this dying one.
|
|
214
|
+
if (memo.get(key) === hit)
|
|
215
|
+
memo.delete(key);
|
|
216
|
+
hit.controller.abort();
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
const join = (key, hit, signal) => {
|
|
220
|
+
if (signal?.aborted === true) {
|
|
221
|
+
onCallerAbort(key, hit);
|
|
222
|
+
return Promise.reject(abortError());
|
|
223
|
+
}
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
let settled = false;
|
|
226
|
+
const onAbort = () => {
|
|
227
|
+
if (settled)
|
|
228
|
+
return;
|
|
229
|
+
settled = true;
|
|
230
|
+
onCallerAbort(key, hit);
|
|
231
|
+
reject(abortError());
|
|
232
|
+
};
|
|
233
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
234
|
+
hit.promise.then((v) => {
|
|
235
|
+
if (settled)
|
|
236
|
+
return;
|
|
237
|
+
settled = true;
|
|
238
|
+
signal?.removeEventListener("abort", onAbort);
|
|
239
|
+
resolve(v);
|
|
240
|
+
}, (e) => {
|
|
241
|
+
if (settled)
|
|
242
|
+
return;
|
|
243
|
+
settled = true;
|
|
244
|
+
signal?.removeEventListener("abort", onAbort);
|
|
245
|
+
reject(e);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
return {
|
|
250
|
+
invokeTransient(toolId, input, opts) {
|
|
251
|
+
// Every pass-through is guarded like the leader's call: a synchronous throw from `inner`
|
|
252
|
+
// must come back as a rejected promise, never escape `invokeTransient`.
|
|
253
|
+
const passThrough = () => callSafely(() => inner.invokeTransient(toolId, input, opts));
|
|
254
|
+
if (toolId !== plugin_mcp_1.MCP_INVOKE_TOOL_ID)
|
|
255
|
+
return passThrough();
|
|
256
|
+
// Everything in `opts` except the one field that cannot change the answer (`signal`) is
|
|
257
|
+
// part of the key: the principal, the stamped identity (`as`, which an authorizer may key
|
|
258
|
+
// grants on), the `label` (without `as`, the transient executionId is `transient:<label>`,
|
|
259
|
+
// and an authorizer may grant per execution), the budget snapshot, and any option added
|
|
260
|
+
// later. Keying on the remainder rather than a list of known fields is what keeps a joiner
|
|
261
|
+
// from being served a result authorized for a different input this function has never
|
|
262
|
+
// heard of.
|
|
263
|
+
const { signal: _signal, principal = null, ...rest } = opts ?? {};
|
|
264
|
+
let key;
|
|
265
|
+
try {
|
|
266
|
+
const seen = new WeakSet();
|
|
267
|
+
if (!isPlainJsonValue(principal, seen) ||
|
|
268
|
+
!isPlainJsonValue(rest, seen) ||
|
|
269
|
+
!isPlainJsonValue(input, seen))
|
|
270
|
+
return passThrough();
|
|
271
|
+
key = (0, core_1.canonicalJson)([principal, rest, input]);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return passThrough();
|
|
275
|
+
}
|
|
276
|
+
const hit = memo.get(key);
|
|
277
|
+
if (hit !== undefined) {
|
|
278
|
+
hit.live += 1;
|
|
279
|
+
return join(key, hit, opts?.signal);
|
|
280
|
+
}
|
|
281
|
+
// Mirrors limitInvoker's fast path: an already-aborted caller must never become the leader
|
|
282
|
+
// — that would call `inner`, create an entry nobody asked to keep, and (since nothing here
|
|
283
|
+
// would yet be subscribed to the resulting promise) risk an unhandled rejection.
|
|
284
|
+
if (opts?.signal?.aborted === true)
|
|
285
|
+
return Promise.reject(abortError());
|
|
286
|
+
const controller = new AbortController();
|
|
287
|
+
let promise;
|
|
288
|
+
try {
|
|
289
|
+
// `inner.invokeTransient` is typed to return a Promise, but a misbehaving implementation
|
|
290
|
+
// can still throw synchronously; the interface contract (and every caller of THIS
|
|
291
|
+
// function) requires a rejected promise instead, never a thrown exception. No entry is
|
|
292
|
+
// created for a call that never actually started.
|
|
293
|
+
promise = inner.invokeTransient(toolId, input, {
|
|
294
|
+
...opts,
|
|
295
|
+
signal: controller.signal,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
catch (e) {
|
|
299
|
+
return Promise.reject(e);
|
|
300
|
+
}
|
|
301
|
+
const entry = { promise, controller, live: 1, settled: false };
|
|
302
|
+
const onSettle = () => {
|
|
303
|
+
entry.settled = true;
|
|
304
|
+
};
|
|
305
|
+
// Attached at entry-creation time — before any joiner's own `.then` — so `entry.settled`
|
|
306
|
+
// is set before `onCallerAbort`'s deferred check can run (see there). Also the
|
|
307
|
+
// backstop: attaching a rejection handler here means NO path can ever leave `promise`
|
|
308
|
+
// unhandled, even if a future caller subscribes late or never at all.
|
|
309
|
+
promise.then(onSettle, onSettle);
|
|
310
|
+
memo.set(key, entry);
|
|
311
|
+
return join(key, entry, opts?.signal);
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=invokers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invokers.js","sourceRoot":"","sources":["../src/invokers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { toolInputJsonSchema } from "@loomwork/agent";
|
|
2
|
+
import type { EffectRuntime } from "@loomwork/effect-runtime";
|
|
3
|
+
import type { DataProvider } from "./dataProvider";
|
|
4
|
+
/** The kind id of a step whose target is a registered loom effect. */
|
|
5
|
+
export declare const LOOM_EFFECT_KIND = "loom-effect";
|
|
6
|
+
/**
|
|
7
|
+
* The failure channel for a target the host did not declare. Served to a client as the fixed
|
|
8
|
+
* `tool-targetUnregistered` code — never the target id or any text of the effect's own.
|
|
9
|
+
*/
|
|
10
|
+
export declare const TARGET_UNREGISTERED = "targetUnregistered";
|
|
11
|
+
/**
|
|
12
|
+
* The failure channel for an effect that itself raised `schemaDrift` (e.g. a target wrapping
|
|
13
|
+
* `mcp.invoke`). Only the provider's OWN pin check may emit `schemaDrift`: a target's drift
|
|
14
|
+
* would otherwise be read as this kind's pin drift, and a repair would re-pin to the inner
|
|
15
|
+
* hash and flip forever. Served as the `tool-targetFailed` code (`tool-failed` to a client).
|
|
16
|
+
*/
|
|
17
|
+
export declare const TARGET_FAILED = "targetFailed";
|
|
18
|
+
/** The schema of an effect's input, as `tool$({ inputSchema })` takes it. */
|
|
19
|
+
export type EffectInputSchema = Parameters<typeof toolInputJsonSchema>[0];
|
|
20
|
+
/**
|
|
21
|
+
* The pin of an effect's input contract: `jsonSchemaHash` (the one canonical hash MCP pins use)
|
|
22
|
+
* of the schema's JSON-Schema form. Use it BOTH when the host builds a planner `CapabilityRef`
|
|
23
|
+
* (`toolSchemaHash`) and when it lists the effect for {@link loomEffectProvider} (via
|
|
24
|
+
* `{ inputSchema }`) so the two come from one source: editing the effect's zod schema moves
|
|
25
|
+
* the pin, and every stored step drifts.
|
|
26
|
+
*/
|
|
27
|
+
export declare function effectPin(schema: EffectInputSchema): string;
|
|
28
|
+
/** How a listed effect's pin is obtained: computed from its schema, or asserted by the host. */
|
|
29
|
+
export type EffectEntry = {
|
|
30
|
+
readonly inputSchema: NonNullable<EffectInputSchema>;
|
|
31
|
+
} | {
|
|
32
|
+
readonly pin: string;
|
|
33
|
+
};
|
|
34
|
+
export interface LoomEffectProviderOptions {
|
|
35
|
+
/**
|
|
36
|
+
* The runtime the effects are registered with. Read at CONSTRUCTION only (`has` / `modeOf` /
|
|
37
|
+
* `kindOf`): hydration is a read path that runs per viewer load, unmemoised and retried, so
|
|
38
|
+
* every listed effect must be registered, explicitly `mode: "ambient"` (ADR 0073 — loom
|
|
39
|
+
* refuses only an explicit `journaled` one on the transient path, so an UNDECLARED mode is
|
|
40
|
+
* not a read declaration and is refused here), and not a `human` / `llm` kind.
|
|
41
|
+
*/
|
|
42
|
+
readonly runtime: Pick<EffectRuntime, "has" | "modeOf" | "kindOf">;
|
|
43
|
+
/**
|
|
44
|
+
* The effects a stored step may name. `{ inputSchema }` computes the pin with
|
|
45
|
+
* {@link effectPin} (a schema change drifts every stored step); `{ pin }` is HOST-ASSERTED —
|
|
46
|
+
* nothing checks it against the effect, so the host owns changing it when callers must
|
|
47
|
+
* re-author. A step naming an effect not listed is refused (`tool-targetUnregistered`) and
|
|
48
|
+
* nothing runs. Pins must be non-empty.
|
|
49
|
+
*/
|
|
50
|
+
readonly effects: Readonly<Record<string, EffectEntry>>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The `loom-effect` kind: a step whose `toolName` is the id of an effect registered with the
|
|
54
|
+
* loom effect runtime (a `tool$`, a read-model query effect, an A2A call…), invoked through the
|
|
55
|
+
* SAME transient invoker and therefore the same authoriser, budget and principal as the MCP
|
|
56
|
+
* kind. Construction refuses an effect that is unregistered, not explicitly `ambient`, or a
|
|
57
|
+
* `human` / `llm` kind.
|
|
58
|
+
*
|
|
59
|
+
* An effect's call is not memoised across a pass (the in-pass memo coalesces `mcp.invoke` only:
|
|
60
|
+
* an arbitrary effect is not known to be a pure read), so two steps naming one effect with equal
|
|
61
|
+
* arguments call it twice. (Memoising `ambient` effects is a possible follow-up.)
|
|
62
|
+
*
|
|
63
|
+
* A persisted experience crosses a version boundary only through the log: an older in-memory
|
|
64
|
+
* `executeProvider` / `seal` that predates kinds would run a kinded step as an MCP call.
|
|
65
|
+
*/
|
|
66
|
+
export declare function loomEffectProvider(options: LoomEffectProviderOptions): DataProvider;
|
|
67
|
+
//# sourceMappingURL=loomEffectProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loomEffectProvider.d.ts","sourceRoot":"","sources":["../src/loomEffectProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,mBAAmB,uBAAuB,CAAC;AAExD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAE5C,6EAA6E;AAC7E,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE1E;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAE3D;AAYD,gGAAgG;AAChG,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7B,MAAM,WAAW,yBAAyB;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC;IACnE;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,YAAY,CAiDnF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TARGET_FAILED = exports.TARGET_UNREGISTERED = exports.LOOM_EFFECT_KIND = void 0;
|
|
4
|
+
exports.effectPin = effectPin;
|
|
5
|
+
exports.loomEffectProvider = loomEffectProvider;
|
|
6
|
+
const agent_1 = require("@loomwork/agent");
|
|
7
|
+
const core_1 = require("@loomwork/core");
|
|
8
|
+
const plugin_mcp_1 = require("@loomwork/plugin-mcp");
|
|
9
|
+
const plugin_sandbox_1 = require("@loomwork/plugin-sandbox");
|
|
10
|
+
/** The kind id of a step whose target is a registered loom effect. */
|
|
11
|
+
exports.LOOM_EFFECT_KIND = "loom-effect";
|
|
12
|
+
/**
|
|
13
|
+
* The failure channel for a target the host did not declare. Served to a client as the fixed
|
|
14
|
+
* `tool-targetUnregistered` code — never the target id or any text of the effect's own.
|
|
15
|
+
*/
|
|
16
|
+
exports.TARGET_UNREGISTERED = "targetUnregistered";
|
|
17
|
+
/**
|
|
18
|
+
* The failure channel for an effect that itself raised `schemaDrift` (e.g. a target wrapping
|
|
19
|
+
* `mcp.invoke`). Only the provider's OWN pin check may emit `schemaDrift`: a target's drift
|
|
20
|
+
* would otherwise be read as this kind's pin drift, and a repair would re-pin to the inner
|
|
21
|
+
* hash and flip forever. Served as the `tool-targetFailed` code (`tool-failed` to a client).
|
|
22
|
+
*/
|
|
23
|
+
exports.TARGET_FAILED = "targetFailed";
|
|
24
|
+
/**
|
|
25
|
+
* The pin of an effect's input contract: `jsonSchemaHash` (the one canonical hash MCP pins use)
|
|
26
|
+
* of the schema's JSON-Schema form. Use it BOTH when the host builds a planner `CapabilityRef`
|
|
27
|
+
* (`toolSchemaHash`) and when it lists the effect for {@link loomEffectProvider} (via
|
|
28
|
+
* `{ inputSchema }`) so the two come from one source: editing the effect's zod schema moves
|
|
29
|
+
* the pin, and every stored step drifts.
|
|
30
|
+
*/
|
|
31
|
+
function effectPin(schema) {
|
|
32
|
+
return (0, core_1.jsonSchemaHash)((0, agent_1.toolInputJsonSchema)(schema));
|
|
33
|
+
}
|
|
34
|
+
/** The effect kinds a data read can never reach: a person's answer, a model call. */
|
|
35
|
+
const REFUSED_KINDS = new Set(["human", "llm"]);
|
|
36
|
+
/**
|
|
37
|
+
* Effects hydration already reaches through a dedicated plane (the MCP kind, the sandbox
|
|
38
|
+
* reshaper). Exposing them as a data TARGET would let a stored step bypass the pin and the
|
|
39
|
+
* reshaping boundary, so a host cannot list them.
|
|
40
|
+
*/
|
|
41
|
+
const RESERVED_TARGETS = new Set([plugin_mcp_1.MCP_INVOKE_TOOL_ID, plugin_sandbox_1.SANDBOX_TOOL_ID]);
|
|
42
|
+
/**
|
|
43
|
+
* The `loom-effect` kind: a step whose `toolName` is the id of an effect registered with the
|
|
44
|
+
* loom effect runtime (a `tool$`, a read-model query effect, an A2A call…), invoked through the
|
|
45
|
+
* SAME transient invoker and therefore the same authoriser, budget and principal as the MCP
|
|
46
|
+
* kind. Construction refuses an effect that is unregistered, not explicitly `ambient`, or a
|
|
47
|
+
* `human` / `llm` kind.
|
|
48
|
+
*
|
|
49
|
+
* An effect's call is not memoised across a pass (the in-pass memo coalesces `mcp.invoke` only:
|
|
50
|
+
* an arbitrary effect is not known to be a pure read), so two steps naming one effect with equal
|
|
51
|
+
* arguments call it twice. (Memoising `ambient` effects is a possible follow-up.)
|
|
52
|
+
*
|
|
53
|
+
* A persisted experience crosses a version boundary only through the log: an older in-memory
|
|
54
|
+
* `executeProvider` / `seal` that predates kinds would run a kinded step as an MCP call.
|
|
55
|
+
*/
|
|
56
|
+
function loomEffectProvider(options) {
|
|
57
|
+
const { runtime } = options;
|
|
58
|
+
const pins = new Map();
|
|
59
|
+
for (const [id, entry] of Object.entries(options.effects)) {
|
|
60
|
+
if (id === "")
|
|
61
|
+
throw new TypeError("loomEffectProvider: an effect id is empty");
|
|
62
|
+
if (RESERVED_TARGETS.has(id))
|
|
63
|
+
throw new TypeError(`loomEffectProvider: "${id}" is reached through its own plane (the MCP kind / the sandbox) and cannot be a data target`);
|
|
64
|
+
if (!runtime.has(id))
|
|
65
|
+
throw new TypeError(`loomEffectProvider: effect "${id}" is not registered with the runtime`);
|
|
66
|
+
if (runtime.modeOf(id) !== "ambient")
|
|
67
|
+
throw new TypeError(`loomEffectProvider: effect "${id}" is not declared mode "ambient" (ADR 0073); a data read may not run an undeclared or journaled effect`);
|
|
68
|
+
const kind = runtime.kindOf(id);
|
|
69
|
+
if (kind !== undefined && REFUSED_KINDS.has(kind))
|
|
70
|
+
throw new TypeError(`loomEffectProvider: effect "${id}" is a ${kind} effect, not a data read`);
|
|
71
|
+
const pin = entry !== null && typeof entry === "object" && "inputSchema" in entry
|
|
72
|
+
? effectPin(entry.inputSchema)
|
|
73
|
+
: entry?.pin;
|
|
74
|
+
if (typeof pin !== "string" || pin === "")
|
|
75
|
+
throw new TypeError(`loomEffectProvider: effect "${id}" needs a non-empty pin`);
|
|
76
|
+
pins.set(id, pin);
|
|
77
|
+
}
|
|
78
|
+
return Object.freeze({
|
|
79
|
+
kind: exports.LOOM_EFFECT_KIND,
|
|
80
|
+
async invoke(call, invoker, opts) {
|
|
81
|
+
const current = pins.get(call.target);
|
|
82
|
+
if (current === undefined)
|
|
83
|
+
throw new core_1.TypedFailure(exports.TARGET_UNREGISTERED, { target: call.target });
|
|
84
|
+
if (current !== call.expectedPin)
|
|
85
|
+
throw new core_1.TypedFailure("schemaDrift", {
|
|
86
|
+
toolName: call.target,
|
|
87
|
+
expectedSchemaHash: call.expectedPin,
|
|
88
|
+
observedHash: current,
|
|
89
|
+
});
|
|
90
|
+
try {
|
|
91
|
+
return await invoker.invokeTransient(call.target, call.args, opts);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
if (e instanceof core_1.TypedFailure && e.channel === "schemaDrift")
|
|
95
|
+
throw new core_1.TypedFailure(exports.TARGET_FAILED, { target: call.target });
|
|
96
|
+
throw e;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=loomEffectProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loomEffectProvider.js","sourceRoot":"","sources":["../src/loomEffectProvider.ts"],"names":[],"mappings":"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"}
|