codexmate 0.0.30 → 0.0.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/README.md +363 -421
- package/README.zh.md +371 -354
- package/cli/agents-files.js +224 -224
- package/cli/archive-helpers.js +446 -446
- package/cli/auth-profiles.js +375 -375
- package/cli/builtin-proxy.js +1725 -1725
- package/cli/claude-proxy.js +1022 -1022
- package/cli/config-bootstrap.js +402 -402
- package/cli/config-health.js +454 -454
- package/cli/doctor-core.js +903 -903
- package/cli/import-skills-url.js +356 -356
- package/cli/local-bridge.js +324 -324
- package/cli/openai-bridge.js +1653 -1653
- package/cli/openclaw-config.js +629 -629
- package/cli/session-convert-args.js +69 -65
- package/cli/session-convert-io.js +82 -82
- package/cli/session-convert.js +150 -43
- package/cli/session-usage.concurrent.js +28 -28
- package/cli/session-usage.js +118 -118
- package/cli/session-usage.models.js +176 -176
- package/cli/skills.js +1141 -1141
- package/cli/zip-commands.js +510 -510
- package/cli.js +15829 -15481
- package/lib/automation.js +404 -404
- package/lib/cli-file-utils.js +151 -151
- package/lib/cli-models-utils.js +440 -440
- package/lib/cli-network-utils.js +190 -190
- package/lib/cli-path-utils.js +85 -85
- package/lib/cli-session-utils.js +121 -121
- package/lib/cli-sessions.js +426 -417
- package/lib/cli-utils.js +155 -155
- package/lib/cli-webhook.js +126 -126
- package/lib/download-artifacts.js +92 -92
- package/lib/mcp-stdio.js +453 -453
- package/lib/task-orchestrator.js +869 -869
- package/lib/text-diff.js +303 -303
- package/lib/workflow-engine.js +340 -340
- package/package.json +76 -76
- package/plugins/README.md +20 -20
- package/plugins/README.zh-CN.md +20 -20
- package/plugins/prompt-templates/comment-polish/index.mjs +25 -25
- package/plugins/prompt-templates/computed.mjs +253 -253
- package/plugins/prompt-templates/index.mjs +8 -8
- package/plugins/prompt-templates/manifest.mjs +15 -15
- package/plugins/prompt-templates/methods.mjs +553 -553
- package/plugins/prompt-templates/overview.mjs +91 -91
- package/plugins/prompt-templates/ownership.mjs +19 -19
- package/plugins/prompt-templates/rule-ack/index.mjs +21 -21
- package/plugins/prompt-templates/storage.mjs +64 -64
- package/plugins/registry.mjs +16 -16
- package/web-ui/app.js +647 -645
- package/web-ui/index.html +36 -36
- package/web-ui/logic.agents-diff.mjs +386 -386
- package/web-ui/logic.claude.mjs +168 -168
- package/web-ui/logic.codex.mjs +69 -69
- package/web-ui/logic.mjs +5 -5
- package/web-ui/logic.runtime.mjs +128 -128
- package/web-ui/logic.session-convert.mjs +70 -70
- package/web-ui/logic.sessions.mjs +781 -765
- package/web-ui/modules/api.mjs +90 -90
- package/web-ui/modules/app.computed.dashboard.mjs +248 -248
- package/web-ui/modules/app.computed.index.mjs +17 -17
- package/web-ui/modules/app.computed.main-tabs.mjs +205 -205
- package/web-ui/modules/app.computed.session.mjs +693 -999
- package/web-ui/modules/app.constants.mjs +15 -15
- package/web-ui/modules/app.methods.agents.mjs +651 -632
- package/web-ui/modules/app.methods.claude-config.mjs +200 -200
- package/web-ui/modules/app.methods.codex-config.mjs +861 -917
- package/web-ui/modules/app.methods.index.mjs +94 -94
- package/web-ui/modules/app.methods.install.mjs +205 -205
- package/web-ui/modules/app.methods.navigation.mjs +774 -774
- package/web-ui/modules/app.methods.openclaw-core.mjs +814 -814
- package/web-ui/modules/app.methods.openclaw-editing.mjs +372 -372
- package/web-ui/modules/app.methods.openclaw-persist.mjs +369 -369
- package/web-ui/modules/app.methods.providers.mjs +529 -529
- package/web-ui/modules/app.methods.runtime.mjs +345 -345
- package/web-ui/modules/app.methods.session-actions.mjs +591 -593
- package/web-ui/modules/app.methods.session-browser.mjs +1012 -984
- package/web-ui/modules/app.methods.session-timeline.mjs +479 -479
- package/web-ui/modules/app.methods.session-trash.mjs +438 -438
- package/web-ui/modules/app.methods.startup-claude.mjs +537 -534
- package/web-ui/modules/app.methods.task-orchestration.mjs +556 -556
- package/web-ui/modules/app.methods.webhook.mjs +79 -79
- package/web-ui/modules/config-mode.computed.mjs +124 -124
- package/web-ui/modules/config-template-confirm-pref.mjs +33 -33
- package/web-ui/modules/i18n.dict.mjs +3177 -3146
- package/web-ui/modules/i18n.mjs +62 -62
- package/web-ui/modules/plugins.computed.mjs +3 -3
- package/web-ui/modules/plugins.methods.mjs +3 -3
- package/web-ui/modules/plugins.storage.mjs +11 -11
- package/web-ui/modules/provider-url-display.mjs +17 -17
- package/web-ui/modules/sessions-filters-url.mjs +85 -85
- package/web-ui/modules/skills.computed.mjs +107 -107
- package/web-ui/modules/skills.methods.mjs +482 -482
- package/web-ui/partials/index/layout-footer.html +13 -13
- package/web-ui/partials/index/layout-header.html +503 -500
- package/web-ui/partials/index/modal-config-template-agents.html +185 -174
- package/web-ui/partials/index/modal-confirm-toast.html +32 -32
- package/web-ui/partials/index/modal-health-check.html +45 -45
- package/web-ui/partials/index/modal-openclaw-config.html +280 -280
- package/web-ui/partials/index/modal-skills.html +200 -200
- package/web-ui/partials/index/modals-basic.html +162 -162
- package/web-ui/partials/index/panel-config-claude.html +136 -194
- package/web-ui/partials/index/panel-config-codex.html +197 -337
- package/web-ui/partials/index/panel-config-openclaw.html +83 -83
- package/web-ui/partials/index/panel-dashboard.html +219 -219
- package/web-ui/partials/index/panel-docs.html +115 -147
- package/web-ui/partials/index/panel-market.html +177 -177
- package/web-ui/partials/index/panel-orchestration.html +391 -391
- package/web-ui/partials/index/panel-plugins.html +253 -253
- package/web-ui/partials/index/panel-sessions.html +313 -308
- package/web-ui/partials/index/panel-settings.html +190 -190
- package/web-ui/partials/index/panel-trash.html +83 -88
- package/web-ui/partials/index/panel-usage.html +138 -371
- package/web-ui/res/json5.min.js +1 -1
- package/web-ui/res/vue.global.prod.js +13 -13
- package/web-ui/session-helpers.mjs +591 -594
- package/web-ui/source-bundle.cjs +233 -233
- package/web-ui/styles/base-theme.css +281 -281
- package/web-ui/styles/bridge-pool.css +197 -0
- package/web-ui/styles/controls-forms.css +433 -422
- package/web-ui/styles/dashboard.css +406 -406
- package/web-ui/styles/docs-panel.css +245 -271
- package/web-ui/styles/feedback.css +108 -108
- package/web-ui/styles/health-check-dialog.css +144 -144
- package/web-ui/styles/layout-shell.css +638 -626
- package/web-ui/styles/modals-core.css +466 -466
- package/web-ui/styles/navigation-panels.css +391 -391
- package/web-ui/styles/openclaw-structured.css +266 -266
- package/web-ui/styles/plugins-panel.css +564 -564
- package/web-ui/styles/responsive.css +392 -454
- package/web-ui/styles/sessions-list.css +647 -417
- package/web-ui/styles/sessions-preview.css +407 -407
- package/web-ui/styles/sessions-toolbar-trash.css +518 -334
- package/web-ui/styles/sessions-usage.css +588 -1040
- package/web-ui/styles/settings-panel.css +349 -349
- package/web-ui/styles/skills-list.css +305 -305
- package/web-ui/styles/skills-market.css +429 -429
- package/web-ui/styles/task-orchestration.css +822 -822
- package/web-ui/styles/titles-cards.css +472 -472
- package/web-ui/styles/trash-panel.css +90 -90
- package/web-ui/styles/webhook.css +81 -81
- package/web-ui/styles.css +24 -23
- package/web-ui.html +17 -17
package/lib/workflow-engine.js
CHANGED
|
@@ -1,340 +1,340 @@
|
|
|
1
|
-
function isPlainObject(value) {
|
|
2
|
-
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
function getPathValue(source, path) {
|
|
6
|
-
if (!path || typeof path !== 'string') {
|
|
7
|
-
return undefined;
|
|
8
|
-
}
|
|
9
|
-
const parts = path
|
|
10
|
-
.split('.')
|
|
11
|
-
.map((part) => part.trim())
|
|
12
|
-
.filter(Boolean);
|
|
13
|
-
let current = source;
|
|
14
|
-
for (const part of parts) {
|
|
15
|
-
if (current === null || current === undefined) {
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
if (Array.isArray(current)) {
|
|
19
|
-
const index = Number.parseInt(part, 10);
|
|
20
|
-
if (!Number.isFinite(index)) {
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
current = current[index];
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
if (!isPlainObject(current)) {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
current = current[part];
|
|
30
|
-
}
|
|
31
|
-
return current;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function resolveTemplateString(template, context) {
|
|
35
|
-
if (typeof template !== 'string') {
|
|
36
|
-
return template;
|
|
37
|
-
}
|
|
38
|
-
const direct = template.match(/^\{\{\s*([^{}]+?)\s*\}\}$/);
|
|
39
|
-
if (direct) {
|
|
40
|
-
return getPathValue(context, direct[1].trim());
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const pattern = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
44
|
-
return template.replace(pattern, (_, rawPath) => {
|
|
45
|
-
const value = getPathValue(context, String(rawPath || '').trim());
|
|
46
|
-
if (value === undefined || value === null) {
|
|
47
|
-
return '';
|
|
48
|
-
}
|
|
49
|
-
if (typeof value === 'object') {
|
|
50
|
-
try {
|
|
51
|
-
return JSON.stringify(value);
|
|
52
|
-
} catch (_) {
|
|
53
|
-
return '';
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return String(value);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function resolveTemplateValue(value, context) {
|
|
61
|
-
if (Array.isArray(value)) {
|
|
62
|
-
return value.map((item) => resolveTemplateValue(item, context));
|
|
63
|
-
}
|
|
64
|
-
if (isPlainObject(value)) {
|
|
65
|
-
const result = {};
|
|
66
|
-
for (const [key, item] of Object.entries(value)) {
|
|
67
|
-
result[key] = resolveTemplateValue(item, context);
|
|
68
|
-
}
|
|
69
|
-
return result;
|
|
70
|
-
}
|
|
71
|
-
if (typeof value === 'string') {
|
|
72
|
-
return resolveTemplateString(value, context);
|
|
73
|
-
}
|
|
74
|
-
return value;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function evaluateStepCondition(condition, context) {
|
|
78
|
-
if (!condition) {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
if (!isPlainObject(condition)) {
|
|
82
|
-
return !!condition;
|
|
83
|
-
}
|
|
84
|
-
const path = typeof condition.path === 'string' ? condition.path.trim() : '';
|
|
85
|
-
if (!path) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
const value = getPathValue(context, path);
|
|
89
|
-
if (Object.prototype.hasOwnProperty.call(condition, 'equals')) {
|
|
90
|
-
return value === condition.equals;
|
|
91
|
-
}
|
|
92
|
-
if (Object.prototype.hasOwnProperty.call(condition, 'notEquals')) {
|
|
93
|
-
return value !== condition.notEquals;
|
|
94
|
-
}
|
|
95
|
-
if (Object.prototype.hasOwnProperty.call(condition, 'truthy')) {
|
|
96
|
-
return condition.truthy ? !!value : !value;
|
|
97
|
-
}
|
|
98
|
-
if (Object.prototype.hasOwnProperty.call(condition, 'exists')) {
|
|
99
|
-
return condition.exists ? value !== undefined : value === undefined;
|
|
100
|
-
}
|
|
101
|
-
return !!value;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function isFailurePayload(payload) {
|
|
105
|
-
if (!payload || typeof payload !== 'object') {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
if (typeof payload.error === 'string' && payload.error.trim()) {
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
if (payload.success === false) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function toIso(value) {
|
|
118
|
-
const date = value ? new Date(value) : new Date();
|
|
119
|
-
if (Number.isNaN(date.getTime())) {
|
|
120
|
-
return '';
|
|
121
|
-
}
|
|
122
|
-
return date.toISOString();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function validateWorkflowDefinition(definition, options = {}) {
|
|
126
|
-
const issues = [];
|
|
127
|
-
const knownTools = options.knownTools instanceof Set ? options.knownTools : new Set();
|
|
128
|
-
|
|
129
|
-
if (!definition || !isPlainObject(definition)) {
|
|
130
|
-
return { ok: false, issues: [{ code: 'workflow-invalid', message: 'workflow must be an object' }] };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const id = typeof definition.id === 'string' ? definition.id.trim() : '';
|
|
134
|
-
if (!id) {
|
|
135
|
-
issues.push({ code: 'workflow-id-required', message: 'workflow.id is required' });
|
|
136
|
-
}
|
|
137
|
-
const steps = Array.isArray(definition.steps) ? definition.steps : [];
|
|
138
|
-
if (steps.length === 0) {
|
|
139
|
-
issues.push({ code: 'workflow-steps-required', message: 'workflow.steps must be a non-empty array' });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const stepIds = new Set();
|
|
143
|
-
for (const step of steps) {
|
|
144
|
-
if (!step || !isPlainObject(step)) {
|
|
145
|
-
issues.push({ code: 'workflow-step-invalid', message: 'workflow step must be an object' });
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
const stepId = typeof step.id === 'string' ? step.id.trim() : '';
|
|
149
|
-
if (!stepId) {
|
|
150
|
-
issues.push({ code: 'workflow-step-id-required', message: 'workflow step id is required' });
|
|
151
|
-
} else if (stepIds.has(stepId)) {
|
|
152
|
-
issues.push({ code: 'workflow-step-id-duplicate', message: `duplicate step id: ${stepId}` });
|
|
153
|
-
} else {
|
|
154
|
-
stepIds.add(stepId);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const tool = typeof step.tool === 'string' ? step.tool.trim() : '';
|
|
158
|
-
if (!tool) {
|
|
159
|
-
issues.push({ code: 'workflow-step-tool-required', message: `step ${stepId || '(unknown)'} missing tool` });
|
|
160
|
-
} else if (knownTools.size > 0 && !knownTools.has(tool)) {
|
|
161
|
-
issues.push({ code: 'workflow-step-tool-unknown', message: `step ${stepId || '(unknown)'} unknown tool: ${tool}` });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (step.when !== undefined && step.when !== null && !isPlainObject(step.when)) {
|
|
165
|
-
issues.push({ code: 'workflow-step-when-invalid', message: `step ${stepId || '(unknown)'} when must be an object` });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (issues.length > 0) {
|
|
170
|
-
return {
|
|
171
|
-
ok: false,
|
|
172
|
-
issues,
|
|
173
|
-
error: issues[0].message
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return { ok: true, issues: [] };
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function executeWorkflowDefinition(definition, input = {}, options = {}) {
|
|
181
|
-
const invokeTool = typeof options.invokeTool === 'function'
|
|
182
|
-
? options.invokeTool
|
|
183
|
-
: async () => ({ error: 'invokeTool is not configured' });
|
|
184
|
-
const allowWrite = options.allowWrite === true;
|
|
185
|
-
const dryRun = options.dryRun === true;
|
|
186
|
-
|
|
187
|
-
const startedAtTs = Date.now();
|
|
188
|
-
const context = {
|
|
189
|
-
input: isPlainObject(input) ? input : {},
|
|
190
|
-
steps: {}
|
|
191
|
-
};
|
|
192
|
-
const steps = Array.isArray(definition && definition.steps) ? definition.steps : [];
|
|
193
|
-
const logs = [];
|
|
194
|
-
let overallError = '';
|
|
195
|
-
let lastOutput = null;
|
|
196
|
-
|
|
197
|
-
for (const step of steps) {
|
|
198
|
-
const stepId = typeof step.id === 'string' ? step.id.trim() : '';
|
|
199
|
-
const startedStepTs = Date.now();
|
|
200
|
-
const baseLog = {
|
|
201
|
-
id: stepId || '',
|
|
202
|
-
tool: typeof step.tool === 'string' ? step.tool.trim() : '',
|
|
203
|
-
startedAt: toIso(startedStepTs)
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const conditionPass = evaluateStepCondition(step.when, context);
|
|
207
|
-
if (!conditionPass) {
|
|
208
|
-
const skippedLog = {
|
|
209
|
-
...baseLog,
|
|
210
|
-
status: 'skipped',
|
|
211
|
-
reason: 'condition-not-met',
|
|
212
|
-
endedAt: toIso(Date.now()),
|
|
213
|
-
durationMs: Date.now() - startedStepTs
|
|
214
|
-
};
|
|
215
|
-
logs.push(skippedLog);
|
|
216
|
-
context.steps[stepId] = {
|
|
217
|
-
status: 'skipped',
|
|
218
|
-
output: null,
|
|
219
|
-
error: '',
|
|
220
|
-
args: null
|
|
221
|
-
};
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const resolvedArgs = resolveTemplateValue(isPlainObject(step.arguments) ? step.arguments : {}, context);
|
|
226
|
-
const isWriteStep = step.write === true;
|
|
227
|
-
if (isWriteStep && !allowWrite) {
|
|
228
|
-
const error = `write step requires allowWrite: ${stepId || baseLog.tool}`;
|
|
229
|
-
const failedLog = {
|
|
230
|
-
...baseLog,
|
|
231
|
-
args: resolvedArgs,
|
|
232
|
-
status: 'failed',
|
|
233
|
-
error,
|
|
234
|
-
endedAt: toIso(Date.now()),
|
|
235
|
-
durationMs: Date.now() - startedStepTs
|
|
236
|
-
};
|
|
237
|
-
logs.push(failedLog);
|
|
238
|
-
context.steps[stepId] = {
|
|
239
|
-
status: 'failed',
|
|
240
|
-
output: null,
|
|
241
|
-
error,
|
|
242
|
-
args: resolvedArgs
|
|
243
|
-
};
|
|
244
|
-
overallError = error;
|
|
245
|
-
if (!step.continueOnError) {
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (isWriteStep && dryRun) {
|
|
252
|
-
const dryRunLog = {
|
|
253
|
-
...baseLog,
|
|
254
|
-
args: resolvedArgs,
|
|
255
|
-
status: 'skipped',
|
|
256
|
-
reason: 'dry-run-write-step',
|
|
257
|
-
endedAt: toIso(Date.now()),
|
|
258
|
-
durationMs: Date.now() - startedStepTs
|
|
259
|
-
};
|
|
260
|
-
logs.push(dryRunLog);
|
|
261
|
-
context.steps[stepId] = {
|
|
262
|
-
status: 'skipped',
|
|
263
|
-
output: null,
|
|
264
|
-
error: '',
|
|
265
|
-
args: resolvedArgs
|
|
266
|
-
};
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
let payload;
|
|
271
|
-
let stepError = '';
|
|
272
|
-
try {
|
|
273
|
-
payload = await invokeTool(baseLog.tool, resolvedArgs, {
|
|
274
|
-
step,
|
|
275
|
-
context,
|
|
276
|
-
allowWrite,
|
|
277
|
-
dryRun
|
|
278
|
-
});
|
|
279
|
-
if (isFailurePayload(payload)) {
|
|
280
|
-
stepError = typeof payload.error === 'string' && payload.error.trim()
|
|
281
|
-
? payload.error.trim()
|
|
282
|
-
: `step failed: ${stepId || baseLog.tool}`;
|
|
283
|
-
}
|
|
284
|
-
} catch (error) {
|
|
285
|
-
stepError = error && error.message ? error.message : String(error || 'step execution failed');
|
|
286
|
-
payload = { error: stepError };
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const status = stepError ? 'failed' : 'success';
|
|
290
|
-
const endedAtTs = Date.now();
|
|
291
|
-
const stepLog = {
|
|
292
|
-
...baseLog,
|
|
293
|
-
args: resolvedArgs,
|
|
294
|
-
status,
|
|
295
|
-
output: payload,
|
|
296
|
-
error: stepError,
|
|
297
|
-
endedAt: toIso(endedAtTs),
|
|
298
|
-
durationMs: endedAtTs - startedStepTs
|
|
299
|
-
};
|
|
300
|
-
logs.push(stepLog);
|
|
301
|
-
context.steps[stepId] = {
|
|
302
|
-
status,
|
|
303
|
-
output: payload,
|
|
304
|
-
error: stepError,
|
|
305
|
-
args: resolvedArgs
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
if (!stepError) {
|
|
309
|
-
lastOutput = payload;
|
|
310
|
-
} else {
|
|
311
|
-
overallError = stepError;
|
|
312
|
-
if (!step.continueOnError) {
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const endedAtTs = Date.now();
|
|
319
|
-
const hasFailed = logs.some((item) => item.status === 'failed');
|
|
320
|
-
const result = {
|
|
321
|
-
success: !hasFailed,
|
|
322
|
-
startedAt: toIso(startedAtTs),
|
|
323
|
-
endedAt: toIso(endedAtTs),
|
|
324
|
-
durationMs: endedAtTs - startedAtTs,
|
|
325
|
-
steps: logs,
|
|
326
|
-
output: lastOutput || null
|
|
327
|
-
};
|
|
328
|
-
if (overallError) {
|
|
329
|
-
result.error = overallError;
|
|
330
|
-
}
|
|
331
|
-
return result;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
module.exports = {
|
|
335
|
-
getPathValue,
|
|
336
|
-
resolveTemplateValue,
|
|
337
|
-
evaluateStepCondition,
|
|
338
|
-
validateWorkflowDefinition,
|
|
339
|
-
executeWorkflowDefinition
|
|
340
|
-
};
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function getPathValue(source, path) {
|
|
6
|
+
if (!path || typeof path !== 'string') {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const parts = path
|
|
10
|
+
.split('.')
|
|
11
|
+
.map((part) => part.trim())
|
|
12
|
+
.filter(Boolean);
|
|
13
|
+
let current = source;
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
if (current === null || current === undefined) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(current)) {
|
|
19
|
+
const index = Number.parseInt(part, 10);
|
|
20
|
+
if (!Number.isFinite(index)) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
current = current[index];
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!isPlainObject(current)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
current = current[part];
|
|
30
|
+
}
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveTemplateString(template, context) {
|
|
35
|
+
if (typeof template !== 'string') {
|
|
36
|
+
return template;
|
|
37
|
+
}
|
|
38
|
+
const direct = template.match(/^\{\{\s*([^{}]+?)\s*\}\}$/);
|
|
39
|
+
if (direct) {
|
|
40
|
+
return getPathValue(context, direct[1].trim());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pattern = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
44
|
+
return template.replace(pattern, (_, rawPath) => {
|
|
45
|
+
const value = getPathValue(context, String(rawPath || '').trim());
|
|
46
|
+
if (value === undefined || value === null) {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === 'object') {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify(value);
|
|
52
|
+
} catch (_) {
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return String(value);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveTemplateValue(value, context) {
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
return value.map((item) => resolveTemplateValue(item, context));
|
|
63
|
+
}
|
|
64
|
+
if (isPlainObject(value)) {
|
|
65
|
+
const result = {};
|
|
66
|
+
for (const [key, item] of Object.entries(value)) {
|
|
67
|
+
result[key] = resolveTemplateValue(item, context);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
if (typeof value === 'string') {
|
|
72
|
+
return resolveTemplateString(value, context);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function evaluateStepCondition(condition, context) {
|
|
78
|
+
if (!condition) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (!isPlainObject(condition)) {
|
|
82
|
+
return !!condition;
|
|
83
|
+
}
|
|
84
|
+
const path = typeof condition.path === 'string' ? condition.path.trim() : '';
|
|
85
|
+
if (!path) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
const value = getPathValue(context, path);
|
|
89
|
+
if (Object.prototype.hasOwnProperty.call(condition, 'equals')) {
|
|
90
|
+
return value === condition.equals;
|
|
91
|
+
}
|
|
92
|
+
if (Object.prototype.hasOwnProperty.call(condition, 'notEquals')) {
|
|
93
|
+
return value !== condition.notEquals;
|
|
94
|
+
}
|
|
95
|
+
if (Object.prototype.hasOwnProperty.call(condition, 'truthy')) {
|
|
96
|
+
return condition.truthy ? !!value : !value;
|
|
97
|
+
}
|
|
98
|
+
if (Object.prototype.hasOwnProperty.call(condition, 'exists')) {
|
|
99
|
+
return condition.exists ? value !== undefined : value === undefined;
|
|
100
|
+
}
|
|
101
|
+
return !!value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isFailurePayload(payload) {
|
|
105
|
+
if (!payload || typeof payload !== 'object') {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (typeof payload.error === 'string' && payload.error.trim()) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (payload.success === false) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toIso(value) {
|
|
118
|
+
const date = value ? new Date(value) : new Date();
|
|
119
|
+
if (Number.isNaN(date.getTime())) {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
return date.toISOString();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validateWorkflowDefinition(definition, options = {}) {
|
|
126
|
+
const issues = [];
|
|
127
|
+
const knownTools = options.knownTools instanceof Set ? options.knownTools : new Set();
|
|
128
|
+
|
|
129
|
+
if (!definition || !isPlainObject(definition)) {
|
|
130
|
+
return { ok: false, issues: [{ code: 'workflow-invalid', message: 'workflow must be an object' }] };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const id = typeof definition.id === 'string' ? definition.id.trim() : '';
|
|
134
|
+
if (!id) {
|
|
135
|
+
issues.push({ code: 'workflow-id-required', message: 'workflow.id is required' });
|
|
136
|
+
}
|
|
137
|
+
const steps = Array.isArray(definition.steps) ? definition.steps : [];
|
|
138
|
+
if (steps.length === 0) {
|
|
139
|
+
issues.push({ code: 'workflow-steps-required', message: 'workflow.steps must be a non-empty array' });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const stepIds = new Set();
|
|
143
|
+
for (const step of steps) {
|
|
144
|
+
if (!step || !isPlainObject(step)) {
|
|
145
|
+
issues.push({ code: 'workflow-step-invalid', message: 'workflow step must be an object' });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const stepId = typeof step.id === 'string' ? step.id.trim() : '';
|
|
149
|
+
if (!stepId) {
|
|
150
|
+
issues.push({ code: 'workflow-step-id-required', message: 'workflow step id is required' });
|
|
151
|
+
} else if (stepIds.has(stepId)) {
|
|
152
|
+
issues.push({ code: 'workflow-step-id-duplicate', message: `duplicate step id: ${stepId}` });
|
|
153
|
+
} else {
|
|
154
|
+
stepIds.add(stepId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const tool = typeof step.tool === 'string' ? step.tool.trim() : '';
|
|
158
|
+
if (!tool) {
|
|
159
|
+
issues.push({ code: 'workflow-step-tool-required', message: `step ${stepId || '(unknown)'} missing tool` });
|
|
160
|
+
} else if (knownTools.size > 0 && !knownTools.has(tool)) {
|
|
161
|
+
issues.push({ code: 'workflow-step-tool-unknown', message: `step ${stepId || '(unknown)'} unknown tool: ${tool}` });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (step.when !== undefined && step.when !== null && !isPlainObject(step.when)) {
|
|
165
|
+
issues.push({ code: 'workflow-step-when-invalid', message: `step ${stepId || '(unknown)'} when must be an object` });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (issues.length > 0) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
issues,
|
|
173
|
+
error: issues[0].message
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { ok: true, issues: [] };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function executeWorkflowDefinition(definition, input = {}, options = {}) {
|
|
181
|
+
const invokeTool = typeof options.invokeTool === 'function'
|
|
182
|
+
? options.invokeTool
|
|
183
|
+
: async () => ({ error: 'invokeTool is not configured' });
|
|
184
|
+
const allowWrite = options.allowWrite === true;
|
|
185
|
+
const dryRun = options.dryRun === true;
|
|
186
|
+
|
|
187
|
+
const startedAtTs = Date.now();
|
|
188
|
+
const context = {
|
|
189
|
+
input: isPlainObject(input) ? input : {},
|
|
190
|
+
steps: {}
|
|
191
|
+
};
|
|
192
|
+
const steps = Array.isArray(definition && definition.steps) ? definition.steps : [];
|
|
193
|
+
const logs = [];
|
|
194
|
+
let overallError = '';
|
|
195
|
+
let lastOutput = null;
|
|
196
|
+
|
|
197
|
+
for (const step of steps) {
|
|
198
|
+
const stepId = typeof step.id === 'string' ? step.id.trim() : '';
|
|
199
|
+
const startedStepTs = Date.now();
|
|
200
|
+
const baseLog = {
|
|
201
|
+
id: stepId || '',
|
|
202
|
+
tool: typeof step.tool === 'string' ? step.tool.trim() : '',
|
|
203
|
+
startedAt: toIso(startedStepTs)
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const conditionPass = evaluateStepCondition(step.when, context);
|
|
207
|
+
if (!conditionPass) {
|
|
208
|
+
const skippedLog = {
|
|
209
|
+
...baseLog,
|
|
210
|
+
status: 'skipped',
|
|
211
|
+
reason: 'condition-not-met',
|
|
212
|
+
endedAt: toIso(Date.now()),
|
|
213
|
+
durationMs: Date.now() - startedStepTs
|
|
214
|
+
};
|
|
215
|
+
logs.push(skippedLog);
|
|
216
|
+
context.steps[stepId] = {
|
|
217
|
+
status: 'skipped',
|
|
218
|
+
output: null,
|
|
219
|
+
error: '',
|
|
220
|
+
args: null
|
|
221
|
+
};
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const resolvedArgs = resolveTemplateValue(isPlainObject(step.arguments) ? step.arguments : {}, context);
|
|
226
|
+
const isWriteStep = step.write === true;
|
|
227
|
+
if (isWriteStep && !allowWrite) {
|
|
228
|
+
const error = `write step requires allowWrite: ${stepId || baseLog.tool}`;
|
|
229
|
+
const failedLog = {
|
|
230
|
+
...baseLog,
|
|
231
|
+
args: resolvedArgs,
|
|
232
|
+
status: 'failed',
|
|
233
|
+
error,
|
|
234
|
+
endedAt: toIso(Date.now()),
|
|
235
|
+
durationMs: Date.now() - startedStepTs
|
|
236
|
+
};
|
|
237
|
+
logs.push(failedLog);
|
|
238
|
+
context.steps[stepId] = {
|
|
239
|
+
status: 'failed',
|
|
240
|
+
output: null,
|
|
241
|
+
error,
|
|
242
|
+
args: resolvedArgs
|
|
243
|
+
};
|
|
244
|
+
overallError = error;
|
|
245
|
+
if (!step.continueOnError) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (isWriteStep && dryRun) {
|
|
252
|
+
const dryRunLog = {
|
|
253
|
+
...baseLog,
|
|
254
|
+
args: resolvedArgs,
|
|
255
|
+
status: 'skipped',
|
|
256
|
+
reason: 'dry-run-write-step',
|
|
257
|
+
endedAt: toIso(Date.now()),
|
|
258
|
+
durationMs: Date.now() - startedStepTs
|
|
259
|
+
};
|
|
260
|
+
logs.push(dryRunLog);
|
|
261
|
+
context.steps[stepId] = {
|
|
262
|
+
status: 'skipped',
|
|
263
|
+
output: null,
|
|
264
|
+
error: '',
|
|
265
|
+
args: resolvedArgs
|
|
266
|
+
};
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let payload;
|
|
271
|
+
let stepError = '';
|
|
272
|
+
try {
|
|
273
|
+
payload = await invokeTool(baseLog.tool, resolvedArgs, {
|
|
274
|
+
step,
|
|
275
|
+
context,
|
|
276
|
+
allowWrite,
|
|
277
|
+
dryRun
|
|
278
|
+
});
|
|
279
|
+
if (isFailurePayload(payload)) {
|
|
280
|
+
stepError = typeof payload.error === 'string' && payload.error.trim()
|
|
281
|
+
? payload.error.trim()
|
|
282
|
+
: `step failed: ${stepId || baseLog.tool}`;
|
|
283
|
+
}
|
|
284
|
+
} catch (error) {
|
|
285
|
+
stepError = error && error.message ? error.message : String(error || 'step execution failed');
|
|
286
|
+
payload = { error: stepError };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const status = stepError ? 'failed' : 'success';
|
|
290
|
+
const endedAtTs = Date.now();
|
|
291
|
+
const stepLog = {
|
|
292
|
+
...baseLog,
|
|
293
|
+
args: resolvedArgs,
|
|
294
|
+
status,
|
|
295
|
+
output: payload,
|
|
296
|
+
error: stepError,
|
|
297
|
+
endedAt: toIso(endedAtTs),
|
|
298
|
+
durationMs: endedAtTs - startedStepTs
|
|
299
|
+
};
|
|
300
|
+
logs.push(stepLog);
|
|
301
|
+
context.steps[stepId] = {
|
|
302
|
+
status,
|
|
303
|
+
output: payload,
|
|
304
|
+
error: stepError,
|
|
305
|
+
args: resolvedArgs
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
if (!stepError) {
|
|
309
|
+
lastOutput = payload;
|
|
310
|
+
} else {
|
|
311
|
+
overallError = stepError;
|
|
312
|
+
if (!step.continueOnError) {
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const endedAtTs = Date.now();
|
|
319
|
+
const hasFailed = logs.some((item) => item.status === 'failed');
|
|
320
|
+
const result = {
|
|
321
|
+
success: !hasFailed,
|
|
322
|
+
startedAt: toIso(startedAtTs),
|
|
323
|
+
endedAt: toIso(endedAtTs),
|
|
324
|
+
durationMs: endedAtTs - startedAtTs,
|
|
325
|
+
steps: logs,
|
|
326
|
+
output: lastOutput || null
|
|
327
|
+
};
|
|
328
|
+
if (overallError) {
|
|
329
|
+
result.error = overallError;
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = {
|
|
335
|
+
getPathValue,
|
|
336
|
+
resolveTemplateValue,
|
|
337
|
+
evaluateStepCondition,
|
|
338
|
+
validateWorkflowDefinition,
|
|
339
|
+
executeWorkflowDefinition
|
|
340
|
+
};
|