@peers-app/peers-sdk 0.25.18 → 0.25.19

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.
Files changed (33) hide show
  1. package/README.md +9 -1
  2. package/dist/contracts/__tests__/action-dispatch.test.d.ts +1 -0
  3. package/dist/contracts/__tests__/action-dispatch.test.js +339 -0
  4. package/dist/contracts/__tests__/generic-tool-dispatch.test.d.ts +1 -0
  5. package/dist/contracts/__tests__/generic-tool-dispatch.test.js +173 -0
  6. package/dist/contracts/action-dispatch.d.ts +12 -0
  7. package/dist/contracts/action-dispatch.js +73 -0
  8. package/dist/contracts/contract-provider-router.js +7 -1
  9. package/dist/contracts/contract-proxy.d.ts +47 -0
  10. package/dist/contracts/contract-proxy.js +152 -16
  11. package/dist/contracts/index.d.ts +2 -1
  12. package/dist/contracts/index.js +5 -2
  13. package/dist/contracts/system/contract-catalog-contract.d.ts +2 -0
  14. package/dist/data/messages.d.ts +3 -3
  15. package/dist/data/package-versions.d.ts +10 -10
  16. package/dist/data/tool-access.d.ts +3 -1
  17. package/dist/data/tool-access.js +5 -2
  18. package/dist/data/tool-access.test.js +7 -0
  19. package/dist/data/tools.d.ts +7 -3
  20. package/dist/data/tools.js +7 -1
  21. package/dist/invites/invite-messages.d.ts +2 -2
  22. package/dist/package-loader/host-authority-loading.test.d.ts +1 -0
  23. package/dist/package-loader/host-authority-loading.test.js +87 -0
  24. package/dist/package-loader/host-dispatch-policy.test.d.ts +1 -0
  25. package/dist/package-loader/host-dispatch-policy.test.js +56 -0
  26. package/dist/package-loader/isolated-artifact.d.ts +16 -2
  27. package/dist/package-loader/isolated-artifact.js +40 -3
  28. package/dist/package-loader/isolated-artifact.test.js +38 -0
  29. package/dist/package-loader/isolated-runtime.d.ts +15 -0
  30. package/dist/package-loader/package-loader.d.ts +2 -0
  31. package/dist/package-loader/package-loader.js +60 -0
  32. package/dist/package-loader/package-loader.test.js +1 -1
  33. package/package.json +1 -1
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ test("host-backed package endpoints honor declared propagation and reread live metadata", async () => {
5
+ const packageId = (0, index_1.newid)();
6
+ const contractId = (0, index_1.newid)();
7
+ const userId = (0, index_1.newid)();
8
+ const groupId = (0, index_1.newid)();
9
+ const dataContext = {
10
+ dataContextId: groupId,
11
+ groupId,
12
+ tableContainer: {
13
+ getTable: (meta) => meta.name === "Groups"
14
+ ? { get: async () => ({ founderUserId: (0, index_1.newid)() }) }
15
+ : { findOne: async () => ({ userId, role: index_1.GroupMemberRole.Admin }) },
16
+ },
17
+ };
18
+ Object.assign(dataContext, {
19
+ userContext: {
20
+ userId,
21
+ deviceId: () => (0, index_1.newid)(),
22
+ loadingPromise: Promise.resolve(),
23
+ userDataContext: dataContext,
24
+ getDataContext: () => dataContext,
25
+ },
26
+ });
27
+ const loader = new index_1.PackageLoader(dataContext);
28
+ const make = (name, level, fn) => ({
29
+ tool: {
30
+ toolId: (0, index_1.newid)(),
31
+ name,
32
+ accessLevel: level,
33
+ code: "",
34
+ usageDescription: name,
35
+ inputSchema: { type: index_1.IOSchemaType.simple, fields: [] },
36
+ outputSchema: { type: index_1.IOSchemaType.simple, fields: [] },
37
+ },
38
+ toolFn: fn,
39
+ });
40
+ const route = make("route", 20, (_, ctx) => ctx.dispatchAction("act", {}));
41
+ route.tool.authorityPropagation = "preserve";
42
+ const effect = jest.fn(() => "admin target");
43
+ (0, index_1.registerContractResolver)(contractId, 1, () => ({
44
+ contractId,
45
+ version: 1,
46
+ tools: { route },
47
+ actionAliases: { act: "target" },
48
+ actionTools: { target: make("target", 60, effect) },
49
+ }), { providerPackageId: packageId });
50
+ loader.contractRegistry.register(packageId, {
51
+ contractId,
52
+ version: 1,
53
+ name: "Arbitrary",
54
+ description: "",
55
+ tools: [{ name: "route", usageDescription: "route", inputFields: [], outputFields: [] }],
56
+ tables: [],
57
+ observables: [],
58
+ events: [],
59
+ });
60
+ const endpoint = await loader.resolveContractEndpoint({
61
+ contractId,
62
+ version: 1,
63
+ providerPackageId: packageId,
64
+ context: { callerUserId: userId, dataContext },
65
+ notify: async () => undefined,
66
+ });
67
+ const call = {
68
+ contractId,
69
+ version: 1,
70
+ member: "tool",
71
+ name: "route",
72
+ operation: "run",
73
+ args: [{}],
74
+ dataContextId: groupId,
75
+ };
76
+ try {
77
+ await expect(endpoint.handleCall(call)).resolves.toBe("admin target");
78
+ // Metadata is read on every call: an ordinary Reader wrapper caps the Admin at Reader.
79
+ route.tool.authorityPropagation = "cap";
80
+ await expect(endpoint.handleCall(call)).rejects.toThrow("strict delegation");
81
+ expect(effect).toHaveBeenCalledTimes(1);
82
+ }
83
+ finally {
84
+ endpoint.dispose();
85
+ await loader.dispose();
86
+ }
87
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ it("host dispatch honors preferred providers, missing-provider policy, and owner pinning", async () => {
5
+ const dataContext = { dataContextId: (0, index_1.newid)() };
6
+ const loader = new index_1.PackageLoader(dataContext);
7
+ const caller = (0, index_1.newid)();
8
+ const preferred = (0, index_1.newid)();
9
+ const alternate = (0, index_1.newid)();
10
+ const contractId = (0, index_1.newid)();
11
+ const definition = {
12
+ contractId,
13
+ version: 1,
14
+ name: "Target",
15
+ description: "",
16
+ tables: [],
17
+ events: [],
18
+ observables: [],
19
+ tools: [{ name: "target", usageDescription: "", inputFields: [], outputFields: [] }],
20
+ };
21
+ loader.contractRegistry.register(alternate, definition);
22
+ loader.contractRegistry.register(preferred, definition);
23
+ loader.hostConsumes.set(caller, [
24
+ { contractId, version: 1, providerPackageIds: [preferred], fallbackToDefaultProvider: false },
25
+ ]);
26
+ const resolve = jest
27
+ .spyOn(loader, "resolveContractEndpoint")
28
+ .mockImplementation(async (request) => ({ handleCall: async () => request.providerPackageId, dispose: () => undefined }));
29
+ const context = {
30
+ dataContext,
31
+ callerUserId: (0, index_1.newid)(),
32
+ authority: {
33
+ userId: (0, index_1.newid)(),
34
+ userAccessLevel: 60,
35
+ assistantAccessLevel: 40,
36
+ strictActionScope: true,
37
+ },
38
+ };
39
+ const dispatch = (providerPackageId) => loader.dispatchToolFromProvider(caller, { contractId, version: 1, toolName: "target", providerPackageId }, {}, context);
40
+ try {
41
+ await expect(dispatch()).resolves.toBe(preferred);
42
+ await expect(dispatch(alternate)).rejects.toThrow("Provider substitution");
43
+ loader.contractRegistry.unregister(preferred);
44
+ await expect(dispatch()).rejects.toThrow("No declared contract provider");
45
+ loader.hostConsumes.get(caller)[0].fallbackToDefaultProvider = true;
46
+ await expect(dispatch()).resolves.toBe(alternate);
47
+ loader.contractRegistry.register(caller, definition);
48
+ loader.hostConsumes.delete(caller);
49
+ await expect(dispatch(alternate)).rejects.toThrow("Provider substitution");
50
+ await expect(dispatch()).resolves.toBe(caller);
51
+ expect(resolve).toHaveBeenCalledTimes(3);
52
+ }
53
+ finally {
54
+ await loader.dispose();
55
+ }
56
+ });
@@ -22,7 +22,8 @@ export interface IIsolatedContractToolRef {
22
22
  *
23
23
  * Shape matches {@link IContractTool}. `suggestedAccessLevel` is the default
24
24
  * effective level until install-time approval exists and calls `approveTool`.
25
- * Omit it to keep the fail-closed Self default.
25
+ * Omitted admission is None (0) for a preservation request, otherwise fail-closed Self (100).
26
+ * Operator approval changes propagation, never admission.
26
27
  */
27
28
  export interface IIsolatedContractTool extends Omit<IContractTool, "inputFields" | "outputFields"> {
28
29
  /** Defaults to `[]` during validation when omitted. */
@@ -33,9 +34,13 @@ export interface IIsolatedContractTool extends Omit<IContractTool, "inputFields"
33
34
  * Default effective access level for this tool.
34
35
  *
35
36
  * Used until an administrator explicitly approves the tool at install time.
36
- * Omit to keep the fail-closed Self default.
37
+ * Omit for None (0) when requesting preservation; other tools default to Self (100).
37
38
  */
38
39
  suggestedAccessLevel?: number;
40
+ /** Declared outgoing authority mode; `preserve` omits this tool's own level from strict narrowing. */
41
+ authorityPropagation?: "cap" | "preserve";
42
+ /** Checked package-internal target, omitted from the public contract definition. */
43
+ private?: boolean;
39
44
  /**
40
45
  * Per-invoke deadline in milliseconds for this tool.
41
46
  *
@@ -136,6 +141,11 @@ export interface IIsolatedConsumedContract {
136
141
  providerPackageIds?: string[];
137
142
  fallbackToDefaultProvider?: boolean;
138
143
  }
144
+ /** Package-owned catalog selector bound to a registered canonical action. */
145
+ export interface IIsolatedActionAlias {
146
+ name: string;
147
+ target: IIsolatedContractToolRef;
148
+ }
139
149
  /**
140
150
  * Pure-data package description. Validated on the host; never executed there.
141
151
  */
@@ -149,6 +159,8 @@ export interface IIsolatedPackageManifest {
149
159
  /** Host allowlist and credential refs for System HTTP. Omit to deny outbound HTTP. */
150
160
  network?: IIsolatedPackageNetwork;
151
161
  appNavs?: IAppNav[];
162
+ /** Private checked targets, independent of the frozen Voice Skill catalog. */
163
+ actionAliases?: IIsolatedActionAlias[];
152
164
  }
153
165
  /**
154
166
  * Installable isolated package: declarative manifest plus provider source
@@ -181,6 +193,8 @@ export interface IIsolatedInstalledPackage {
181
193
  tables: IIsolatedPackageTable[];
182
194
  definitions: IContractDefinition[];
183
195
  appNavs?: IAppNav[];
196
+ /** Private checked targets, independent of the frozen Voice Skill catalog. */
197
+ actionAliases?: IIsolatedActionAlias[];
184
198
  }
185
199
  /** Host-contract member guests use to request a consumed-contract tool call. */
186
200
  export declare const ISOLATED_CONSUME_HOST_MEMBER = "contract.consume";
@@ -241,6 +241,8 @@ function validateProvidedContract(contract, index, packageTables) {
241
241
  "inputFields",
242
242
  "outputFields",
243
243
  "suggestedAccessLevel",
244
+ "authorityPropagation",
245
+ "private",
244
246
  "remoteAccessLevel",
245
247
  "timeoutMs",
246
248
  ], toolLabel);
@@ -256,6 +258,12 @@ function validateProvidedContract(contract, index, packageTables) {
256
258
  }
257
259
  tool.inputFields = validateFields(tool.inputFields, `${toolLabel}.inputFields`);
258
260
  tool.outputFields = validateFields(tool.outputFields, `${toolLabel}.outputFields`);
261
+ if (tool.authorityPropagation !== undefined &&
262
+ tool.authorityPropagation !== "cap" &&
263
+ tool.authorityPropagation !== "preserve")
264
+ throw new Error(`${toolLabel}.authorityPropagation must be cap or preserve`);
265
+ if (tool.private !== undefined && typeof tool.private !== "boolean")
266
+ throw new Error(`${toolLabel}.private must be a boolean`);
259
267
  if (tool.suggestedAccessLevel !== undefined) {
260
268
  if (!Number.isInteger(tool.suggestedAccessLevel) ||
261
269
  tool.suggestedAccessLevel < access_level_1.MIN_ACCESS_LEVEL ||
@@ -570,7 +578,16 @@ function validateIsolatedPackageArtifact(artifact) {
570
578
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
571
579
  throw new Error("Package artifact.manifest is required");
572
580
  }
573
- assertOnlyKeys(manifest, ["packageId", "version", "provides", "consumes", "tables", "network", "appNavs"], "manifest");
581
+ assertOnlyKeys(manifest, [
582
+ "packageId",
583
+ "version",
584
+ "provides",
585
+ "consumes",
586
+ "tables",
587
+ "network",
588
+ "appNavs",
589
+ "actionAliases",
590
+ ], "manifest");
574
591
  if (typeof providerSource !== "string" || providerSource.trim() === "") {
575
592
  throw new Error("Package artifact.providerSource must be a non-empty string");
576
593
  }
@@ -598,6 +615,22 @@ function validateIsolatedPackageArtifact(artifact) {
598
615
  for (const [index, dep] of manifest.consumes.entries()) {
599
616
  validateConsumedContract(dep, index);
600
617
  }
618
+ if (manifest.actionAliases !== undefined) {
619
+ if (!Array.isArray(manifest.actionAliases))
620
+ throw new Error("actionAliases must be an array");
621
+ const names = new Set();
622
+ for (const alias of manifest.actionAliases) {
623
+ if (!alias || typeof alias.name !== "string" || !alias.name || names.has(alias.name)) {
624
+ throw new Error("Invalid or duplicate action alias");
625
+ }
626
+ names.add(alias.name);
627
+ const target = alias.target;
628
+ if (!target ||
629
+ !packageProvidesTool(manifest.provides, target.contractId, target.version, target.toolName)) {
630
+ throw new Error(`Action alias ${alias.name} has no registered target`);
631
+ }
632
+ }
633
+ }
601
634
  manifest.appNavs = validateAppNavs(manifest.appNavs);
602
635
  validatePackageNetwork(manifest);
603
636
  return manifest;
@@ -650,12 +683,16 @@ function serializeIsolatedPackageArtifact(artifact) {
650
683
  * @param packageTables - Package-owned schemas available for contract exposure.
651
684
  */
652
685
  function isolatedContractToDefinition(contract, packageTables) {
653
- const tools = contract.tools.map((tool) => ({
686
+ const tools = contract.tools
687
+ .filter((tool) => !tool.private)
688
+ .map((tool) => ({
654
689
  name: tool.name,
655
690
  usageDescription: tool.usageDescription,
656
691
  inputFields: tool.inputFields ?? [],
657
692
  outputFields: tool.outputFields ?? [],
658
- ...(tool.remoteAccessLevel !== undefined ? { remoteAccessLevel: tool.remoteAccessLevel } : {}),
693
+ ...(tool.remoteAccessLevel !== undefined
694
+ ? { remoteAccessLevel: tool.remoteAccessLevel }
695
+ : {}),
659
696
  }));
660
697
  const tables = (contract.tables ?? []).map((tableRef) => {
661
698
  const table = findPackageTable(packageTables, tableRef.name);
@@ -40,6 +40,16 @@ function makeArtifact(overrides = {}) {
40
40
  };
41
41
  }
42
42
  describe("isolated package artifact", () => {
43
+ test("round-trips a generic authority request and rejects unknown modes", () => {
44
+ const { artifact } = makeArtifact();
45
+ const tool = artifact.manifest.provides[0].tools[0];
46
+ tool.authorityPropagation = "preserve";
47
+ expect(() => (0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)).not.toThrow();
48
+ expect((0, isolated_artifact_1.parseIsolatedPackageArtifact)((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)).manifest.provides[0]
49
+ .tools[0]).toMatchObject({ authorityPropagation: "preserve" });
50
+ tool.authorityPropagation = "elevate";
51
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/authorityPropagation/);
52
+ });
43
53
  test("serializes and parses a valid envelope", () => {
44
54
  const { artifact, contractId } = makeArtifact();
45
55
  const bundle = (0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact);
@@ -62,6 +72,34 @@ describe("isolated package artifact", () => {
62
72
  const manifest = (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact);
63
73
  expect(manifest.tables).toEqual([]);
64
74
  });
75
+ test("validates private action aliases without extending the public contract", () => {
76
+ const { artifact, contractId } = makeArtifact();
77
+ artifact.manifest.provides[0].tools.push({
78
+ name: "private-write",
79
+ usageDescription: "Internal write",
80
+ private: true,
81
+ suggestedAccessLevel: access_level_1.AccessLevel.Writer,
82
+ });
83
+ artifact.manifest.actionAliases = [
84
+ {
85
+ name: "write",
86
+ target: { contractId, version: 1, toolName: "private-write" },
87
+ },
88
+ ];
89
+ const validated = (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact);
90
+ expect((0, isolated_artifact_1.isolatedContractToDefinition)(validated.provides[0], []).tools.map((tool) => tool.name)).toEqual(["greet"]);
91
+ expect((0, isolated_artifact_1.parseIsolatedPackageArtifact)((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)).manifest
92
+ .actionAliases).toEqual(artifact.manifest.actionAliases);
93
+ artifact.manifest.actionAliases.push(artifact.manifest.actionAliases[0]);
94
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow("duplicate action alias");
95
+ artifact.manifest.actionAliases = [
96
+ {
97
+ name: "write",
98
+ target: { contractId, version: 1, toolName: "missing" },
99
+ },
100
+ ];
101
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow("no registered target");
102
+ });
65
103
  test("accepts a package-private table without adding it to the contract definition", () => {
66
104
  const { artifact } = makeArtifact();
67
105
  artifact.manifest.tables = [
@@ -15,6 +15,14 @@ export type IsolatedContractTableOperation = (typeof ISOLATED_CONTRACT_TABLE_OPE
15
15
  */
16
16
  export interface IIsolatedCallPrincipal {
17
17
  userAccessLevel: number;
18
+ /** Signed identity retained by the host across local hops. */
19
+ userId?: string;
20
+ /** Strict runtime tool-dispatch scope; never supplied by guest arguments. */
21
+ strictActionScope?: boolean;
22
+ /** Effective level of the currently executing ordinary tool. */
23
+ callingToolAccessLevel?: number;
24
+ /** Effective permission from the host, never requested package metadata. */
25
+ authorityPropagation?: "cap" | "preserve";
18
26
  /** Omit for a direct user invocation. */
19
27
  assistantAccessLevel?: number;
20
28
  }
@@ -86,6 +94,13 @@ export interface IIsolatedPackageRuntime {
86
94
  * @param principal - Trusted caller authority. Defaults to Self when omitted.
87
95
  */
88
96
  invokeOwnedTool?(packageId: string, ref: IIsolatedContractToolRef, args: unknown, principal?: IIsolatedCallPrincipal): Promise<unknown>;
97
+ /** Narrow a projected action turn by the trusted consumer's selected contract consume ceiling. */
98
+ actionDispatchPrincipal?(principal: IIsolatedCallPrincipal, consumerPackageId?: string, ref?: IIsolatedContractToolRef): IIsolatedCallPrincipal;
99
+ /** Project aliases whose wrapper and canonical targets allow the trusted caller. */
100
+ authorizedActionNames?(packageId: string, principal: IIsolatedCallPrincipal, consumerPackageId?: string, selectedContract?: {
101
+ contractId: string;
102
+ version: number;
103
+ }): string[] | Promise<string[]>;
89
104
  /** Terminate every worker and drop installed state. */
90
105
  dispose(): Promise<void>;
91
106
  }
@@ -28,6 +28,7 @@ export declare class PackageLoader {
28
28
  static PeersSDK: any;
29
29
  /** Host-injected Zod module used as the `zod` require. */
30
30
  static Zod: any;
31
+ private hostConsumes;
31
32
  private packageInstances;
32
33
  /** Packages that were installed via the contract-based path (tools/assistants/tables already saved with packageId). */
33
34
  private contractInstalledPackages;
@@ -64,6 +65,7 @@ export declare class PackageLoader {
64
65
  isIsolatedInstalled(packageId: string): boolean;
65
66
  /** Return whether this host configured an isolated-package runtime. */
66
67
  supportsIsolatedPackages(): boolean;
68
+ private dispatchToolFromProvider;
67
69
  /**
68
70
  * Return the active provider selected by this data context's trusted registry.
69
71
  *
@@ -45,6 +45,7 @@ class PackageLoader {
45
45
  static PeersSDK;
46
46
  /** Host-injected Zod module used as the `zod` require. */
47
47
  static Zod;
48
+ hostConsumes = new Map();
48
49
  packageInstances = {};
49
50
  /** Packages that were installed via the contract-based path (tools/assistants/tables already saved with packageId). */
50
51
  contractInstalledPackages = new Set();
@@ -114,6 +115,62 @@ class PackageLoader {
114
115
  supportsIsolatedPackages() {
115
116
  return this.isolatedRuntime !== undefined;
116
117
  }
118
+ async dispatchToolFromProvider(packageId, target, args, context) {
119
+ if (!context.authority?.userId ||
120
+ !context.dataContext ||
121
+ context.dataContext.dataContextId !== this.dataContext.dataContextId)
122
+ throw new Error("Missing trusted execution context");
123
+ if (target.targetDeviceId)
124
+ throw new Error("Strict delegated calls cannot target another device until authority transport is supported");
125
+ if (target.dataContextId && target.dataContextId !== this.dataContext.dataContextId)
126
+ throw new Error("Cross-context action dispatch is denied");
127
+ const own = this.contractRegistry
128
+ .getRegisteredProviderIds(target.contractId, target.version)
129
+ .includes(packageId);
130
+ const consume = this.hostConsumes
131
+ .get(packageId)
132
+ ?.find((c) => c.contractId === target.contractId && c.version === target.version);
133
+ if (!own && !consume)
134
+ throw new Error("Undeclared contract consumption");
135
+ if (own && target.providerPackageId && target.providerPackageId !== packageId && !consume)
136
+ throw new Error("Provider substitution is denied for package-owned targets");
137
+ const selected = own && (!target.providerPackageId || target.providerPackageId === packageId)
138
+ ? packageId
139
+ : consume
140
+ ? this.contractRegistry.selectProvider(consume)?.providerPackageId
141
+ : undefined;
142
+ if (!selected)
143
+ throw new Error("No declared contract provider is available");
144
+ if (target.providerPackageId && target.providerPackageId !== selected)
145
+ throw new Error("Provider substitution is denied");
146
+ const providerPackageId = selected;
147
+ const authority = this.isolatedRuntime?.actionDispatchPrincipal?.(context.authority, packageId, target) ??
148
+ context.authority;
149
+ const callContext = { ...context, authority, callerPackageId: packageId };
150
+ const endpoint = await this.resolveContractEndpoint({
151
+ contractId: target.contractId,
152
+ version: target.version,
153
+ dataContextId: this.dataContext.dataContextId,
154
+ providerPackageId,
155
+ context: callContext,
156
+ notify: async () => undefined,
157
+ });
158
+ try {
159
+ return await endpoint.handleCall({
160
+ contractId: target.contractId,
161
+ version: target.version,
162
+ name: target.toolName,
163
+ member: "tool",
164
+ operation: "run",
165
+ args: [args],
166
+ providerPackageId,
167
+ dataContextId: this.dataContext.dataContextId,
168
+ }, callContext);
169
+ }
170
+ finally {
171
+ endpoint.dispose();
172
+ }
173
+ }
117
174
  /**
118
175
  * Return the active provider selected by this data context's trusted registry.
119
176
  *
@@ -205,6 +262,7 @@ class PackageLoader {
205
262
  }
206
263
  : resolution;
207
264
  const endpoint = (0, contract_proxy_1.createContractProviderEndpoint)(localResolution, {
265
+ dispatchTool: (target, args, context) => this.dispatchToolFromProvider(providerPackageId, target, args, context),
208
266
  context: request.context,
209
267
  notify: (notification) => request.notify({
210
268
  ...notification,
@@ -318,6 +376,7 @@ class PackageLoader {
318
376
  * @param packageId - Package to unload.
319
377
  */
320
378
  async unloadPackage(packageId) {
379
+ this.hostConsumes.delete(packageId);
321
380
  if (this.isolatedRuntime?.isInstalled(packageId)) {
322
381
  await this.isolatedRuntime.uninstall(packageId);
323
382
  }
@@ -629,6 +688,7 @@ class PackageLoader {
629
688
  await (0, contract_package_loader_1.installContractPackage)(this.dataContext, packageDefinition, {
630
689
  registry: this.contractRegistry,
631
690
  });
691
+ this.hostConsumes.set(packageDefinition.packageId, packageDefinition.consumes);
632
692
  this.contractInstalledPackages.add(packageDefinition.packageId);
633
693
  await this.correctPackageVersion(pkg, selectedPackageVersion, packageDefinition.version);
634
694
  // Build a proper IPeersPackage from the contract definition so callers
@@ -176,7 +176,7 @@ describe("PackageLoader.contractRegistry", () => {
176
176
  name: "invoke",
177
177
  operation: "run",
178
178
  args: [],
179
- })).rejects.toThrow("missing caller identity for tool 'invoke'");
179
+ })).rejects.toThrow("missing caller identity or trusted context for tool 'invoke'");
180
180
  expect(write).not.toHaveBeenCalled();
181
181
  }
182
182
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.25.18",
3
+ "version": "0.25.19",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"