@hailer/mcp 1.3.21 → 1.3.32
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/.claude/skills/create-and-publish-app/SKILL.md +44 -56
- package/.claude/skills/hailer-app-builder/SKILL.md +414 -970
- package/.claude/skills/hailer-app-primitives/SKILL.md +742 -0
- package/.claude/skills/hailer-apps-pictures/SKILL.md +191 -87
- package/.claude/skills/hailer-design-patterns/SKILL.md +317 -0
- package/.claude/skills/hailer-design-system/SKILL.md +202 -149
- package/.claude/skills/hailer-docs-search/SKILL.md +75 -0
- package/.claude/skills/hailer-workflow-archetypes/SKILL.md +301 -0
- package/.claude/skills/insight-join-patterns/SKILL.md +313 -0
- package/.claude/skills/publish-hailer-app/SKILL.md +211 -0
- package/.claude/skills/sdk-activity-patterns/SKILL.md +257 -105
- package/.claude/skills/sdk-function-fields/SKILL.md +253 -492
- package/.claude/skills/sdk-insight-calculations/SKILL.md +364 -0
- package/.claude/skills/sdk-insight-queries/SKILL.md +192 -397
- package/.claude/skills/sdk-ws-config-skill/SKILL.md +265 -720
- package/.claude/skills/tool-response-verification/SKILL.md +143 -0
- package/CLAUDE.md +45 -19
- package/dist/bot/bot.d.ts.map +1 -1
- package/dist/bot/bot.js +21 -1
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/services/helper-prompt.d.ts +1 -1
- package/dist/bot/services/helper-prompt.d.ts.map +1 -1
- package/dist/bot/services/helper-prompt.js +4 -6
- package/dist/bot/services/helper-prompt.js.map +1 -1
- package/dist/bot/services/message-formatter.d.ts.map +1 -1
- package/dist/bot/services/message-formatter.js +2 -1
- package/dist/bot/services/message-formatter.js.map +1 -1
- package/dist/bot/services/system-prompt.d.ts.map +1 -1
- package/dist/bot/services/system-prompt.js +21 -34
- package/dist/bot/services/system-prompt.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/lib/object-id.d.ts +9 -0
- package/dist/lib/object-id.d.ts.map +1 -0
- package/dist/lib/object-id.js +15 -0
- package/dist/lib/object-id.js.map +1 -0
- package/dist/mcp/hailer-rpc.d.ts +3 -0
- package/dist/mcp/hailer-rpc.d.ts.map +1 -1
- package/dist/mcp/hailer-rpc.js +7 -0
- package/dist/mcp/hailer-rpc.js.map +1 -1
- package/dist/mcp/tool-profiles.d.ts +1 -0
- package/dist/mcp/tool-profiles.d.ts.map +1 -1
- package/dist/mcp/tool-profiles.js +21 -4
- package/dist/mcp/tool-profiles.js.map +1 -1
- package/dist/mcp/tools/aliases.d.ts.map +1 -1
- package/dist/mcp/tools/aliases.js +14 -6
- package/dist/mcp/tools/aliases.js.map +1 -1
- package/dist/mcp/tools/app-core.d.ts.map +1 -1
- package/dist/mcp/tools/app-core.js +6 -3
- package/dist/mcp/tools/app-core.js.map +1 -1
- package/dist/mcp/tools/app-marketplace.d.ts.map +1 -1
- package/dist/mcp/tools/app-marketplace.js +7 -2
- package/dist/mcp/tools/app-marketplace.js.map +1 -1
- package/dist/mcp/tools/docs.d.ts +20 -0
- package/dist/mcp/tools/docs.d.ts.map +1 -0
- package/dist/mcp/tools/docs.js +87 -0
- package/dist/mcp/tools/docs.js.map +1 -0
- package/dist/mcp/tools/function-fields.d.ts +17 -0
- package/dist/mcp/tools/function-fields.d.ts.map +1 -0
- package/dist/mcp/tools/function-fields.js +717 -0
- package/dist/mcp/tools/function-fields.js.map +1 -0
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +4 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/workflow.d.ts +0 -1
- package/dist/mcp/tools/workflow.d.ts.map +1 -1
- package/dist/mcp/tools/workflow.js +1 -190
- package/dist/mcp/tools/workflow.js.map +1 -1
- package/dist/public-chat/graduate.d.ts +5 -4
- package/dist/public-chat/graduate.d.ts.map +1 -1
- package/dist/public-chat/graduate.js +226 -76
- package/dist/public-chat/graduate.js.map +1 -1
- package/dist/public-chat/handler.d.ts.map +1 -1
- package/dist/public-chat/handler.js +10 -2
- package/dist/public-chat/handler.js.map +1 -1
- package/dist/public-chat/index.d.ts.map +1 -1
- package/dist/public-chat/index.js +12 -1
- package/dist/public-chat/index.js.map +1 -1
- package/dist/public-chat/session-store.d.ts +22 -1
- package/dist/public-chat/session-store.d.ts.map +1 -1
- package/dist/public-chat/session-store.js +56 -2
- package/dist/public-chat/session-store.js.map +1 -1
- package/dist/public-chat/studio-prewarm.js +1 -1
- package/dist/public-chat/studio-prewarm.js.map +1 -1
- package/dist/public-chat/system-prompt.d.ts +1 -1
- package/dist/public-chat/system-prompt.d.ts.map +1 -1
- package/dist/public-chat/system-prompt.js +161 -83
- package/dist/public-chat/system-prompt.js.map +1 -1
- package/package.json +1 -1
- package/.claude/skills/hailer-project-protocol/SKILL.md +0 -398
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Function Fields tools.
|
|
4
|
+
*
|
|
5
|
+
* User-facing name: Function Fields. Backend endpoints still use Fast
|
|
6
|
+
* Functions naming in a few places; keep that wording out of MCP tool names.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.functionFieldTools = exports.repairFunctionValuesTool = exports.saveFunctionFieldTool = exports.previewWorkflowFunctionEvaluationTool = exports.verifyWorkflowFunctionValuesTool = exports.assistFunctionFieldTool = exports.dryRunFunctionFieldTool = exports.explainFunctionFieldGraphTool = void 0;
|
|
10
|
+
const zod_1 = require("zod");
|
|
11
|
+
const tool_registry_1 = require("../tool-registry");
|
|
12
|
+
const UserContextCache_1 = require("../UserContextCache");
|
|
13
|
+
const response_builder_1 = require("../utils/response-builder");
|
|
14
|
+
const logger_1 = require("../../lib/logger");
|
|
15
|
+
const object_id_1 = require("../../lib/object-id");
|
|
16
|
+
const logger = (0, logger_1.createLogger)({ component: 'function-fields-tools' });
|
|
17
|
+
const nameFieldAliases = new Set(['name', 'nameField', '::name']);
|
|
18
|
+
const validStatuses = [
|
|
19
|
+
'ok',
|
|
20
|
+
'ok_with_warnings',
|
|
21
|
+
'stale',
|
|
22
|
+
'missing',
|
|
23
|
+
'trusted_editable',
|
|
24
|
+
'blocked',
|
|
25
|
+
'evaluation_error',
|
|
26
|
+
'dependency_unverified',
|
|
27
|
+
];
|
|
28
|
+
const functionVariablesSchema = zod_1.z.union([zod_1.z.record(zod_1.z.any()), zod_1.z.string()]);
|
|
29
|
+
const optionalFunctionVariablesSchema = functionVariablesSchema.optional();
|
|
30
|
+
const optionalRecordOrStringSchema = zod_1.z.union([zod_1.z.record(zod_1.z.any()), zod_1.z.string()]).optional();
|
|
31
|
+
function parseRecord(value, label, fallback = {}) {
|
|
32
|
+
if (!value) {
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
36
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
37
|
+
throw new Error(`${label} must be a JSON object.`);
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
function hasOwnArg(args, key) {
|
|
42
|
+
return Object.prototype.hasOwnProperty.call(args, key);
|
|
43
|
+
}
|
|
44
|
+
function addIfPresent(update, key, value, shouldAdd) {
|
|
45
|
+
if (shouldAdd) {
|
|
46
|
+
update[key] = value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function normalizeFunctionFieldId(fieldId) {
|
|
50
|
+
return nameFieldAliases.has(fieldId) ? 'name' : fieldId;
|
|
51
|
+
}
|
|
52
|
+
function isBackendResolvableFieldId(fieldId) {
|
|
53
|
+
return (0, object_id_1.isObjectId)(fieldId) || fieldId === 'name';
|
|
54
|
+
}
|
|
55
|
+
function resolveWorkflow(context, workflowIdOrKey) {
|
|
56
|
+
return (context.init.processes || []).find((workflow) => workflow._id === workflowIdOrKey || workflow.key === workflowIdOrKey);
|
|
57
|
+
}
|
|
58
|
+
function nameFieldEntry(workflow) {
|
|
59
|
+
return {
|
|
60
|
+
fieldId: 'name',
|
|
61
|
+
field: {
|
|
62
|
+
label: workflow.nameColumnText || 'Name',
|
|
63
|
+
type: 'text',
|
|
64
|
+
editable: workflow.nameEditable,
|
|
65
|
+
functionEnabled: workflow.nameFunctionEnabled,
|
|
66
|
+
function: workflow.nameFunction,
|
|
67
|
+
functionVariables: workflow.nameFunctionVariables,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function findFieldEntry(workflow, fieldIdOrKey, options = {}) {
|
|
72
|
+
const fieldId = normalizeFunctionFieldId(fieldIdOrKey);
|
|
73
|
+
if (fieldId === 'name') {
|
|
74
|
+
return nameFieldEntry(workflow);
|
|
75
|
+
}
|
|
76
|
+
const direct = workflow.fields?.[fieldIdOrKey];
|
|
77
|
+
if (direct) {
|
|
78
|
+
return { fieldId: fieldIdOrKey, field: direct };
|
|
79
|
+
}
|
|
80
|
+
for (const [candidateId, candidateField] of Object.entries(workflow.fields || {})) {
|
|
81
|
+
if (candidateField?.key === fieldIdOrKey) {
|
|
82
|
+
return { fieldId: candidateId, field: candidateField };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (options.allowLegacyDataNameAlias && fieldIdOrKey === 'data') {
|
|
86
|
+
return nameFieldEntry(workflow);
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
function findWorkflowForField(context, fieldIdOrKey) {
|
|
91
|
+
const normalizedFieldId = normalizeFunctionFieldId(fieldIdOrKey);
|
|
92
|
+
const matches = (context.init.processes || []).filter((workflow) => normalizedFieldId === 'name' || !!findFieldEntry(workflow, fieldIdOrKey, { allowLegacyDataNameAlias: false }));
|
|
93
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
94
|
+
}
|
|
95
|
+
function resolveWorkflowAndField(context, workflowIdOrKey, fieldIdOrKey) {
|
|
96
|
+
const workflow = workflowIdOrKey ? resolveWorkflow(context, workflowIdOrKey) : findWorkflowForField(context, fieldIdOrKey);
|
|
97
|
+
if (!workflow) {
|
|
98
|
+
const normalizedFieldId = normalizeFunctionFieldId(fieldIdOrKey);
|
|
99
|
+
const matches = (context.init.processes || []).filter((candidate) => normalizedFieldId === 'name' || !!findFieldEntry(candidate, fieldIdOrKey, { allowLegacyDataNameAlias: false }));
|
|
100
|
+
return { fieldId: normalizedFieldId, ambiguous: matches.length > 1 };
|
|
101
|
+
}
|
|
102
|
+
const fieldEntry = findFieldEntry(workflow, fieldIdOrKey);
|
|
103
|
+
return {
|
|
104
|
+
workflow,
|
|
105
|
+
fieldId: fieldEntry?.fieldId || normalizeFunctionFieldId(fieldIdOrKey),
|
|
106
|
+
field: fieldEntry?.field,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function expectedOutputForFieldType(type) {
|
|
110
|
+
switch (type) {
|
|
111
|
+
case 'numeric':
|
|
112
|
+
case 'numericunit':
|
|
113
|
+
return 'number | null';
|
|
114
|
+
case 'date':
|
|
115
|
+
case 'time':
|
|
116
|
+
case 'datetime':
|
|
117
|
+
return 'millisecond timestamp number | null';
|
|
118
|
+
case 'daterange':
|
|
119
|
+
case 'timerange':
|
|
120
|
+
case 'datetimerange':
|
|
121
|
+
return '{ start: number, end: number } | null';
|
|
122
|
+
case 'users':
|
|
123
|
+
case 'teams':
|
|
124
|
+
return 'id string array | null';
|
|
125
|
+
case 'activitylink':
|
|
126
|
+
case 'country':
|
|
127
|
+
return 'id string or object shape | null';
|
|
128
|
+
default:
|
|
129
|
+
return 'non-empty string | null';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function toStringArray(value) {
|
|
133
|
+
if (!value) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
return Array.isArray(value) ? value.map(item => String(item)) : [String(value)];
|
|
137
|
+
}
|
|
138
|
+
function findFieldAcrossWorkflows(context, fieldIdOrKey) {
|
|
139
|
+
for (const workflow of context.init.processes || []) {
|
|
140
|
+
const entry = findFieldEntry(workflow, fieldIdOrKey);
|
|
141
|
+
if (entry) {
|
|
142
|
+
return { workflow, fieldId: entry.fieldId, field: entry.field };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
function dependencyDescriptions(context, workflow, functionVariables) {
|
|
148
|
+
return Object.entries(functionVariables).map(([name, rawDependency]) => {
|
|
149
|
+
if (Array.isArray(rawDependency)) {
|
|
150
|
+
return {
|
|
151
|
+
name,
|
|
152
|
+
description: 'Legacy stacked dependency configuration. Convert to in-code fallback before relying on the current Function Fields engine.',
|
|
153
|
+
source: 'legacy stacked dependency',
|
|
154
|
+
type: 'unknown',
|
|
155
|
+
shape: 'stacked',
|
|
156
|
+
relationType: 'stacked',
|
|
157
|
+
editable: false,
|
|
158
|
+
selector: rawDependency,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const dependency = rawDependency && typeof rawDependency === 'object' ? rawDependency : {};
|
|
162
|
+
const relationType = dependency.type || '';
|
|
163
|
+
const data = toStringArray(dependency.data);
|
|
164
|
+
let source = data.join(' -> ');
|
|
165
|
+
let field;
|
|
166
|
+
switch (relationType) {
|
|
167
|
+
case '=': {
|
|
168
|
+
const local = data[0] ? findFieldEntry(workflow, data[0]) : undefined;
|
|
169
|
+
field = local?.field;
|
|
170
|
+
source = field?.label ? `${field.label} (${local?.fieldId})` : source || 'same activity field';
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case '>': {
|
|
174
|
+
const link = data[0] ? findFieldEntry(workflow, data[0]) : undefined;
|
|
175
|
+
const target = data[1] ? findFieldAcrossWorkflows(context, data[1]) : {};
|
|
176
|
+
field = target.field;
|
|
177
|
+
const linkLabel = link?.field?.label || data[0] || 'linked activity';
|
|
178
|
+
const targetLabel = target.field?.label || data[1] || 'target field';
|
|
179
|
+
source = `${linkLabel} -> ${targetLabel}`;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case '<': {
|
|
183
|
+
const remoteWorkflow = data[0] ? resolveWorkflow(context, data[0]) : undefined;
|
|
184
|
+
const remoteField = remoteWorkflow && data[1] ? findFieldEntry(remoteWorkflow, data[1]) : undefined;
|
|
185
|
+
field = remoteField?.field;
|
|
186
|
+
source = `${remoteWorkflow?.name || data[0] || 'linked-from workflow'} -> ${field?.label || data[1] || 'field'}`;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case '?':
|
|
190
|
+
source = `static selector: ${data.join(' / ')}`;
|
|
191
|
+
break;
|
|
192
|
+
default:
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
name,
|
|
197
|
+
description: relationType === '<' ? 'Linked-from values are arrays.' : '',
|
|
198
|
+
source,
|
|
199
|
+
type: field?.type || 'unknown',
|
|
200
|
+
shape: relationType === '<' ? 'array' : 'scalar',
|
|
201
|
+
relationType,
|
|
202
|
+
editable: !!field?.editable,
|
|
203
|
+
selector: data.length ? data : null,
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function repairEndpoint(scope, apply, force) {
|
|
208
|
+
const pathSegment = scope === 'activity' ? 'Path' : 'WorkflowPath';
|
|
209
|
+
if (!apply) {
|
|
210
|
+
return `v3.activity.repair${pathSegment}DryRun`;
|
|
211
|
+
}
|
|
212
|
+
if (force) {
|
|
213
|
+
return `v3.activity.forceRepair${pathSegment}`;
|
|
214
|
+
}
|
|
215
|
+
return `v3.activity.repair${pathSegment}`;
|
|
216
|
+
}
|
|
217
|
+
function extractEvaluationError(result, fieldId) {
|
|
218
|
+
const candidateFieldIds = new Set([fieldId]);
|
|
219
|
+
if (fieldId === 'name') {
|
|
220
|
+
candidateFieldIds.add('data');
|
|
221
|
+
}
|
|
222
|
+
if (fieldId === 'data') {
|
|
223
|
+
candidateFieldIds.add('name');
|
|
224
|
+
}
|
|
225
|
+
const evaluatedField = result?.activity?.fields?.find((field) => candidateFieldIds.has(field?.id) && field?.error);
|
|
226
|
+
if (evaluatedField?.error) {
|
|
227
|
+
return evaluatedField.error;
|
|
228
|
+
}
|
|
229
|
+
if (candidateFieldIds.has('name') && result?.activity?.nameError) {
|
|
230
|
+
return result.activity.nameError;
|
|
231
|
+
}
|
|
232
|
+
for (const candidateId of candidateFieldIds) {
|
|
233
|
+
const metaError = result?.meta?.fields?.[candidateId]?.error;
|
|
234
|
+
if (metaError) {
|
|
235
|
+
return metaError;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return result?.meta?.error || result?.error || null;
|
|
239
|
+
}
|
|
240
|
+
function workflowNotFoundResponse(workflowId) {
|
|
241
|
+
return (0, response_builder_1.errorResponse)(`Workflow not found: ${workflowId}`, 'Use describe_workflows to find the workflow ID or key, then retry.');
|
|
242
|
+
}
|
|
243
|
+
function fieldNotFoundResponse(fieldId, workflow) {
|
|
244
|
+
return (0, response_builder_1.errorResponse)(`Function Field target not found: ${fieldId}`, workflow ? `Workflow: ${workflow.name || workflow._id}` : 'Pass workflowId when the field ID/key is ambiguous.');
|
|
245
|
+
}
|
|
246
|
+
function unresolvedFieldKeyResponse(fieldId) {
|
|
247
|
+
return (0, response_builder_1.errorResponse)(`Cannot resolve field "${fieldId}" to a backend Function Field ID.`, 'Pass workflowId when using a field key, or pass a 24-character field ID. Use fieldId="name" for the workflow name Function Field.');
|
|
248
|
+
}
|
|
249
|
+
function resolveBackendFieldId(context, fieldIdOrKey, workflowIdOrKey) {
|
|
250
|
+
const resolved = resolveWorkflowAndField(context, workflowIdOrKey, fieldIdOrKey);
|
|
251
|
+
if (workflowIdOrKey && !resolved.workflow) {
|
|
252
|
+
return { response: workflowNotFoundResponse(workflowIdOrKey) };
|
|
253
|
+
}
|
|
254
|
+
if (resolved.ambiguous) {
|
|
255
|
+
return { response: fieldNotFoundResponse(fieldIdOrKey, resolved.workflow), ambiguous: true };
|
|
256
|
+
}
|
|
257
|
+
if (!resolved.field && !isBackendResolvableFieldId(resolved.fieldId)) {
|
|
258
|
+
return { response: unresolvedFieldKeyResponse(fieldIdOrKey), workflow: resolved.workflow };
|
|
259
|
+
}
|
|
260
|
+
return { fieldId: resolved.fieldId, workflow: resolved.workflow, field: resolved.field };
|
|
261
|
+
}
|
|
262
|
+
function resolveWorkflowFieldId(workflow, fieldIdOrKey) {
|
|
263
|
+
const entry = findFieldEntry(workflow, fieldIdOrKey);
|
|
264
|
+
const fieldId = entry?.fieldId || normalizeFunctionFieldId(fieldIdOrKey);
|
|
265
|
+
if (!entry && !isBackendResolvableFieldId(fieldId)) {
|
|
266
|
+
return { response: fieldNotFoundResponse(fieldIdOrKey, workflow) };
|
|
267
|
+
}
|
|
268
|
+
return { fieldId };
|
|
269
|
+
}
|
|
270
|
+
function resolveWorkflowFieldIds(workflow, fieldIdsOrKeys) {
|
|
271
|
+
const fieldIds = [];
|
|
272
|
+
for (const fieldIdOrKey of fieldIdsOrKeys) {
|
|
273
|
+
const resolved = resolveWorkflowFieldId(workflow, fieldIdOrKey);
|
|
274
|
+
if (resolved.response) {
|
|
275
|
+
return { response: resolved.response };
|
|
276
|
+
}
|
|
277
|
+
fieldIds.push(resolved.fieldId);
|
|
278
|
+
}
|
|
279
|
+
return { fieldIds: Array.from(new Set(fieldIds)) };
|
|
280
|
+
}
|
|
281
|
+
function resolveRepairScope(args) {
|
|
282
|
+
if (args.scope) {
|
|
283
|
+
return args.scope;
|
|
284
|
+
}
|
|
285
|
+
if (args.activityId && args.workflowId) {
|
|
286
|
+
return (0, response_builder_1.errorResponse)('Repair scope is required when both activityId and workflowId are provided.', 'Set scope="activity" for one activity path, or scope="workflow" for the whole workflow field.');
|
|
287
|
+
}
|
|
288
|
+
return args.activityId ? 'activity' : 'workflow';
|
|
289
|
+
}
|
|
290
|
+
async function executeActivityRepair(args, context) {
|
|
291
|
+
if (!args.activityId) {
|
|
292
|
+
return (0, response_builder_1.errorResponse)('activityId is required for activity-scoped repair.');
|
|
293
|
+
}
|
|
294
|
+
const resolvedField = resolveBackendFieldId(context, args.fieldId, args.workflowId);
|
|
295
|
+
if (resolvedField.response) {
|
|
296
|
+
return resolvedField.response;
|
|
297
|
+
}
|
|
298
|
+
const endpoint = repairEndpoint('activity', args.apply, args.force);
|
|
299
|
+
const result = await context.hailer.request(endpoint, [{
|
|
300
|
+
activityId: args.activityId,
|
|
301
|
+
fieldId: resolvedField.fieldId,
|
|
302
|
+
incomingMaxDepth: args.incomingMaxDepth,
|
|
303
|
+
outgoingMaxDepth: args.outgoingMaxDepth,
|
|
304
|
+
includeValues: args.includeValues,
|
|
305
|
+
propagateDependents: args.propagateDependents,
|
|
306
|
+
...(args.apply ? { writeValues: true } : { force: args.force }),
|
|
307
|
+
}]);
|
|
308
|
+
return (0, response_builder_1.jsonResponse)(result, `Function Field repair result (${endpoint})`);
|
|
309
|
+
}
|
|
310
|
+
async function executeWorkflowRepair(args, context) {
|
|
311
|
+
if (!args.workflowId) {
|
|
312
|
+
return (0, response_builder_1.errorResponse)('workflowId is required for workflow-scoped repair.');
|
|
313
|
+
}
|
|
314
|
+
const workflow = resolveWorkflow(context, args.workflowId);
|
|
315
|
+
if (!workflow) {
|
|
316
|
+
return workflowNotFoundResponse(args.workflowId);
|
|
317
|
+
}
|
|
318
|
+
const resolvedField = resolveWorkflowFieldId(workflow, args.fieldId);
|
|
319
|
+
if (resolvedField.response) {
|
|
320
|
+
return resolvedField.response;
|
|
321
|
+
}
|
|
322
|
+
const endpoint = repairEndpoint('workflow', args.apply, args.force);
|
|
323
|
+
const result = await context.hailer.request(endpoint, [{
|
|
324
|
+
workflowId: workflow._id,
|
|
325
|
+
fieldId: resolvedField.fieldId,
|
|
326
|
+
incomingMaxDepth: args.incomingMaxDepth,
|
|
327
|
+
outgoingMaxDepth: args.outgoingMaxDepth,
|
|
328
|
+
includeValues: args.includeValues,
|
|
329
|
+
propagateDependents: args.propagateDependents,
|
|
330
|
+
limit: args.limit,
|
|
331
|
+
includeActivityResults: args.includeActivityResults,
|
|
332
|
+
...(args.apply ? { writeValues: true } : { force: args.force }),
|
|
333
|
+
}]);
|
|
334
|
+
return (0, response_builder_1.jsonResponse)(result, `Function Field repair result (${endpoint})`);
|
|
335
|
+
}
|
|
336
|
+
const explainFunctionFieldGraphSchema = zod_1.z.object({
|
|
337
|
+
activityId: zod_1.z.string().min(1).describe('Representative activity ID for the graph root'),
|
|
338
|
+
fieldId: zod_1.z.string().min(1).describe('Field ID/key, or "name" for the workflow name Function Field'),
|
|
339
|
+
workflowId: zod_1.z.string().optional().describe('Optional workflow ID/key for resolving field keys before calling the Fast Functions backend'),
|
|
340
|
+
direction: zod_1.z.enum(['incoming', 'outgoing', 'both']).default('both').describe('Graph direction to inspect'),
|
|
341
|
+
graphMode: zod_1.z.enum(['runtime', 'configured']).default('runtime').describe('runtime = actual current engine behavior; configured = declared dependencies'),
|
|
342
|
+
maxDepth: zod_1.z.number().int().min(0).max(16).default(8).describe('Default depth when incoming/outgoing depths are omitted'),
|
|
343
|
+
incomingMaxDepth: zod_1.z.number().int().min(0).max(16).optional().describe('Incoming dependency depth'),
|
|
344
|
+
outgoingMaxDepth: zod_1.z.number().int().min(0).max(16).optional().describe('Outgoing dependent depth'),
|
|
345
|
+
includeValues: zod_1.z.boolean().default(false).describe('Include raw values in graph output. Keep false unless values are needed.'),
|
|
346
|
+
aiExplanation: zod_1.z.boolean().default(false).describe('Call the backend AI graph explainer instead of returning the deterministic graph'),
|
|
347
|
+
});
|
|
348
|
+
exports.explainFunctionFieldGraphTool = {
|
|
349
|
+
name: 'explain_function_field_graph',
|
|
350
|
+
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
351
|
+
description: 'Explain a Function Field dependency graph for one activity field. Read-only. Start with graphMode="runtime"; use "configured" for declared dependency/cycle questions.',
|
|
352
|
+
schema: explainFunctionFieldGraphSchema,
|
|
353
|
+
async execute(args, context) {
|
|
354
|
+
try {
|
|
355
|
+
const resolved = resolveBackendFieldId(context, args.fieldId, args.workflowId);
|
|
356
|
+
if (resolved.response) {
|
|
357
|
+
return resolved.response;
|
|
358
|
+
}
|
|
359
|
+
const fieldId = resolved.fieldId;
|
|
360
|
+
const endpoint = args.aiExplanation ? 'v3.activity.explainNode.assist' : 'v3.activity.explainNode';
|
|
361
|
+
const result = await context.hailer.request(endpoint, [{
|
|
362
|
+
activityId: args.activityId,
|
|
363
|
+
fieldId,
|
|
364
|
+
direction: args.direction,
|
|
365
|
+
graphMode: args.graphMode,
|
|
366
|
+
maxDepth: args.maxDepth,
|
|
367
|
+
incomingMaxDepth: args.incomingMaxDepth,
|
|
368
|
+
outgoingMaxDepth: args.outgoingMaxDepth,
|
|
369
|
+
includeValues: args.includeValues,
|
|
370
|
+
}]);
|
|
371
|
+
return (0, response_builder_1.jsonResponse)(result, `Function Field graph (${endpoint})`);
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
logger.error('Failed to explain Function Field graph', error, { activityId: args.activityId, fieldId: args.fieldId });
|
|
375
|
+
return (0, response_builder_1.errorResponse)('Failed to explain Function Field graph', error instanceof Error ? error.message : String(error));
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
const dryRunFunctionFieldSchema = zod_1.z.object({
|
|
380
|
+
activityId: zod_1.z.string().min(1).describe('Representative activity ID to evaluate against'),
|
|
381
|
+
fieldId: zod_1.z.string().min(1).describe('Field ID/key, or "name" for the workflow name Function Field'),
|
|
382
|
+
workflowId: zod_1.z.string().optional().describe('Workflow ID/key. Required when fieldId is a key or current saved code/dependencies should be reused.'),
|
|
383
|
+
code: zod_1.z.string().optional().describe('Function body to dry-run. If omitted, the saved Function Field code is used.'),
|
|
384
|
+
function: zod_1.z.string().optional().describe('Deprecated alias for code; accepted for stale clients.'),
|
|
385
|
+
functionVariables: optionalFunctionVariablesSchema.describe('Dependency configuration. If omitted, the saved functionVariables are used when workflowId/field can be resolved.'),
|
|
386
|
+
incomingMaxDepth: zod_1.z.number().int().min(0).max(16).default(16).describe('Incoming graph depth for resolving dependencies'),
|
|
387
|
+
evaluateIncomingNodes: zod_1.z.boolean().default(true).describe('Evaluate upstream Function Field nodes before this dry-run'),
|
|
388
|
+
});
|
|
389
|
+
exports.dryRunFunctionFieldTool = {
|
|
390
|
+
name: 'dry_run_function_field',
|
|
391
|
+
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
392
|
+
description: 'Dry-run current or proposed Function Field code on one activity via v2.activities.evaluate. Read-only; use before save_function_field.',
|
|
393
|
+
schema: dryRunFunctionFieldSchema,
|
|
394
|
+
async execute(args, context) {
|
|
395
|
+
try {
|
|
396
|
+
const resolved = resolveWorkflowAndField(context, args.workflowId, args.fieldId);
|
|
397
|
+
if (args.workflowId && !resolved.workflow) {
|
|
398
|
+
return workflowNotFoundResponse(args.workflowId);
|
|
399
|
+
}
|
|
400
|
+
const field = resolved.field;
|
|
401
|
+
const code = args.code ?? args.function ?? field?.function;
|
|
402
|
+
const hasExplicitVariables = hasOwnArg(args, 'functionVariables');
|
|
403
|
+
if (resolved.ambiguous && !(resolved.fieldId === 'name' && code && hasExplicitVariables)) {
|
|
404
|
+
return fieldNotFoundResponse(args.fieldId, resolved.workflow);
|
|
405
|
+
}
|
|
406
|
+
if (!code) {
|
|
407
|
+
return (0, response_builder_1.errorResponse)('Function Field code is required', 'Pass code, or pass workflowId + fieldId so the saved function body can be reused.');
|
|
408
|
+
}
|
|
409
|
+
if (!field && !hasExplicitVariables) {
|
|
410
|
+
return (0, response_builder_1.errorResponse)('Function Field dependencies are required.', 'Pass functionVariables explicitly, or pass workflowId + fieldId so saved Fast Functions dependencies can be resolved.');
|
|
411
|
+
}
|
|
412
|
+
const variables = hasExplicitVariables
|
|
413
|
+
? parseRecord(args.functionVariables, 'functionVariables')
|
|
414
|
+
: parseRecord(field?.functionVariables, 'saved functionVariables', {});
|
|
415
|
+
if (!isBackendResolvableFieldId(resolved.fieldId)) {
|
|
416
|
+
return unresolvedFieldKeyResponse(args.fieldId);
|
|
417
|
+
}
|
|
418
|
+
const result = await context.hailer.request('v2.activities.evaluate', [
|
|
419
|
+
args.activityId,
|
|
420
|
+
resolved.fieldId,
|
|
421
|
+
code,
|
|
422
|
+
variables,
|
|
423
|
+
{
|
|
424
|
+
incomingMaxDepth: args.incomingMaxDepth,
|
|
425
|
+
evaluateIncomingNodes: args.evaluateIncomingNodes,
|
|
426
|
+
},
|
|
427
|
+
]);
|
|
428
|
+
const evaluationError = extractEvaluationError(result, resolved.fieldId);
|
|
429
|
+
if (evaluationError) {
|
|
430
|
+
return (0, response_builder_1.errorResponse)('Function Field dry-run failed evaluation.', `${evaluationError}\n\nDry-run response:\n${JSON.stringify(result, null, 2)}`);
|
|
431
|
+
}
|
|
432
|
+
return (0, response_builder_1.jsonResponse)(result, 'Function Field dry-run result');
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
logger.error('Function Field dry-run failed', error, { activityId: args.activityId, fieldId: args.fieldId });
|
|
436
|
+
return (0, response_builder_1.errorResponse)('Function Field dry-run failed', error instanceof Error ? error.message : String(error));
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
const assistFunctionFieldSchema = zod_1.z.object({
|
|
441
|
+
mode: zod_1.z.enum(['generate', 'improve', 'explain']).describe('generate creates code, improve updates code, explain describes current code'),
|
|
442
|
+
instruction: zod_1.z.string().max(4000).default('').describe('User request or improvement goal'),
|
|
443
|
+
activityId: zod_1.z.string().min(1).describe('Representative activity ID'),
|
|
444
|
+
workflowId: zod_1.z.string().min(1).describe('Workflow ID/key for the Function Field'),
|
|
445
|
+
fieldId: zod_1.z.string().min(1).describe('Field ID/key, or "name" for the workflow name Function Field'),
|
|
446
|
+
code: zod_1.z.string().optional().describe('Current code. If omitted, saved code is used.'),
|
|
447
|
+
dependencies: zod_1.z.array(zod_1.z.record(zod_1.z.any())).optional().describe('Dependency descriptions. If omitted, they are derived from functionVariables.'),
|
|
448
|
+
functionVariables: optionalFunctionVariablesSchema.describe('Dependency configuration. If omitted, saved functionVariables are used.'),
|
|
449
|
+
currentEvaluation: optionalRecordOrStringSchema.describe('Optional dry-run result or evaluation context from dry_run_function_field'),
|
|
450
|
+
expectedOutput: zod_1.z.string().optional().describe('Expected output contract. If omitted, inferred from field type.'),
|
|
451
|
+
});
|
|
452
|
+
exports.assistFunctionFieldTool = {
|
|
453
|
+
name: 'assist_function_field',
|
|
454
|
+
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
455
|
+
description: 'Ask the backend Function Field assistant to generate, improve, or explain code. Read-only suggestion only; it never saves workflow config.',
|
|
456
|
+
schema: assistFunctionFieldSchema,
|
|
457
|
+
async execute(args, context) {
|
|
458
|
+
try {
|
|
459
|
+
const resolved = resolveWorkflowAndField(context, args.workflowId, args.fieldId);
|
|
460
|
+
if (!resolved.workflow) {
|
|
461
|
+
return workflowNotFoundResponse(args.workflowId);
|
|
462
|
+
}
|
|
463
|
+
if (!resolved.field) {
|
|
464
|
+
return fieldNotFoundResponse(args.fieldId, resolved.workflow);
|
|
465
|
+
}
|
|
466
|
+
const variables = hasOwnArg(args, 'functionVariables')
|
|
467
|
+
? parseRecord(args.functionVariables, 'functionVariables')
|
|
468
|
+
: parseRecord(resolved.field.functionVariables, 'saved functionVariables', {});
|
|
469
|
+
const dependencies = args.dependencies || dependencyDescriptions(context, resolved.workflow, variables);
|
|
470
|
+
const code = args.code ?? resolved.field.function ?? '';
|
|
471
|
+
const result = await context.hailer.request('v3.activity.functionField.assist', [{
|
|
472
|
+
mode: args.mode,
|
|
473
|
+
instruction: args.instruction,
|
|
474
|
+
code,
|
|
475
|
+
activityId: args.activityId,
|
|
476
|
+
workflow: {
|
|
477
|
+
id: resolved.workflow._id,
|
|
478
|
+
name: resolved.workflow.name,
|
|
479
|
+
},
|
|
480
|
+
field: {
|
|
481
|
+
id: resolved.fieldId,
|
|
482
|
+
label: resolved.field.label || resolved.fieldId,
|
|
483
|
+
type: resolved.field.type || 'text',
|
|
484
|
+
editable: !!resolved.field.editable,
|
|
485
|
+
expectedOutput: args.expectedOutput || expectedOutputForFieldType(resolved.field.type),
|
|
486
|
+
},
|
|
487
|
+
dependencies,
|
|
488
|
+
functionVariables: variables,
|
|
489
|
+
currentEvaluation: parseRecord(args.currentEvaluation, 'currentEvaluation', {}),
|
|
490
|
+
}]);
|
|
491
|
+
return (0, response_builder_1.jsonResponse)(result, 'Function Field assistant result');
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
logger.error('Function Field assistant failed', error, { workflowId: args.workflowId, fieldId: args.fieldId });
|
|
495
|
+
return (0, response_builder_1.errorResponse)('Function Field assistant failed', error instanceof Error ? error.message : String(error));
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
const verifyWorkflowFunctionValuesSchema = zod_1.z.object({
|
|
500
|
+
workflowId: zod_1.z.string().min(1).describe('Workflow ID/key to verify'),
|
|
501
|
+
fieldId: zod_1.z.string().optional().describe('Single Function Field ID/key'),
|
|
502
|
+
fieldIds: zod_1.z.array(zod_1.z.string()).optional().describe('Function Field IDs/keys to verify'),
|
|
503
|
+
activityIds: zod_1.z.array(zod_1.z.string()).optional().describe('Optional activity IDs to scope the scan'),
|
|
504
|
+
incomingMaxDepth: zod_1.z.number().int().min(0).max(16).default(1),
|
|
505
|
+
mode: zod_1.z.enum(['runtime', 'forcePreview']).default('runtime'),
|
|
506
|
+
includeValues: zod_1.z.boolean().default(false),
|
|
507
|
+
includeResults: zod_1.z.boolean().default(true),
|
|
508
|
+
statuses: zod_1.z.array(zod_1.z.enum(validStatuses)).optional().describe('Filter returned statuses'),
|
|
509
|
+
limit: zod_1.z.number().int().min(1).max(100000).optional(),
|
|
510
|
+
maxNodes: zod_1.z.number().int().min(1).max(1000000).default(100000),
|
|
511
|
+
});
|
|
512
|
+
exports.verifyWorkflowFunctionValuesTool = {
|
|
513
|
+
name: 'verify_workflow_function_values',
|
|
514
|
+
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
515
|
+
description: 'Read-only scan for stored/computed Function Field drift in a workflow. Use before repair_function_values.',
|
|
516
|
+
schema: verifyWorkflowFunctionValuesSchema,
|
|
517
|
+
async execute(args, context) {
|
|
518
|
+
try {
|
|
519
|
+
const workflow = resolveWorkflow(context, args.workflowId);
|
|
520
|
+
if (!workflow) {
|
|
521
|
+
return workflowNotFoundResponse(args.workflowId);
|
|
522
|
+
}
|
|
523
|
+
const requestedFieldIds = Array.from(new Set([
|
|
524
|
+
...(args.fieldIds || []),
|
|
525
|
+
...(args.fieldId ? [args.fieldId] : []),
|
|
526
|
+
]));
|
|
527
|
+
if (!requestedFieldIds.length) {
|
|
528
|
+
return (0, response_builder_1.errorResponse)('At least one fieldId or fieldIds entry is required.');
|
|
529
|
+
}
|
|
530
|
+
const resolvedFieldIds = resolveWorkflowFieldIds(workflow, requestedFieldIds);
|
|
531
|
+
if (resolvedFieldIds.response) {
|
|
532
|
+
return resolvedFieldIds.response;
|
|
533
|
+
}
|
|
534
|
+
const result = await context.hailer.request('v3.activity.verifyWorkflowValues', [{
|
|
535
|
+
workflowId: workflow._id,
|
|
536
|
+
fieldIds: resolvedFieldIds.fieldIds,
|
|
537
|
+
activityIds: args.activityIds,
|
|
538
|
+
incomingMaxDepth: args.incomingMaxDepth,
|
|
539
|
+
mode: args.mode,
|
|
540
|
+
includeValues: args.includeValues,
|
|
541
|
+
includeResults: args.includeResults,
|
|
542
|
+
statuses: args.statuses,
|
|
543
|
+
limit: args.limit,
|
|
544
|
+
maxNodes: args.maxNodes,
|
|
545
|
+
}]);
|
|
546
|
+
return (0, response_builder_1.jsonResponse)(result, 'Workflow Function Field verification result');
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
logger.error('Workflow Function Field verification failed', error, { workflowId: args.workflowId });
|
|
550
|
+
return (0, response_builder_1.errorResponse)('Workflow Function Field verification failed', error instanceof Error ? error.message : String(error));
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
const previewWorkflowFunctionEvaluationSchema = zod_1.z.object({
|
|
555
|
+
action: zod_1.z.enum(['start', 'logs']).optional().describe('start begins a preview run; logs polls a previous run. Defaults to logs when runId is present.'),
|
|
556
|
+
workflowId: zod_1.z.string().optional().describe('Workflow ID/key. Required for action=start.'),
|
|
557
|
+
fieldIds: zod_1.z.array(zod_1.z.string()).optional().describe('Optional Function Field IDs/keys to preview. Omit for all active Function Fields.'),
|
|
558
|
+
runId: zod_1.z.string().optional().describe('Run ID returned by action=start. Required for action=logs.'),
|
|
559
|
+
afterTs: zod_1.z.number().int().min(0).optional().describe('Only return logs newer than this timestamp'),
|
|
560
|
+
limit: zod_1.z.number().int().min(1).max(500).default(200).describe('Maximum log entries to return'),
|
|
561
|
+
});
|
|
562
|
+
exports.previewWorkflowFunctionEvaluationTool = {
|
|
563
|
+
name: 'preview_workflow_function_evaluation',
|
|
564
|
+
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
565
|
+
description: 'Start or poll a read-only workflow Function Field evaluation preview. This always uses apply=false and never writes values.',
|
|
566
|
+
schema: previewWorkflowFunctionEvaluationSchema,
|
|
567
|
+
async execute(args, context) {
|
|
568
|
+
try {
|
|
569
|
+
const action = args.action || (args.runId ? 'logs' : 'start');
|
|
570
|
+
if (action === 'logs' && !args.runId) {
|
|
571
|
+
return (0, response_builder_1.errorResponse)('runId is required when action="logs".');
|
|
572
|
+
}
|
|
573
|
+
if (action === 'logs') {
|
|
574
|
+
const result = await context.hailer.request('v3.activity.evaluateWorkflowFunctions.logs', [{
|
|
575
|
+
runId: args.runId,
|
|
576
|
+
afterTs: args.afterTs,
|
|
577
|
+
limit: args.limit,
|
|
578
|
+
}]);
|
|
579
|
+
return (0, response_builder_1.jsonResponse)(result, 'Workflow Function Field preview logs');
|
|
580
|
+
}
|
|
581
|
+
if (!args.workflowId) {
|
|
582
|
+
return (0, response_builder_1.errorResponse)('workflowId is required when action="start".');
|
|
583
|
+
}
|
|
584
|
+
const workflow = resolveWorkflow(context, args.workflowId);
|
|
585
|
+
if (!workflow) {
|
|
586
|
+
return workflowNotFoundResponse(args.workflowId);
|
|
587
|
+
}
|
|
588
|
+
const resolvedFieldIds = args.fieldIds ? resolveWorkflowFieldIds(workflow, args.fieldIds) : {};
|
|
589
|
+
if (resolvedFieldIds.response) {
|
|
590
|
+
return resolvedFieldIds.response;
|
|
591
|
+
}
|
|
592
|
+
const result = await context.hailer.request('v3.activity.evaluateWorkflowFunctions.start', [{
|
|
593
|
+
processId: workflow._id,
|
|
594
|
+
fieldIds: resolvedFieldIds.fieldIds,
|
|
595
|
+
apply: false,
|
|
596
|
+
}]);
|
|
597
|
+
return (0, response_builder_1.jsonResponse)(result, 'Workflow Function Field preview started');
|
|
598
|
+
}
|
|
599
|
+
catch (error) {
|
|
600
|
+
logger.error('Workflow Function Field preview failed', error, { workflowId: args.workflowId, runId: args.runId });
|
|
601
|
+
return (0, response_builder_1.errorResponse)('Workflow Function Field preview failed', error instanceof Error ? error.message : String(error));
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
const saveFunctionFieldSchema = zod_1.z.object({
|
|
606
|
+
workflowId: zod_1.z.string().min(1).describe('Workflow ID/key containing the Function Field'),
|
|
607
|
+
fieldId: zod_1.z.string().min(1).describe('Field ID/key, or "name" for the workflow name Function Field'),
|
|
608
|
+
code: zod_1.z.string().optional().describe('Function body to save'),
|
|
609
|
+
functionVariables: optionalFunctionVariablesSchema.describe('Dependency configuration to save'),
|
|
610
|
+
functionEnabled: zod_1.z.boolean().optional().describe('Enable or disable the Function Field'),
|
|
611
|
+
confirmWrite: zod_1.z.boolean().default(false).describe('Must be true only after the user explicitly asked to save Function Field configuration.'),
|
|
612
|
+
});
|
|
613
|
+
exports.saveFunctionFieldTool = {
|
|
614
|
+
name: 'save_function_field',
|
|
615
|
+
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
616
|
+
description: 'Write Function Field configuration with process.update_field/process.set_info. Requires confirmWrite=true after dry-run/validation and explicit user save intent.',
|
|
617
|
+
schema: saveFunctionFieldSchema,
|
|
618
|
+
async execute(args, context) {
|
|
619
|
+
try {
|
|
620
|
+
if (!args.confirmWrite) {
|
|
621
|
+
return (0, response_builder_1.errorResponse)('Write confirmation required.', 'Set confirmWrite=true only after the user explicitly asked to save Function Field configuration.');
|
|
622
|
+
}
|
|
623
|
+
const resolved = resolveWorkflowAndField(context, args.workflowId, args.fieldId);
|
|
624
|
+
if (!resolved.workflow) {
|
|
625
|
+
return workflowNotFoundResponse(args.workflowId);
|
|
626
|
+
}
|
|
627
|
+
if (!resolved.field) {
|
|
628
|
+
return fieldNotFoundResponse(args.fieldId, resolved.workflow);
|
|
629
|
+
}
|
|
630
|
+
const hasCode = hasOwnArg(args, 'code');
|
|
631
|
+
const hasVariables = hasOwnArg(args, 'functionVariables');
|
|
632
|
+
const hasEnabled = hasOwnArg(args, 'functionEnabled');
|
|
633
|
+
if (!hasCode && !hasVariables && !hasEnabled) {
|
|
634
|
+
return (0, response_builder_1.errorResponse)('Nothing to save.', 'Pass code, functionVariables, functionEnabled, or a combination.');
|
|
635
|
+
}
|
|
636
|
+
const variables = hasVariables ? parseRecord(args.functionVariables, 'functionVariables') : undefined;
|
|
637
|
+
const update = {};
|
|
638
|
+
if (resolved.fieldId === 'name') {
|
|
639
|
+
addIfPresent(update, 'nameFunction', args.code, hasCode);
|
|
640
|
+
addIfPresent(update, 'nameFunctionVariables', variables, hasVariables);
|
|
641
|
+
addIfPresent(update, 'nameFunctionEnabled', args.functionEnabled, hasEnabled);
|
|
642
|
+
await context.hailer.request('process.set_info', [resolved.workflow._id, update]);
|
|
643
|
+
}
|
|
644
|
+
if (resolved.fieldId !== 'name') {
|
|
645
|
+
addIfPresent(update, 'function', args.code, hasCode);
|
|
646
|
+
addIfPresent(update, 'functionVariables', variables, hasVariables);
|
|
647
|
+
addIfPresent(update, 'functionEnabled', args.functionEnabled, hasEnabled);
|
|
648
|
+
await context.hailer.request('process.update_field', [resolved.workflow._id, resolved.fieldId, update]);
|
|
649
|
+
}
|
|
650
|
+
UserContextCache_1.UserContextCache.clearContext(context.apiKey);
|
|
651
|
+
return (0, response_builder_1.jsonResponse)({
|
|
652
|
+
saved: true,
|
|
653
|
+
workflowId: resolved.workflow._id,
|
|
654
|
+
fieldId: resolved.fieldId,
|
|
655
|
+
updatedKeys: Object.keys(update),
|
|
656
|
+
}, 'Function Field configuration saved');
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
logger.error('Function Field save failed', error, { workflowId: args.workflowId, fieldId: args.fieldId });
|
|
660
|
+
return (0, response_builder_1.errorResponse)('Function Field save failed', error instanceof Error ? error.message : String(error));
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
const repairFunctionValuesSchema = zod_1.z.object({
|
|
665
|
+
scope: zod_1.z.enum(['activity', 'workflow']).optional().describe('activity repairs one activity path; workflow repairs a field across active workflow activities'),
|
|
666
|
+
activityId: zod_1.z.string().optional().describe('Activity ID. Required for scope=activity.'),
|
|
667
|
+
workflowId: zod_1.z.string().optional().describe('Workflow ID/key. Required for scope=workflow.'),
|
|
668
|
+
fieldId: zod_1.z.string().min(1).describe('Function Field ID/key to repair'),
|
|
669
|
+
apply: zod_1.z.boolean().default(false).describe('false = dry-run/preview only; true = write repaired values'),
|
|
670
|
+
confirmWrite: zod_1.z.boolean().default(false).describe('Required with apply=true after explicit user repair/apply intent.'),
|
|
671
|
+
force: zod_1.z.boolean().default(false).describe('Force repair does not trust editable values as cycle breakers'),
|
|
672
|
+
incomingMaxDepth: zod_1.z.number().int().min(0).max(16).default(8),
|
|
673
|
+
outgoingMaxDepth: zod_1.z.number().int().min(0).max(16).default(8),
|
|
674
|
+
includeValues: zod_1.z.boolean().optional().describe('Include values in repair output. Defaults are backend-specific.'),
|
|
675
|
+
propagateDependents: zod_1.z.boolean().optional().describe('Also evaluate downstream dependents'),
|
|
676
|
+
limit: zod_1.z.number().int().min(1).max(10000).optional().describe('Workflow scope only: max activities to inspect'),
|
|
677
|
+
includeActivityResults: zod_1.z.boolean().optional().describe('Workflow scope only: include per-activity repair results'),
|
|
678
|
+
});
|
|
679
|
+
exports.repairFunctionValuesTool = {
|
|
680
|
+
name: 'repair_function_values',
|
|
681
|
+
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
682
|
+
description: 'Preview or apply Function Field value repairs. Defaults to dry-run. apply=true writes values and requires confirmWrite=true.',
|
|
683
|
+
schema: repairFunctionValuesSchema,
|
|
684
|
+
async execute(args, context) {
|
|
685
|
+
try {
|
|
686
|
+
if (args.apply && !args.confirmWrite) {
|
|
687
|
+
return (0, response_builder_1.errorResponse)('Write confirmation required.', 'Run with apply=false first, review the plan, then set apply=true and confirmWrite=true only after explicit user repair/apply intent.');
|
|
688
|
+
}
|
|
689
|
+
const scope = resolveRepairScope(args);
|
|
690
|
+
if (typeof scope !== 'string') {
|
|
691
|
+
return scope;
|
|
692
|
+
}
|
|
693
|
+
return await (scope === 'activity'
|
|
694
|
+
? executeActivityRepair(args, context)
|
|
695
|
+
: executeWorkflowRepair(args, context));
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
logger.error('Function Field repair failed', error, {
|
|
699
|
+
scope: args.scope,
|
|
700
|
+
activityId: args.activityId,
|
|
701
|
+
workflowId: args.workflowId,
|
|
702
|
+
fieldId: args.fieldId,
|
|
703
|
+
});
|
|
704
|
+
return (0, response_builder_1.errorResponse)('Function Field repair failed', error instanceof Error ? error.message : String(error));
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
exports.functionFieldTools = [
|
|
709
|
+
exports.explainFunctionFieldGraphTool,
|
|
710
|
+
exports.dryRunFunctionFieldTool,
|
|
711
|
+
exports.assistFunctionFieldTool,
|
|
712
|
+
exports.verifyWorkflowFunctionValuesTool,
|
|
713
|
+
exports.previewWorkflowFunctionEvaluationTool,
|
|
714
|
+
exports.saveFunctionFieldTool,
|
|
715
|
+
exports.repairFunctionValuesTool,
|
|
716
|
+
];
|
|
717
|
+
//# sourceMappingURL=function-fields.js.map
|