rhombus-node-mcp 0.1.56 → 0.1.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/access-control-tool-api.js +231 -32
- package/dist/api/alarm-monitoring-tool-api.js +50 -1
- package/dist/api/automated-prompts-tool-api.js +9 -17
- package/dist/api/camera-policy-tool-api.js +122 -0
- package/dist/api/camera-tool-api.js +3 -5
- package/dist/api/camera-uptime-tool-api.js +62 -25
- package/dist/api/clips-tool-api.js +45 -9
- package/dist/api/create-tool-api.js +55 -7
- package/dist/api/door-tool-api.js +74 -13
- package/dist/api/entity-lookup-tool-api.js +8 -1
- package/dist/api/events-tool-api.js +79 -6
- package/dist/api/faces-tool-api.js +112 -7
- package/dist/api/get-accessible-apps.js +68 -6
- package/dist/api/get-entity-tool-api.js +12 -12
- package/dist/api/get-org-information-tool-api.js +2 -2
- package/dist/api/guest-management-tool-api.js +75 -1
- package/dist/api/location-tool-api.js +120 -28
- package/dist/api/lpr-tool-api.js +71 -16
- package/dist/api/policy-alerts-tool-api.js +7 -3
- package/dist/api/report-tool-api.js +4 -1
- package/dist/api/rules-tool-api.js +6 -16
- package/dist/api/schedule-tool-api.js +107 -0
- package/dist/api/search-tool-api.js +5 -13
- package/dist/api/update-tool-api.js +96 -1
- package/dist/api/user-tool-api.js +58 -13
- package/dist/createServer.js +18 -6
- package/dist/filtering-utils.js +421 -29
- package/dist/index.js +1 -1
- package/dist/network/network.js +122 -10
- package/dist/network/org-reference-cache.js +74 -0
- package/dist/telemetry/tracingProxy.js +4 -0
- package/dist/tools/entity-lookup-tool.js +6 -4
- package/dist/tools/get-entity-tool.js +40 -20
- package/dist/tools/get-org-information-tool.js +30 -2
- package/dist/tools/time-conversion-tool.js +59 -36
- package/dist/tools/time-tool.js +12 -3
- package/dist/tools/user-tool.js +100 -2
- package/dist/tools-console/access-anomaly-tool.js +2 -19
- package/dist/tools-console/access-control-tool.js +328 -2
- package/dist/tools-console/alarm-monitoring-tool.js +72 -2
- package/dist/tools-console/analytics-tool.js +2 -2
- package/dist/tools-console/badge-timeline-tool.js +2 -21
- package/dist/tools-console/camera-policy-tool.js +252 -0
- package/dist/tools-console/camera-tool.js +111 -89
- package/dist/tools-console/clips-tool.js +21 -31
- package/dist/tools-console/count-tool.js +8 -0
- package/dist/tools-console/create-camera-policy-tool.js +302 -122
- package/dist/tools-console/door-schedule-exception-tool.js +2 -33
- package/dist/tools-console/door-tool.js +130 -2
- package/dist/tools-console/elements-access-anomaly-tool.js +2 -19
- package/dist/tools-console/elements-badge-timeline-tool.js +2 -21
- package/dist/tools-console/elements-lost-badge-tool.js +2 -17
- package/dist/tools-console/elements-tool.js +2 -29
- package/dist/tools-console/events-tool.js +22 -101
- package/dist/tools-console/faces-tool.js +254 -28
- package/dist/tools-console/guest-management-tool.js +127 -2
- package/dist/tools-console/location-tool.js +156 -35
- package/dist/tools-console/lost-badge-tool.js +2 -17
- package/dist/tools-console/lpr-tool.js +89 -23
- package/dist/tools-console/netbox-access-anomaly-tool.js +2 -19
- package/dist/tools-console/netbox-badge-timeline-tool.js +2 -21
- package/dist/tools-console/netbox-lost-badge-tool.js +2 -17
- package/dist/tools-console/netbox-tool.js +2 -29
- package/dist/tools-console/onguard-tool.js +2 -30
- package/dist/tools-console/person-tracking-tool.js +2 -2
- package/dist/tools-console/policy-alerts-tool.js +5 -29
- package/dist/tools-console/reboot-cameras-tool.js +17 -2
- package/dist/tools-console/report-tool.js +10 -40
- package/dist/tools-console/schedule-tool.js +190 -0
- package/dist/tools-console/update-tool.js +298 -168
- package/dist/tools-console/video-walls-tool.js +93 -21
- package/dist/types/access-anomaly-tool-types.js +22 -2
- package/dist/types/access-control-tool-types.js +116 -1
- package/dist/types/alarm-monitoring-tool-types.js +42 -1
- package/dist/types/badge-timeline-tool-types.js +18 -3
- package/dist/types/camera-policy-tool-types.js +104 -0
- package/dist/types/camera-tool-types.js +80 -1
- package/dist/types/clips-tool-types.js +27 -3
- package/dist/types/create-camera-policy-tool-types.js +275 -26
- package/dist/types/door-schedule-exception-tool-types.js +20 -6
- package/dist/types/door-tool-types.js +36 -4
- package/dist/types/entity-lookup-tool-types.js +6 -1
- package/dist/types/events-tools-types.js +24 -16
- package/dist/types/faces-tools-types.js +106 -3
- package/dist/types/get-entity-tool-types.js +76 -12
- package/dist/types/guest-management-tool-types.js +103 -1
- package/dist/types/location-tool-types.js +100 -14
- package/dist/types/lost-badge-tool-types.js +17 -2
- package/dist/types/lpr-tool-types.js +47 -8
- package/dist/types/onguard-tool-types.js +21 -1
- package/dist/types/policy-alerts-tool-types.js +1 -1
- package/dist/types/report-tool-types.js +29 -1
- package/dist/types/schedule-tool-types.js +370 -0
- package/dist/types/schema.js +479 -9
- package/dist/types/time-conversion-tool-types.js +3 -4
- package/dist/types/update-tool-types.js +40 -18
- package/dist/types/user-tool-types.js +44 -1
- package/dist/types/video-walls-tool-types.js +30 -2
- package/dist/types/zod-schemas.js +5128 -996
- package/dist/util.js +62 -3
- package/dist/utils/entity-name-match.js +11 -0
- package/package.json +3 -1
|
@@ -1,141 +1,333 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { createCameraPolicy } from "../api/create-camera-policy-tool-api.js";
|
|
3
|
-
import { ApiPayloadSchema, OUTPUT_SCHEMA } from "../types/create-camera-policy-tool-types.js";
|
|
3
|
+
import { ApiPayloadSchema, OUTPUT_SCHEMA, SCHEDULE_CONFIGS_EXAMPLE, SUGGESTED_CAMERA_ACTIVITIES, isEmptyScheduleConfigs, parseCameraUuids, parseScheduleConfigs, } from "../types/create-camera-policy-tool-types.js";
|
|
4
4
|
import { postApi } from "../network/network.js";
|
|
5
5
|
const TOOL_NAME = "create-camera-policy-tool";
|
|
6
6
|
const TOOL_DESCRIPTION = `
|
|
7
|
-
A tool for creating a camera policy
|
|
7
|
+
A tool for creating a camera policy.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
Then, the user is presented with a form to configure the schedules for the policy.
|
|
11
|
-
Finally, the user is presented with a form to assign the policy to cameras.
|
|
9
|
+
Preferred (single call): pass name, description, orgUuid, scheduleConfigs, and cameraUuids together — the tool creates the policy, configures its schedule triggers, and assigns the cameras all in one call. Omit policyUuid; it is created for you.
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
Legacy step-by-step: calling with only a subset of args advances one phase at a time (name/description/orgUuid to create → policyUuid+scheduleConfigs for schedules → policyUuid+cameraUuids for camera assignment).
|
|
12
|
+
|
|
13
|
+
Errors are labelled: a "RETRYABLE" error means nothing was written and you should call again with corrected arguments; a "PARTIAL STATE" error means part of the policy now exists and you must stop and report it.
|
|
14
14
|
`;
|
|
15
|
+
// All args are optional: each step of the flow uses a different subset, and
|
|
16
|
+
// requiring the unused ones forces callers to pad with "" — the guided
|
|
17
|
+
// workflow's "pass ONLY this step's args" instruction then fails validation.
|
|
15
18
|
const TOOL_ARGS = {
|
|
16
19
|
// Step 1: Policy creation
|
|
17
|
-
name: z.string().describe("Policy name (for creating policy)"),
|
|
18
|
-
description: z.string().describe("Policy description (for creating policy)"),
|
|
19
|
-
orgUuid: z.string().describe("Organization UUID (for creating policy)"),
|
|
20
|
+
name: z.string().optional().describe("Policy name (for creating policy)"),
|
|
21
|
+
description: z.string().optional().describe("Policy description (for creating policy)"),
|
|
22
|
+
orgUuid: z.string().optional().describe("Organization UUID (for creating policy)"),
|
|
20
23
|
// Step 2: Schedule configuration
|
|
21
|
-
policyUuid: z
|
|
22
|
-
|
|
24
|
+
policyUuid: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Policy UUID of an EXISTING policy (for configuring schedules or assigning cameras on an already-created policy). Omit it when creating a policy — including on a single full call that also passes scheduleConfigs and cameraUuids."),
|
|
28
|
+
scheduleConfigs: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe(`JSON array string of schedule configurations: [{"scheduleUuid": <uuid>, "activities": [<activity>, ...]}]. ` +
|
|
32
|
+
`"activities" must be a real JSON array of activity strings, not a string containing an array, and each ` +
|
|
33
|
+
`value must be an API activity constant (e.g. ${SUGGESTED_CAMERA_ACTIVITIES.slice(0, 6).join(", ")}) — ` +
|
|
34
|
+
`never a display label like "Human Movement". Example: ${SCHEDULE_CONFIGS_EXAMPLE}`),
|
|
23
35
|
// Step 3: Camera assignment
|
|
24
|
-
cameraUuids: z
|
|
25
|
-
|
|
36
|
+
cameraUuids: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Comma-separated camera UUIDs to assign policy to, e.g. "uuidA,uuidB". Camera uuids only — resolve names to uuids first.'),
|
|
40
|
+
policyName: z.string().optional().describe("Policy name (for reference)"),
|
|
26
41
|
};
|
|
27
42
|
const TOOL_ARGS_SCHEMA = z.object(TOOL_ARGS);
|
|
43
|
+
// Every result from this tool MUST carry structuredContent: the tool registers
|
|
44
|
+
// an outputSchema, and the MCP SDK rejects any result without structuredContent
|
|
45
|
+
// as "MCP error -32602" — which REPLACES the real failure text and historically
|
|
46
|
+
// made the model (and user) see a validation crash instead of the API error.
|
|
47
|
+
function errorResult(text) {
|
|
48
|
+
return {
|
|
49
|
+
isError: true,
|
|
50
|
+
content: [{ type: "text", text }],
|
|
51
|
+
structuredContent: { needUserInput: false, message: text },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Errors are labelled by whether anything was written, because the caller cannot
|
|
55
|
+
// otherwise tell. The guided workflow says "on error, STOP and make no further
|
|
56
|
+
// calls" — correct after a partial mutation (a retry would create a SECOND
|
|
57
|
+
// policy) and wrong before one, where a corrected retry is free. Prod 2026-08-04:
|
|
58
|
+
// a malformed `scheduleConfigs` was rejected before any write, the model stopped
|
|
59
|
+
// and apologized, and the identical request succeeded when the user asked again.
|
|
60
|
+
const RETRYABLE_PREFIX = "RETRYABLE — nothing was created or changed by this call.";
|
|
61
|
+
const PARTIAL_PREFIX = "PARTIAL STATE — do NOT retry this call and do NOT create another policy.";
|
|
62
|
+
const RETRY_INSTRUCTION = "Fix the arguments named above, then call this tool ONCE more with corrected arguments. Do not tell the user the policy could not be created unless a corrected retry also fails.";
|
|
63
|
+
function retryableError(detail) {
|
|
64
|
+
return errorResult(`${RETRYABLE_PREFIX} ${detail} ${RETRY_INSTRUCTION}`);
|
|
65
|
+
}
|
|
66
|
+
/** Retryable, but a policy from an earlier call already exists — don't re-create it. */
|
|
67
|
+
function retryableStepError(detail, existingPolicyUuid) {
|
|
68
|
+
return errorResult(`${RETRYABLE_PREFIX} Policy ${existingPolicyUuid} already exists from an earlier call — do NOT create another one. ${detail} ${RETRY_INSTRUCTION}`);
|
|
69
|
+
}
|
|
70
|
+
function partialStateError(detail) {
|
|
71
|
+
return errorResult(`${PARTIAL_PREFIX} ${detail} Report exactly this state to the user.`);
|
|
72
|
+
}
|
|
73
|
+
// postApi returns {error: true, status: "..."} on transport/HTTP failures and
|
|
74
|
+
// domain errors arrive as {error: true, errorMsg: "..."} — normalize both.
|
|
75
|
+
function apiErrorText(result) {
|
|
76
|
+
return (result.errorMsg?.trim() ||
|
|
77
|
+
result.status?.trim() ||
|
|
78
|
+
"the Rhombus API reported a failure without a message");
|
|
79
|
+
}
|
|
80
|
+
/** api2 can succeed while reporting a caveat; dropping it hides real problems. */
|
|
81
|
+
function apiWarningSuffix(result) {
|
|
82
|
+
const warning = result.warningMsg?.trim();
|
|
83
|
+
return warning ? ` Note from the API: ${warning}` : "";
|
|
84
|
+
}
|
|
28
85
|
const TOOL_HANDLER = async (args, extra) => {
|
|
29
86
|
const { name, description, orgUuid, policyUuid, scheduleConfigs, cameraUuids, policyName } = args;
|
|
87
|
+
// An empty list is how the legacy step-1 call spells "no schedules yet", so it
|
|
88
|
+
// must not select the schedule phase. Detected by parsing, not by comparing to
|
|
89
|
+
// the literal "[]" — "[ ]" and "\"[]\"" used to slip past that compare and
|
|
90
|
+
// fall through into policy creation, silently creating a DUPLICATE policy.
|
|
91
|
+
const hasScheduleConfigs = Boolean(scheduleConfigs?.trim() && !isEmptyScheduleConfigs(scheduleConfigs));
|
|
92
|
+
const hasFullInputSet = Boolean(name?.trim() && hasScheduleConfigs && cameraUuids?.trim());
|
|
93
|
+
// A full input set PLUS a policyUuid is ambiguous: create everything, or treat
|
|
94
|
+
// the uuid as an existing policy? It used to silently fall through to
|
|
95
|
+
// camera-assignment-only and then report "setup completely finished" for a
|
|
96
|
+
// policy whose schedules were never configured.
|
|
97
|
+
if (hasFullInputSet && policyUuid?.trim()) {
|
|
98
|
+
return retryableError(`Ambiguous arguments: name, scheduleConfigs, and cameraUuids were all provided (a full creation call) together with policyUuid "${policyUuid.trim()}". ` +
|
|
99
|
+
`To create a new policy, drop policyUuid. To modify the existing policy ${policyUuid.trim()}, drop name and send policyUuid with ONE of scheduleConfigs or cameraUuids.`);
|
|
100
|
+
}
|
|
101
|
+
// Full run: all inputs present and no pre-existing policyUuid — create the
|
|
102
|
+
// policy, configure schedules, and assign cameras in ONE call. The guided
|
|
103
|
+
// workflow collects everything before confirming, and executing across three
|
|
104
|
+
// model round trips proved fragile: the model wobbles at each call boundary
|
|
105
|
+
// (re-renders forms, splits args wrong), so the boundaries are removed.
|
|
106
|
+
if (hasFullInputSet) {
|
|
107
|
+
// Validate BOTH structured inputs BEFORE any mutation, so anything wrong
|
|
108
|
+
// with them is a clean retryable failure rather than a half-built policy.
|
|
109
|
+
const parsedConfigs = parseScheduleConfigs(scheduleConfigs);
|
|
110
|
+
if (!parsedConfigs.ok) {
|
|
111
|
+
return retryableError(parsedConfigs.message);
|
|
112
|
+
}
|
|
113
|
+
const parsedCameras = parseCameraUuids(cameraUuids);
|
|
114
|
+
if (!parsedCameras.ok) {
|
|
115
|
+
return retryableError(parsedCameras.message);
|
|
116
|
+
}
|
|
117
|
+
const scheduledTriggers = parsedConfigs.value.map(config => ({
|
|
118
|
+
scheduleUuid: config.scheduleUuid,
|
|
119
|
+
triggerSet: config.activities.map(activity => ({ activity })),
|
|
120
|
+
}));
|
|
121
|
+
const cameraList = parsedCameras.value;
|
|
122
|
+
// Phase 1: create.
|
|
123
|
+
let createdUuid;
|
|
124
|
+
let createWarning = "";
|
|
125
|
+
try {
|
|
126
|
+
const payload = ApiPayloadSchema.parse({
|
|
127
|
+
policy: {
|
|
128
|
+
name,
|
|
129
|
+
description: description?.trim() ? description : undefined,
|
|
130
|
+
orgUuid: orgUuid?.trim() ? orgUuid : undefined,
|
|
131
|
+
scheduledTriggers: [],
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
const result = await createCameraPolicy(payload, extra._meta?.requestModifiers, extra.sessionId);
|
|
135
|
+
if (result.error) {
|
|
136
|
+
return retryableError(`Creating the policy failed: ${apiErrorText(result)}.`);
|
|
137
|
+
}
|
|
138
|
+
if (!result.policyUuid) {
|
|
139
|
+
return retryableError("Creating the policy returned no policyUuid, so it did not succeed.");
|
|
140
|
+
}
|
|
141
|
+
createdUuid = result.policyUuid;
|
|
142
|
+
createWarning = apiWarningSuffix(result);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return retryableError(`Creating the policy failed: ${error instanceof Error ? error.message : "Unknown error"}.`);
|
|
146
|
+
}
|
|
147
|
+
// Phase 2: schedules (updateCameraPolicy REPLACES the policy object, so
|
|
148
|
+
// resend the identity fields — see the legacy schedule branch below).
|
|
149
|
+
try {
|
|
150
|
+
const result = await postApi({
|
|
151
|
+
route: "/policy/updateCameraPolicy",
|
|
152
|
+
body: {
|
|
153
|
+
policy: {
|
|
154
|
+
uuid: createdUuid,
|
|
155
|
+
name,
|
|
156
|
+
description: description?.trim() ? description : undefined,
|
|
157
|
+
scheduledTriggers,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
modifiers: extra._meta?.requestModifiers,
|
|
161
|
+
sessionId: extra.sessionId,
|
|
162
|
+
});
|
|
163
|
+
if (result.error) {
|
|
164
|
+
return partialStateError(`Policy "${name}" was created (uuid ${createdUuid}) but configuring its schedules failed: ${apiErrorText(result)}. No cameras were assigned.`);
|
|
165
|
+
}
|
|
166
|
+
createWarning += apiWarningSuffix(result);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
return partialStateError(`Policy "${name}" was created (uuid ${createdUuid}) but configuring its schedules failed: ${error instanceof Error ? error.message : "Unknown error"}. No cameras were assigned.`);
|
|
170
|
+
}
|
|
171
|
+
// Phase 3: cameras.
|
|
172
|
+
try {
|
|
173
|
+
const result = await postApi({
|
|
174
|
+
route: "/camera/updateDetailsBulkV2",
|
|
175
|
+
body: {
|
|
176
|
+
cameraBulkDetails: cameraList.map(cameraUuid => ({
|
|
177
|
+
uuid: cameraUuid,
|
|
178
|
+
policyUuid: createdUuid,
|
|
179
|
+
policyUuidUpdated: true,
|
|
180
|
+
})),
|
|
181
|
+
},
|
|
182
|
+
modifiers: extra._meta?.requestModifiers,
|
|
183
|
+
sessionId: extra.sessionId,
|
|
184
|
+
});
|
|
185
|
+
if (result?.error) {
|
|
186
|
+
return partialStateError(`Policy "${name}" was created with its schedules (uuid ${createdUuid}) but camera assignment failed: ${apiErrorText(result)}.`);
|
|
187
|
+
}
|
|
188
|
+
createWarning += apiWarningSuffix(result);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return partialStateError(`Policy "${name}" was created with its schedules (uuid ${createdUuid}) but camera assignment failed: ${error instanceof Error ? error.message : "Unknown error"}.`);
|
|
192
|
+
}
|
|
193
|
+
const fullRunResponse = {
|
|
194
|
+
needUserInput: false,
|
|
195
|
+
message: `Policy "${name}" created with ${scheduledTriggers.length} schedule trigger(s) and assigned to ${cameraList.length} camera(s).${createWarning}`,
|
|
196
|
+
policyUuid: createdUuid,
|
|
197
|
+
policyName: name,
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: `🎉 Camera policy setup completely finished!\n\n✅ Policy "${name}" created with ${scheduledTriggers.length} schedule trigger(s)\n✅ Assigned to ${cameraList.length} camera(s)\n✅ Policy is now fully active${createWarning}`,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
structuredContent: fullRunResponse,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
30
209
|
// Step 3: Camera assignment (if cameraUuids provided)
|
|
31
210
|
if (cameraUuids?.trim()) {
|
|
211
|
+
// Guard BEFORE the API call: an empty policyUuid here would write
|
|
212
|
+
// policyUuid: "" with policyUuidUpdated: true to every listed camera —
|
|
213
|
+
// i.e. UNASSIGN their existing policies, not assign the new one.
|
|
214
|
+
if (!policyUuid?.trim()) {
|
|
215
|
+
return retryableError(`Cannot assign cameras: policyUuid is empty, so the policy has not been created yet. ` +
|
|
216
|
+
`Either pass the full set (name, scheduleConfigs, cameraUuids) in one call to create everything at once, or create the policy first (name/description/orgUuid call) and pass the returned policyUuid.`);
|
|
217
|
+
}
|
|
218
|
+
const parsedCameras = parseCameraUuids(cameraUuids);
|
|
219
|
+
if (!parsedCameras.ok) {
|
|
220
|
+
// Previously an unusable cameraUuids string (e.g. ",") left this branch
|
|
221
|
+
// with no return at all and fell through into policy creation, creating a
|
|
222
|
+
// duplicate policy and reporting success.
|
|
223
|
+
return retryableStepError(parsedCameras.message, policyUuid.trim());
|
|
224
|
+
}
|
|
225
|
+
const cameraList = parsedCameras.value;
|
|
32
226
|
try {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.filter((uuid) => uuid);
|
|
37
|
-
if (cameraList.length > 0) {
|
|
38
|
-
const cameraPayload = {
|
|
227
|
+
const result = await postApi({
|
|
228
|
+
route: "/camera/updateDetailsBulkV2",
|
|
229
|
+
body: {
|
|
39
230
|
cameraBulkDetails: cameraList.map(cameraUuid => ({
|
|
40
231
|
uuid: cameraUuid,
|
|
41
232
|
policyUuid: policyUuid,
|
|
42
233
|
policyUuidUpdated: true,
|
|
43
234
|
})),
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
needUserInput: false,
|
|
53
|
-
message: `Excellent! Policy created and assigned to ${cameraList.length} camera(s)!`,
|
|
54
|
-
policyUuid,
|
|
55
|
-
policyName,
|
|
56
|
-
};
|
|
57
|
-
return {
|
|
58
|
-
content: [
|
|
59
|
-
{
|
|
60
|
-
type: "text",
|
|
61
|
-
text: `🎉 Camera policy setup completely finished!\n\n✅ Policy "${policyName}" assigned to ${cameraList.length} camera(s)\n✅ Policy is now fully active\n\nYour cameras will now generate alerts according to the policy configuration.`,
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
structuredContent: jsonResultResponse,
|
|
65
|
-
};
|
|
235
|
+
},
|
|
236
|
+
modifiers: extra._meta?.requestModifiers,
|
|
237
|
+
sessionId: extra.sessionId,
|
|
238
|
+
});
|
|
239
|
+
if (result?.error) {
|
|
240
|
+
// Assigning cameras is idempotent, so a corrected retry is safe — but
|
|
241
|
+
// the policy itself already exists and must not be created again.
|
|
242
|
+
return retryableStepError(`Assigning the policy to the cameras failed: ${apiErrorText(result)}.`, policyUuid);
|
|
66
243
|
}
|
|
67
|
-
|
|
68
|
-
|
|
244
|
+
const warning = apiWarningSuffix(result);
|
|
245
|
+
const jsonResultResponse = {
|
|
246
|
+
needUserInput: false,
|
|
247
|
+
message: `Policy "${policyName?.trim() || policyUuid}" assigned to ${cameraList.length} camera(s).${warning}`,
|
|
248
|
+
policyUuid,
|
|
249
|
+
policyName,
|
|
250
|
+
};
|
|
69
251
|
return {
|
|
70
252
|
content: [
|
|
71
253
|
{
|
|
72
254
|
type: "text",
|
|
73
|
-
|
|
255
|
+
// Claims only what THIS call did: schedules may or may not have been
|
|
256
|
+
// configured by an earlier call, and this branch cannot know.
|
|
257
|
+
text: `✅ Policy "${policyName?.trim() || policyUuid}" assigned to ${cameraList.length} camera(s).${warning}`,
|
|
74
258
|
},
|
|
75
259
|
],
|
|
260
|
+
structuredContent: jsonResultResponse,
|
|
76
261
|
};
|
|
77
262
|
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return retryableStepError(`Assigning the policy to the cameras failed: ${error instanceof Error ? error.message : "Unknown error"}.`, policyUuid);
|
|
265
|
+
}
|
|
78
266
|
}
|
|
79
267
|
// Step 2: Schedule configuration (if scheduleConfigs provided and non-empty)
|
|
80
|
-
if (
|
|
268
|
+
if (hasScheduleConfigs) {
|
|
269
|
+
if (!policyUuid?.trim()) {
|
|
270
|
+
return retryableError(`Cannot configure schedules: policyUuid is empty. Create the policy first (name/description/orgUuid call) and pass the returned policyUuid, or pass the full set (name, scheduleConfigs, cameraUuids) in one call to do everything at once.`);
|
|
271
|
+
}
|
|
272
|
+
const parsedConfigs = parseScheduleConfigs(scheduleConfigs);
|
|
273
|
+
if (!parsedConfigs.ok) {
|
|
274
|
+
return retryableStepError(parsedConfigs.message, policyUuid.trim());
|
|
275
|
+
}
|
|
276
|
+
const scheduledTriggers = parsedConfigs.value.map(config => ({
|
|
277
|
+
scheduleUuid: config.scheduleUuid,
|
|
278
|
+
triggerSet: config.activities.map(activity => ({ activity })),
|
|
279
|
+
}));
|
|
280
|
+
// /policy/updateCameraPolicy has REPLACE semantics: any field omitted
|
|
281
|
+
// from the policy object is NULLED on the stored policy. Sending only
|
|
282
|
+
// {uuid, scheduledTriggers} erases the name/description that step 1
|
|
283
|
+
// just set (observed in ITG: wizard-created policies with name: null,
|
|
284
|
+
// invisible in the Console). Always resend the identity fields.
|
|
285
|
+
const effectiveName = policyName?.trim() || name?.trim();
|
|
286
|
+
if (!effectiveName) {
|
|
287
|
+
return retryableStepError(`Cannot configure schedules: policyName is required — updateCameraPolicy replaces the whole policy object, so omitting the name would erase it. Pass policyName (and description, if any) along with policyUuid and scheduleConfigs.`, policyUuid.trim());
|
|
288
|
+
}
|
|
81
289
|
try {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
content: [
|
|
101
|
-
{
|
|
102
|
-
type: "text",
|
|
103
|
-
text: `Failed to configure policy schedules: ${result.errorMsg || "Unknown error"}`,
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
// Success - now show camera assignment form
|
|
109
|
-
const jsonResultResponse = {
|
|
110
|
-
needUserInput: true,
|
|
111
|
-
message: `Excellent! Policy schedules configured.\n\nFinal step: Please select which cameras should use this policy.`,
|
|
112
|
-
requestType: "camera-assignment",
|
|
113
|
-
submitAction: "create-camera-policy-tool",
|
|
114
|
-
policyUuid,
|
|
115
|
-
policyName,
|
|
116
|
-
};
|
|
117
|
-
return {
|
|
118
|
-
content: [
|
|
119
|
-
{
|
|
120
|
-
type: "text",
|
|
121
|
-
text: JSON.stringify(jsonResultResponse),
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
structuredContent: jsonResultResponse,
|
|
125
|
-
};
|
|
290
|
+
const result = await postApi({
|
|
291
|
+
route: "/policy/updateCameraPolicy",
|
|
292
|
+
body: {
|
|
293
|
+
policy: {
|
|
294
|
+
uuid: policyUuid,
|
|
295
|
+
name: effectiveName,
|
|
296
|
+
description: description?.trim() ? description : undefined,
|
|
297
|
+
scheduledTriggers,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
modifiers: extra._meta?.requestModifiers,
|
|
301
|
+
sessionId: extra.sessionId,
|
|
302
|
+
});
|
|
303
|
+
if (result.error) {
|
|
304
|
+
// Replacing the schedules is idempotent — safe to retry, as long as the
|
|
305
|
+
// policy is not created a second time.
|
|
306
|
+
return retryableStepError(`Configuring the policy schedules failed: ${apiErrorText(result)}.`, policyUuid);
|
|
126
307
|
}
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
308
|
+
// Neutral, fact-only message: imperative text like "Please select
|
|
309
|
+
// which cameras..." is an instruction the model follows over the
|
|
310
|
+
// workflow playbook (observed: it re-rendered the confirm form or
|
|
311
|
+
// stopped mid-execution to ask for cameras it already had).
|
|
312
|
+
const jsonResultResponse = {
|
|
313
|
+
needUserInput: false,
|
|
314
|
+
message: `Schedules configured for policy "${effectiveName}" (${policyUuid}).${apiWarningSuffix(result)}`,
|
|
315
|
+
policyUuid,
|
|
316
|
+
policyName,
|
|
317
|
+
};
|
|
130
318
|
return {
|
|
131
319
|
content: [
|
|
132
320
|
{
|
|
133
321
|
type: "text",
|
|
134
|
-
text:
|
|
322
|
+
text: JSON.stringify(jsonResultResponse),
|
|
135
323
|
},
|
|
136
324
|
],
|
|
325
|
+
structuredContent: jsonResultResponse,
|
|
137
326
|
};
|
|
138
327
|
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
return retryableStepError(`Configuring the policy schedules failed: ${error instanceof Error ? error.message : "Unknown error"}.`, policyUuid);
|
|
330
|
+
}
|
|
139
331
|
}
|
|
140
332
|
// Step 1: Policy creation (if name provided)
|
|
141
333
|
if (name?.trim()) {
|
|
@@ -150,22 +342,16 @@ const TOOL_HANDLER = async (args, extra) => {
|
|
|
150
342
|
});
|
|
151
343
|
const result = await createCameraPolicy(payload, extra._meta?.requestModifiers, extra.sessionId);
|
|
152
344
|
if (result.error) {
|
|
153
|
-
return {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
text: `Failed to create camera policy: ${result.errorMsg || "Unknown error"}`,
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
};
|
|
345
|
+
return retryableError(`Creating the policy failed: ${apiErrorText(result)}.`);
|
|
346
|
+
}
|
|
347
|
+
if (!result.policyUuid) {
|
|
348
|
+
return retryableError("Creating the policy returned no policyUuid, so it did not succeed.");
|
|
161
349
|
}
|
|
162
|
-
console.error(`[createCameraPolicyTool] --
|
|
163
|
-
//
|
|
350
|
+
console.error(`[createCameraPolicyTool] -- Policy created. Got result ${JSON.stringify(result)}`);
|
|
351
|
+
// Neutral, fact-only message (see the schedule-step comment above).
|
|
164
352
|
const jsonResultResponse = {
|
|
165
|
-
needUserInput:
|
|
166
|
-
message: `
|
|
167
|
-
requestType: "schedule-trigger-configuration",
|
|
168
|
-
submitAction: "create-camera-policy-tool",
|
|
353
|
+
needUserInput: false,
|
|
354
|
+
message: `Policy "${name}" created with UUID: ${result.policyUuid}${apiWarningSuffix(result)}`,
|
|
169
355
|
policyUuid: result.policyUuid,
|
|
170
356
|
policyName: name,
|
|
171
357
|
};
|
|
@@ -180,29 +366,23 @@ const TOOL_HANDLER = async (args, extra) => {
|
|
|
180
366
|
};
|
|
181
367
|
}
|
|
182
368
|
catch (error) {
|
|
183
|
-
return {
|
|
184
|
-
content: [
|
|
185
|
-
{
|
|
186
|
-
type: "text",
|
|
187
|
-
text: `Error creating camera policy: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
188
|
-
},
|
|
189
|
-
],
|
|
190
|
-
};
|
|
369
|
+
return retryableError(`Creating the policy failed: ${error instanceof Error ? error.message : "Unknown error"}.`);
|
|
191
370
|
}
|
|
192
371
|
}
|
|
193
|
-
// Step 0:
|
|
372
|
+
// Step 0: no args provided — tell the caller what's needed (plain facts, no
|
|
373
|
+
// legacy elicitation requestType/submitAction hints).
|
|
374
|
+
const initialFormResponse = {
|
|
375
|
+
needUserInput: true,
|
|
376
|
+
message: "No policy was created — this call had no arguments. To create a camera policy, collect from the user: a policy name (required), an optional description, the schedule triggers, and the cameras to assign, then call this tool again with name, description, orgUuid, scheduleConfigs, and cameraUuids together.",
|
|
377
|
+
};
|
|
194
378
|
return {
|
|
195
379
|
content: [
|
|
196
380
|
{
|
|
197
381
|
type: "text",
|
|
198
|
-
text: JSON.stringify(
|
|
199
|
-
needUserInput: true,
|
|
200
|
-
message: "Please provide the following information to create your camera policy:\n\n1. **Policy Name** (required): A descriptive name for the policy\n2. **Policy Description** (optional): What this policy does\n3. **Organization UUID** (optional): Leave blank to use your current organization\n\nOnce you provide this information, I'll create the policy for you.",
|
|
201
|
-
requestType: "policy-creation-form",
|
|
202
|
-
submitAction: "create-camera-policy-tool",
|
|
203
|
-
}),
|
|
382
|
+
text: JSON.stringify(initialFormResponse),
|
|
204
383
|
},
|
|
205
384
|
],
|
|
385
|
+
structuredContent: initialFormResponse,
|
|
206
386
|
};
|
|
207
387
|
};
|
|
208
388
|
export function createTool(server) {
|
|
@@ -3,40 +3,9 @@ import { CREATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA, DoorScheduleExceptionReque
|
|
|
3
3
|
import { createToolStructuredContent, createToolTextContent, extractFromToolExtra, } from "../util.js";
|
|
4
4
|
const TOOL_NAME = "door-schedule-exception-tool";
|
|
5
5
|
const TOOL_DESCRIPTION = `
|
|
6
|
-
|
|
7
|
-
A door lock/unlock exception is a one-time rule used to change an access controlled door's locked/unlocked state.
|
|
8
|
-
If a lock/unlock exception is enabled, it will overwrite the existing lock/unlock schedule.
|
|
9
|
-
A schedule exception allows you to create a custom schedule that is only active for the specified dates/times.
|
|
10
|
-
Once the date/time a schedule exception is set for passes, the original schedule will resume.
|
|
6
|
+
Manages Rhombus door schedule exceptions — one-time overrides of an access-controlled door's lock/unlock schedule for specific dates/times; the regular schedule resumes once the exception passes. Use for requests like "unlock the front door this Saturday", "cancel/modify the holiday exception", "what schedule exceptions are set for <door/location>", including adding or removing doors on an existing exception.
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
Users through the web console can toggle whether to see expired door schedule exceptions or not. Please mirror this behavior
|
|
14
|
-
when responding to the user.
|
|
15
|
-
|
|
16
|
-
It has the following modes of operation, determined by the "requestType" parameter:
|
|
17
|
-
- ${DoorScheduleExceptionRequestType.CREATE_EXCEPTION}: Create a door schedule exception. Requires exception (DoorScheduleExceptionType object). If locationUuid is missing but doorUuids are provided, the tool will resolve the location automatically.
|
|
18
|
-
- ${DoorScheduleExceptionRequestType.DELETE_EXCEPTION}: Delete a door schedule exception. Requires exceptionUuid.
|
|
19
|
-
- ${DoorScheduleExceptionRequestType.FIND_EXCEPTIONS}: Find door schedule exceptions across the organization, optionally filtered by date range.
|
|
20
|
-
- ${DoorScheduleExceptionRequestType.FIND_EXCEPTIONS_FOR_LOCATION}: Find door schedule exceptions for a location. Requires locationUuid. Supports optional date range filters.
|
|
21
|
-
- ${DoorScheduleExceptionRequestType.FIND_EXCEPTIONS_FOR_DOOR}: Find door schedule exceptions for a door. Requires doorUuid. Supports optional date range filters.
|
|
22
|
-
- ${DoorScheduleExceptionRequestType.GET_EXCEPTION}: Get a single door schedule exception by UUID. Requires exceptionUuid.
|
|
23
|
-
- ${DoorScheduleExceptionRequestType.UPDATE_EXCEPTION}: Update a door schedule exception. Requires exception (DoorScheduleExceptionType object). If intervals are omitted but defaultState and date range are provided, the tool will generate a full-day interval.
|
|
24
|
-
|
|
25
|
-
Use get-entity-tool to look up location and door UUIDs when needed.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
**Mutating doorUuids on an existing exception (add/remove/replace doors):**
|
|
30
|
-
\`update-exception\` REPLACES \`doorUuids\` with whatever you pass — it is not a delta operation. To safely remove or add doors while preserving the others:
|
|
31
|
-
|
|
32
|
-
1. Call \`find-exceptions\` (or \`get-exception\`) to fetch the exception. The response includes the **full \`doorUuids\` array** for that exception — that IS the current door list.
|
|
33
|
-
2. Compute the new array yourself:
|
|
34
|
-
- **Remove doors:** filter the existing \`doorUuids\` array, dropping the ones to remove.
|
|
35
|
-
- **Add doors:** append the new UUIDs to the existing array (deduped).
|
|
36
|
-
- **Replace wholesale:** just use the new set.
|
|
37
|
-
3. Call \`update-exception\` with \`exception.uuid\` and \`exception.doorUuids\` set to your computed array. Other fields (name, dates, intervals, defaultState) are optional — omit them to leave them unchanged.
|
|
38
|
-
|
|
39
|
-
**You already have the current door list in the find-exceptions response.** Do not ask the user for it, do not claim you need additional lookups, and do not refuse the mutation citing missing context. The doorUuids array you got back IS the context.
|
|
8
|
+
Modes via "requestType": ${DoorScheduleExceptionRequestType.CREATE_EXCEPTION}, ${DoorScheduleExceptionRequestType.UPDATE_EXCEPTION}, ${DoorScheduleExceptionRequestType.DELETE_EXCEPTION}, ${DoorScheduleExceptionRequestType.GET_EXCEPTION}, ${DoorScheduleExceptionRequestType.FIND_EXCEPTIONS}, ${DoorScheduleExceptionRequestType.FIND_EXCEPTIONS_FOR_LOCATION}, ${DoorScheduleExceptionRequestType.FIND_EXCEPTIONS_FOR_DOOR} — per-mode requirements and the add/remove-doors procedure are documented on the parameters. Use get-entity-tool to resolve location and door UUIDs.
|
|
40
9
|
`;
|
|
41
10
|
function buildDateRangeFilter(args) {
|
|
42
11
|
return {
|