@sanity/workflow-mcp 0.4.0 → 0.5.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.
- package/README.md +14 -12
- package/dist/index.cjs +108 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +109 -43
- package/dist/index.js.map +1 -1
- package/dist/stdio.js +10 -5
- package/dist/stdio.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
MCP tool surface for the workflows engine. Two jobs, no fluff: operate running
|
|
4
4
|
instances, and author new definitions.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
> the evals drive, plus a stdio MCP entry point for real MCP clients.
|
|
6
|
+
The tool surface is developed against the eval harness in
|
|
7
|
+
[`@sanity/workflow-mcp-evals`](../workflow-mcp-evals) — this package exposes
|
|
8
|
+
the tools the evals drive, plus a stdio MCP entry point for real MCP clients.
|
|
10
9
|
|
|
11
10
|
## What the tools do
|
|
12
11
|
|
|
@@ -57,11 +56,13 @@ it's `bin/run.js`, and once the package is published this is what
|
|
|
57
56
|
node packages/workflow-mcp/bin/run.js
|
|
58
57
|
```
|
|
59
58
|
|
|
60
|
-
Both share the one boot path in `src/stdio.ts
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
Both share the one boot path in `src/stdio.ts`, which reads its
|
|
60
|
+
configuration from the environment: `SANITY_PROJECT_ID`, `SANITY_DATASET`,
|
|
61
|
+
`SANITY_AUTH_TOKEN`, and `WORKFLOW_TAG` are required; `SANITY_API_HOST`,
|
|
62
|
+
`WORKFLOW_RESOURCE_TYPE`, and `WORKFLOW_RESOURCE_ID` are optional. The `dev`
|
|
63
|
+
script loads them from the repo-root `.env`, so copy `.env.example` to `.env`
|
|
64
|
+
to get started. (The CLI has since moved to a `sanity.workflow.ts` config; the
|
|
65
|
+
MCP still takes plain env vars.)
|
|
65
66
|
|
|
66
67
|
## API
|
|
67
68
|
|
|
@@ -69,7 +70,7 @@ to get started — note the MCP _requires_ `SANITY_DATASET`.
|
|
|
69
70
|
import {createEngine} from '@sanity/workflow-engine'
|
|
70
71
|
import {buildTools} from '@sanity/workflow-mcp'
|
|
71
72
|
|
|
72
|
-
const engine = createEngine({client, workflowResource,
|
|
73
|
+
const engine = createEngine({client, workflowResource, tag})
|
|
73
74
|
const {descriptors, impls} = buildTools(engine)
|
|
74
75
|
|
|
75
76
|
// descriptors → feed to Anthropic API's `tools:` param, or to an MCP
|
|
@@ -84,8 +85,9 @@ const {descriptors, impls} = buildTools(engine)
|
|
|
84
85
|
the LLM surface until we have an eval case that needs it (and a
|
|
85
86
|
story for the actor identity that would authorise the override).
|
|
86
87
|
- **No `start_instance` tool.** Starting an instance is an operator
|
|
87
|
-
action that needs context the LLM doesn't have (subject doc
|
|
88
|
-
initial
|
|
88
|
+
action that needs context the LLM doesn't have (subject doc ref,
|
|
89
|
+
initial field values); operators start instances with the workflow
|
|
90
|
+
CLI's `start` command.
|
|
89
91
|
- **No effect completion.** Same logic — effects are queued by the
|
|
90
92
|
engine and drained by the runtime, not by a human or LLM.
|
|
91
93
|
- **No subscription / streaming.** MCP supports it; we haven't needed
|
package/dist/index.cjs
CHANGED
|
@@ -46,31 +46,62 @@ const minimalExample = {
|
|
|
46
46
|
{
|
|
47
47
|
name: "review",
|
|
48
48
|
title: "Editorial review",
|
|
49
|
+
// The decision is data: each action writes it, the transition filters
|
|
50
|
+
// read it. Stage-scoped so a later review round starts with a clean slate.
|
|
51
|
+
fields: [{ type: "string", name: "decision", title: "Review decision" }],
|
|
49
52
|
activities: [
|
|
50
53
|
{
|
|
51
54
|
name: "review",
|
|
52
55
|
title: "Review the draft",
|
|
53
56
|
activation: "auto",
|
|
54
57
|
actions: [
|
|
55
|
-
{
|
|
56
|
-
|
|
58
|
+
{
|
|
59
|
+
name: "approve",
|
|
60
|
+
title: "Approve",
|
|
61
|
+
status: "done",
|
|
62
|
+
ops: [
|
|
63
|
+
{
|
|
64
|
+
type: "field.set",
|
|
65
|
+
target: { field: "decision" },
|
|
66
|
+
value: { type: "literal", value: "approve" }
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
// Rejecting is the reviewer DOING their job — a decision, not a
|
|
71
|
+
// failure — so it resolves `done` and routes via the field.
|
|
72
|
+
{
|
|
73
|
+
name: "reject",
|
|
74
|
+
title: "Reject",
|
|
75
|
+
status: "done",
|
|
76
|
+
ops: [
|
|
77
|
+
{
|
|
78
|
+
type: "field.set",
|
|
79
|
+
target: { field: "decision" },
|
|
80
|
+
value: { type: "literal", value: "reject" }
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
57
84
|
]
|
|
58
85
|
}
|
|
59
86
|
],
|
|
60
87
|
transitions: [
|
|
61
|
-
{
|
|
62
|
-
|
|
88
|
+
{
|
|
89
|
+
name: "to-approved",
|
|
90
|
+
title: "Approve and finish",
|
|
91
|
+
to: "approved",
|
|
92
|
+
filter: "$allActivitiesDone && $fields.decision == 'approve'"
|
|
93
|
+
},
|
|
63
94
|
{
|
|
64
95
|
name: "to-drafting",
|
|
65
96
|
title: "Send back to drafting",
|
|
66
97
|
to: "drafting",
|
|
67
|
-
filter: "$
|
|
98
|
+
filter: "$allActivitiesDone && $fields.decision == 'reject'"
|
|
68
99
|
}
|
|
69
100
|
]
|
|
70
101
|
},
|
|
71
102
|
{ name: "approved", title: "Approved", description: "Terminal \u2014 no transitions out." }
|
|
72
103
|
]
|
|
73
|
-
}, ORIENTATION = "# Authoring a workflow definition\n\nA workflow definition is a plain JSON object. There is no code in it \u2014 every\ncondition (filters, predicates, guards) is a GROQ *string*. Generate the JSON,\nthen call `validate_workflow_definition` to check it; fix the reported errors\nand re-validate until it returns `valid: true`. The validator returns the\n*desugared* definition under `definition` \u2014 that is exactly what would deploy.\n\n## Shape\n\n- **Workflow**: `{ name, title, description?, initialStage, fields?, stages[], predicates? }`.\n `initialStage` must be the `name` of a declared stage. Do NOT include a\n `version` \u2014 definitions are immutable and content-addressed; deploy assigns\n the version from the content, the author never writes one.\n- **Stage**: `{ name, title?, description?, activities?, transitions? }`. A stage with\n no transitions is terminal. Reaching a terminal stage ends the workflow.\n- **Activity**: `{ name, title?, activation?, actions? }`. `activation` is `'auto'`\n (starts when the stage is entered) or `'manual'` (default \u2014 a human starts it).\n- **Action**: `{ name, title?, status?, params?, ops? }`.\n `status: 'done' | 'skipped' | 'failed'` is sugar that flips the *firing activity* to\n that status when the action fires. `ops` are mutations applied when the action\n fires (e.g. `{type:'field.set', target:{field:'x'}, value:{type:'param', param:'p'}}`).\n `params` are values the action collects from the caller (referenced by\n `{type:'param', param:'<name>'}` sources).\n- **Transition**: `{ name, title?, to, filter? }`. `to` must name a declared\n stage. `filter` is a GROQ condition gating the exit; omit it and it defaults\n to `$allActivitiesDone`.\n- **Field** (workflow- or stage-scoped persistent state): `{ type, name, title?, initialValue? }`.\n Scalar `type`s mirror Sanity: `string`, `text` (multiline), `number`,\n `boolean`, `date` (YYYY-MM-DD), `datetime` (ISO), `url`. Plus references\n (`doc.ref`, `doc.refs`, `release.ref`), identities (`actor` \u2014 one concrete\n principal; `assignee` / `assignees` \u2014 one or many user-or-role assignees), and\n the compositional kinds `object` (`{ type:'object', name, fields: [...] }`) and\n `array` (`{ type:'array', name, of: [...] }`) \u2014 `fields`/`of` are themselves\n field shapes, so any structure composes. `initialValue` seeds the
|
|
104
|
+
}, ORIENTATION = "# Authoring a workflow definition\n\nA workflow definition is a plain JSON object. There is no code in it \u2014 every\ncondition (filters, predicates, guards) is a GROQ *string*. Generate the JSON,\nthen call `validate_workflow_definition` to check it; fix the reported errors\nand re-validate until it returns `valid: true`. The validator returns the\n*desugared* definition under `definition` \u2014 that is exactly what would deploy.\n\n## Shape\n\n- **Workflow**: `{ name, title, description?, initialStage, fields?, stages[], predicates? }`.\n `initialStage` must be the `name` of a declared stage. Do NOT include a\n `version` \u2014 definitions are immutable and content-addressed; deploy assigns\n the version from the content, the author never writes one.\n- **Stage**: `{ name, title?, description?, activities?, transitions? }`. A stage with\n no transitions is terminal. Reaching a terminal stage ends the workflow.\n- **Activity**: `{ name, title?, activation?, actions? }`. `activation` is `'auto'`\n (starts when the stage is entered) or `'manual'` (default \u2014 a human starts it).\n- **Action**: `{ name, title?, status?, params?, ops? }`.\n `status: 'done' | 'skipped' | 'failed'` is sugar that flips the *firing activity* to\n that status when the action fires. Status is a health axis, not a decision:\n a routine decision (reject, send back, decline, hold) resolves `'done'` and\n writes the decision into a field the transition filters read \u2014 reserve\n `'failed'` for work that genuinely could not complete (e.g. a missed\n deadline via `failWhen`). `ops` are mutations applied when the action\n fires (e.g. `{type:'field.set', target:{field:'x'}, value:{type:'param', param:'p'}}`).\n `params` are values the action collects from the caller (referenced by\n `{type:'param', param:'<name>'}` sources).\n- **Transition**: `{ name, title?, to, filter? }`. `to` must name a declared\n stage. `filter` is a GROQ condition gating the exit; omit it and it defaults\n to `$allActivitiesDone`.\n- **Field** (workflow- or stage-scoped persistent state): `{ type, name, title?, initialValue? }`.\n Scalar `type`s mirror Sanity: `string`, `text` (multiline), `number`,\n `boolean`, `date` (YYYY-MM-DD), `datetime` (ISO), `url`. Plus references\n (`doc.ref`, `doc.refs`, `release.ref`), identities (`actor` \u2014 one concrete\n principal; `assignee` / `assignees` \u2014 one or many user-or-role assignees), and\n the compositional kinds `object` (`{ type:'object', name, fields: [...] }`) and\n `array` (`{ type:'array', name, of: [...] }`) \u2014 `fields`/`of` are themselves\n field shapes, so any structure composes. `initialValue` seeds the field once at\n materialisation and is **optional** \u2014 omit it for op-filled working memory\n (the common default). Arms: `{type:'input'}` (the caller supplies it when the\n instance starts), `{type:'query', query:'<groq>'}` (computed from the lake),\n `{type:'literal', value:<json>}`, or `{type:'fieldRead', field:'<name>'}`.\n By convention a workflow has an `input`-sourced `doc.ref` named `subject` \u2014 the\n headline document it is about.\n Two list sugars desugar to `array`: `{type:'todoList', name}` (ad-hoc\n status-tracked work \u2014 rows `{ label, status, assignee?, dueDate? }`) and\n `{type:'notes', name}` (an append-only audit/comment log \u2014 rows\n `{ body, actor, at }`; pairs with the `audit` op, which stamps `actor`/`at`).\n\n## GROQ in conditions\n\nBuilt-in variables available in filters/predicates (the engine's exported\n`CONDITION_VARS` inventory is the source of truth): `$allActivitiesDone`,\n`$anyActivityFailed` (booleans over the current stage's activities), `$activities` (the activity\nlist), `$fields` (field values), `$now`, `$self`/`$stage`/`$parent`/`$ancestors`\n(instance identity + position), `$effectStatus` (effect name \u2192 `'done'`/`'failed'`\nof the run queued during the CURRENT stage entry \u2014 the re-entry-safe way to gate\non an effect having drained, e.g.\n`completeWhen: \"defined($effectStatus['my.effect'])\"`), and the caller-scoped\nvars `$actor` (the acting user), `$assigned` (whether the caller is the\nactivity's assignee \u2014 the idiomatic permission gate, used as\n`filter: '$assigned'`), and `$can`. Define\nreusable named conditions under top-level `predicates: { name: '<groq>' }` and\nreference them as `$name` (e.g.\n`predicates: { ready: \"count($activities[status != 'done']) == 0\" }` \u2192 `$ready`).\n\nConditions evaluate against an in-memory snapshot (the instance + its subject +\nfield-declared docs) \u2014 **never** scan by `_type` (e.g. `*[_type==\"article\"]`);\nthat is a discovery query and the validator rejects it. To bring a document into\nscope, declare a `doc.ref` field for it.\n\n## Sugars worth knowing\n\n- Action `status: 'done' | 'skipped' | 'failed'` \u2014 flips the firing activity (shown above).\n- Omitted transition `filter` \u2014 defaults to `$allActivitiesDone`.\n- Omitted activity `activation` \u2014 defaults to `'manual'`.\n\n## Modeling defaults\n\nValid is not the same as good. Prefer these unless the request says otherwise:\n\n- **A decline/reject loops back.** Route a rejected / changes-requested\n transition to an *earlier* stage for revision (e.g. `review \u2192 drafting` gated\n on a `decision` field the reject action wrote), not to a terminal stage.\n Reserve terminal stages for completion and for explicit\n cancellation/abandonment \u2014 a workflow should not dead-end just because\n something was declined.\n- **Decisions are fields, not failures.** When a stage branches on a human\n decision, declare a stage-scoped `string` field (stage scope resets on\n re-entry, so loop-backs start clean), have each deciding action `field.set`\n it, and gate every outbound transition on its value (Example 2). Do not\n encode a decision as `status: 'failed'` + `$anyActivityFailed` \u2014 reporting\n would count healthy loops as failures.\n- **Prefer draft \u2192 review.** Model an author working in a drafting stage who\n submits, then a review stage that gates. Don't add more review stages unless\n the request asks for multiple approvers or rounds.\n- **When the shape is ambiguous, pick the conventional one and confirm** with the\n user rather than inventing extra stages.\n\n## Rules the validator enforces\n\n- Stage names, activity names (per stage) and transition names are unique.\n- Every transition `to` and `initialStage` names a declared stage.\n- Custom `predicates` must not shadow a built-in (e.g. `allActivitiesDone`).\n- Every GROQ string must parse and must not be a `_type` discovery scan.", AUTHORING_GUIDE = `${ORIENTATION}
|
|
74
105
|
|
|
75
106
|
## Examples
|
|
76
107
|
|
|
@@ -86,13 +117,13 @@ ${JSON.stringify(reviewLoopExample, null, 2)}
|
|
|
86
117
|
`, LIST_CAP = 25, TOOL_DESCRIPTORS = [
|
|
87
118
|
{
|
|
88
119
|
name: "list_workflow_instances",
|
|
89
|
-
description: "List workflow instances in the configured workspace. Use this when you need to find a workflow but don't already know its instance id, or to survey what's in flight. Returns a compact summary \u2014 id, workflow
|
|
120
|
+
description: "List workflow instances in the configured workspace. Use this when you need to find a workflow but don't already know its instance id, or to survey what's in flight. Returns a compact summary \u2014 id, `definition` (the workflow definition's `name`) and human-readable `workflowTitle`, current stage, whether the instance is done, and (when the workflow has a conventional subject document) a `subject` field with the subject doc's ref and title. Use `workflowTitle` when the user names the workflow by type (e.g. \"article reviews\") and `subject.title` when they name a specific in-flight instance by what it's about (e.g. \"the article-review about pricing\"). Capped at " + String(25) + " results. Do NOT use this to inspect a single known instance \u2014 use get_workflow_state for that, the response will be richer.",
|
|
90
121
|
input_schema: {
|
|
91
122
|
type: "object",
|
|
92
123
|
properties: {
|
|
93
124
|
definition: {
|
|
94
125
|
type: "string",
|
|
95
|
-
description: "Optional. Restrict to instances of this workflow definition (e.g. 'article-review')."
|
|
126
|
+
description: "Optional. Restrict to instances of this workflow definition, by its `name` (e.g. 'article-review')."
|
|
96
127
|
},
|
|
97
128
|
status: {
|
|
98
129
|
type: "string",
|
|
@@ -150,6 +181,11 @@ ${JSON.stringify(reviewLoopExample, null, 2)}
|
|
|
150
181
|
action: {
|
|
151
182
|
type: "string",
|
|
152
183
|
description: "The id of the action on that activity. Must be one of the actions listed as allowed=true on the activity."
|
|
184
|
+
},
|
|
185
|
+
params: {
|
|
186
|
+
type: "object",
|
|
187
|
+
description: `Optional. Values for the action's declared params, keyed by param name (e.g. {"note": "Unsupported claim in paragraph 3."}). Required when the action declares a required param \u2014 get_workflow_state lists each action's params and whether they are required. The shape is per-action, so this is a free-form object; the engine validates the supplied values against the action's declared params and rejects the call if a required one is missing.`,
|
|
188
|
+
additionalProperties: !0
|
|
153
189
|
}
|
|
154
190
|
},
|
|
155
191
|
required: ["instance_id", "activity", "action"],
|
|
@@ -235,7 +271,11 @@ function projectSummary(doc, subjectTitles) {
|
|
|
235
271
|
...subject !== void 0 ? { subject } : {}
|
|
236
272
|
};
|
|
237
273
|
}
|
|
238
|
-
function projectState(
|
|
274
|
+
function projectState({
|
|
275
|
+
instance,
|
|
276
|
+
evaluation,
|
|
277
|
+
subjectTitles
|
|
278
|
+
}) {
|
|
239
279
|
const workflowTitle = JSON.parse(instance.definitionSnapshot).title, stageTitle = evaluation.currentStage.stage.title, activities = evaluation.currentStage.activities.map((te) => {
|
|
240
280
|
const actions = te.actions.map((ae) => {
|
|
241
281
|
const verdict = workflowEngine.actionVerdict(te, ae);
|
|
@@ -279,15 +319,21 @@ function isInstanceDone(instance) {
|
|
|
279
319
|
return !1;
|
|
280
320
|
}
|
|
281
321
|
}
|
|
282
|
-
const DISABLED_REASON_TEXT = {
|
|
283
|
-
"filter-failed": "action's filter condition did not hold for this actor",
|
|
284
|
-
"activity-not-active": "activity is not currently active",
|
|
285
|
-
"stage-terminal": "stage is terminal \u2014 no further actions possible",
|
|
286
|
-
"instance-completed": "instance has already completed",
|
|
287
|
-
"requirements-unmet": "activity's declared requirements are not yet satisfied"
|
|
288
|
-
};
|
|
289
322
|
function formatDisabledReason(reason) {
|
|
290
|
-
|
|
323
|
+
switch (reason.kind) {
|
|
324
|
+
case "filter-failed":
|
|
325
|
+
return "action's filter condition did not hold for this actor";
|
|
326
|
+
case "activity-not-active":
|
|
327
|
+
return "activity is not currently active";
|
|
328
|
+
case "stage-terminal":
|
|
329
|
+
return "stage is terminal \u2014 no further actions possible";
|
|
330
|
+
case "mutation-guard-denied":
|
|
331
|
+
return `blocked by mutation guard(s): ${workflowEngine.deniedGuardLabels(reason.denied).join(", ")}`;
|
|
332
|
+
case "instance-completed":
|
|
333
|
+
return "instance has already completed";
|
|
334
|
+
case "requirements-unmet":
|
|
335
|
+
return "activity's declared requirements are not yet satisfied";
|
|
336
|
+
}
|
|
291
337
|
}
|
|
292
338
|
function summariseHistoryEntry(entry) {
|
|
293
339
|
switch (entry._type) {
|
|
@@ -352,11 +398,11 @@ function buildTools(engine, options = {}) {
|
|
|
352
398
|
},
|
|
353
399
|
get_workflow_state: async (rawInput) => {
|
|
354
400
|
const input = parseInstanceIdInput(rawInput, "get_workflow_state");
|
|
355
|
-
return getInstanceState(engine, input.instance_id, accessOverride);
|
|
401
|
+
return getInstanceState({ engine, instanceId: input.instance_id, accessOverride });
|
|
356
402
|
},
|
|
357
403
|
diagnose_workflow: async (rawInput) => {
|
|
358
404
|
const input = parseInstanceIdInput(rawInput, "diagnose_workflow");
|
|
359
|
-
return getInstanceDiagnosis(engine, input.instance_id, accessOverride);
|
|
405
|
+
return getInstanceDiagnosis({ engine, instanceId: input.instance_id, accessOverride });
|
|
360
406
|
},
|
|
361
407
|
fire_action: async (rawInput) => {
|
|
362
408
|
const input = parseFireActionInput(rawInput);
|
|
@@ -364,8 +410,9 @@ function buildTools(engine, options = {}) {
|
|
|
364
410
|
instanceId: input.instance_id,
|
|
365
411
|
activity: input.activity,
|
|
366
412
|
action: input.action,
|
|
413
|
+
...input.params !== void 0 ? { params: input.params } : {},
|
|
367
414
|
...accessOverride !== void 0 ? { access: accessOverride } : {}
|
|
368
|
-
}), getInstanceState(engine, input.instance_id, accessOverride);
|
|
415
|
+
}), getInstanceState({ engine, instanceId: input.instance_id, accessOverride });
|
|
369
416
|
},
|
|
370
417
|
// Authoring tools — engine-independent: they teach and check the DSL, they
|
|
371
418
|
// don't touch the lake or an instance.
|
|
@@ -387,33 +434,44 @@ function parseListInput(raw) {
|
|
|
387
434
|
const obj = raw, status = obj.status === "done" || obj.status === "any" ? obj.status : "in_flight", definition = typeof obj.definition == "string" ? obj.definition : void 0;
|
|
388
435
|
return definition !== void 0 ? { definition, status } : { status };
|
|
389
436
|
}
|
|
390
|
-
function
|
|
437
|
+
function asObject(raw, tool) {
|
|
391
438
|
if (raw === null || typeof raw != "object")
|
|
392
|
-
throw new Error(
|
|
393
|
-
|
|
439
|
+
throw new Error(`${tool}: input must be an object`);
|
|
440
|
+
return raw;
|
|
441
|
+
}
|
|
442
|
+
function requireString({
|
|
443
|
+
obj,
|
|
444
|
+
key,
|
|
445
|
+
tool
|
|
446
|
+
}) {
|
|
447
|
+
const value = obj[key];
|
|
448
|
+
if (typeof value != "string" || value === "")
|
|
449
|
+
throw new Error(`${tool}: ${key} is required`);
|
|
450
|
+
return value;
|
|
451
|
+
}
|
|
452
|
+
function parseValidateInput(raw) {
|
|
453
|
+
const obj = asObject(raw, "validate_workflow_definition");
|
|
394
454
|
if (obj.definition === null || typeof obj.definition != "object")
|
|
395
455
|
throw new Error("validate_workflow_definition: `definition` is required and must be an object");
|
|
396
456
|
return { definition: obj.definition };
|
|
397
457
|
}
|
|
398
458
|
function parseInstanceIdInput(raw, tool) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const obj = raw;
|
|
402
|
-
if (typeof obj.instance_id != "string" || obj.instance_id === "")
|
|
403
|
-
throw new Error(`${tool}: instance_id is required`);
|
|
404
|
-
return { instance_id: obj.instance_id };
|
|
459
|
+
const obj = asObject(raw, tool);
|
|
460
|
+
return { instance_id: requireString({ obj, key: "instance_id", tool }) };
|
|
405
461
|
}
|
|
406
462
|
function parseFireActionInput(raw) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
463
|
+
const obj = asObject(raw, "fire_action");
|
|
464
|
+
let params;
|
|
465
|
+
if (obj.params !== void 0) {
|
|
466
|
+
if (obj.params === null || typeof obj.params != "object" || Array.isArray(obj.params))
|
|
467
|
+
throw new Error("fire_action: params must be an object");
|
|
468
|
+
params = obj.params;
|
|
469
|
+
}
|
|
413
470
|
return {
|
|
414
|
-
instance_id: obj
|
|
415
|
-
activity: obj
|
|
416
|
-
action: obj
|
|
471
|
+
instance_id: requireString({ obj, key: "instance_id", tool: "fire_action" }),
|
|
472
|
+
activity: requireString({ obj, key: "activity", tool: "fire_action" }),
|
|
473
|
+
action: requireString({ obj, key: "action", tool: "fire_action" }),
|
|
474
|
+
...params !== void 0 ? { params } : {}
|
|
417
475
|
};
|
|
418
476
|
}
|
|
419
477
|
async function listInstances(engine, input) {
|
|
@@ -429,14 +487,22 @@ async function listInstances(engine, input) {
|
|
|
429
487
|
instances: filtered.map((doc) => projectSummary(doc, subjectTitles))
|
|
430
488
|
};
|
|
431
489
|
}
|
|
432
|
-
async function getInstanceState(
|
|
433
|
-
|
|
490
|
+
async function getInstanceState({
|
|
491
|
+
engine,
|
|
492
|
+
instanceId,
|
|
493
|
+
accessOverride
|
|
494
|
+
}) {
|
|
495
|
+
const evaluation = await engine.evaluate({
|
|
434
496
|
instanceId,
|
|
435
497
|
...accessOverride !== void 0 ? { access: accessOverride } : {}
|
|
436
498
|
}), instance = await engine.getInstance({ instanceId }), subjectTitles = await fetchSubjectTitles(engine, [instance]);
|
|
437
|
-
return projectState(instance, evaluation, subjectTitles);
|
|
499
|
+
return projectState({ instance, evaluation, subjectTitles });
|
|
438
500
|
}
|
|
439
|
-
async function getInstanceDiagnosis(
|
|
501
|
+
async function getInstanceDiagnosis({
|
|
502
|
+
engine,
|
|
503
|
+
instanceId,
|
|
504
|
+
accessOverride
|
|
505
|
+
}) {
|
|
440
506
|
const { diagnosis, remediations } = await engine.diagnose({
|
|
441
507
|
instanceId,
|
|
442
508
|
...accessOverride !== void 0 ? { access: accessOverride } : {}
|