foliko 2.0.6 → 2.0.8
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/settings.local.json +2 -1
- package/docs/public-api.md +3 -3
- package/examples/workflow.js +62 -57
- package/package.json +2 -3
- package/plugins/core/scheduler/CHANGELOG.md +31 -0
- package/plugins/core/scheduler/index.js +576 -638
- package/plugins/core/workflow/context.js +941 -0
- package/plugins/core/workflow/engine.js +66 -0
- package/plugins/core/workflow/examples/01-basic.js +42 -0
- package/plugins/core/workflow/examples/01-basic.json +30 -0
- package/plugins/core/workflow/examples/02-choice.js +75 -0
- package/plugins/core/workflow/examples/02-choice.json +59 -0
- package/plugins/core/workflow/examples/03-chain-style.js +114 -0
- package/plugins/core/workflow/examples/03-each.json +41 -0
- package/plugins/core/workflow/examples/04-parallel.js +52 -0
- package/plugins/core/workflow/examples/04-parallel.json +51 -0
- package/plugins/core/workflow/examples/05-chain-style.json +68 -0
- package/plugins/core/workflow/examples/05-each.js +65 -0
- package/plugins/core/workflow/examples/06-script.js +82 -0
- package/plugins/core/workflow/examples/07-module-export.js +43 -0
- package/plugins/core/workflow/examples/08-default-export.js +29 -0
- package/plugins/core/workflow/examples/09-next-with-args.js +50 -0
- package/plugins/core/workflow/examples/10-logger.js +34 -0
- package/plugins/core/workflow/examples/11-storage.js +68 -0
- package/plugins/core/workflow/examples/simple.js +77 -0
- package/plugins/core/workflow/examples/simple.json +75 -0
- package/plugins/core/workflow/index.js +124 -58
- package/plugins/core/workflow/js-runner.js +318 -0
- package/plugins/core/workflow/json-runner.js +323 -0
- package/plugins/core/workflow/stages/action.js +211 -0
- package/plugins/core/workflow/stages/choice.js +74 -0
- package/plugins/core/workflow/stages/delay.js +73 -0
- package/plugins/core/workflow/stages/each.js +123 -0
- package/plugins/core/workflow/stages/parallel.js +69 -0
- package/plugins/core/workflow/stages/try.js +142 -0
- package/plugins/executors/data-splitter/index.js +3 -2
- package/plugins/memory/index.js +10 -3
- package/sandbox/check-context.js +5 -0
- package/sandbox/test-context.js +27 -0
- package/sandbox/test-fixes.js +40 -0
- package/sandbox/test-hello-js.js +46 -0
- package/skills/workflows/SKILL.md +715 -613
- package/src/agent/chat.js +8 -1
- package/src/agent/main.js +5 -1
- package/src/cli/ui/chat-ui.js +4 -3
- package/src/common/json-safe.js +20 -0
- package/src/framework/framework.js +58 -2
- package/src/index.js +10 -0
- package/src/plugin/base.js +7 -4
- package/src/plugin/manager.js +95 -1
- package/src/utils/sandbox.js +1 -1
- package/skills/workflows/workflow-troubleshooting/DEBUGGING.md +0 -197
- package/skills/workflows/workflow-troubleshooting/SKILL.md +0 -331
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* each stage executor
|
|
3
|
+
* Loop over items
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class EachStage {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.name = config.name;
|
|
9
|
+
this.each = config.each; // Array to iterate over
|
|
10
|
+
this.as = config.as || 'item'; // Item variable name
|
|
11
|
+
this.index = config.index || 'index'; // Index variable name
|
|
12
|
+
this.stages = config.stages || []; // Child stages to execute for each item
|
|
13
|
+
this.maxIterations = config.maxIterations || 100;
|
|
14
|
+
this.until = config.until; // Early exit condition
|
|
15
|
+
this._log = require('../../../../src/common/logger').logger.child('EachStage');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute the each stage
|
|
20
|
+
* @param {FlowContext} context
|
|
21
|
+
* @returns {Promise<Array>} results for each iteration
|
|
22
|
+
*/
|
|
23
|
+
async execute(context) {
|
|
24
|
+
this._log.debug(`Executing each loop: ${this.name}`);
|
|
25
|
+
|
|
26
|
+
// Resolve the array to iterate
|
|
27
|
+
let array;
|
|
28
|
+
if (typeof this.each === 'string') {
|
|
29
|
+
// Strip optional surrounding {{ }} so users can write either
|
|
30
|
+
// "each": "items" (bare identifier)
|
|
31
|
+
// "each": "{{items}}" (templated)
|
|
32
|
+
// "each": "{{init.items}}" (stage.field)
|
|
33
|
+
let path = this.each.trim();
|
|
34
|
+
const m = path.match(/^\{\{([^}]+)\}\}$/);
|
|
35
|
+
if (m) path = m[1].trim();
|
|
36
|
+
|
|
37
|
+
// Use the low-level _resolvePath so we get the actual array/object back
|
|
38
|
+
// (String.prototype.replace in resolveValue would coerce non-strings.)
|
|
39
|
+
let resolved = context._resolvePath(path);
|
|
40
|
+
if (resolved === undefined && path.startsWith('[')) {
|
|
41
|
+
// Support inline array literal: each: "['a','b']"
|
|
42
|
+
try { resolved = JSON.parse(path); } catch {}
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(resolved)) {
|
|
45
|
+
array = resolved;
|
|
46
|
+
} else if (resolved && typeof resolved === 'object') {
|
|
47
|
+
// Convert object to entries (so {{k, v}} style iteration works)
|
|
48
|
+
array = Object.entries(resolved);
|
|
49
|
+
} else {
|
|
50
|
+
throw new Error(`each "${this.each}" resolved to non-iterable: ${typeof resolved}`);
|
|
51
|
+
}
|
|
52
|
+
} else if (Array.isArray(this.each)) {
|
|
53
|
+
array = this.each;
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error(`each stage "${this.name}" requires an array to iterate`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const results = [];
|
|
59
|
+
const maxIter = Math.min(this.maxIterations, array.length);
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < maxIter; i++) {
|
|
62
|
+
// Set loop variables
|
|
63
|
+
context.data[this.as] = array[i];
|
|
64
|
+
context.data[this.index] = i;
|
|
65
|
+
context.data.loopIndex = i; // Legacy alias
|
|
66
|
+
|
|
67
|
+
this._log.debug(`Each iteration ${i}: ${this.as}=`, array[i]);
|
|
68
|
+
|
|
69
|
+
// Execute child stages sequentially
|
|
70
|
+
let stageResults = [];
|
|
71
|
+
for (const stageConfig of this.stages) {
|
|
72
|
+
const stageExecutor = context._stageExecutor;
|
|
73
|
+
if (stageExecutor) {
|
|
74
|
+
const result = await stageExecutor.executeStage(stageConfig, context);
|
|
75
|
+
stageResults.push(result);
|
|
76
|
+
|
|
77
|
+
// Check for early exit
|
|
78
|
+
if (context._destroyed) {
|
|
79
|
+
this._log.debug('Flow destroyed, stopping each loop');
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
results.push(stageResults);
|
|
85
|
+
|
|
86
|
+
// Check until condition
|
|
87
|
+
if (this.until) {
|
|
88
|
+
try {
|
|
89
|
+
const { evaluateInSandbox } = require('../../../../../src/utils/sandbox');
|
|
90
|
+
const evalContext = {
|
|
91
|
+
input: context.input,
|
|
92
|
+
data: context.data,
|
|
93
|
+
variables: Object.fromEntries(context._results),
|
|
94
|
+
previousResult: context._sentData,
|
|
95
|
+
item: array[i],
|
|
96
|
+
index: i,
|
|
97
|
+
};
|
|
98
|
+
const shouldStop = evaluateInSandbox(this.until, evalContext, { timeout: 5000 });
|
|
99
|
+
if (shouldStop) {
|
|
100
|
+
this._log.debug(`Each loop terminated at iteration ${i} by until condition`);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
this._log.warn(`until condition error: ${err.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Cleanup loop variables
|
|
110
|
+
delete context.data[this.as];
|
|
111
|
+
delete context.data[this.index];
|
|
112
|
+
delete context.data.loopIndex;
|
|
113
|
+
|
|
114
|
+
// Store results by name if this stage has a name
|
|
115
|
+
if (this.name) {
|
|
116
|
+
context.setResult(this.name, results);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { EachStage };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parallel stage executor
|
|
3
|
+
* Run stages concurrently
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class ParallelStage {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.name = config.name;
|
|
9
|
+
this.parallel = config.parallel || []; // Array of stages to run in parallel
|
|
10
|
+
this._log = require('../../../../src/common/logger').logger.child('ParallelStage');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Execute the parallel stage
|
|
15
|
+
* @param {FlowContext} context
|
|
16
|
+
* @returns {Promise<Array>} results from all parallel stages
|
|
17
|
+
*/
|
|
18
|
+
async execute(context) {
|
|
19
|
+
this._log.debug(`Executing parallel: ${this.name}`);
|
|
20
|
+
|
|
21
|
+
if (!this.parallel || this.parallel.length === 0) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const stageExecutor = context._stageExecutor;
|
|
26
|
+
if (!stageExecutor) {
|
|
27
|
+
throw new Error('Stage executor not set in context');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Execute all stages concurrently
|
|
31
|
+
const promises = this.parallel.map(async (stageConfig) => {
|
|
32
|
+
try {
|
|
33
|
+
return await stageExecutor.executeStage(stageConfig, context);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
this._log.warn(`Parallel stage error: ${err.message}`);
|
|
36
|
+
return { error: err.message };
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const results = await Promise.all(promises);
|
|
41
|
+
|
|
42
|
+
// Build result object keyed by stage name (not plain array)
|
|
43
|
+
const resultByName = {};
|
|
44
|
+
for (let i = 0; i < this.parallel.length; i++) {
|
|
45
|
+
const stageConfig = this.parallel[i];
|
|
46
|
+
const name = stageConfig.name || String(i);
|
|
47
|
+
resultByName[name] = results[i];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Store named results in context
|
|
51
|
+
// If the parallel block itself has a name, store the keyed object
|
|
52
|
+
if (this.name) {
|
|
53
|
+
context.setResult(this.name, resultByName);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Also store individual named results for access via {{stageName}}
|
|
57
|
+
for (let i = 0; i < this.parallel.length; i++) {
|
|
58
|
+
const stageConfig = this.parallel[i];
|
|
59
|
+
if (stageConfig.name) {
|
|
60
|
+
context.setResult(stageConfig.name, results[i]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this._log.debug(`Parallel completed: ${results.length} stages`);
|
|
65
|
+
return resultByName;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { ParallelStage };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* try stage executor
|
|
3
|
+
*
|
|
4
|
+
* Wraps a sequence of child stages in a try/catch so a single failure
|
|
5
|
+
* doesn't tear down the entire workflow. Optional `finally` stages run
|
|
6
|
+
* regardless of outcome.
|
|
7
|
+
*
|
|
8
|
+
* Configuration:
|
|
9
|
+
* {
|
|
10
|
+
* "name": "safe-fetch", // optional, allows {{safe-fetch.success}} access
|
|
11
|
+
* "try": {
|
|
12
|
+
* "stages": [ ... ] // required
|
|
13
|
+
* },
|
|
14
|
+
* "catch": { // optional
|
|
15
|
+
* "stages": [ ... ],
|
|
16
|
+
* "as": "err" // optional name to inject error.message into context.data
|
|
17
|
+
* },
|
|
18
|
+
* "finally": { // optional
|
|
19
|
+
* "stages": [ ... ]
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* Result shape (stored on the named stage and returned):
|
|
24
|
+
* {
|
|
25
|
+
* success: true | false,
|
|
26
|
+
* result: <last try stage output>, // when success=true
|
|
27
|
+
* error: "<message>", // when success=false (optional stack)
|
|
28
|
+
* caught: [ ...catch stage results... ], // when catch ran
|
|
29
|
+
* finally: [ ...finally stage results... ] // when finally ran
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
class TryStage {
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.name = config.name;
|
|
36
|
+
this.tryConfig = config.try || { stages: [] };
|
|
37
|
+
this.catchConfig = config.catch || null;
|
|
38
|
+
this.finallyConfig = config.finally || null;
|
|
39
|
+
this._log = require('../../../../src/common/logger').logger.child('TryStage');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Execute a list of sub-stages using the shared StageExecutor.
|
|
44
|
+
* Wraps each individual stage in its own try/catch so a single stage failure
|
|
45
|
+
* doesn't break the chain unless it's the top-level try.
|
|
46
|
+
*/
|
|
47
|
+
async _runStages(stages, context, { stopOnError = false } = {}) {
|
|
48
|
+
if (!stages || !Array.isArray(stages) || stages.length === 0) return [];
|
|
49
|
+
const stageExecutor = context._stageExecutor;
|
|
50
|
+
if (!stageExecutor) {
|
|
51
|
+
throw new Error('Stage executor not set in context');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const stage of stages) {
|
|
56
|
+
try {
|
|
57
|
+
const result = await stageExecutor.executeStage(stage, context);
|
|
58
|
+
out.push(result);
|
|
59
|
+
if (context._destroyed) break;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (stopOnError) {
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
this._log.warn(`Stage "${stage.name || '(unnamed)'}" failed inside try: ${err.message}`);
|
|
65
|
+
out.push({ error: err.message, stage: stage.name });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async execute(context) {
|
|
72
|
+
this._log.debug(`Executing try stage: ${this.name || '(unnamed)'}`);
|
|
73
|
+
|
|
74
|
+
let success = true;
|
|
75
|
+
let error = null;
|
|
76
|
+
let tryResult = null;
|
|
77
|
+
let caughtResult = null;
|
|
78
|
+
let finallyResult = null;
|
|
79
|
+
|
|
80
|
+
// === try block ===
|
|
81
|
+
try {
|
|
82
|
+
tryResult = await this._runStages(this.tryConfig.stages || [], context, { stopOnError: true });
|
|
83
|
+
} catch (err) {
|
|
84
|
+
success = false;
|
|
85
|
+
error = err;
|
|
86
|
+
this._log.debug(`Try block failed: ${err.message}`);
|
|
87
|
+
|
|
88
|
+
// === catch block ===
|
|
89
|
+
if (this.catchConfig) {
|
|
90
|
+
// Optionally expose the error under a chosen key in context.data
|
|
91
|
+
if (this.catchConfig.as) {
|
|
92
|
+
context.data[this.catchConfig.as] = err;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
caughtResult = await this._runStages(this.catchConfig.stages || [], context);
|
|
96
|
+
} catch (catchErr) {
|
|
97
|
+
this._log.warn(`catch block itself failed: ${catchErr.message}`);
|
|
98
|
+
caughtResult = [{ error: catchErr.message }];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// === finally block (always runs) ===
|
|
104
|
+
if (this.finallyConfig) {
|
|
105
|
+
try {
|
|
106
|
+
finallyResult = await this._runStages(this.finallyConfig.stages || [], context);
|
|
107
|
+
} catch (finallyErr) {
|
|
108
|
+
this._log.warn(`finally block failed: ${finallyErr.message}`);
|
|
109
|
+
finallyResult = [{ error: finallyErr.message }];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Cleanup injected error reference so it doesn't leak past the try stage
|
|
114
|
+
if (this.catchConfig && this.catchConfig.as) {
|
|
115
|
+
delete context.data[this.catchConfig.as];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const output = success
|
|
119
|
+
? {
|
|
120
|
+
success: true,
|
|
121
|
+
result: tryResult && tryResult.length > 0 ? tryResult[tryResult.length - 1] : null,
|
|
122
|
+
stages: tryResult,
|
|
123
|
+
finally: finallyResult,
|
|
124
|
+
}
|
|
125
|
+
: {
|
|
126
|
+
success: false,
|
|
127
|
+
error: error.message,
|
|
128
|
+
stack: error.stack,
|
|
129
|
+
caught: caughtResult,
|
|
130
|
+
stages: tryResult,
|
|
131
|
+
finally: finallyResult,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
if (this.name) {
|
|
135
|
+
context.setResult(this.name, output);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return output;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { TryStage };
|
|
@@ -7,6 +7,7 @@ const { Plugin } = require('../../../src/plugin/base');
|
|
|
7
7
|
const { z } = require('zod');
|
|
8
8
|
const { logger } = require('../../../src/common/logger');
|
|
9
9
|
const { DataSplitter } = require('../../../src/utils/data-splitter');
|
|
10
|
+
const { safeStringify } = require('../../../src/common/json-safe');
|
|
10
11
|
|
|
11
12
|
const AUTO_SPLIT_THRESHOLD = 50000;
|
|
12
13
|
const PROMPT_DIR = __dirname;
|
|
@@ -177,9 +178,9 @@ class DataSplitterPlugin extends Plugin {
|
|
|
177
178
|
|
|
178
179
|
let checkContent = '';
|
|
179
180
|
if (result && typeof result === 'object' && result.data) {
|
|
180
|
-
checkContent = typeof result.data === 'string' ? result.data :
|
|
181
|
+
checkContent = typeof result.data === 'string' ? result.data : safeStringify(result.data);
|
|
181
182
|
} else {
|
|
182
|
-
checkContent = typeof result === 'string' ? result :
|
|
183
|
+
checkContent = typeof result === 'string' ? result : safeStringify(result);
|
|
183
184
|
}
|
|
184
185
|
if (!checkContent || checkContent.length < this.config.autoSplitThreshold) return;
|
|
185
186
|
};
|
package/plugins/memory/index.js
CHANGED
|
@@ -1139,10 +1139,17 @@ ${prompt}`
|
|
|
1139
1139
|
_setupDynamicRefresh(framework) {
|
|
1140
1140
|
this._store.on('change', ({ action, memory }) => {
|
|
1141
1141
|
const mainAgent = framework._mainAgent
|
|
1142
|
-
if (mainAgent)
|
|
1143
|
-
|
|
1144
|
-
|
|
1142
|
+
if (!mainAgent) return
|
|
1143
|
+
// 局部失效 memory-context part(不波及其它 parts),
|
|
1144
|
+
// 由 framework._setupPromptRegistryAutoRefresh 监听 'invalidate' 事件
|
|
1145
|
+
// 标记 agent 的 _systemPromptDirty=true
|
|
1146
|
+
if (framework.prompts?.invalidate) {
|
|
1147
|
+
framework.prompts.invalidate(this.name, 'memory-context')
|
|
1148
|
+
} else {
|
|
1149
|
+
// 兜底:直接调 agent 的私有方法
|
|
1150
|
+
mainAgent._invalidateSystemPromptCache?.()
|
|
1145
1151
|
}
|
|
1152
|
+
mainAgent._refreshContext?.()
|
|
1146
1153
|
})
|
|
1147
1154
|
}
|
|
1148
1155
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const src = fs.readFileSync('plugins/core/workflow/context.js', 'utf8');
|
|
4
|
+
console.log('async tool(name, args) present?', src.includes('async tool(name, args)'));
|
|
5
|
+
console.log('get tool() present?', src.includes('get tool()'));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { FlowContext } = require('../plugins/core/workflow/context');
|
|
2
|
+
const c = new FlowContext({}, 'sid');
|
|
3
|
+
const proto = Object.getPrototypeOf(c);
|
|
4
|
+
console.log('tool:', typeof c.tool, c.tool.name);
|
|
5
|
+
console.log('_executeTool on instance:', typeof c._executeTool);
|
|
6
|
+
console.log('_executeTool on proto:', typeof proto._executeTool);
|
|
7
|
+
console.log('proto tool-related keys:', Object.getOwnPropertyNames(proto).filter(n => n.includes('tool')));
|
|
8
|
+
console.log('proto _-prefixed keys:', Object.getOwnPropertyNames(proto).filter(n => n.startsWith('_')));
|
|
9
|
+
|
|
10
|
+
// Try calling tool
|
|
11
|
+
c.setFramework({ executeTool: async () => ({ success: true }) });
|
|
12
|
+
(async () => {
|
|
13
|
+
try {
|
|
14
|
+
const r = await c.tool('foo', {});
|
|
15
|
+
console.log('tool() result:', r);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.log('tool() error:', e.message);
|
|
18
|
+
}
|
|
19
|
+
// What if we assign _executeTool directly?
|
|
20
|
+
console.log('--- now direct _executeTool call ---');
|
|
21
|
+
try {
|
|
22
|
+
const r2 = await c._executeTool('foo', {});
|
|
23
|
+
console.log('_executeTool() result:', r2);
|
|
24
|
+
} catch (e2) {
|
|
25
|
+
console.log('_executeTool() error:', e2.message);
|
|
26
|
+
}
|
|
27
|
+
})();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const helloPath = path.resolve('.foliko/workflows/hello-js.js');
|
|
5
|
+
delete require.cache[helloPath];
|
|
6
|
+
const helloModule = require(helloPath);
|
|
7
|
+
|
|
8
|
+
const t = typeof helloModule;
|
|
9
|
+
console.log('hello-js.js type:', t);
|
|
10
|
+
|
|
11
|
+
if (t === 'function') {
|
|
12
|
+
try {
|
|
13
|
+
const wf = helloModule({});
|
|
14
|
+
console.log('returned keys:', Object.keys(wf));
|
|
15
|
+
console.log(
|
|
16
|
+
'steps count:',
|
|
17
|
+
Array.isArray(wf.steps) ? wf.steps.length : 'N/A'
|
|
18
|
+
);
|
|
19
|
+
console.log('OK: js workflow shape is v2 compatible');
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.log('execution failed (expected):', e.message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Also verify the manager.js and workflow/index.js parse OK
|
|
26
|
+
const filesToCheck = [
|
|
27
|
+
'src/plugin/manager.js',
|
|
28
|
+
'plugins/core/workflow/index.js',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const rel of filesToCheck) {
|
|
32
|
+
const p = path.resolve(rel);
|
|
33
|
+
delete require.cache[p];
|
|
34
|
+
try {
|
|
35
|
+
const m = require(p);
|
|
36
|
+
console.log(`OK ${rel}: parsed, exports=`, Object.keys(m).slice(0, 5));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.log(`FAIL ${rel}:`, e.message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
(async () => {
|
|
2
|
+
try {
|
|
3
|
+
const { JsRunner } = require('../plugins/core/workflow/js-runner');
|
|
4
|
+
const stubFramework = {
|
|
5
|
+
executeTool: async (name, args, sid) => {
|
|
6
|
+
console.log(' [tool]', name, JSON.stringify(args));
|
|
7
|
+
return { success: true, data: 'tool_ok', echo: args };
|
|
8
|
+
},
|
|
9
|
+
runWithContext: async (ctx, fn) => fn(),
|
|
10
|
+
getExecutionContext: () => ({ sessionId: 'test-session' })
|
|
11
|
+
};
|
|
12
|
+
// Override the proxy used by JsRunner to bind tool/extension/etc.
|
|
13
|
+
// (see fix in _createFlowApi below)
|
|
14
|
+
const origRequire = require.cache[require.resolve('../plugins/core/workflow/js-runner.js')].exports;
|
|
15
|
+
console.log('JsRunner exports:', Object.keys(origRequire));
|
|
16
|
+
console.log('tool keys at runtime:');
|
|
17
|
+
const { FlowContext } = require('../plugins/core/workflow/context');
|
|
18
|
+
const c = new FlowContext({}, 'sid');
|
|
19
|
+
c.setFramework(stubFramework);
|
|
20
|
+
const t = c.tool;
|
|
21
|
+
console.log(' t:', t);
|
|
22
|
+
console.log(' Object.getOwnPropertyNames:', Object.getOwnPropertyNames(t));
|
|
23
|
+
console.log(' for-in:');
|
|
24
|
+
for (const k in t) { console.log(' -', k); }
|
|
25
|
+
console.log(' execute type:', typeof t.execute);
|
|
26
|
+
const r = new JsRunner(stubFramework);
|
|
27
|
+
const wfPath = require.resolve('../.foliko/workflows/hello-js.js');
|
|
28
|
+
delete require.cache[wfPath];
|
|
29
|
+
const wf = require(wfPath);
|
|
30
|
+
const res = await r.execute(wf, {}, 'test-session');
|
|
31
|
+
console.log('Result:', JSON.stringify(res, null, 2));
|
|
32
|
+
|
|
33
|
+
// Direct test of $.tool
|
|
34
|
+
console.log('Direct $.tool test...');
|
|
35
|
+
const $obj = { data: {}, get tool() { return c.tool; }, push: () => {}, next: () => {} };
|
|
36
|
+
try {
|
|
37
|
+
const out = await $.tool.execute('notification_send', { title: 'x', message: 'y' });
|
|
38
|
+
console.log('Direct $.tool result:', out);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.log('Direct $.tool error:', e.message);
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('OUTER ERROR:', err && err.stack || err);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
})();
|