@peers-app/peers-sdk 0.25.21 → 0.26.1

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 (87) hide show
  1. package/dist/contracts/__tests__/builder.test.js +0 -2
  2. package/dist/contracts/__tests__/contract-provider-router.test.js +51 -0
  3. package/dist/contracts/__tests__/contract-proxy.test.js +21 -0
  4. package/dist/contracts/__tests__/contract-resolvers.test.js +84 -0
  5. package/dist/contracts/__tests__/legacy-removal.test.d.ts +1 -0
  6. package/dist/contracts/__tests__/legacy-removal.test.js +90 -0
  7. package/dist/contracts/__tests__/system-contracts.test.js +88 -0
  8. package/dist/contracts/__tests__/tool-catalog-inventory.test.d.ts +1 -0
  9. package/dist/contracts/__tests__/tool-catalog-inventory.test.js +44 -0
  10. package/dist/contracts/__tests__/tool-catalog-service.test.d.ts +1 -0
  11. package/dist/contracts/__tests__/tool-catalog-service.test.js +446 -0
  12. package/dist/contracts/__tests__/tool-catalog.test.d.ts +1 -0
  13. package/dist/contracts/__tests__/tool-catalog.test.js +924 -0
  14. package/dist/contracts/__tests__/workflow-context.test.d.ts +1 -0
  15. package/dist/contracts/__tests__/workflow-context.test.js +61 -0
  16. package/dist/contracts/builder.d.ts +9 -8
  17. package/dist/contracts/builder.js +34 -16
  18. package/dist/contracts/contract-provider-router.d.ts +12 -1
  19. package/dist/contracts/contract-provider-router.js +40 -2
  20. package/dist/contracts/contract-proxy.d.ts +20 -6
  21. package/dist/contracts/contract-proxy.js +23 -12
  22. package/dist/contracts/contract-resolvers.d.ts +22 -3
  23. package/dist/contracts/contract-resolvers.js +86 -7
  24. package/dist/contracts/extract.d.ts +7 -0
  25. package/dist/contracts/extract.js +3 -0
  26. package/dist/contracts/index.d.ts +11 -4
  27. package/dist/contracts/index.js +14 -2
  28. package/dist/contracts/registry.d.ts +3 -0
  29. package/dist/contracts/registry.js +2 -0
  30. package/dist/contracts/system/desktop-tools-contract.d.ts +5 -0
  31. package/dist/contracts/system/desktop-tools-contract.js +88 -0
  32. package/dist/contracts/system/host-tools-contract.d.ts +5 -0
  33. package/dist/contracts/system/host-tools-contract.js +76 -0
  34. package/dist/contracts/system/index.d.ts +3 -0
  35. package/dist/contracts/system/index.js +3 -0
  36. package/dist/contracts/system/tool-catalog-contract.d.ts +73 -0
  37. package/dist/contracts/system/tool-catalog-contract.js +70 -0
  38. package/dist/contracts/system-contracts.d.ts +3 -1
  39. package/dist/contracts/system-contracts.js +20 -6
  40. package/dist/contracts/tool-catalog-identity.d.ts +35 -0
  41. package/dist/contracts/tool-catalog-identity.js +88 -0
  42. package/dist/contracts/tool-catalog-migrations.d.ts +12 -0
  43. package/dist/contracts/tool-catalog-migrations.js +179 -0
  44. package/dist/contracts/tool-catalog-service.d.ts +30 -0
  45. package/dist/contracts/tool-catalog-service.js +287 -0
  46. package/dist/contracts/tool-catalog.d.ts +96 -0
  47. package/dist/contracts/tool-catalog.js +542 -0
  48. package/dist/contracts/types.d.ts +7 -5
  49. package/dist/data/messages.d.ts +3 -3
  50. package/dist/data/orm/sql.data-source.d.ts +5 -0
  51. package/dist/data/orm/sql.data-source.js +28 -10
  52. package/dist/data/orm/sql.data-source.test.d.ts +1 -1
  53. package/dist/data/orm/sql.data-source.test.js +160 -0
  54. package/dist/data/orm/table-container.d.ts +14 -0
  55. package/dist/data/orm/table-container.js +20 -0
  56. package/dist/data/tool-access-approvals.d.ts +6 -6
  57. package/dist/data/tool-catalog-fields.d.ts +31 -0
  58. package/dist/data/tool-catalog-fields.js +67 -0
  59. package/dist/data/tools.d.ts +38 -10
  60. package/dist/data/tools.js +31 -8
  61. package/dist/data/workflow-logs.d.ts +6 -2
  62. package/dist/data/workflow-logs.js +7 -5
  63. package/dist/data/workflow-runs.d.ts +6 -6
  64. package/dist/index.d.ts +0 -2
  65. package/dist/index.js +1 -3
  66. package/dist/package-loader/contract-package-loader.d.ts +12 -3
  67. package/dist/package-loader/contract-package-loader.js +54 -50
  68. package/dist/package-loader/contract-package-loader.test.d.ts +1 -1
  69. package/dist/package-loader/contract-package-loader.test.js +172 -13
  70. package/dist/package-loader/host-authority-loading.test.js +16 -3
  71. package/dist/package-loader/isolated-runtime.d.ts +14 -2
  72. package/dist/package-loader/isolated-table-gateway.d.ts +3 -1
  73. package/dist/package-loader/isolated-table-gateway.js +54 -5
  74. package/dist/package-loader/isolated-table-gateway.test.js +123 -0
  75. package/dist/package-loader/package-loader.d.ts +14 -2
  76. package/dist/package-loader/package-loader.js +184 -62
  77. package/dist/package-loader/package-loader.test.js +674 -1
  78. package/dist/package-loader/resolve-consumed-contracts.js +1 -1
  79. package/dist/system-ids.d.ts +6 -0
  80. package/dist/system-ids.js +7 -1
  81. package/dist/types/peers-package.d.ts +3 -2
  82. package/dist/types/workflow-run-context.d.ts +12 -2
  83. package/package.json +1 -1
  84. package/dist/tools/index.d.ts +0 -1
  85. package/dist/tools/index.js +0 -17
  86. package/dist/tools/tools-factory.d.ts +0 -5
  87. package/dist/tools/tools-factory.js +0 -34
@@ -0,0 +1 @@
1
+ import "../../index";
@@ -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
- /** Full runtime tool instances for this contract (used by the install flow). */
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
- /** Full tool instances with executable toolFn for runtime registration. */
39
- set toolInstances(instances: IToolInstance[]);
40
- get toolInstances(): IToolInstance[];
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
- toolInstances: IToolInstance[];
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 executable `toolInstances` /
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
- /** Full runtime tool instances for this contract (used by the install flow). */
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
- /** Full tool instances with executable toolFn for runtime registration. */
68
- set toolInstances(instances) {
69
- this._toolInstances = instances;
70
- }
71
- get toolInstances() {
72
- return this._toolInstances;
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
- toolInstances: [...this._toolInstances],
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 executable `toolInstances` /
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 allToolInstances = [];
333
+ const allToolMetadata = [];
316
334
  const allTableDefinitions = [];
317
335
  for (const cb of this._contracts) {
318
- const { definition, alsoImplements, toolInstances, tableDefinitions } = cb._build();
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
- allToolInstances.push(...toolInstances);
341
+ allToolMetadata.push(...toolMetadata);
324
342
  allTableDefinitions.push(...tableDefinitions);
325
343
  }
326
- if (contracts.length === 0 && (allToolInstances.length > 0 || allTableDefinitions.length > 0)) {
327
- throw new Error("Tables and executable tools must be declared on a contract (use pkg.contract(...), then assign .tables, .tools, .toolInstances, or .tableDefinitions on that builder). UI-only packages omit contracts and do not register tables or tools.");
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
- toolInstances: allToolInstances,
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
- const endpoint = await getEndpoint(endpointKey, endpointRequest);
150
- const result = await endpoint.handleCall(routedCall, context);
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
- * The live implementation a **provider** dispatches contract calls to. This is the
312
- * runtime-callable counterpart to {@link IResolvedContract} (which additionally tracks
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, IToolInstance>;
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, IToolInstance>;
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 authority = await assertContractToolAccess(tool, call, context);
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: context.dataContext?.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 !== context.dataContext?.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 (!context.dataContext || !frame.userId)
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
- ...context,
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
- ...context,
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
- callerPackageId: context.callerPackageId,
670
+ workflow: undefined,
671
+ callerPackageId: caller.callerPackageId,
669
672
  actorUserId: checked.userId,
670
673
  authority: checked,
671
- callContext: { ...context, authority: checked },
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
- callerPackageId: context.callerPackageId,
682
+ workflow: caller.callerDeviceId || caller.callerPackageId ? undefined : workflow,
683
+ callerPackageId: caller.callerPackageId,
680
684
  actorUserId: authority.userId,
681
- callContext: { ...context, authority },
682
- incomingCallContext: context,
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