@peers-app/peers-sdk 0.21.7 → 0.23.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.
Files changed (53) hide show
  1. package/dist/context/user-context-singleton.d.ts +42 -0
  2. package/dist/context/user-context-singleton.js +45 -0
  3. package/dist/contracts/__tests__/contract-proxy.test.js +51 -1
  4. package/dist/contracts/__tests__/contract-resolvers.test.js +65 -0
  5. package/dist/contracts/__tests__/playground.test.js +5 -2
  6. package/dist/contracts/__tests__/system-contracts.test.js +3 -0
  7. package/dist/contracts/contract-proxy.d.ts +27 -0
  8. package/dist/contracts/contract-proxy.js +37 -1
  9. package/dist/contracts/contract-resolvers.d.ts +10 -0
  10. package/dist/contracts/contract-resolvers.js +21 -3
  11. package/dist/contracts/index.d.ts +3 -2
  12. package/dist/contracts/index.js +5 -2
  13. package/dist/contracts/system/index.d.ts +2 -0
  14. package/dist/contracts/system/index.js +5 -1
  15. package/dist/contracts/system/tool-access-control-contract.d.ts +35 -0
  16. package/dist/contracts/system/tool-access-control-contract.js +70 -0
  17. package/dist/data/access-level.d.ts +72 -0
  18. package/dist/data/access-level.js +109 -0
  19. package/dist/data/access-level.test.d.ts +1 -0
  20. package/dist/data/access-level.test.js +53 -0
  21. package/dist/data/assistants.d.ts +24 -0
  22. package/dist/data/assistants.js +21 -0
  23. package/dist/data/group-member-roles.d.ts +9 -0
  24. package/dist/data/group-member-roles.js +9 -0
  25. package/dist/data/group-permissions.d.ts +55 -1
  26. package/dist/data/group-permissions.js +76 -6
  27. package/dist/data/index.d.ts +3 -1
  28. package/dist/data/index.js +3 -1
  29. package/dist/data/orm/sql.data-source.d.ts +10 -0
  30. package/dist/data/orm/sql.data-source.js +48 -30
  31. package/dist/data/orm/sql.data-source.test.js +13 -0
  32. package/dist/data/package-version-resolver.test.js +2 -0
  33. package/dist/data/tool-access-approvals.d.ts +126 -0
  34. package/dist/data/tool-access-approvals.js +86 -0
  35. package/dist/data/tool-access.d.ts +162 -0
  36. package/dist/data/tool-access.js +235 -0
  37. package/dist/data/tool-access.test.d.ts +1 -0
  38. package/dist/data/tool-access.test.js +159 -0
  39. package/dist/data/tools.d.ts +15 -0
  40. package/dist/data/tools.js +11 -0
  41. package/dist/data/workflow-logs.d.ts +5 -5
  42. package/dist/data/workflow-runs.d.ts +2 -2
  43. package/dist/device-pairing/types.d.ts +6 -6
  44. package/dist/package-installer/package-creator.d.ts +31 -0
  45. package/dist/package-installer/package-creator.js +140 -40
  46. package/dist/package-installer/package-creator.test.js +113 -11
  47. package/dist/rpc-types.d.ts +0 -17
  48. package/dist/rpc-types.js +0 -15
  49. package/dist/system-ids.d.ts +13 -0
  50. package/dist/system-ids.js +14 -1
  51. package/package.json +1 -1
  52. package/dist/data/voice-messages.d.ts +0 -33
  53. package/dist/data/voice-messages.js +0 -33
@@ -0,0 +1,162 @@
1
+ /** Workflow-run var naming the Assistant whose delegated authority applies. */
2
+ export declare const ACCESS_ASSISTANT_ID_VAR = "accessAssistantId";
3
+ /** Workflow-run var used as a display / fallback Assistant id. */
4
+ export declare const ASSISTANT_ID_VAR = "assistantId";
5
+ /** Workflow-run var set when the user invoked a tool directly (CLI/UI/RPC). */
6
+ export declare const DIRECT_USER_INVOCATION_VAR = "directUserInvocation";
7
+ /** Workflow-run var pointing at a host-consumed approval that authorizes this run. */
8
+ export declare const HOST_APPROVED_APPROVAL_ID_VAR = "hostApprovedApprovalId";
9
+ /**
10
+ * Host-owned access keys. Models must not set these via `set-variable` or
11
+ * `run-a-workflow` child vars.
12
+ */
13
+ export declare const RESERVED_ACCESS_VAR_KEYS: readonly ["accessAssistantId", "assistantId", "directUserInvocation", "hostApprovedApprovalId"];
14
+ /** How long a pending approval stays actionable. */
15
+ export declare const TOOL_ACCESS_APPROVAL_TTL_MS: number;
16
+ /** How long an approved thread- or context-scoped grant remains reusable. */
17
+ export declare const TOOL_ACCESS_GRANT_TTL_MS: number;
18
+ /** Outcome of {@link decideToolAccess}. */
19
+ export type ToolAccessDecision = {
20
+ kind: "allow";
21
+ } | {
22
+ kind: "deny";
23
+ reason: string;
24
+ } | {
25
+ kind: "approval_required";
26
+ reason: string;
27
+ };
28
+ /**
29
+ * Decide whether a tool may run automatically, needs step-up approval, or is denied.
30
+ *
31
+ * Policy (`U` user, `A` Assistant, `T` tool):
32
+ * - Direct user invocation: allow when `T <= U`, otherwise deny.
33
+ * - Assistant invocation: deny when `T > U`; allow when `T <= A` or a matching
34
+ * grant exists; require approval when `A < T <= U`.
35
+ *
36
+ * @param args.userAccessLevel - Current user's group/personal access.
37
+ * @param args.toolAccessLevel - Required level declared on the tool.
38
+ * @param args.assistantAccessLevel - Assistant delegated level; omit for direct user calls.
39
+ * @param args.hasMatchingGrant - True when a reusable scoped grant already covers this call.
40
+ */
41
+ export declare function decideToolAccess(args: {
42
+ userAccessLevel: number;
43
+ toolAccessLevel: number;
44
+ assistantAccessLevel?: number;
45
+ hasMatchingGrant?: boolean;
46
+ }): ToolAccessDecision;
47
+ /**
48
+ * Return whether a user may invoke an Assistant in the current context.
49
+ *
50
+ * @param userAccessLevel - Current user's access.
51
+ * @param assistantAccessLevel - Assistant minimum / delegated level.
52
+ */
53
+ export declare function canInvokeAssistant(userAccessLevel: number, assistantAccessLevel: number): boolean;
54
+ /**
55
+ * Canonical hash of tool arguments used to bind one-time approvals.
56
+ *
57
+ * @param args - Tool input object; `undefined` hashes as an empty object.
58
+ */
59
+ export declare function hashToolArgs(args: Record<string, unknown> | undefined): string;
60
+ /**
61
+ * Append the required access level to a model-facing tool description.
62
+ *
63
+ * @param usageDescription - Existing tool usage text.
64
+ * @param accessLevel - Required level, already resolved or raw.
65
+ */
66
+ export declare function annotateToolAccessDescription(usageDescription: string, accessLevel: unknown): string;
67
+ /**
68
+ * Return the fail-closed access level for a record that may omit the field.
69
+ *
70
+ * @param record - Tool or Assistant-like object.
71
+ */
72
+ export declare function recordAccessLevel(record: {
73
+ accessLevel?: unknown;
74
+ } | undefined): number;
75
+ /**
76
+ * Choose the delegated Assistant ceiling for a run.
77
+ *
78
+ * Direct user calls have no Assistant ceiling (`undefined`). Otherwise only
79
+ * levels from existing Assistant records are considered; a missing record
80
+ * does not default to Self. When both an access-assistant and the run default
81
+ * resolve, the lower level wins so vars cannot escalate past the host-owned
82
+ * default Assistant.
83
+ *
84
+ * @returns `undefined` for direct user calls; otherwise 0–100 (`0` if none resolve).
85
+ */
86
+ export declare function resolveDelegatedAccessLevel(args: {
87
+ directUser: boolean;
88
+ accessAssistantLevel?: number;
89
+ defaultAssistantLevel?: number;
90
+ }): number | undefined;
91
+ /**
92
+ * Return whether `name` is a host-owned access-control workflow var.
93
+ *
94
+ * @param name - Candidate variable name.
95
+ */
96
+ export declare function isReservedAccessVarName(name: string): boolean;
97
+ /**
98
+ * Return the thread root id for a message: its parent, or itself if it is the root.
99
+ *
100
+ * Thread-scoped grants must key off this id so a follow-up reply in the same
101
+ * thread matches the original approval.
102
+ *
103
+ * @param message - Message or message-like object; `undefined` returns `undefined`.
104
+ */
105
+ export declare function threadRootMessageId(message: {
106
+ messageId?: string;
107
+ messageParentId?: string;
108
+ } | undefined): string | undefined;
109
+ /**
110
+ * Structured result returned to a model when a tool needs user approval.
111
+ */
112
+ export interface IToolAccessApprovalRequiredResult {
113
+ status: "approval_required";
114
+ approvalId: string;
115
+ toolId: string;
116
+ toolName: string;
117
+ requiredAccessLevel: number;
118
+ requiredAccessLabel: string;
119
+ message: string;
120
+ }
121
+ /**
122
+ * Build the model-visible payload for a pending approval.
123
+ */
124
+ export declare function approvalRequiredResult(args: {
125
+ approvalId: string;
126
+ toolId: string;
127
+ toolName: string;
128
+ requiredAccessLevel: number;
129
+ message: string;
130
+ }): IToolAccessApprovalRequiredResult;
131
+ /** Workflow plumbing / assistant-runner tools that must not block invocation. */
132
+ export declare const INFRASTRUCTURE_ACCESS_LEVEL: 0;
133
+ /**
134
+ * Remove one-shot host access tickets from a vars object.
135
+ *
136
+ * `directUserInvocation` and `hostApprovedApprovalId` authorize a single
137
+ * workflow run. Persisting them on a thread message lets later calls reuse a
138
+ * once-approval or skip Assistant-level checks.
139
+ *
140
+ * @param vars - Workflow or message vars; `undefined` becomes `{}`.
141
+ */
142
+ export declare function omitEphemeralAccessVars(vars: Record<string, unknown> | undefined): Record<string, unknown>;
143
+ /**
144
+ * Strip every host-owned access key, including Assistant identity vars.
145
+ *
146
+ * Use this before showing vars to a model or copying caller-supplied child
147
+ * vars onto a nested run.
148
+ *
149
+ * @param vars - Workflow or message vars; `undefined` becomes `{}`.
150
+ */
151
+ export declare function omitReservedAccessVars(vars: Record<string, unknown> | undefined): Record<string, unknown>;
152
+ /**
153
+ * Copy access-authority vars onto a child workflow so nested runs cannot escalate.
154
+ *
155
+ * Caller-supplied reserved keys are stripped. The child keeps the parent's
156
+ * Assistant id and whether the *parent* was a direct user invocation.
157
+ *
158
+ * @param parentVars - Parent workflow vars.
159
+ * @param parentDefaultAssistantId - Parent run's default Assistant.
160
+ * @param childVars - Vars about to be stored on the child.
161
+ */
162
+ export declare function inheritAccessVars(parentVars: Record<string, unknown> | undefined, parentDefaultAssistantId: string | undefined, childVars: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INFRASTRUCTURE_ACCESS_LEVEL = exports.TOOL_ACCESS_GRANT_TTL_MS = exports.TOOL_ACCESS_APPROVAL_TTL_MS = exports.RESERVED_ACCESS_VAR_KEYS = exports.HOST_APPROVED_APPROVAL_ID_VAR = exports.DIRECT_USER_INVOCATION_VAR = exports.ASSISTANT_ID_VAR = exports.ACCESS_ASSISTANT_ID_VAR = void 0;
4
+ exports.decideToolAccess = decideToolAccess;
5
+ exports.canInvokeAssistant = canInvokeAssistant;
6
+ exports.hashToolArgs = hashToolArgs;
7
+ exports.annotateToolAccessDescription = annotateToolAccessDescription;
8
+ exports.recordAccessLevel = recordAccessLevel;
9
+ exports.resolveDelegatedAccessLevel = resolveDelegatedAccessLevel;
10
+ exports.isReservedAccessVarName = isReservedAccessVarName;
11
+ exports.threadRootMessageId = threadRootMessageId;
12
+ exports.approvalRequiredResult = approvalRequiredResult;
13
+ exports.omitEphemeralAccessVars = omitEphemeralAccessVars;
14
+ exports.omitReservedAccessVars = omitReservedAccessVars;
15
+ exports.inheritAccessVars = inheritAccessVars;
16
+ const keys_1 = require("../keys");
17
+ const access_level_1 = require("./access-level");
18
+ /** Workflow-run var naming the Assistant whose delegated authority applies. */
19
+ exports.ACCESS_ASSISTANT_ID_VAR = "accessAssistantId";
20
+ /** Workflow-run var used as a display / fallback Assistant id. */
21
+ exports.ASSISTANT_ID_VAR = "assistantId";
22
+ /** Workflow-run var set when the user invoked a tool directly (CLI/UI/RPC). */
23
+ exports.DIRECT_USER_INVOCATION_VAR = "directUserInvocation";
24
+ /** Workflow-run var pointing at a host-consumed approval that authorizes this run. */
25
+ exports.HOST_APPROVED_APPROVAL_ID_VAR = "hostApprovedApprovalId";
26
+ /**
27
+ * Host-owned access keys. Models must not set these via `set-variable` or
28
+ * `run-a-workflow` child vars.
29
+ */
30
+ exports.RESERVED_ACCESS_VAR_KEYS = [
31
+ exports.ACCESS_ASSISTANT_ID_VAR,
32
+ exports.ASSISTANT_ID_VAR,
33
+ exports.DIRECT_USER_INVOCATION_VAR,
34
+ exports.HOST_APPROVED_APPROVAL_ID_VAR,
35
+ ];
36
+ /** How long a pending approval stays actionable. */
37
+ exports.TOOL_ACCESS_APPROVAL_TTL_MS = 15 * 60 * 1000;
38
+ /** How long an approved thread- or context-scoped grant remains reusable. */
39
+ exports.TOOL_ACCESS_GRANT_TTL_MS = 15 * 60 * 1000;
40
+ /**
41
+ * Decide whether a tool may run automatically, needs step-up approval, or is denied.
42
+ *
43
+ * Policy (`U` user, `A` Assistant, `T` tool):
44
+ * - Direct user invocation: allow when `T <= U`, otherwise deny.
45
+ * - Assistant invocation: deny when `T > U`; allow when `T <= A` or a matching
46
+ * grant exists; require approval when `A < T <= U`.
47
+ *
48
+ * @param args.userAccessLevel - Current user's group/personal access.
49
+ * @param args.toolAccessLevel - Required level declared on the tool.
50
+ * @param args.assistantAccessLevel - Assistant delegated level; omit for direct user calls.
51
+ * @param args.hasMatchingGrant - True when a reusable scoped grant already covers this call.
52
+ */
53
+ function decideToolAccess(args) {
54
+ const userAccessLevel = (0, access_level_1.resolveAccessLevel)(args.userAccessLevel);
55
+ const toolAccessLevel = (0, access_level_1.resolveAccessLevel)(args.toolAccessLevel);
56
+ const toolLabel = `${(0, access_level_1.getAccessLevelLabel)(toolAccessLevel)} (${toolAccessLevel})`;
57
+ const userLabel = `${(0, access_level_1.getAccessLevelLabel)(userAccessLevel)} (${userAccessLevel})`;
58
+ if (toolAccessLevel > userAccessLevel) {
59
+ return {
60
+ kind: "deny",
61
+ reason: `Tool requires ${toolLabel}, but the current user has ${userLabel}.`,
62
+ };
63
+ }
64
+ if (args.assistantAccessLevel == null) {
65
+ return { kind: "allow" };
66
+ }
67
+ const assistantAccessLevel = (0, access_level_1.resolveAccessLevel)(args.assistantAccessLevel);
68
+ if (args.hasMatchingGrant || toolAccessLevel <= assistantAccessLevel) {
69
+ return { kind: "allow" };
70
+ }
71
+ return {
72
+ kind: "approval_required",
73
+ reason: `Tool requires ${toolLabel}. ${(0, access_level_1.getAccessLevelLabel)(assistantAccessLevel)} (${assistantAccessLevel}) can request it; ${userLabel} must approve.`,
74
+ };
75
+ }
76
+ /**
77
+ * Return whether a user may invoke an Assistant in the current context.
78
+ *
79
+ * @param userAccessLevel - Current user's access.
80
+ * @param assistantAccessLevel - Assistant minimum / delegated level.
81
+ */
82
+ function canInvokeAssistant(userAccessLevel, assistantAccessLevel) {
83
+ return (0, access_level_1.resolveAccessLevel)(userAccessLevel) >= (0, access_level_1.resolveAccessLevel)(assistantAccessLevel);
84
+ }
85
+ /**
86
+ * Canonical hash of tool arguments used to bind one-time approvals.
87
+ *
88
+ * @param args - Tool input object; `undefined` hashes as an empty object.
89
+ */
90
+ function hashToolArgs(args) {
91
+ return (0, keys_1.hashObject)(args && typeof args === "object" && !Array.isArray(args) ? args : {});
92
+ }
93
+ /**
94
+ * Append the required access level to a model-facing tool description.
95
+ *
96
+ * @param usageDescription - Existing tool usage text.
97
+ * @param accessLevel - Required level, already resolved or raw.
98
+ */
99
+ function annotateToolAccessDescription(usageDescription, accessLevel) {
100
+ const level = (0, access_level_1.resolveAccessLevel)(accessLevel);
101
+ const annotation = `Required access level: ${(0, access_level_1.getAccessLevelLabel)(level)} (${level}).`;
102
+ if (usageDescription.includes("Required access level:")) {
103
+ return usageDescription;
104
+ }
105
+ return `${usageDescription}\n\n${annotation}`;
106
+ }
107
+ /**
108
+ * Return the fail-closed access level for a record that may omit the field.
109
+ *
110
+ * @param record - Tool or Assistant-like object.
111
+ */
112
+ function recordAccessLevel(record) {
113
+ return record?.accessLevel == null
114
+ ? access_level_1.DEFAULT_ACCESS_LEVEL
115
+ : (0, access_level_1.resolveAccessLevel)(record.accessLevel);
116
+ }
117
+ /**
118
+ * Choose the delegated Assistant ceiling for a run.
119
+ *
120
+ * Direct user calls have no Assistant ceiling (`undefined`). Otherwise only
121
+ * levels from existing Assistant records are considered; a missing record
122
+ * does not default to Self. When both an access-assistant and the run default
123
+ * resolve, the lower level wins so vars cannot escalate past the host-owned
124
+ * default Assistant.
125
+ *
126
+ * @returns `undefined` for direct user calls; otherwise 0–100 (`0` if none resolve).
127
+ */
128
+ function resolveDelegatedAccessLevel(args) {
129
+ if (args.directUser) {
130
+ return undefined;
131
+ }
132
+ const levels = [];
133
+ if (args.accessAssistantLevel != null) {
134
+ levels.push((0, access_level_1.resolveAccessLevel)(args.accessAssistantLevel));
135
+ }
136
+ if (args.defaultAssistantLevel != null) {
137
+ levels.push((0, access_level_1.resolveAccessLevel)(args.defaultAssistantLevel));
138
+ }
139
+ if (levels.length === 0) {
140
+ return access_level_1.AccessLevel.None;
141
+ }
142
+ return Math.min(...levels);
143
+ }
144
+ /**
145
+ * Return whether `name` is a host-owned access-control workflow var.
146
+ *
147
+ * @param name - Candidate variable name.
148
+ */
149
+ function isReservedAccessVarName(name) {
150
+ return exports.RESERVED_ACCESS_VAR_KEYS.includes(name);
151
+ }
152
+ /**
153
+ * Return the thread root id for a message: its parent, or itself if it is the root.
154
+ *
155
+ * Thread-scoped grants must key off this id so a follow-up reply in the same
156
+ * thread matches the original approval.
157
+ *
158
+ * @param message - Message or message-like object; `undefined` returns `undefined`.
159
+ */
160
+ function threadRootMessageId(message) {
161
+ if (!message) {
162
+ return undefined;
163
+ }
164
+ return message.messageParentId || message.messageId;
165
+ }
166
+ /**
167
+ * Build the model-visible payload for a pending approval.
168
+ */
169
+ function approvalRequiredResult(args) {
170
+ return {
171
+ status: "approval_required",
172
+ approvalId: args.approvalId,
173
+ toolId: args.toolId,
174
+ toolName: args.toolName,
175
+ requiredAccessLevel: args.requiredAccessLevel,
176
+ requiredAccessLabel: (0, access_level_1.getAccessLevelLabel)(args.requiredAccessLevel),
177
+ message: args.message,
178
+ };
179
+ }
180
+ /** Workflow plumbing / assistant-runner tools that must not block invocation. */
181
+ exports.INFRASTRUCTURE_ACCESS_LEVEL = access_level_1.AccessLevel.None;
182
+ /**
183
+ * Remove one-shot host access tickets from a vars object.
184
+ *
185
+ * `directUserInvocation` and `hostApprovedApprovalId` authorize a single
186
+ * workflow run. Persisting them on a thread message lets later calls reuse a
187
+ * once-approval or skip Assistant-level checks.
188
+ *
189
+ * @param vars - Workflow or message vars; `undefined` becomes `{}`.
190
+ */
191
+ function omitEphemeralAccessVars(vars) {
192
+ if (!vars) {
193
+ return {};
194
+ }
195
+ const next = { ...vars };
196
+ delete next[exports.DIRECT_USER_INVOCATION_VAR];
197
+ delete next[exports.HOST_APPROVED_APPROVAL_ID_VAR];
198
+ return next;
199
+ }
200
+ /**
201
+ * Strip every host-owned access key, including Assistant identity vars.
202
+ *
203
+ * Use this before showing vars to a model or copying caller-supplied child
204
+ * vars onto a nested run.
205
+ *
206
+ * @param vars - Workflow or message vars; `undefined` becomes `{}`.
207
+ */
208
+ function omitReservedAccessVars(vars) {
209
+ const next = omitEphemeralAccessVars(vars);
210
+ delete next[exports.ACCESS_ASSISTANT_ID_VAR];
211
+ delete next[exports.ASSISTANT_ID_VAR];
212
+ return next;
213
+ }
214
+ /**
215
+ * Copy access-authority vars onto a child workflow so nested runs cannot escalate.
216
+ *
217
+ * Caller-supplied reserved keys are stripped. The child keeps the parent's
218
+ * Assistant id and whether the *parent* was a direct user invocation.
219
+ *
220
+ * @param parentVars - Parent workflow vars.
221
+ * @param parentDefaultAssistantId - Parent run's default Assistant.
222
+ * @param childVars - Vars about to be stored on the child.
223
+ */
224
+ function inheritAccessVars(parentVars, parentDefaultAssistantId, childVars) {
225
+ const vars = parentVars || {};
226
+ const parentAccessId = (typeof vars[exports.ACCESS_ASSISTANT_ID_VAR] === "string" && vars[exports.ACCESS_ASSISTANT_ID_VAR]) ||
227
+ (typeof vars[exports.ASSISTANT_ID_VAR] === "string" && vars[exports.ASSISTANT_ID_VAR]) ||
228
+ parentDefaultAssistantId;
229
+ return {
230
+ ...omitReservedAccessVars(childVars),
231
+ [exports.ACCESS_ASSISTANT_ID_VAR]: parentAccessId,
232
+ [exports.ASSISTANT_ID_VAR]: parentAccessId,
233
+ [exports.DIRECT_USER_INVOCATION_VAR]: vars[exports.DIRECT_USER_INVOCATION_VAR] ?? false,
234
+ };
235
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const access_level_1 = require("./access-level");
4
+ const tool_access_1 = require("./tool-access");
5
+ describe("decideToolAccess", () => {
6
+ it("allows a direct user call at or below their ceiling", () => {
7
+ expect((0, tool_access_1.decideToolAccess)({
8
+ userAccessLevel: access_level_1.AccessLevel.Self,
9
+ toolAccessLevel: access_level_1.AccessLevel.Admin,
10
+ }).kind).toBe("allow");
11
+ });
12
+ it("denies a tool above the user ceiling", () => {
13
+ const decision = (0, tool_access_1.decideToolAccess)({
14
+ userAccessLevel: access_level_1.AccessLevel.Writer,
15
+ toolAccessLevel: access_level_1.AccessLevel.Admin,
16
+ assistantAccessLevel: access_level_1.AccessLevel.Writer,
17
+ });
18
+ expect(decision.kind).toBe("deny");
19
+ });
20
+ it("allows an Assistant to run tools at or below its level", () => {
21
+ expect((0, tool_access_1.decideToolAccess)({
22
+ userAccessLevel: access_level_1.AccessLevel.Self,
23
+ assistantAccessLevel: access_level_1.AccessLevel.Writer,
24
+ toolAccessLevel: access_level_1.AccessLevel.Writer,
25
+ }).kind).toBe("allow");
26
+ });
27
+ it("requires approval when A < T <= U", () => {
28
+ const decision = (0, tool_access_1.decideToolAccess)({
29
+ userAccessLevel: access_level_1.AccessLevel.Self,
30
+ assistantAccessLevel: access_level_1.AccessLevel.Writer,
31
+ toolAccessLevel: access_level_1.AccessLevel.Admin,
32
+ });
33
+ expect(decision.kind).toBe("approval_required");
34
+ });
35
+ it("allows a matching grant to skip another prompt", () => {
36
+ expect((0, tool_access_1.decideToolAccess)({
37
+ userAccessLevel: access_level_1.AccessLevel.Self,
38
+ assistantAccessLevel: access_level_1.AccessLevel.Writer,
39
+ toolAccessLevel: access_level_1.AccessLevel.Admin,
40
+ hasMatchingGrant: true,
41
+ }).kind).toBe("allow");
42
+ });
43
+ });
44
+ describe("canInvokeAssistant", () => {
45
+ it("denies users below the Assistant level", () => {
46
+ expect((0, tool_access_1.canInvokeAssistant)(access_level_1.AccessLevel.Writer, access_level_1.AccessLevel.Admin)).toBe(false);
47
+ expect((0, tool_access_1.canInvokeAssistant)(access_level_1.AccessLevel.Admin, access_level_1.AccessLevel.Admin)).toBe(true);
48
+ });
49
+ });
50
+ describe("hashToolArgs", () => {
51
+ it("is stable for key order", () => {
52
+ expect((0, tool_access_1.hashToolArgs)({ b: 1, a: 2 })).toBe((0, tool_access_1.hashToolArgs)({ a: 2, b: 1 }));
53
+ });
54
+ });
55
+ describe("annotateToolAccessDescription", () => {
56
+ it("appends the required level once", () => {
57
+ const once = (0, tool_access_1.annotateToolAccessDescription)("Does a thing.", 40);
58
+ expect(once).toContain("Writer (40)");
59
+ expect((0, tool_access_1.annotateToolAccessDescription)(once, 40)).toBe(once);
60
+ });
61
+ });
62
+ describe("recordAccessLevel", () => {
63
+ it("defaults missing records to 100", () => {
64
+ expect((0, tool_access_1.recordAccessLevel)(undefined)).toBe(100);
65
+ expect((0, tool_access_1.recordAccessLevel)({})).toBe(100);
66
+ expect((0, tool_access_1.recordAccessLevel)({ accessLevel: 20 })).toBe(20);
67
+ });
68
+ });
69
+ describe("resolveDelegatedAccessLevel", () => {
70
+ it("omits an Assistant ceiling for direct user calls", () => {
71
+ expect((0, tool_access_1.resolveDelegatedAccessLevel)({
72
+ directUser: true,
73
+ accessAssistantLevel: access_level_1.AccessLevel.Writer,
74
+ defaultAssistantLevel: access_level_1.AccessLevel.Writer,
75
+ })).toBeUndefined();
76
+ });
77
+ it("takes the lower of access-assistant and default Assistant", () => {
78
+ expect((0, tool_access_1.resolveDelegatedAccessLevel)({
79
+ directUser: false,
80
+ accessAssistantLevel: access_level_1.AccessLevel.Admin,
81
+ defaultAssistantLevel: access_level_1.AccessLevel.Writer,
82
+ })).toBe(access_level_1.AccessLevel.Writer);
83
+ });
84
+ it("fails closed to None when no Assistant record resolved", () => {
85
+ expect((0, tool_access_1.resolveDelegatedAccessLevel)({ directUser: false })).toBe(access_level_1.AccessLevel.None);
86
+ });
87
+ });
88
+ describe("threadRootMessageId", () => {
89
+ it("uses the parent when present and the message itself when it is the root", () => {
90
+ expect((0, tool_access_1.threadRootMessageId)({
91
+ messageId: "00reply0000000000000000001",
92
+ messageParentId: "00root00000000000000000001",
93
+ })).toBe("00root00000000000000000001");
94
+ expect((0, tool_access_1.threadRootMessageId)({ messageId: "00root00000000000000000001" })).toBe("00root00000000000000000001");
95
+ expect((0, tool_access_1.threadRootMessageId)(undefined)).toBeUndefined();
96
+ });
97
+ });
98
+ describe("isReservedAccessVarName", () => {
99
+ it("matches host-owned access keys only", () => {
100
+ expect((0, tool_access_1.isReservedAccessVarName)(tool_access_1.DIRECT_USER_INVOCATION_VAR)).toBe(true);
101
+ expect((0, tool_access_1.isReservedAccessVarName)(tool_access_1.ASSISTANT_ID_VAR)).toBe(true);
102
+ expect((0, tool_access_1.isReservedAccessVarName)("cwd")).toBe(false);
103
+ });
104
+ });
105
+ describe("omitEphemeralAccessVars", () => {
106
+ it("strips one-shot host tickets and leaves other vars", () => {
107
+ expect((0, tool_access_1.omitEphemeralAccessVars)({
108
+ cwd: "/tmp",
109
+ [tool_access_1.DIRECT_USER_INVOCATION_VAR]: true,
110
+ [tool_access_1.HOST_APPROVED_APPROVAL_ID_VAR]: "00testapproval0000000001",
111
+ })).toEqual({ cwd: "/tmp" });
112
+ expect((0, tool_access_1.omitEphemeralAccessVars)(undefined)).toEqual({});
113
+ });
114
+ });
115
+ describe("omitReservedAccessVars", () => {
116
+ it("strips Assistant identity as well as host tickets", () => {
117
+ expect((0, tool_access_1.omitReservedAccessVars)({
118
+ cwd: "/tmp",
119
+ [tool_access_1.ACCESS_ASSISTANT_ID_VAR]: "assistant-a",
120
+ [tool_access_1.ASSISTANT_ID_VAR]: "assistant-a",
121
+ [tool_access_1.DIRECT_USER_INVOCATION_VAR]: true,
122
+ })).toEqual({ cwd: "/tmp" });
123
+ });
124
+ });
125
+ describe("inheritAccessVars", () => {
126
+ it("copies parent authority and does not let child vars escalate", () => {
127
+ const inherited = (0, tool_access_1.inheritAccessVars)({ [tool_access_1.ACCESS_ASSISTANT_ID_VAR]: "assistant-a", [tool_access_1.DIRECT_USER_INVOCATION_VAR]: false }, "assistant-parent", { [tool_access_1.ACCESS_ASSISTANT_ID_VAR]: "assistant-b", [tool_access_1.DIRECT_USER_INVOCATION_VAR]: true, foo: 1 });
128
+ expect(inherited[tool_access_1.ACCESS_ASSISTANT_ID_VAR]).toBe("assistant-a");
129
+ expect(inherited[tool_access_1.ASSISTANT_ID_VAR]).toBe("assistant-a");
130
+ expect(inherited[tool_access_1.DIRECT_USER_INVOCATION_VAR]).toBe(false);
131
+ expect(inherited.foo).toBe(1);
132
+ });
133
+ it("does not copy a host-approved approval id onto the child", () => {
134
+ const inherited = (0, tool_access_1.inheritAccessVars)({ [tool_access_1.HOST_APPROVED_APPROVAL_ID_VAR]: "00parentapproval00000001" }, "assistant-parent", { [tool_access_1.HOST_APPROVED_APPROVAL_ID_VAR]: "00childapproval000000001", foo: 1 });
135
+ expect(inherited[tool_access_1.HOST_APPROVED_APPROVAL_ID_VAR]).toBeUndefined();
136
+ expect(inherited.foo).toBe(1);
137
+ });
138
+ it("ignores a child-supplied assistantId", () => {
139
+ const inherited = (0, tool_access_1.inheritAccessVars)({ [tool_access_1.ACCESS_ASSISTANT_ID_VAR]: "assistant-a" }, "assistant-parent", {
140
+ [tool_access_1.ASSISTANT_ID_VAR]: "assistant-admin",
141
+ foo: 1,
142
+ });
143
+ expect(inherited[tool_access_1.ASSISTANT_ID_VAR]).toBe("assistant-a");
144
+ expect(inherited[tool_access_1.ACCESS_ASSISTANT_ID_VAR]).toBe("assistant-a");
145
+ });
146
+ });
147
+ describe("approvalRequiredResult", () => {
148
+ it("includes a model-visible status", () => {
149
+ const result = (0, tool_access_1.approvalRequiredResult)({
150
+ approvalId: "000peers0tacapprove000001",
151
+ toolId: "000peers0tool00000sendmsg",
152
+ toolName: "send-message",
153
+ requiredAccessLevel: 60,
154
+ message: "Needs approval",
155
+ });
156
+ expect(result.status).toBe("approval_required");
157
+ expect(result.requiredAccessLabel).toBe("Admin");
158
+ });
159
+ });
@@ -1,10 +1,12 @@
1
1
  import { z } from "zod";
2
2
  import type { DataContext } from "../context/data-context";
3
+ /** Shape of a tool input, output, or config schema. */
3
4
  export declare enum IOSchemaType {
4
5
  none = "none",
5
6
  simple = "simple",
6
7
  complex = "complex"
7
8
  }
9
+ /** Zod schema for {@link IOSchema}. */
8
10
  export declare const ioSchema: z.ZodObject<{
9
11
  type: z.ZodNativeEnum<typeof IOSchemaType>;
10
12
  fields: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -55,7 +57,9 @@ export declare const ioSchema: z.ZodObject<{
55
57
  subType?: string | undefined;
56
58
  }[] | undefined;
57
59
  }>;
60
+ /** Field list plus complexity for a tool IO surface. */
58
61
  export type IOSchema = z.infer<typeof ioSchema>;
62
+ /** Zod schema for a persisted {@link ITool} row. */
59
63
  export declare const toolSchema: z.ZodObject<{
60
64
  toolId: z.ZodEffects<z.ZodString, string, string>;
61
65
  name: z.ZodString;
@@ -215,6 +219,7 @@ export declare const toolSchema: z.ZodObject<{
215
219
  codeRepository: z.ZodOptional<z.ZodString>;
216
220
  isAssistantRunner: z.ZodOptional<z.ZodBoolean>;
217
221
  packageId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
222
+ accessLevel: z.ZodDefault<z.ZodNumber>;
218
223
  }, "strip", z.ZodTypeAny, {
219
224
  code: string;
220
225
  name: string;
@@ -244,6 +249,7 @@ export declare const toolSchema: z.ZodObject<{
244
249
  subType?: string | undefined;
245
250
  }[];
246
251
  };
252
+ accessLevel: number;
247
253
  packageId?: string | undefined;
248
254
  detailedDescription?: string | undefined;
249
255
  configSchema?: {
@@ -305,14 +311,22 @@ export declare const toolSchema: z.ZodObject<{
305
311
  } | undefined;
306
312
  codeRepository?: string | undefined;
307
313
  isAssistantRunner?: boolean | undefined;
314
+ accessLevel?: number | undefined;
308
315
  }>;
316
+ /** A tool definition stored in the Tools table. */
309
317
  export type ITool = z.infer<typeof toolSchema>;
318
+ /** Runnable tool: persisted metadata plus the in-memory implementation. */
310
319
  export interface IToolInstance {
311
320
  tool: ITool;
312
321
  toolFn: (...args: any[]) => any;
313
322
  inputSchema?: z.ZodObject<any>;
314
323
  outputSchema?: z.ZodObject<any>;
315
324
  }
325
+ /**
326
+ * Accessor for the Tools table.
327
+ *
328
+ * @param dataContext - Optional data context; defaults to the current singleton.
329
+ */
316
330
  export declare function Tools(dataContext?: DataContext): import("..").Table<{
317
331
  code: string;
318
332
  name: string;
@@ -342,6 +356,7 @@ export declare function Tools(dataContext?: DataContext): import("..").Table<{
342
356
  subType?: string | undefined;
343
357
  }[];
344
358
  };
359
+ accessLevel: number;
345
360
  packageId?: string | undefined;
346
361
  detailedDescription?: string | undefined;
347
362
  configSchema?: {
@@ -6,18 +6,22 @@ const zod_1 = require("zod");
6
6
  const user_context_singleton_1 = require("../context/user-context-singleton");
7
7
  const field_type_1 = require("../types/field-type");
8
8
  const zod_types_1 = require("../types/zod-types");
9
+ const access_level_1 = require("./access-level");
9
10
  const table_definitions_system_1 = require("./orm/table-definitions.system");
10
11
  const types_1 = require("./orm/types");
12
+ /** Shape of a tool input, output, or config schema. */
11
13
  var IOSchemaType;
12
14
  (function (IOSchemaType) {
13
15
  IOSchemaType["none"] = "none";
14
16
  IOSchemaType["simple"] = "simple";
15
17
  IOSchemaType["complex"] = "complex";
16
18
  })(IOSchemaType || (exports.IOSchemaType = IOSchemaType = {}));
19
+ /** Zod schema for {@link IOSchema}. */
17
20
  exports.ioSchema = zod_1.z.object({
18
21
  type: zod_1.z.nativeEnum(IOSchemaType),
19
22
  fields: field_type_1.fieldSchema.array().default([]),
20
23
  });
24
+ /** Zod schema for a persisted {@link ITool} row. */
21
25
  exports.toolSchema = zod_1.z.object({
22
26
  toolId: zod_types_1.zodPeerId,
23
27
  name: zod_1.z.string().describe("The name of the tool"),
@@ -44,6 +48,7 @@ exports.toolSchema = zod_1.z.object({
44
48
  .optional()
45
49
  .describe("Whether this tool is a model that can be used to run an assistant"),
46
50
  packageId: zod_types_1.zodPeerId.optional().describe("The package that installed this tool, if any"),
51
+ accessLevel: access_level_1.accessLevelSchema.describe("Required access level (0-100) to run this tool. Unclassified tools default to 100 (Self)."),
47
52
  });
48
53
  const metaData = {
49
54
  name: "Tools",
@@ -52,8 +57,14 @@ const metaData = {
52
57
  fields: (0, types_1.schemaToFields)(exports.toolSchema),
53
58
  iconClassName: "bi bi-tools",
54
59
  indexes: [{ fields: ["name"] }, { fields: ["isAssistantRunner"] }],
60
+ versionNumber: 1,
55
61
  };
56
62
  (0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.toolSchema);
63
+ /**
64
+ * Accessor for the Tools table.
65
+ *
66
+ * @param dataContext - Optional data context; defaults to the current singleton.
67
+ */
57
68
  function Tools(dataContext) {
58
69
  return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.toolSchema);
59
70
  }