@peers-app/peers-sdk 0.25.21 → 0.26.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/dist/contracts/__tests__/builder.test.js +0 -2
- package/dist/contracts/__tests__/contract-provider-router.test.js +51 -0
- package/dist/contracts/__tests__/contract-proxy.test.js +21 -0
- package/dist/contracts/__tests__/contract-resolvers.test.js +84 -0
- package/dist/contracts/__tests__/legacy-removal.test.d.ts +1 -0
- package/dist/contracts/__tests__/legacy-removal.test.js +90 -0
- package/dist/contracts/__tests__/system-contracts.test.js +88 -0
- package/dist/contracts/__tests__/tool-catalog-inventory.test.d.ts +1 -0
- package/dist/contracts/__tests__/tool-catalog-inventory.test.js +44 -0
- package/dist/contracts/__tests__/tool-catalog-service.test.d.ts +1 -0
- package/dist/contracts/__tests__/tool-catalog-service.test.js +446 -0
- package/dist/contracts/__tests__/tool-catalog.test.d.ts +1 -0
- package/dist/contracts/__tests__/tool-catalog.test.js +649 -0
- package/dist/contracts/__tests__/workflow-context.test.d.ts +1 -0
- package/dist/contracts/__tests__/workflow-context.test.js +61 -0
- package/dist/contracts/builder.d.ts +9 -8
- package/dist/contracts/builder.js +34 -16
- package/dist/contracts/contract-provider-router.d.ts +12 -1
- package/dist/contracts/contract-provider-router.js +40 -2
- package/dist/contracts/contract-proxy.d.ts +20 -6
- package/dist/contracts/contract-proxy.js +23 -12
- package/dist/contracts/contract-resolvers.d.ts +22 -3
- package/dist/contracts/contract-resolvers.js +86 -7
- package/dist/contracts/extract.d.ts +7 -0
- package/dist/contracts/extract.js +3 -0
- package/dist/contracts/index.d.ts +11 -4
- package/dist/contracts/index.js +14 -2
- package/dist/contracts/registry.d.ts +3 -0
- package/dist/contracts/registry.js +2 -0
- package/dist/contracts/system/desktop-tools-contract.d.ts +5 -0
- package/dist/contracts/system/desktop-tools-contract.js +88 -0
- package/dist/contracts/system/host-tools-contract.d.ts +5 -0
- package/dist/contracts/system/host-tools-contract.js +76 -0
- package/dist/contracts/system/index.d.ts +3 -0
- package/dist/contracts/system/index.js +3 -0
- package/dist/contracts/system/tool-catalog-contract.d.ts +73 -0
- package/dist/contracts/system/tool-catalog-contract.js +70 -0
- package/dist/contracts/system-contracts.d.ts +3 -1
- package/dist/contracts/system-contracts.js +20 -6
- package/dist/contracts/tool-catalog-identity.d.ts +35 -0
- package/dist/contracts/tool-catalog-identity.js +88 -0
- package/dist/contracts/tool-catalog-migrations.d.ts +12 -0
- package/dist/contracts/tool-catalog-migrations.js +179 -0
- package/dist/contracts/tool-catalog-service.d.ts +30 -0
- package/dist/contracts/tool-catalog-service.js +287 -0
- package/dist/contracts/tool-catalog.d.ts +91 -0
- package/dist/contracts/tool-catalog.js +490 -0
- package/dist/contracts/types.d.ts +7 -5
- package/dist/data/messages.d.ts +3 -3
- package/dist/data/orm/sql.data-source.d.ts +5 -0
- package/dist/data/orm/sql.data-source.js +28 -10
- package/dist/data/orm/sql.data-source.test.d.ts +1 -1
- package/dist/data/orm/sql.data-source.test.js +160 -0
- package/dist/data/orm/table-container.d.ts +14 -0
- package/dist/data/orm/table-container.js +20 -0
- package/dist/data/tool-access-approvals.d.ts +6 -6
- package/dist/data/tool-catalog-fields.d.ts +31 -0
- package/dist/data/tool-catalog-fields.js +67 -0
- package/dist/data/tools.d.ts +38 -10
- package/dist/data/tools.js +31 -8
- package/dist/data/workflow-logs.d.ts +6 -2
- package/dist/data/workflow-logs.js +7 -5
- package/dist/data/workflow-runs.d.ts +6 -6
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -3
- package/dist/package-loader/contract-package-loader.d.ts +12 -3
- package/dist/package-loader/contract-package-loader.js +54 -50
- package/dist/package-loader/contract-package-loader.test.d.ts +1 -1
- package/dist/package-loader/contract-package-loader.test.js +172 -13
- package/dist/package-loader/host-authority-loading.test.js +16 -3
- package/dist/package-loader/isolated-runtime.d.ts +14 -2
- package/dist/package-loader/isolated-table-gateway.d.ts +3 -1
- package/dist/package-loader/isolated-table-gateway.js +54 -5
- package/dist/package-loader/isolated-table-gateway.test.js +123 -0
- package/dist/package-loader/package-loader.d.ts +14 -2
- package/dist/package-loader/package-loader.js +184 -62
- package/dist/package-loader/package-loader.test.js +674 -1
- package/dist/package-loader/resolve-consumed-contracts.js +1 -1
- package/dist/system-ids.d.ts +6 -0
- package/dist/system-ids.js +7 -1
- package/dist/types/peers-package.d.ts +3 -2
- package/dist/types/workflow-run-context.d.ts +12 -2
- package/package.json +1 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -17
- package/dist/tools/tools-factory.d.ts +0 -5
- package/dist/tools/tools-factory.js +0 -34
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
require("../../index");
|
|
4
|
+
const contract_proxy_1 = require("../contract-proxy");
|
|
5
|
+
test("only the directly invoked tool receives workflow capability", async () => {
|
|
6
|
+
const workflow = { dataContextId: "group" };
|
|
7
|
+
const nested = jest.fn();
|
|
8
|
+
const action = jest.fn((_args, context) => {
|
|
9
|
+
expect(context.workflow).toBeUndefined();
|
|
10
|
+
expect(context.callContext.workflow).toBeUndefined();
|
|
11
|
+
expect(context.incomingCallContext.workflow).toBeUndefined();
|
|
12
|
+
});
|
|
13
|
+
const resolution = {
|
|
14
|
+
contractId: "contract",
|
|
15
|
+
version: 1,
|
|
16
|
+
forwardCallContext: true,
|
|
17
|
+
actionAliases: { do: "action" },
|
|
18
|
+
actionTools: {
|
|
19
|
+
action: { tool: { name: "action", usageDescription: "", accessLevel: 0 }, toolFn: action },
|
|
20
|
+
},
|
|
21
|
+
tools: {
|
|
22
|
+
run: {
|
|
23
|
+
tool: { name: "run", usageDescription: "", accessLevel: 0 },
|
|
24
|
+
toolFn: async (_args, context) => {
|
|
25
|
+
expect((0, contract_proxy_1.requireWorkflowRunContext)(context, "run")).toBe(workflow);
|
|
26
|
+
expect(context.callContext.workflow).toBeUndefined();
|
|
27
|
+
expect(context.incomingCallContext.workflow).toBeUndefined();
|
|
28
|
+
await context.dispatchTool({ contractId: "other", version: 1, toolName: "target" }, {});
|
|
29
|
+
await context.dispatchAction("do", {});
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const tables = { tableContainer: { getTable: () => ({ get: async () => undefined }) } };
|
|
35
|
+
const dataContext = {
|
|
36
|
+
dataContextId: "group",
|
|
37
|
+
userContext: {
|
|
38
|
+
userId: "u",
|
|
39
|
+
loadingPromise: Promise.resolve(),
|
|
40
|
+
userDataContext: tables,
|
|
41
|
+
getDataContext: () => tables,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const provider = (0, contract_proxy_1.createContractProvider)(resolution, { dispatchTool: nested });
|
|
45
|
+
await provider({
|
|
46
|
+
contractId: "contract",
|
|
47
|
+
version: 1,
|
|
48
|
+
member: "tool",
|
|
49
|
+
operation: "run",
|
|
50
|
+
name: "run",
|
|
51
|
+
args: [{}],
|
|
52
|
+
}, { dataContext, callerUserId: "u", workflow });
|
|
53
|
+
expect(nested.mock.calls[0][2].workflow).toBeUndefined();
|
|
54
|
+
expect(action).toHaveBeenCalled();
|
|
55
|
+
expect(() => (0, contract_proxy_1.requireWorkflowRunContext)({ dataContextId: "other", workflow }, "run")).toThrow("same data context");
|
|
56
|
+
});
|
|
57
|
+
test("workflow policy is retained by shape extraction", () => {
|
|
58
|
+
const { extractToolShape } = require("../extract");
|
|
59
|
+
expect(extractToolShape({ tool: { name: "run", usageDescription: "" }, requiresWorkflowRun: true })
|
|
60
|
+
.requiresWorkflowRun).toBe(true);
|
|
61
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ITableDefinition } from "../data/orm/table-definitions.type";
|
|
2
|
-
import type { IToolInstance } from "../data/tools";
|
|
3
2
|
import type { IContractConsumer, IContractConsumerBase } from "./contract-proxy";
|
|
4
3
|
import type { IExtractableTableDef, IExtractableToolInstance } from "./extract";
|
|
4
|
+
import type { IToolCatalogMetadata } from "./tool-catalog";
|
|
5
5
|
import type { IAlsoImplementsDeclaration, IConsumedContractHandle, IConsumedContractOptions, IContractDefinition, IContractEvent, IContractObservable, IPackageDefinitionResult, IRemoteContractHandle } from "./types";
|
|
6
6
|
/**
|
|
7
7
|
* Scoped builder for a single contract within a package. Returned by
|
|
@@ -17,8 +17,7 @@ export declare class ContractBuilder {
|
|
|
17
17
|
private _observables;
|
|
18
18
|
private _events;
|
|
19
19
|
private _alsoImplements;
|
|
20
|
-
|
|
21
|
-
private _toolInstances;
|
|
20
|
+
private warnedAboutToolInstances;
|
|
22
21
|
/** Full table definitions for this contract (used by the install flow). */
|
|
23
22
|
private _tableDefinitions;
|
|
24
23
|
constructor(contractId: string, version: number, name: string, description?: string | undefined);
|
|
@@ -35,9 +34,11 @@ export declare class ContractBuilder {
|
|
|
35
34
|
*/
|
|
36
35
|
set events(events: IContractEvent[]);
|
|
37
36
|
get events(): IContractEvent[];
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Discard executable assignments from bundles compiled against an older SDK.
|
|
39
|
+
* @deprecated Implement tools with registerContractResolver instead.
|
|
40
|
+
*/
|
|
41
|
+
set toolInstances(_instances: never);
|
|
41
42
|
/** Full table definitions for runtime table registration. */
|
|
42
43
|
set tableDefinitions(defs: ITableDefinition[]);
|
|
43
44
|
get tableDefinitions(): ITableDefinition[];
|
|
@@ -53,7 +54,7 @@ export declare class ContractBuilder {
|
|
|
53
54
|
_build(): {
|
|
54
55
|
definition: IContractDefinition;
|
|
55
56
|
alsoImplements: IAlsoImplementsDeclaration[];
|
|
56
|
-
|
|
57
|
+
toolMetadata: IToolCatalogMetadata[];
|
|
57
58
|
tableDefinitions: ITableDefinition[];
|
|
58
59
|
};
|
|
59
60
|
}
|
|
@@ -108,7 +109,7 @@ export declare function rejectConsumedContractHandle(handle: IConsumedContractHa
|
|
|
108
109
|
* Top-level builder passed to the `definePackage` callback.
|
|
109
110
|
*
|
|
110
111
|
* Packages may define **zero** contracts (e.g. UI-only apps with no persisted
|
|
111
|
-
* tables). Tables, tool shapes, and
|
|
112
|
+
* tables). Tables, tool shapes, and runtime
|
|
112
113
|
* `tableDefinitions` are only attached via {@link PackageBuilder.contract} —
|
|
113
114
|
* there is no package-level API for them, so persistence always implies a
|
|
114
115
|
* contract.
|
|
@@ -25,8 +25,7 @@ class ContractBuilder {
|
|
|
25
25
|
_observables = [];
|
|
26
26
|
_events = [];
|
|
27
27
|
_alsoImplements = [];
|
|
28
|
-
|
|
29
|
-
_toolInstances = [];
|
|
28
|
+
warnedAboutToolInstances = false;
|
|
30
29
|
/** Full table definitions for this contract (used by the install flow). */
|
|
31
30
|
_tableDefinitions = [];
|
|
32
31
|
constructor(contractId, version, name, description) {
|
|
@@ -64,12 +63,15 @@ class ContractBuilder {
|
|
|
64
63
|
get events() {
|
|
65
64
|
return this._events;
|
|
66
65
|
}
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Discard executable assignments from bundles compiled against an older SDK.
|
|
68
|
+
* @deprecated Implement tools with registerContractResolver instead.
|
|
69
|
+
*/
|
|
70
|
+
set toolInstances(_instances) {
|
|
71
|
+
if (this.warnedAboutToolInstances)
|
|
72
|
+
return;
|
|
73
|
+
this.warnedAboutToolInstances = true;
|
|
74
|
+
console.warn(`Contract ${this.name} (${this.contractId}@${this.version}) assigned toolInstances, which no longer run; implement these tools with registerContractResolver (docs/Packages/contracts.md#migrating-from-toolinstances)`);
|
|
73
75
|
}
|
|
74
76
|
/** Full table definitions for runtime table registration. */
|
|
75
77
|
set tableDefinitions(defs) {
|
|
@@ -103,7 +105,23 @@ class ContractBuilder {
|
|
|
103
105
|
events: [...this._events],
|
|
104
106
|
},
|
|
105
107
|
alsoImplements: [...this._alsoImplements],
|
|
106
|
-
|
|
108
|
+
toolMetadata: this._tools.flatMap(({ tool }) => {
|
|
109
|
+
const { isAssistantRunner, configSchema, detailedDescription } = tool;
|
|
110
|
+
return isAssistantRunner !== undefined ||
|
|
111
|
+
configSchema !== undefined ||
|
|
112
|
+
detailedDescription !== undefined
|
|
113
|
+
? [
|
|
114
|
+
{
|
|
115
|
+
contractId: this.contractId,
|
|
116
|
+
version: this.version,
|
|
117
|
+
toolName: tool.name,
|
|
118
|
+
isAssistantRunner,
|
|
119
|
+
configSchema,
|
|
120
|
+
detailedDescription,
|
|
121
|
+
},
|
|
122
|
+
]
|
|
123
|
+
: [];
|
|
124
|
+
}),
|
|
107
125
|
tableDefinitions: [...this._tableDefinitions],
|
|
108
126
|
};
|
|
109
127
|
}
|
|
@@ -188,7 +206,7 @@ function rejectConsumedContractHandle(handle, reason) {
|
|
|
188
206
|
* Top-level builder passed to the `definePackage` callback.
|
|
189
207
|
*
|
|
190
208
|
* Packages may define **zero** contracts (e.g. UI-only apps with no persisted
|
|
191
|
-
* tables). Tables, tool shapes, and
|
|
209
|
+
* tables). Tables, tool shapes, and runtime
|
|
192
210
|
* `tableDefinitions` are only attached via {@link PackageBuilder.contract} —
|
|
193
211
|
* there is no package-level API for them, so persistence always implies a
|
|
194
212
|
* contract.
|
|
@@ -312,19 +330,19 @@ class PackageBuilder {
|
|
|
312
330
|
}
|
|
313
331
|
const contracts = [];
|
|
314
332
|
const alsoImplementsMap = new Map();
|
|
315
|
-
const
|
|
333
|
+
const allToolMetadata = [];
|
|
316
334
|
const allTableDefinitions = [];
|
|
317
335
|
for (const cb of this._contracts) {
|
|
318
|
-
const { definition, alsoImplements,
|
|
336
|
+
const { definition, alsoImplements, toolMetadata, tableDefinitions } = cb._build();
|
|
319
337
|
contracts.push(definition);
|
|
320
338
|
if (alsoImplements.length > 0) {
|
|
321
339
|
alsoImplementsMap.set((0, types_1.contractKey)(cb.contractId, cb.version), alsoImplements);
|
|
322
340
|
}
|
|
323
|
-
|
|
341
|
+
allToolMetadata.push(...toolMetadata);
|
|
324
342
|
allTableDefinitions.push(...tableDefinitions);
|
|
325
343
|
}
|
|
326
|
-
if (contracts.length === 0 &&
|
|
327
|
-
throw new Error("Tables
|
|
344
|
+
if (contracts.length === 0 && allTableDefinitions.length > 0) {
|
|
345
|
+
throw new Error("Tables must be declared on a contract (use pkg.contract(...), then assign .tables or .tableDefinitions on that builder). UI-only packages omit contracts and do not register tables.");
|
|
328
346
|
}
|
|
329
347
|
return {
|
|
330
348
|
packageId: this._packageId,
|
|
@@ -335,7 +353,7 @@ class PackageBuilder {
|
|
|
335
353
|
alsoImplements: alsoImplementsMap,
|
|
336
354
|
assistants: [...this.assistants],
|
|
337
355
|
appNavs: [...this.appNavs],
|
|
338
|
-
|
|
356
|
+
...(allToolMetadata.length ? { toolMetadata: allToolMetadata } : {}),
|
|
339
357
|
tableDefinitions: allTableDefinitions,
|
|
340
358
|
};
|
|
341
359
|
}
|
|
@@ -55,7 +55,7 @@ export interface IContractProviderEndpointRequest {
|
|
|
55
55
|
* Resolves an endpoint for an authorized contract/version/data-context route.
|
|
56
56
|
*
|
|
57
57
|
* The callback may adapt local live objects or return a remote/sandbox-backed
|
|
58
|
-
* endpoint. The router caches the result until disposal.
|
|
58
|
+
* endpoint. The router caches the result until invalidation, retirement or disposal.
|
|
59
59
|
*/
|
|
60
60
|
export type ContractProviderEndpointResolver = (request: IContractProviderEndpointRequest) => IContractProviderEndpoint | Promise<IContractProviderEndpoint>;
|
|
61
61
|
/**
|
|
@@ -95,6 +95,15 @@ export interface IContractProviderRouter {
|
|
|
95
95
|
/** Remove the request handler and dispose every resolved provider endpoint. */
|
|
96
96
|
dispose(): void;
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Signals that this exact endpoint rejected a call before invoking provider code.
|
|
100
|
+
* Routers may refresh that endpoint and retry once. The endpoint identity prevents
|
|
101
|
+
* a retirement error from a downstream call from replaying its caller's effects.
|
|
102
|
+
*/
|
|
103
|
+
export declare class RetiredContractProviderEndpointError extends Error {
|
|
104
|
+
readonly endpoint: IContractProviderEndpoint;
|
|
105
|
+
constructor(endpoint: IContractProviderEndpoint);
|
|
106
|
+
}
|
|
98
107
|
/**
|
|
99
108
|
* Own the sole {@link CONTRACT_REQUEST_CHANNEL} handler for a shared connection.
|
|
100
109
|
*
|
|
@@ -106,6 +115,8 @@ export interface IContractProviderRouter {
|
|
|
106
115
|
* idempotent and skip reauthorization while still reaching the endpoint that owns the live
|
|
107
116
|
* source. Disposing the router removes its request handler and disposes all cached endpoints,
|
|
108
117
|
* including endpoints whose asynchronous resolution finishes during teardown.
|
|
118
|
+
* A lifetime guard's retirement rejection refreshes the cached endpoint once,
|
|
119
|
+
* before any provider code has run. Errors from provider execution are not retried.
|
|
109
120
|
*
|
|
110
121
|
* @param transport Duplex contract transport shared by all contracts on one connection.
|
|
111
122
|
* @param opts Trusted authorization, endpoint resolution, and caller context hooks.
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RetiredContractProviderEndpointError = void 0;
|
|
3
4
|
exports.createContractProviderRouter = createContractProviderRouter;
|
|
4
5
|
const contract_transport_1 = require("./contract-transport");
|
|
6
|
+
/**
|
|
7
|
+
* Signals that this exact endpoint rejected a call before invoking provider code.
|
|
8
|
+
* Routers may refresh that endpoint and retry once. The endpoint identity prevents
|
|
9
|
+
* a retirement error from a downstream call from replaying its caller's effects.
|
|
10
|
+
*/
|
|
11
|
+
class RetiredContractProviderEndpointError extends Error {
|
|
12
|
+
endpoint;
|
|
13
|
+
constructor(endpoint) {
|
|
14
|
+
super("Host contract endpoint has been retired");
|
|
15
|
+
this.endpoint = endpoint;
|
|
16
|
+
this.name = "RetiredContractProviderEndpointError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.RetiredContractProviderEndpointError = RetiredContractProviderEndpointError;
|
|
5
20
|
const routeKey = (contractId, version, providerPackageId, dataContextId, targetDeviceId) => JSON.stringify([
|
|
6
21
|
contractId,
|
|
7
22
|
version,
|
|
@@ -29,6 +44,8 @@ function assertRemoteTargetCallSupported(call) {
|
|
|
29
44
|
* idempotent and skip reauthorization while still reaching the endpoint that owns the live
|
|
30
45
|
* source. Disposing the router removes its request handler and disposes all cached endpoints,
|
|
31
46
|
* including endpoints whose asynchronous resolution finishes during teardown.
|
|
47
|
+
* A lifetime guard's retirement rejection refreshes the cached endpoint once,
|
|
48
|
+
* before any provider code has run. Errors from provider execution are not retried.
|
|
32
49
|
*
|
|
33
50
|
* @param transport Duplex contract transport shared by all contracts on one connection.
|
|
34
51
|
* @param opts Trusted authorization, endpoint resolution, and caller context hooks.
|
|
@@ -146,8 +163,29 @@ function createContractProviderRouter(transport, opts) {
|
|
|
146
163
|
: undefined;
|
|
147
164
|
endpointRequest.providerPackageId = providerPackageId;
|
|
148
165
|
const endpointKey = routeKey(call.contractId, call.version, providerPackageId, authorization.dataContextId, targetDeviceId);
|
|
149
|
-
|
|
150
|
-
|
|
166
|
+
let endpoint = await getEndpoint(endpointKey, endpointRequest);
|
|
167
|
+
let result;
|
|
168
|
+
try {
|
|
169
|
+
result = await endpoint.handleCall(routedCall, context);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (disposed ||
|
|
173
|
+
!(error instanceof RetiredContractProviderEndpointError) ||
|
|
174
|
+
error.endpoint !== endpoint)
|
|
175
|
+
throw error;
|
|
176
|
+
// Only retire the entry that rejected this call. Another concurrent call
|
|
177
|
+
// may already have refreshed the same route and attached subscriptions.
|
|
178
|
+
if (endpointEntries.get(endpointKey)?.endpoint === endpoint) {
|
|
179
|
+
endpointEntries.delete(endpointKey);
|
|
180
|
+
for (const [id, route] of subscriptionRoutes) {
|
|
181
|
+
if (route.endpointKey === endpointKey)
|
|
182
|
+
subscriptionRoutes.delete(id);
|
|
183
|
+
}
|
|
184
|
+
endpoint.dispose();
|
|
185
|
+
}
|
|
186
|
+
endpoint = await getEndpoint(endpointKey, endpointRequest);
|
|
187
|
+
result = await endpoint.handleCall(routedCall, context);
|
|
188
|
+
}
|
|
151
189
|
if (isSubscribe && subscriptionId) {
|
|
152
190
|
const subscriptionRoute = {
|
|
153
191
|
endpointKey,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { DataContext } from "../context/data-context";
|
|
2
2
|
import type { Table } from "../data/orm/table";
|
|
3
|
-
import type { IToolInstance } from "../data/tools";
|
|
3
|
+
import type { ITool, IToolInstance } from "../data/tools";
|
|
4
4
|
import type { Event, ISubscriptionResult } from "../events";
|
|
5
5
|
import { type Observable } from "../observable";
|
|
6
6
|
import type { IIsolatedCallPrincipal } from "../package-loader/isolated-runtime";
|
|
7
|
+
import type { IWorkflowRunContext } from "../types/workflow-run-context";
|
|
7
8
|
import { type IContractTransport } from "./contract-transport";
|
|
8
9
|
import type { IContractDefinition, IRemoteContractConsumer } from "./types";
|
|
9
10
|
/**
|
|
@@ -222,6 +223,8 @@ export interface IContractConsumer extends IContractConsumerBase {
|
|
|
222
223
|
* contract calls unless a host explicitly opts in.
|
|
223
224
|
*/
|
|
224
225
|
export interface IContractToolContext {
|
|
226
|
+
/** Direct host invocation only; never forwarded to nested, remote or isolated calls. */
|
|
227
|
+
workflow?: IWorkflowRunContext;
|
|
225
228
|
/** Authorized data context in which the tool is executing. */
|
|
226
229
|
dataContextId: string;
|
|
227
230
|
/**
|
|
@@ -308,19 +311,26 @@ export declare function createRemoteContractConsumer(contract: IContractDefiniti
|
|
|
308
311
|
dataContextId?: string;
|
|
309
312
|
}): IRemoteContractConsumer<IContractConsumer>;
|
|
310
313
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
* the providing package); the proxy layer only needs the live members.
|
|
314
|
+
* Provider-local executable metadata. Existing IToolInstance values remain assignable;
|
|
315
|
+
* new implementations need no catalog ID, persisted row, or executable registration.
|
|
314
316
|
*/
|
|
317
|
+
export interface IContractToolImplementation {
|
|
318
|
+
/** Provider metadata; the contract member name supplies identity, without a Tools row. */
|
|
319
|
+
tool: Pick<ITool, "name" | "usageDescription"> & Partial<Pick<ITool, "toolId" | "detailedDescription" | "inputSchema" | "outputSchema" | "accessLevel" | "authorityPropagation" | "remoteAccessLevel">>;
|
|
320
|
+
toolFn: IToolInstance["toolFn"];
|
|
321
|
+
inputSchema?: IToolInstance["inputSchema"];
|
|
322
|
+
outputSchema?: IToolInstance["outputSchema"];
|
|
323
|
+
}
|
|
324
|
+
/** Context-bound live members of a provider; executable ownership stays with the contract. */
|
|
315
325
|
export interface IContractResolution {
|
|
316
326
|
contractId: string;
|
|
317
327
|
version: number;
|
|
318
328
|
tables?: Record<string, Table<any>>;
|
|
319
|
-
tools?: Record<string,
|
|
329
|
+
tools?: Record<string, IContractToolImplementation>;
|
|
320
330
|
/** Tool used for alias catalog admission; defaults to a tool requesting preservation. */
|
|
321
331
|
actionDispatcher?: string;
|
|
322
332
|
/** Private canonical tools; selectors never carry their own numeric permissions. */
|
|
323
|
-
actionTools?: Record<string,
|
|
333
|
+
actionTools?: Record<string, IContractToolImplementation>;
|
|
324
334
|
/** Package-owned selector-to-tool mapping, validated by the host. */
|
|
325
335
|
actionAliases?: Record<string, string>;
|
|
326
336
|
/**
|
|
@@ -365,6 +375,8 @@ export interface IContractResolution {
|
|
|
365
375
|
}
|
|
366
376
|
/** Identity of the caller, supplied by the transport layer for permission checks. */
|
|
367
377
|
export interface IContractCallContext {
|
|
378
|
+
/** In-process workflow capability; never serialized or propagated to nested calls. */
|
|
379
|
+
workflow?: IWorkflowRunContext;
|
|
368
380
|
/** User id of the caller (e.g. the remote device's user), if known. */
|
|
369
381
|
callerUserId?: string;
|
|
370
382
|
/** User id of the local device executing the call. */
|
|
@@ -531,4 +543,6 @@ export declare function createContractProviderEndpoint(resolution: IContractReso
|
|
|
531
543
|
* @param opts Optional permission gate and default caller context.
|
|
532
544
|
*/
|
|
533
545
|
export declare function createContractProviderSession(resolution: IContractResolution, transport: IContractTransport, opts?: IContractProviderSessionOptions): IContractProviderSession;
|
|
546
|
+
/** Require the directly invoked host tool’s workflow in the authorized data context. */
|
|
547
|
+
export declare function requireWorkflowRunContext(toolContext: IContractToolContext | undefined, toolName: string): IWorkflowRunContext;
|
|
534
548
|
export {};
|
|
@@ -8,6 +8,7 @@ exports.authorizedContractActionNames = authorizedContractActionNames;
|
|
|
8
8
|
exports.createContractProvider = createContractProvider;
|
|
9
9
|
exports.createContractProviderEndpoint = createContractProviderEndpoint;
|
|
10
10
|
exports.createContractProviderSession = createContractProviderSession;
|
|
11
|
+
exports.requireWorkflowRunContext = requireWorkflowRunContext;
|
|
11
12
|
const access_level_1 = require("../data/access-level");
|
|
12
13
|
const group_permissions_1 = require("../data/group-permissions");
|
|
13
14
|
const decorators_1 = require("../data/orm/decorators");
|
|
@@ -623,7 +624,8 @@ function createContractProvider(resolution, opts = {}) {
|
|
|
623
624
|
}
|
|
624
625
|
if (resolution.actionTools && !Object.keys(resolution.actionAliases ?? {}).length)
|
|
625
626
|
throw new Error("Action dispatcher requires registered checked actions");
|
|
626
|
-
const
|
|
627
|
+
const { workflow, ...caller } = context;
|
|
628
|
+
const authority = await assertContractToolAccess(tool, call, caller);
|
|
627
629
|
if (resolution.toolContext ||
|
|
628
630
|
resolution.forwardCallContext ||
|
|
629
631
|
resolution.actionAliases ||
|
|
@@ -631,24 +633,24 @@ function createContractProvider(resolution, opts = {}) {
|
|
|
631
633
|
if (call.args.length > 1) {
|
|
632
634
|
throw new Error("Permission denied: tool runs accept a single argument when host context is forwarded");
|
|
633
635
|
}
|
|
634
|
-
const base = resolution.toolContext ?? {
|
|
635
|
-
dataContextId:
|
|
636
|
+
const { workflow: _inheritedWorkflow, ...base } = resolution.toolContext ?? {
|
|
637
|
+
dataContextId: caller.dataContext?.dataContextId ?? "",
|
|
636
638
|
getVariable: async () => {
|
|
637
639
|
throw new Error("Persistent variables are not available to this contract tool");
|
|
638
640
|
},
|
|
639
641
|
};
|
|
640
642
|
const dispatchTool = (frame) => async (target, args) => {
|
|
641
643
|
if (target.dataContextId &&
|
|
642
|
-
target.dataContextId !==
|
|
644
|
+
target.dataContextId !== caller.dataContext?.dataContextId)
|
|
643
645
|
throw new Error("Cross-context action dispatch is denied");
|
|
644
646
|
if (target.targetDeviceId)
|
|
645
647
|
throw new Error("Strict delegated calls cannot target another device until authority transport is supported");
|
|
646
|
-
if (!
|
|
648
|
+
if (!caller.dataContext || !frame.userId)
|
|
647
649
|
throw new Error("Missing trusted execution context");
|
|
648
650
|
if (!opts.dispatchTool)
|
|
649
651
|
throw new Error("Runtime tool dispatch is unavailable");
|
|
650
652
|
return opts.dispatchTool(target, args, {
|
|
651
|
-
...
|
|
653
|
+
...caller,
|
|
652
654
|
authority: (0, action_dispatch_1.narrowActionPrincipal)(frame, true),
|
|
653
655
|
});
|
|
654
656
|
};
|
|
@@ -659,16 +661,17 @@ function createContractProvider(resolution, opts = {}) {
|
|
|
659
661
|
throw new Error(`Unknown action selector: ${name}`);
|
|
660
662
|
const actionCall = { ...call, name: target, args: [args] };
|
|
661
663
|
const actionContext = {
|
|
662
|
-
...
|
|
664
|
+
...caller,
|
|
663
665
|
authority: { ...frame, strictActionScope: true },
|
|
664
666
|
};
|
|
665
667
|
const checked = await assertContractToolAccess(action, actionCall, actionContext);
|
|
666
668
|
return action.toolFn(args, {
|
|
667
669
|
...capabilities,
|
|
668
|
-
|
|
670
|
+
workflow: undefined,
|
|
671
|
+
callerPackageId: caller.callerPackageId,
|
|
669
672
|
actorUserId: checked.userId,
|
|
670
673
|
authority: checked,
|
|
671
|
-
callContext: { ...
|
|
674
|
+
callContext: { ...caller, authority: checked },
|
|
672
675
|
incomingCallContext: actionContext,
|
|
673
676
|
dispatchAction: checkedActionDispatcher(checked, capabilities),
|
|
674
677
|
dispatchTool: dispatchTool(checked),
|
|
@@ -676,10 +679,11 @@ function createContractProvider(resolution, opts = {}) {
|
|
|
676
679
|
};
|
|
677
680
|
return tool.toolFn(call.args[0], {
|
|
678
681
|
...base,
|
|
679
|
-
|
|
682
|
+
workflow: caller.callerDeviceId || caller.callerPackageId ? undefined : workflow,
|
|
683
|
+
callerPackageId: caller.callerPackageId,
|
|
680
684
|
actorUserId: authority.userId,
|
|
681
|
-
callContext: { ...
|
|
682
|
-
incomingCallContext:
|
|
685
|
+
callContext: { ...caller, authority },
|
|
686
|
+
incomingCallContext: caller,
|
|
683
687
|
dispatchTool: dispatchTool(authority),
|
|
684
688
|
authority,
|
|
685
689
|
dispatchAction: resolution.actionAliases
|
|
@@ -982,3 +986,10 @@ function createContractProviderSession(resolution, transport, opts = {}) {
|
|
|
982
986
|
},
|
|
983
987
|
};
|
|
984
988
|
}
|
|
989
|
+
/** Require the directly invoked host tool’s workflow in the authorized data context. */
|
|
990
|
+
function requireWorkflowRunContext(toolContext, toolName) {
|
|
991
|
+
const workflow = toolContext?.workflow;
|
|
992
|
+
if (!workflow || workflow.dataContextId !== toolContext?.dataContextId)
|
|
993
|
+
throw new Error(`Tool ${toolName} requires a workflow run in the same data context`);
|
|
994
|
+
return workflow;
|
|
995
|
+
}
|
|
@@ -23,6 +23,18 @@ export interface ILocalContractVariableRequest {
|
|
|
23
23
|
}
|
|
24
24
|
/** Host callback that resolves plaintext only inside a local provider process. */
|
|
25
25
|
export type LocalContractVariableResolver = (request: ILocalContractVariableRequest) => Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Capture synchronous bundle registrations without publishing candidate code.
|
|
28
|
+
* The returned callback replaces this context's provider resolvers only when
|
|
29
|
+
* called in the loader's guarded activation turn. Throwing evaluation discards
|
|
30
|
+
* the batch; asynchronous installation never holds a process-wide capture open.
|
|
31
|
+
* Package resolvers always require an owning context and cannot change system
|
|
32
|
+
* signed-out routing or another package's implementation.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function stagePackageContractResolvers(dataContext: DataContext, providerPackageId: string, evaluate: () => void): () => void;
|
|
36
|
+
/** Retire one context's host resolver code without changing catalog history. @internal */
|
|
37
|
+
export declare function unregisterPackageContractResolvers(dataContext: DataContext, providerPackageId: string): void;
|
|
26
38
|
/** Options for a provider-scoped {@link registerContractResolver} registration. */
|
|
27
39
|
export interface IContractResolverRegistrationOptions {
|
|
28
40
|
/**
|
|
@@ -43,6 +55,8 @@ export type ContractRemoteIdentity = Pick<IDeviceInfo, "deviceId" | "userId" | "
|
|
|
43
55
|
* Register the resolver that provides the live implementation for a contract version.
|
|
44
56
|
* Later registrations for the same contract+version replace earlier ones (matching the
|
|
45
57
|
* registry's "latest provider wins for dev contracts" behavior).
|
|
58
|
+
* During PackageLoader bundle evaluation, registration is staged for activation
|
|
59
|
+
* in the owning context; direct runtime registrations remain process-wide.
|
|
46
60
|
*/
|
|
47
61
|
export declare function registerContractResolver(contractId: string, version: number, resolver: ContractResolver, opts?: IContractResolverRegistrationOptions): void;
|
|
48
62
|
/**
|
|
@@ -52,8 +66,10 @@ export declare function registerContractResolver(contractId: string, version: nu
|
|
|
52
66
|
* and the legacy unscoped registration remains the compatibility fallback.
|
|
53
67
|
* An unscoped lookup may use a sole scoped resolver for callers that have not
|
|
54
68
|
* yet adopted provider selection; multiple scoped providers remain ambiguous.
|
|
69
|
+
* Supplying `dataContext` first checks host packages activated in that context.
|
|
70
|
+
* Their staged registrations never replace global system/compatibility resolvers.
|
|
55
71
|
*/
|
|
56
|
-
export declare function getContractResolver(contractId: string, version: number, providerPackageId?: string): ContractResolver | undefined;
|
|
72
|
+
export declare function getContractResolver(contractId: string, version: number, providerPackageId?: string, dataContext?: DataContext): ContractResolver | undefined;
|
|
57
73
|
/**
|
|
58
74
|
* Verify that a connection- or message-authenticated remote identity matches a valid
|
|
59
75
|
* self-signed Users record in the provider's personal or shared-group data.
|
|
@@ -73,6 +89,8 @@ export declare function verifyRemoteContractIdentity(userContext: UserContext, r
|
|
|
73
89
|
* {@link TrustLevel.Self}. Unsigned discovery stubs and key mismatches fail closed.
|
|
74
90
|
*/
|
|
75
91
|
export declare function isSelfTrustedContractCaller(userContext: UserContext, remoteIdentity: ContractRemoteIdentity): Promise<boolean>;
|
|
92
|
+
/** Whether a contract must remain on the local host, including Device Operations targets. */
|
|
93
|
+
export declare function isLocalOnlyContractId(contractId: string): boolean;
|
|
76
94
|
/**
|
|
77
95
|
* Authorize one inbound contract call from **another device**.
|
|
78
96
|
*
|
|
@@ -87,7 +105,7 @@ export declare function isSelfTrustedContractCaller(userContext: UserContext, re
|
|
|
87
105
|
* group the provider belongs to, must be a tool run, the contract definition registered
|
|
88
106
|
* for that group must mark the tool with {@link IContractTool.remoteAccessLevel}, and
|
|
89
107
|
* the caller's role in that group must meet it. Missing loader, definition, or level
|
|
90
|
-
* fails closed.
|
|
108
|
+
* fails closed. Catalog readiness precedes reading the group's definitions.
|
|
91
109
|
*
|
|
92
110
|
* @param call Inbound call.
|
|
93
111
|
* @param userContext Provider's user context.
|
|
@@ -102,7 +120,8 @@ export declare function authorizeRemoteContractCall(call: IContractCall, userCon
|
|
|
102
120
|
* deferred until each call. This prevents renderer startup from racing provider
|
|
103
121
|
* registration. Calls may target only the local user's personal context or a
|
|
104
122
|
* currently known group context; arbitrary valid IDs are rejected before
|
|
105
|
-
* `UserContext.getDataContext` can create a new context.
|
|
123
|
+
* `UserContext.getDataContext` can create a new context. Provider selection waits
|
|
124
|
+
* for catalog readiness, including explicit provider requests on a cold context.
|
|
106
125
|
*
|
|
107
126
|
* Contracts registered with `requiresUserContext: false` resolve before sign-in
|
|
108
127
|
* when `peekUserContext` reports that no user is loaded. Every other call still
|