@tom2012/cc-web 2026.6.17-b → 2026.9.6-c
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/CHANGELOG.json +96 -21
- package/backend/dist/adapters/claude-adapter.d.ts +4 -0
- package/backend/dist/adapters/claude-adapter.d.ts.map +1 -1
- package/backend/dist/adapters/claude-adapter.js +18 -2
- package/backend/dist/adapters/claude-adapter.js.map +1 -1
- package/backend/dist/adapters/types.d.ts +8 -0
- package/backend/dist/adapters/types.d.ts.map +1 -1
- package/backend/dist/auth.d.ts.map +0 -0
- package/backend/dist/config.d.ts +0 -0
- package/backend/dist/config.js +0 -0
- package/backend/dist/config.js.map +0 -0
- package/backend/dist/hooks-manager.d.ts +0 -1
- package/backend/dist/hooks-manager.d.ts.map +1 -1
- package/backend/dist/hooks-manager.js +19 -19
- package/backend/dist/hooks-manager.js.map +1 -1
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +76 -1
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/logger.d.ts.map +1 -1
- package/backend/dist/logger.js +3 -0
- package/backend/dist/logger.js.map +1 -1
- package/backend/dist/routes/auth.d.ts +0 -0
- package/backend/dist/routes/auth.d.ts.map +0 -0
- package/backend/dist/routes/auth.js.map +0 -0
- package/backend/dist/routes/projects.d.ts.map +0 -0
- package/backend/dist/routes/projects.js.map +0 -0
- package/backend/dist/session-manager.d.ts +0 -0
- package/backend/dist/session-manager.d.ts.map +0 -0
- package/backend/dist/session-manager.js +0 -0
- package/backend/dist/session-manager.js.map +0 -0
- package/backend/dist/terminal-manager.d.ts +10 -1
- package/backend/dist/terminal-manager.d.ts.map +1 -1
- package/backend/dist/terminal-manager.js +19 -2
- package/backend/dist/terminal-manager.js.map +1 -1
- package/backend/dist/types.d.ts +0 -0
- package/backend/dist/types.d.ts.map +0 -0
- package/backend/dist/types.js +0 -0
- package/backend/dist/types.js.map +0 -0
- package/frontend/dist/assets/{ChatOverlay-Dp1BzsYb.js → ChatOverlay-CymdKeQK.js} +1 -1
- package/frontend/dist/assets/{ClaudeMemPage-CpzMjJnb.js → ClaudeMemPage-BZ4UtUoz.js} +1 -1
- package/frontend/dist/assets/{GraphPreview-DDY1yVOX.js → GraphPreview-21EwV_e_.js} +1 -1
- package/frontend/dist/assets/{MobilePage-BnnE820D.js → MobilePage-99vv9cWh.js} +3 -3
- package/frontend/dist/assets/{NotebookPage-BOiYcs-l.js → NotebookPage-B-mcSRJm.js} +1 -1
- package/frontend/dist/assets/{OfficePreview-C5N2gr8-.js → OfficePreview-C3rHS2Te.js} +2 -2
- package/frontend/dist/assets/{PdfPreview-COG-9AQa.js → PdfPreview-B-toyaoA.js} +1 -1
- package/frontend/dist/assets/{ProjectPage-ARPH_J5o.js → ProjectPage-CShqyrUl.js} +4 -4
- package/frontend/dist/assets/{ProjectSettingsPage-CMWcT-5r.js → ProjectSettingsPage-FxkEGuUP.js} +1 -1
- package/frontend/dist/assets/{SettingsPage-BMrIDtg8.js → SettingsPage-B-bqNp6b.js} +1 -1
- package/frontend/dist/assets/{SkillHubPage-Bq93A7Rt.js → SkillHubPage-DN79j9Is.js} +1 -1
- package/frontend/dist/assets/{check-circle-2-BGT1eH1f.js → check-circle-2-DRT7EX5I.js} +1 -1
- package/frontend/dist/assets/{chevron-down-Bbg11n9i.js → chevron-down-DdtdiM4b.js} +1 -1
- package/frontend/dist/assets/{chevron-left-BmStQriC.js → chevron-left-Chv-EE6G.js} +1 -1
- package/frontend/dist/assets/{file-text-D9WjmbhK.js → file-text-D3ZDULSr.js} +1 -1
- package/frontend/dist/assets/{index-EFhMcMWk.js → index-BIGQ6n-i.js} +2 -2
- package/frontend/dist/assets/index-BpdaJxoa.css +1 -0
- package/frontend/dist/assets/{index-BRqvDxO6.js → index-CH1oLpmL.js} +1 -1
- package/frontend/dist/assets/{index-Bhp9CVTp.js → index-Dbm49BgL.js} +1 -1
- package/frontend/dist/assets/{index-COZnAgez.js → index-xJR85RJZ.js} +1 -1
- package/frontend/dist/assets/{jszip.min-Dyo__hKc.js → jszip.min-CMcZF4_M.js} +1 -1
- package/frontend/dist/assets/{save-CAjdgnp7.js → save-Dhw-I0uF.js} +1 -1
- package/frontend/dist/assets/{search-DmWhhICR.js → search-zoOED2R0.js} +1 -1
- package/frontend/dist/assets/{select-DAfsBGCS.js → select-B7xEE81_.js} +1 -1
- package/frontend/dist/assets/{tabs-zra46FZv.js → tabs-BCHX4LWy.js} +1 -1
- package/frontend/dist/assets/{textarea-BG4Tvy0e.js → textarea-CGPmR1Ae.js} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +36 -10
- package/backend/dist/backup/config.d.ts +0 -10
- package/backend/dist/backup/config.d.ts.map +0 -1
- package/backend/dist/backup/config.js +0 -128
- package/backend/dist/backup/config.js.map +0 -1
- package/backend/dist/backup/engine.d.ts +0 -22
- package/backend/dist/backup/engine.d.ts.map +0 -1
- package/backend/dist/backup/engine.js +0 -298
- package/backend/dist/backup/engine.js.map +0 -1
- package/backend/dist/backup/providers/dropbox.d.ts +0 -25
- package/backend/dist/backup/providers/dropbox.d.ts.map +0 -1
- package/backend/dist/backup/providers/dropbox.js +0 -320
- package/backend/dist/backup/providers/dropbox.js.map +0 -1
- package/backend/dist/backup/providers/google-drive.d.ts +0 -29
- package/backend/dist/backup/providers/google-drive.d.ts.map +0 -1
- package/backend/dist/backup/providers/google-drive.js +0 -303
- package/backend/dist/backup/providers/google-drive.js.map +0 -1
- package/backend/dist/backup/providers/index.d.ts +0 -3
- package/backend/dist/backup/providers/index.d.ts.map +0 -1
- package/backend/dist/backup/providers/index.js +0 -19
- package/backend/dist/backup/providers/index.js.map +0 -1
- package/backend/dist/backup/providers/onedrive.d.ts +0 -29
- package/backend/dist/backup/providers/onedrive.d.ts.map +0 -1
- package/backend/dist/backup/providers/onedrive.js +0 -317
- package/backend/dist/backup/providers/onedrive.js.map +0 -1
- package/backend/dist/backup/scheduler.d.ts +0 -6
- package/backend/dist/backup/scheduler.d.ts.map +0 -1
- package/backend/dist/backup/scheduler.js +0 -55
- package/backend/dist/backup/scheduler.js.map +0 -1
- package/backend/dist/backup/types.d.ts +0 -73
- package/backend/dist/backup/types.d.ts.map +0 -1
- package/backend/dist/backup/types.js +0 -4
- package/backend/dist/backup/types.js.map +0 -1
- package/backend/dist/chat-process-manager.d.ts +0 -45
- package/backend/dist/chat-process-manager.d.ts.map +0 -1
- package/backend/dist/chat-process-manager.js +0 -258
- package/backend/dist/chat-process-manager.js.map +0 -1
- package/backend/dist/flows/runner.d.ts +0 -37
- package/backend/dist/flows/runner.d.ts.map +0 -1
- package/backend/dist/flows/runner.js +0 -660
- package/backend/dist/flows/runner.js.map +0 -1
- package/backend/dist/flows/store.d.ts +0 -40
- package/backend/dist/flows/store.d.ts.map +0 -1
- package/backend/dist/flows/store.js +0 -284
- package/backend/dist/flows/store.js.map +0 -1
- package/backend/dist/flows/types.d.ts +0 -176
- package/backend/dist/flows/types.d.ts.map +0 -1
- package/backend/dist/flows/types.js +0 -25
- package/backend/dist/flows/types.js.map +0 -1
- package/backend/dist/information/condenser.d.ts +0 -12
- package/backend/dist/information/condenser.d.ts.map +0 -1
- package/backend/dist/information/condenser.js +0 -568
- package/backend/dist/information/condenser.js.map +0 -1
- package/backend/dist/information/conversation-sync.d.ts +0 -22
- package/backend/dist/information/conversation-sync.d.ts.map +0 -1
- package/backend/dist/information/conversation-sync.js +0 -500
- package/backend/dist/information/conversation-sync.js.map +0 -1
- package/backend/dist/information/types.d.ts +0 -36
- package/backend/dist/information/types.d.ts.map +0 -1
- package/backend/dist/information/types.js +0 -4
- package/backend/dist/information/types.js.map +0 -1
- package/backend/dist/memory-pool/buoyancy.d.ts +0 -2
- package/backend/dist/memory-pool/buoyancy.d.ts.map +0 -1
- package/backend/dist/memory-pool/buoyancy.js +0 -12
- package/backend/dist/memory-pool/buoyancy.js.map +0 -1
- package/backend/dist/memory-pool/global-pool-manager.d.ts +0 -12
- package/backend/dist/memory-pool/global-pool-manager.d.ts.map +0 -1
- package/backend/dist/memory-pool/global-pool-manager.js +0 -295
- package/backend/dist/memory-pool/global-pool-manager.js.map +0 -1
- package/backend/dist/memory-pool/pool-lock.d.ts +0 -2
- package/backend/dist/memory-pool/pool-lock.d.ts.map +0 -1
- package/backend/dist/memory-pool/pool-lock.js +0 -31
- package/backend/dist/memory-pool/pool-lock.js.map +0 -1
- package/backend/dist/memory-pool/pool-manager.d.ts +0 -57
- package/backend/dist/memory-pool/pool-manager.d.ts.map +0 -1
- package/backend/dist/memory-pool/pool-manager.js +0 -322
- package/backend/dist/memory-pool/pool-manager.js.map +0 -1
- package/backend/dist/memory-pool/templates.d.ts +0 -5
- package/backend/dist/memory-pool/templates.d.ts.map +0 -1
- package/backend/dist/memory-pool/templates.js +0 -234
- package/backend/dist/memory-pool/templates.js.map +0 -1
- package/backend/dist/memory-pool/types.d.ts +0 -61
- package/backend/dist/memory-pool/types.d.ts.map +0 -1
- package/backend/dist/memory-pool/types.js +0 -4
- package/backend/dist/memory-pool/types.js.map +0 -1
- package/backend/dist/plan-control/checker.d.ts +0 -3
- package/backend/dist/plan-control/checker.d.ts.map +0 -1
- package/backend/dist/plan-control/checker.js +0 -103
- package/backend/dist/plan-control/checker.js.map +0 -1
- package/backend/dist/plan-control/executor.d.ts +0 -93
- package/backend/dist/plan-control/executor.d.ts.map +0 -1
- package/backend/dist/plan-control/executor.js +0 -955
- package/backend/dist/plan-control/executor.js.map +0 -1
- package/backend/dist/plan-control/parser.d.ts +0 -12
- package/backend/dist/plan-control/parser.d.ts.map +0 -1
- package/backend/dist/plan-control/parser.js +0 -216
- package/backend/dist/plan-control/parser.js.map +0 -1
- package/backend/dist/plan-control/templates.d.ts +0 -4
- package/backend/dist/plan-control/templates.d.ts.map +0 -1
- package/backend/dist/plan-control/templates.js +0 -271
- package/backend/dist/plan-control/templates.js.map +0 -1
- package/backend/dist/plan-control/types.d.ts +0 -102
- package/backend/dist/plan-control/types.d.ts.map +0 -1
- package/backend/dist/plan-control/types.js +0 -12
- package/backend/dist/plan-control/types.js.map +0 -1
- package/backend/dist/routes/backup.d.ts +0 -4
- package/backend/dist/routes/backup.d.ts.map +0 -1
- package/backend/dist/routes/backup.js +0 -274
- package/backend/dist/routes/backup.js.map +0 -1
- package/backend/dist/routes/flows.d.ts +0 -5
- package/backend/dist/routes/flows.d.ts.map +0 -1
- package/backend/dist/routes/flows.js +0 -457
- package/backend/dist/routes/flows.js.map +0 -1
- package/backend/dist/routes/global-flows.d.ts +0 -3
- package/backend/dist/routes/global-flows.d.ts.map +0 -1
- package/backend/dist/routes/global-flows.js +0 -90
- package/backend/dist/routes/global-flows.js.map +0 -1
- package/backend/dist/routes/information.d.ts +0 -3
- package/backend/dist/routes/information.d.ts.map +0 -1
- package/backend/dist/routes/information.js +0 -159
- package/backend/dist/routes/information.js.map +0 -1
- package/backend/dist/routes/memory-pool.d.ts +0 -3
- package/backend/dist/routes/memory-pool.d.ts.map +0 -1
- package/backend/dist/routes/memory-pool.js +0 -837
- package/backend/dist/routes/memory-pool.js.map +0 -1
- package/backend/dist/routes/plan-control.d.ts +0 -15
- package/backend/dist/routes/plan-control.d.ts.map +0 -1
- package/backend/dist/routes/plan-control.js +0 -337
- package/backend/dist/routes/plan-control.js.map +0 -1
- package/backend/dist/routes/share.d.ts +0 -3
- package/backend/dist/routes/share.d.ts.map +0 -1
- package/backend/dist/routes/share.js +0 -167
- package/backend/dist/routes/share.js.map +0 -1
- package/backend/dist/routes/sounds.d.ts +0 -3
- package/backend/dist/routes/sounds.d.ts.map +0 -1
- package/backend/dist/routes/sounds.js +0 -285
- package/backend/dist/routes/sounds.js.map +0 -1
- package/backend/dist/tracks/__tests__/verify-starter-templates.d.ts +0 -24
- package/backend/dist/tracks/__tests__/verify-starter-templates.d.ts.map +0 -1
- package/backend/dist/tracks/__tests__/verify-starter-templates.js +0 -175
- package/backend/dist/tracks/__tests__/verify-starter-templates.js.map +0 -1
- package/backend/dist/tracks/__tests__/verify-track-cancel.d.ts +0 -23
- package/backend/dist/tracks/__tests__/verify-track-cancel.d.ts.map +0 -1
- package/backend/dist/tracks/__tests__/verify-track-cancel.js +0 -153
- package/backend/dist/tracks/__tests__/verify-track-cancel.js.map +0 -1
- package/backend/dist/tracks/__tests__/verify-track-t1.d.ts +0 -11
- package/backend/dist/tracks/__tests__/verify-track-t1.d.ts.map +0 -1
- package/backend/dist/tracks/__tests__/verify-track-t1.js +0 -293
- package/backend/dist/tracks/__tests__/verify-track-t1.js.map +0 -1
- package/backend/dist/tracks/__tests__/verify-track.d.ts +0 -20
- package/backend/dist/tracks/__tests__/verify-track.d.ts.map +0 -1
- package/backend/dist/tracks/__tests__/verify-track.js +0 -187
- package/backend/dist/tracks/__tests__/verify-track.js.map +0 -1
- package/backend/dist/tracks/registry.d.ts +0 -61
- package/backend/dist/tracks/registry.d.ts.map +0 -1
- package/backend/dist/tracks/registry.js +0 -202
- package/backend/dist/tracks/registry.js.map +0 -1
- package/backend/dist/tracks/track-runner.d.ts +0 -69
- package/backend/dist/tracks/track-runner.d.ts.map +0 -1
- package/backend/dist/tracks/track-runner.js +0 -158
- package/backend/dist/tracks/track-runner.js.map +0 -1
- package/backend/dist/tracks/train-loader.d.ts +0 -44
- package/backend/dist/tracks/train-loader.d.ts.map +0 -1
- package/backend/dist/tracks/train-loader.js +0 -32
- package/backend/dist/tracks/train-loader.js.map +0 -1
- package/backend/dist/tracks/types-train.d.ts +0 -12
- package/backend/dist/tracks/types-train.d.ts.map +0 -1
- package/backend/dist/tracks/types-train.js +0 -13
- package/backend/dist/tracks/types-train.js.map +0 -1
- package/backend/dist/usage-terminal.d.ts +0 -18
- package/backend/dist/usage-terminal.d.ts.map +0 -1
- package/backend/dist/usage-terminal.js +0 -191
- package/backend/dist/usage-terminal.js.map +0 -1
- package/frontend/dist/assets/index-GfCaMXPO.css +0 -1
|
@@ -1,660 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.flowRunner = exports.FlowRunner = void 0;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
|
-
const path = __importStar(require("path"));
|
|
39
|
-
const events_1 = require("events");
|
|
40
|
-
const uuid_1 = require("uuid");
|
|
41
|
-
const logger_1 = require("../logger");
|
|
42
|
-
const terminal_paste_1 = require("../terminal-paste");
|
|
43
|
-
const store_1 = require("./store");
|
|
44
|
-
const log = (0, logger_1.modLogger)('flow-runner');
|
|
45
|
-
// ── Value rendering & sanitization ────────────────────────────────────────
|
|
46
|
-
/** Strip terminal control bytes that would corrupt bracketed-paste mode or
|
|
47
|
-
* Ink TUI state when the value is later injected into a prompt. ESC sequences
|
|
48
|
-
* (incl. paste-mode markers) can confuse the agent's input parser; bare CR
|
|
49
|
-
* prematurely closes paste mode. We keep LF and TAB so multi-line content
|
|
50
|
-
* renders normally. */
|
|
51
|
-
function sanitizeForPrompt(s) {
|
|
52
|
-
return s.replace(/[\x1b\r]/g, '');
|
|
53
|
-
}
|
|
54
|
-
/** Format an arbitrary JSON value for prompt injection. Strings pass through
|
|
55
|
-
* (after sanitize); other values get JSON.stringify with 2-space indent so
|
|
56
|
-
* arrays/objects are human-readable to the LLM. `undefined` becomes the
|
|
57
|
-
* literal "(未设置)" marker so the LLM knows the variable hasn't been
|
|
58
|
-
* written yet rather than seeing a missing field silently. */
|
|
59
|
-
function formatValueForPrompt(value) {
|
|
60
|
-
if (value === undefined || value === null)
|
|
61
|
-
return '(未设置)';
|
|
62
|
-
if (typeof value === 'string')
|
|
63
|
-
return sanitizeForPrompt(value);
|
|
64
|
-
return sanitizeForPrompt(JSON.stringify(value, null, 2));
|
|
65
|
-
}
|
|
66
|
-
/** Substitute `{{var:name}}` and `{{const:name}}` tokens in the prompt
|
|
67
|
-
* template with the current value from workflow_data. Unset declared
|
|
68
|
-
* variables (those without initialValue and not yet written) render as
|
|
69
|
-
* "(未设置)" — the validator has already gated on the name being declared
|
|
70
|
-
* at save time, so an unknown name at runtime is impossible by construction.
|
|
71
|
-
* (codex P1-C: previously this returned "[ERROR ...]" which leaked into
|
|
72
|
-
* prompts whenever a declared-but-uninitialized variable was referenced.) */
|
|
73
|
-
function renderTemplate(tpl, data) {
|
|
74
|
-
return tpl
|
|
75
|
-
.replace(/\{\{var:([^}]+)\}\}/g, (_m, name) => {
|
|
76
|
-
return formatValueForPrompt(data.variables[name.trim()]);
|
|
77
|
-
})
|
|
78
|
-
.replace(/\{\{const:([^}]+)\}\}/g, (_m, name) => {
|
|
79
|
-
return formatValueForPrompt(data.constants[name.trim()]);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
// ── Prompt block builders ─────────────────────────────────────────────────
|
|
83
|
-
/** Build a "current variable values" context block for the prompt head. */
|
|
84
|
-
function buildReadVarBlock(names, def, data) {
|
|
85
|
-
if (names.length === 0)
|
|
86
|
-
return '';
|
|
87
|
-
const byName = new Map((def.variables ?? []).map((v) => [v.name, v]));
|
|
88
|
-
const lines = ['──────── 流变量当前值 ────────'];
|
|
89
|
-
for (const name of names) {
|
|
90
|
-
const v = byName.get(name);
|
|
91
|
-
if (!v)
|
|
92
|
-
continue;
|
|
93
|
-
const meaning = v.description || '(无描述)';
|
|
94
|
-
lines.push(`变量 \`${v.name}\`(含义:${meaning}):`);
|
|
95
|
-
lines.push(formatValueForPrompt(data.variables[name]));
|
|
96
|
-
lines.push('');
|
|
97
|
-
}
|
|
98
|
-
lines.push('');
|
|
99
|
-
return lines.join('\n');
|
|
100
|
-
}
|
|
101
|
-
/** Build a "constants" context block. Constants are stable per run, so we
|
|
102
|
-
* just dump value + description. */
|
|
103
|
-
function buildReadConstBlock(names, def, data) {
|
|
104
|
-
if (names.length === 0)
|
|
105
|
-
return '';
|
|
106
|
-
const byName = new Map((def.constants ?? []).map((c) => [c.name, c]));
|
|
107
|
-
const lines = ['──────── 流常量 ────────'];
|
|
108
|
-
for (const name of names) {
|
|
109
|
-
const c = byName.get(name);
|
|
110
|
-
if (!c)
|
|
111
|
-
continue;
|
|
112
|
-
const meaning = c.description || '(无描述)';
|
|
113
|
-
lines.push(`常量 \`${c.name}\`(含义:${meaning}):`);
|
|
114
|
-
lines.push(formatValueForPrompt(data.constants[name]));
|
|
115
|
-
lines.push('');
|
|
116
|
-
}
|
|
117
|
-
lines.push('');
|
|
118
|
-
return lines.join('\n');
|
|
119
|
-
}
|
|
120
|
-
/** Build the prompt suffix instructing the LLM to write each `writeVariables`
|
|
121
|
-
* entry into workflow_data.variables[name]. */
|
|
122
|
-
function buildWriteVarBlock(names, def) {
|
|
123
|
-
if (names.length === 0)
|
|
124
|
-
return '';
|
|
125
|
-
const byName = new Map((def.variables ?? []).map((v) => [v.name, v]));
|
|
126
|
-
const lines = [
|
|
127
|
-
'',
|
|
128
|
-
'──────── 变量写入指令 ────────',
|
|
129
|
-
'完成本任务后,请按下面列出的含义判断每个变量的值,',
|
|
130
|
-
'用 Edit 或 Write 工具把它们写入 .ccweb/workflow_data.json 的 variables 字段(顶层 key = 变量名)。',
|
|
131
|
-
'保留 workflow_data.json 中其他变量、常量、task_progress 字段不要动。',
|
|
132
|
-
'',
|
|
133
|
-
];
|
|
134
|
-
for (const name of names) {
|
|
135
|
-
const v = byName.get(name);
|
|
136
|
-
if (!v)
|
|
137
|
-
continue;
|
|
138
|
-
lines.push(`- \`${v.name}\` → 写入 variables.${v.name}。含义:${v.description || '(无描述)'}`);
|
|
139
|
-
}
|
|
140
|
-
return lines.join('\n');
|
|
141
|
-
}
|
|
142
|
-
// ── Branch evaluation ─────────────────────────────────────────────────────
|
|
143
|
-
/** Loose equality for branch evaluation. JSON outputs from LLMs frequently
|
|
144
|
-
* type-shift (`true` → `"true"`, `1` → `"1"`); branch authors typically
|
|
145
|
-
* configure the typed primitive, so we coerce common cases. */
|
|
146
|
-
function branchMatches(value, expected) {
|
|
147
|
-
if (Object.is(value, expected))
|
|
148
|
-
return true;
|
|
149
|
-
if (typeof expected === 'boolean') {
|
|
150
|
-
if (typeof value === 'string') {
|
|
151
|
-
const v = value.toLowerCase().trim();
|
|
152
|
-
return (expected && (v === 'true' || v === '1' || v === 'yes')) ||
|
|
153
|
-
(!expected && (v === 'false' || v === '0' || v === 'no'));
|
|
154
|
-
}
|
|
155
|
-
if (typeof value === 'number')
|
|
156
|
-
return expected === (value !== 0);
|
|
157
|
-
}
|
|
158
|
-
if (typeof expected === 'number' && typeof value === 'string') {
|
|
159
|
-
const n = Number(value);
|
|
160
|
-
return !Number.isNaN(n) && n === expected;
|
|
161
|
-
}
|
|
162
|
-
if (typeof expected === 'string' && typeof value === 'number') {
|
|
163
|
-
return value.toString() === expected;
|
|
164
|
-
}
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
class FlowRunner extends events_1.EventEmitter {
|
|
168
|
-
constructor() {
|
|
169
|
-
super(...arguments);
|
|
170
|
-
this.active = new Map();
|
|
171
|
-
this.injector = null;
|
|
172
|
-
}
|
|
173
|
-
setPromptInjector(fn) {
|
|
174
|
-
this.injector = fn;
|
|
175
|
-
}
|
|
176
|
-
start(projectId, folderPath, flowDef, flowFilename) {
|
|
177
|
-
if (this.active.has(projectId)) {
|
|
178
|
-
return { ok: false, reason: 'already-running' };
|
|
179
|
-
}
|
|
180
|
-
const startNode = flowDef.nodes.find((n) => n.id === flowDef.entryNodeId);
|
|
181
|
-
if (!startNode)
|
|
182
|
-
return { ok: false, reason: 'entry-node-not-found' };
|
|
183
|
-
// Initialize workflow_data: constants written once, variables get
|
|
184
|
-
// initialValue (where declared), task_progress reset for this run.
|
|
185
|
-
(0, store_1.initWorkflowData)(folderPath, flowDef);
|
|
186
|
-
const state = {
|
|
187
|
-
flowId: flowDef.id,
|
|
188
|
-
flowFilename,
|
|
189
|
-
runId: (0, uuid_1.v4)(),
|
|
190
|
-
startedAt: Date.now(),
|
|
191
|
-
status: 'running',
|
|
192
|
-
currentNodeId: flowDef.entryNodeId,
|
|
193
|
-
loopCounters: {},
|
|
194
|
-
history: [],
|
|
195
|
-
pauseReason: null,
|
|
196
|
-
};
|
|
197
|
-
(0, store_1.saveFlowState)(folderPath, state);
|
|
198
|
-
const run = {
|
|
199
|
-
projectId,
|
|
200
|
-
folderPath,
|
|
201
|
-
flowDef,
|
|
202
|
-
state,
|
|
203
|
-
watcher: null,
|
|
204
|
-
watcherDebounce: null,
|
|
205
|
-
timeoutTimer: null,
|
|
206
|
-
waitResolve: null,
|
|
207
|
-
userInputResolve: null,
|
|
208
|
-
userInputReject: null,
|
|
209
|
-
currentTaskIndex: null,
|
|
210
|
-
};
|
|
211
|
-
this.active.set(projectId, run);
|
|
212
|
-
this.emit('state', { projectId, state });
|
|
213
|
-
log.info({
|
|
214
|
-
projectId,
|
|
215
|
-
flowId: flowDef.id,
|
|
216
|
-
flowName: flowDef.name,
|
|
217
|
-
schemaVersion: flowDef.schemaVersion,
|
|
218
|
-
entryNodeId: flowDef.entryNodeId,
|
|
219
|
-
nodeCount: flowDef.nodes.length,
|
|
220
|
-
constantCount: (flowDef.constants ?? []).length,
|
|
221
|
-
variableCount: (flowDef.variables ?? []).length,
|
|
222
|
-
runId: state.runId,
|
|
223
|
-
}, 'flow start');
|
|
224
|
-
void this.runLoop(run).catch((err) => {
|
|
225
|
-
log.error({ projectId, err: err instanceof Error ? err.message : String(err) }, 'run loop crashed');
|
|
226
|
-
this.finalize(run, 'failed', err instanceof Error ? err.message : String(err));
|
|
227
|
-
});
|
|
228
|
-
return { ok: true, state };
|
|
229
|
-
}
|
|
230
|
-
resume(projectId) {
|
|
231
|
-
const run = this.active.get(projectId);
|
|
232
|
-
if (!run)
|
|
233
|
-
return false;
|
|
234
|
-
if (run.state.status !== 'paused')
|
|
235
|
-
return false;
|
|
236
|
-
if (run.state.pauseReason === 'awaiting-user-input')
|
|
237
|
-
return false;
|
|
238
|
-
if (run.state.currentNodeId === null)
|
|
239
|
-
return false;
|
|
240
|
-
const prevReason = run.state.pauseReason;
|
|
241
|
-
if (run.state.pauseReason === 'max-retries-exceeded') {
|
|
242
|
-
delete run.state.loopCounters[run.state.currentNodeId];
|
|
243
|
-
}
|
|
244
|
-
run.state.status = 'running';
|
|
245
|
-
run.state.pauseReason = null;
|
|
246
|
-
run.state.pauseDetail = undefined;
|
|
247
|
-
this.persist(run);
|
|
248
|
-
log.info({ projectId, currentNodeId: run.state.currentNodeId, prevReason, resetLoopCounter: prevReason === 'max-retries-exceeded' }, 'flow resume');
|
|
249
|
-
void this.runLoop(run).catch((err) => {
|
|
250
|
-
log.error({ projectId: run.projectId, err: err instanceof Error ? err.message : String(err) }, 'resumed run loop crashed');
|
|
251
|
-
this.finalize(run, 'failed', err instanceof Error ? err.message : String(err));
|
|
252
|
-
});
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
abort(projectId) {
|
|
256
|
-
const run = this.active.get(projectId);
|
|
257
|
-
if (!run)
|
|
258
|
-
return false;
|
|
259
|
-
// Capture resolvers BEFORE clearWaiters — clearWaiters nulls
|
|
260
|
-
// run.waitResolve, so reading it after is a no-op and the in-flight
|
|
261
|
-
// wait Promise hangs forever (codex review P0).
|
|
262
|
-
const wait = run.waitResolve;
|
|
263
|
-
const userReject = run.userInputReject;
|
|
264
|
-
const hadUserInputWait = !!userReject;
|
|
265
|
-
const hadTaskWait = !!wait;
|
|
266
|
-
run.userInputResolve = null;
|
|
267
|
-
run.userInputReject = null;
|
|
268
|
-
wait?.('aborted');
|
|
269
|
-
userReject?.('aborted');
|
|
270
|
-
log.info({ projectId, currentNodeId: run.state.currentNodeId, hadTaskWait, hadUserInputWait }, 'flow abort');
|
|
271
|
-
this.finalize(run, 'aborted');
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
/** Resolves any pending user-input wait with the submitted form data. */
|
|
275
|
-
submitUserInput(projectId, data) {
|
|
276
|
-
const run = this.active.get(projectId);
|
|
277
|
-
if (!run || !run.userInputResolve)
|
|
278
|
-
return false;
|
|
279
|
-
const resolve = run.userInputResolve;
|
|
280
|
-
run.userInputResolve = null;
|
|
281
|
-
run.userInputReject = null;
|
|
282
|
-
log.info({
|
|
283
|
-
projectId,
|
|
284
|
-
currentNodeId: run.state.currentNodeId,
|
|
285
|
-
fieldKeys: Object.keys(data),
|
|
286
|
-
fieldLengths: Object.fromEntries(Object.entries(data).map(([k, v]) => [k, v.length])),
|
|
287
|
-
}, 'flow user-input submitted');
|
|
288
|
-
resolve(data);
|
|
289
|
-
return true;
|
|
290
|
-
}
|
|
291
|
-
getState(projectId) {
|
|
292
|
-
return this.active.get(projectId)?.state ?? (0, store_1.loadFlowState)(this.lastFolderPath(projectId) ?? '');
|
|
293
|
-
}
|
|
294
|
-
isRunning(projectId) {
|
|
295
|
-
return this.active.has(projectId);
|
|
296
|
-
}
|
|
297
|
-
lastFolderPath(projectId) {
|
|
298
|
-
return this.active.get(projectId)?.folderPath ?? null;
|
|
299
|
-
}
|
|
300
|
-
// ── Main loop ─────────────────────────────────────────────────────────
|
|
301
|
-
async runLoop(run) {
|
|
302
|
-
while (run.state.status === 'running' && run.state.currentNodeId !== null) {
|
|
303
|
-
const node = run.flowDef.nodes.find((n) => n.id === run.state.currentNodeId);
|
|
304
|
-
if (!node) {
|
|
305
|
-
this.finalize(run, 'failed', `node id ${run.state.currentNodeId} not found`);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
const histEntry = {
|
|
309
|
-
nodeId: node.id,
|
|
310
|
-
startedAt: Date.now(),
|
|
311
|
-
finishedAt: null,
|
|
312
|
-
outcome: 'ok',
|
|
313
|
-
};
|
|
314
|
-
run.state.history.push(histEntry);
|
|
315
|
-
this.persist(run);
|
|
316
|
-
const outcome = await this.executeNode(run, node);
|
|
317
|
-
histEntry.finishedAt = Date.now();
|
|
318
|
-
histEntry.outcome = outcome.kind === 'ok' ? 'ok'
|
|
319
|
-
: outcome.kind === 'pause' ? 'pause'
|
|
320
|
-
: outcome.kind === 'retry' ? 'retry'
|
|
321
|
-
: 'error';
|
|
322
|
-
if (outcome.kind === 'pause' || outcome.kind === 'error') {
|
|
323
|
-
this.persist(run);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
if (outcome.kind === 'ok') {
|
|
327
|
-
run.state.currentNodeId = outcome.next;
|
|
328
|
-
this.persist(run);
|
|
329
|
-
if (outcome.next === null) {
|
|
330
|
-
this.finalize(run, 'completed');
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
// ── Node executors ────────────────────────────────────────────────────
|
|
337
|
-
async executeNode(run, node) {
|
|
338
|
-
log.info({ projectId: run.projectId, nodeId: node.id, kind: node.kind, name: node.name }, 'executing node');
|
|
339
|
-
this.emit('state', { projectId: run.projectId, state: run.state });
|
|
340
|
-
if (node.kind === 'user-input')
|
|
341
|
-
return this.executeUserInput(run, node);
|
|
342
|
-
if (node.kind === 'llm')
|
|
343
|
-
return this.executeLlm(run, node);
|
|
344
|
-
if (node.kind === 'system-logic')
|
|
345
|
-
return this.executeSystemLogic(run, node);
|
|
346
|
-
return { kind: 'error', message: `unknown node kind: ${node.kind}` };
|
|
347
|
-
}
|
|
348
|
-
async executeUserInput(run, node) {
|
|
349
|
-
// Snapshot context values (bindVariable / bindConstant) so the frontend
|
|
350
|
-
// can render them read-only without a separate fetch.
|
|
351
|
-
const data = (0, store_1.readWorkflowData)(run.folderPath);
|
|
352
|
-
const variablesCtx = {};
|
|
353
|
-
const constantsCtx = {};
|
|
354
|
-
for (const field of node.userInputSchema.fields) {
|
|
355
|
-
if (field.bindVariable && field.bindVariable in data.variables) {
|
|
356
|
-
variablesCtx[field.bindVariable] = data.variables[field.bindVariable];
|
|
357
|
-
}
|
|
358
|
-
if (field.bindConstant && field.bindConstant in data.constants) {
|
|
359
|
-
constantsCtx[field.bindConstant] = data.constants[field.bindConstant];
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
const contextValues = Object.keys(variablesCtx).length > 0 || Object.keys(constantsCtx).length > 0
|
|
363
|
-
? {
|
|
364
|
-
variables: Object.keys(variablesCtx).length > 0 ? variablesCtx : undefined,
|
|
365
|
-
constants: Object.keys(constantsCtx).length > 0 ? constantsCtx : undefined,
|
|
366
|
-
}
|
|
367
|
-
: undefined;
|
|
368
|
-
run.state.status = 'paused';
|
|
369
|
-
run.state.pauseReason = 'awaiting-user-input';
|
|
370
|
-
run.state.pendingUserInput = {
|
|
371
|
-
nodeId: node.id,
|
|
372
|
-
fields: node.userInputSchema.fields,
|
|
373
|
-
contextValues,
|
|
374
|
-
};
|
|
375
|
-
this.persist(run);
|
|
376
|
-
this.emit('user-input', { projectId: run.projectId, nodeId: node.id, fields: node.userInputSchema.fields });
|
|
377
|
-
log.info({
|
|
378
|
-
projectId: run.projectId,
|
|
379
|
-
nodeId: node.id,
|
|
380
|
-
fieldKeys: node.userInputSchema.fields.map((f) => f.key),
|
|
381
|
-
}, 'flow node user-input awaiting');
|
|
382
|
-
let submitted;
|
|
383
|
-
try {
|
|
384
|
-
submitted = await new Promise((resolve, reject) => {
|
|
385
|
-
run.userInputResolve = resolve;
|
|
386
|
-
run.userInputReject = reject;
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
catch {
|
|
390
|
-
return { kind: 'pause' };
|
|
391
|
-
}
|
|
392
|
-
// Merge field values into variables. For bindVariable / bindConstant
|
|
393
|
-
// fields we re-read from workflow_data (defense — client could lie even
|
|
394
|
-
// though the UI disables those inputs); for outputVariable fields we
|
|
395
|
-
// take the submitted value as-is.
|
|
396
|
-
const fresh = (0, store_1.readWorkflowData)(run.folderPath);
|
|
397
|
-
const variableUpdates = {};
|
|
398
|
-
for (const field of node.userInputSchema.fields) {
|
|
399
|
-
if (field.outputVariable) {
|
|
400
|
-
variableUpdates[field.outputVariable] = submitted[field.key] ?? '';
|
|
401
|
-
}
|
|
402
|
-
// bindVariable / bindConstant fields contribute nothing to writes —
|
|
403
|
-
// they're read-only displays.
|
|
404
|
-
}
|
|
405
|
-
if (Object.keys(variableUpdates).length > 0) {
|
|
406
|
-
fresh.variables = { ...fresh.variables, ...variableUpdates };
|
|
407
|
-
(0, store_1.writeWorkflowData)(run.folderPath, fresh);
|
|
408
|
-
}
|
|
409
|
-
run.state.status = 'running';
|
|
410
|
-
run.state.pauseReason = null;
|
|
411
|
-
run.state.pendingUserInput = undefined;
|
|
412
|
-
this.persist(run);
|
|
413
|
-
log.info({
|
|
414
|
-
projectId: run.projectId,
|
|
415
|
-
nodeId: node.id,
|
|
416
|
-
wroteVariables: Object.keys(variableUpdates),
|
|
417
|
-
next: node.next,
|
|
418
|
-
}, 'flow node user-input completed');
|
|
419
|
-
return { kind: 'ok', next: node.next };
|
|
420
|
-
}
|
|
421
|
-
async executeLlm(run, node) {
|
|
422
|
-
if (!this.injector) {
|
|
423
|
-
return { kind: 'error', message: 'no prompt injector configured' };
|
|
424
|
-
}
|
|
425
|
-
// 1. Append a task_progress entry for the LLM to flip when done.
|
|
426
|
-
const taskIndex = (0, store_1.appendTaskProgress)(run.folderPath, {
|
|
427
|
-
nodeId: node.id,
|
|
428
|
-
name: node.name,
|
|
429
|
-
finish: false,
|
|
430
|
-
});
|
|
431
|
-
run.currentTaskIndex = taskIndex;
|
|
432
|
-
// 2. Build prompt: header + read-context blocks + body + write block.
|
|
433
|
-
const data = (0, store_1.readWorkflowData)(run.folderPath);
|
|
434
|
-
const taskHeader = `当前任务 id=${node.id},名为「${node.name}」。\n` +
|
|
435
|
-
`完成后请把 .ccweb/workflow_data.json 中 task_progress[${taskIndex}].finish 改为 true(用 Edit/Write 工具直接更新该 JSON 文件)。\n` +
|
|
436
|
-
'保留 workflow_data.json 中其他字段不要动。\n' +
|
|
437
|
-
'\n──────── 任务正文 ────────\n';
|
|
438
|
-
const refConstBlock = buildReadConstBlock(node.readConstants ?? [], run.flowDef, data);
|
|
439
|
-
const refVarBlock = buildReadVarBlock(node.readVariables ?? [], run.flowDef, data);
|
|
440
|
-
const body = renderTemplate(node.promptTemplate, data);
|
|
441
|
-
const writeVarBlock = buildWriteVarBlock(node.writeVariables ?? [], run.flowDef);
|
|
442
|
-
const fullPrompt = `${taskHeader}${refConstBlock}${refVarBlock}${body}${writeVarBlock}`;
|
|
443
|
-
// 3. Inject into chat.
|
|
444
|
-
this.injector(run.projectId, (0, terminal_paste_1.buildPaste)(fullPrompt));
|
|
445
|
-
log.info({
|
|
446
|
-
projectId: run.projectId,
|
|
447
|
-
nodeId: node.id,
|
|
448
|
-
taskIndex,
|
|
449
|
-
promptSize: fullPrompt.length,
|
|
450
|
-
readVariables: node.readVariables ?? [],
|
|
451
|
-
readConstants: node.readConstants ?? [],
|
|
452
|
-
writeVariables: node.writeVariables ?? [],
|
|
453
|
-
timeoutSec: node.timeoutSec,
|
|
454
|
-
}, 'flow node llm prompt injected');
|
|
455
|
-
// 4. Wait for task_progress[taskIndex].finish OR timeout.
|
|
456
|
-
const outcome = await this.waitForTaskFinish(run, taskIndex, node.timeoutSec * 1000);
|
|
457
|
-
log.info({ projectId: run.projectId, nodeId: node.id, taskIndex, outcome }, 'flow node llm wait outcome');
|
|
458
|
-
if (outcome === 'aborted' || outcome === 'paused') {
|
|
459
|
-
return { kind: 'pause' };
|
|
460
|
-
}
|
|
461
|
-
if (outcome === 'timeout') {
|
|
462
|
-
run.state.status = 'paused';
|
|
463
|
-
run.state.pauseReason = 'timeout';
|
|
464
|
-
run.state.pauseDetail = `node ${node.id} (${node.name}) timed out after ${node.timeoutSec}s`;
|
|
465
|
-
this.emit('error', {
|
|
466
|
-
projectId: run.projectId,
|
|
467
|
-
nodeId: node.id,
|
|
468
|
-
reason: 'timeout',
|
|
469
|
-
detail: run.state.pauseDetail,
|
|
470
|
-
});
|
|
471
|
-
log.warn({ projectId: run.projectId, nodeId: node.id, timeoutSec: node.timeoutSec, taskIndex }, 'flow node llm timeout');
|
|
472
|
-
return { kind: 'pause' };
|
|
473
|
-
}
|
|
474
|
-
// Intentionally do NOT write finishedAt back to workflow_data here:
|
|
475
|
-
// the LLM may keep editing variables briefly after flipping finish=true,
|
|
476
|
-
// and our whole-file RMW would silently drop those edits (codex P1-B).
|
|
477
|
-
// Duration audit lives in FlowState.history (saved by runLoop), which
|
|
478
|
-
// is runner-owned so there's no concurrent writer.
|
|
479
|
-
run.currentTaskIndex = null;
|
|
480
|
-
return { kind: 'ok', next: node.next };
|
|
481
|
-
}
|
|
482
|
-
async executeSystemLogic(run, node) {
|
|
483
|
-
const data = (0, store_1.readWorkflowData)(run.folderPath);
|
|
484
|
-
let matched = null;
|
|
485
|
-
let matchedActual;
|
|
486
|
-
let matchedSource;
|
|
487
|
-
let matchedName;
|
|
488
|
-
for (const rule of node.branches) {
|
|
489
|
-
let actual;
|
|
490
|
-
let source;
|
|
491
|
-
let name;
|
|
492
|
-
if (rule.variable) {
|
|
493
|
-
actual = data.variables[rule.variable];
|
|
494
|
-
source = 'variable';
|
|
495
|
-
name = rule.variable;
|
|
496
|
-
}
|
|
497
|
-
else if (rule.constant) {
|
|
498
|
-
actual = data.constants[rule.constant];
|
|
499
|
-
source = 'constant';
|
|
500
|
-
name = rule.constant;
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
if (branchMatches(actual, rule.equals)) {
|
|
506
|
-
matched = rule;
|
|
507
|
-
matchedActual = actual;
|
|
508
|
-
matchedSource = source;
|
|
509
|
-
matchedName = name;
|
|
510
|
-
break;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
const goto = matched ? matched.goto : (node.defaultGoto ?? null);
|
|
514
|
-
log.info({
|
|
515
|
-
projectId: run.projectId,
|
|
516
|
-
nodeId: node.id,
|
|
517
|
-
matchedSource,
|
|
518
|
-
matchedName,
|
|
519
|
-
matchedEquals: matched ? JSON.stringify(matched.equals) : undefined,
|
|
520
|
-
actualValue: matched ? JSON.stringify(matchedActual) : undefined,
|
|
521
|
-
goto,
|
|
522
|
-
viaDefault: !matched,
|
|
523
|
-
}, 'flow node system-logic branch evaluated');
|
|
524
|
-
if (goto === null) {
|
|
525
|
-
return { kind: 'ok', next: null };
|
|
526
|
-
}
|
|
527
|
-
// Loop edge detection: target id is already in this run's history.
|
|
528
|
-
const visited = new Set(run.state.history.map((h) => h.nodeId));
|
|
529
|
-
const isBackward = visited.has(goto);
|
|
530
|
-
if (isBackward) {
|
|
531
|
-
const count = (run.state.loopCounters[node.id] ?? 0) + 1;
|
|
532
|
-
run.state.loopCounters[node.id] = count;
|
|
533
|
-
log.info({ projectId: run.projectId, nodeId: node.id, goto, loopCount: count, maxRetries: node.maxRetries }, 'flow node system-logic loop edge');
|
|
534
|
-
if (count > node.maxRetries) {
|
|
535
|
-
run.state.status = 'paused';
|
|
536
|
-
run.state.pauseReason = 'max-retries-exceeded';
|
|
537
|
-
run.state.pauseDetail = `node ${node.id} backward edge to ${goto} exceeded maxRetries=${node.maxRetries}`;
|
|
538
|
-
this.emit('error', {
|
|
539
|
-
projectId: run.projectId,
|
|
540
|
-
nodeId: node.id,
|
|
541
|
-
reason: 'max-retries-exceeded',
|
|
542
|
-
detail: run.state.pauseDetail,
|
|
543
|
-
});
|
|
544
|
-
log.warn({ projectId: run.projectId, nodeId: node.id, goto, maxRetries: node.maxRetries }, 'flow node system-logic max-retries exceeded');
|
|
545
|
-
return { kind: 'pause' };
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
return { kind: 'ok', next: goto };
|
|
549
|
-
}
|
|
550
|
-
// ── Wait helpers ──────────────────────────────────────────────────────
|
|
551
|
-
/** Watch workflow_data.json for `task_progress[index].finish = true`. The
|
|
552
|
-
* LLM is told to flip this flag when its work is done. Any other write to
|
|
553
|
-
* workflow_data (variable updates, etc.) also triggers the watcher; the
|
|
554
|
-
* 50ms debounce + finish-check makes that cheap. */
|
|
555
|
-
waitForTaskFinish(run, taskIndex, timeoutMs) {
|
|
556
|
-
return new Promise((resolve) => {
|
|
557
|
-
let settled = false;
|
|
558
|
-
const settle = (v) => {
|
|
559
|
-
if (settled)
|
|
560
|
-
return;
|
|
561
|
-
settled = true;
|
|
562
|
-
this.clearWaiters(run);
|
|
563
|
-
resolve(v);
|
|
564
|
-
};
|
|
565
|
-
run.waitResolve = settle;
|
|
566
|
-
const checkFinish = () => {
|
|
567
|
-
try {
|
|
568
|
-
const d = (0, store_1.readWorkflowData)(run.folderPath);
|
|
569
|
-
return d.task_progress[taskIndex]?.finish === true;
|
|
570
|
-
}
|
|
571
|
-
catch {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
};
|
|
575
|
-
// Initial check — finish may have raced ahead before we attached.
|
|
576
|
-
if (checkFinish()) {
|
|
577
|
-
log.info({ projectId: run.projectId, taskIndex, via: 'initial-check' }, 'flow task_progress finish detected');
|
|
578
|
-
settle('finished');
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
const filePath = (0, store_1.workflowDataPath)(run.folderPath);
|
|
582
|
-
try {
|
|
583
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
584
|
-
}
|
|
585
|
-
catch { /* ignore */ }
|
|
586
|
-
try {
|
|
587
|
-
run.watcher = fs.watch(filePath, { persistent: false }, () => {
|
|
588
|
-
if (settled)
|
|
589
|
-
return;
|
|
590
|
-
if (run.watcherDebounce)
|
|
591
|
-
clearTimeout(run.watcherDebounce);
|
|
592
|
-
run.watcherDebounce = setTimeout(() => {
|
|
593
|
-
run.watcherDebounce = null;
|
|
594
|
-
if (settled)
|
|
595
|
-
return;
|
|
596
|
-
if (checkFinish()) {
|
|
597
|
-
log.info({ projectId: run.projectId, taskIndex, via: 'watcher' }, 'flow task_progress finish detected');
|
|
598
|
-
settle('finished');
|
|
599
|
-
}
|
|
600
|
-
}, 50);
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
catch (err) {
|
|
604
|
-
log.warn({ projectId: run.projectId, err: err instanceof Error ? err.message : String(err) }, 'workflow_data watch failed — falling back to polling');
|
|
605
|
-
const poll = setInterval(() => {
|
|
606
|
-
if (checkFinish()) {
|
|
607
|
-
clearInterval(poll);
|
|
608
|
-
settle('finished');
|
|
609
|
-
}
|
|
610
|
-
}, 500);
|
|
611
|
-
run._poll = poll;
|
|
612
|
-
}
|
|
613
|
-
run.timeoutTimer = setTimeout(() => settle('timeout'), timeoutMs);
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
clearWaiters(run) {
|
|
617
|
-
if (run.watcher) {
|
|
618
|
-
try {
|
|
619
|
-
run.watcher.close();
|
|
620
|
-
}
|
|
621
|
-
catch { /* ignore */ }
|
|
622
|
-
run.watcher = null;
|
|
623
|
-
}
|
|
624
|
-
if (run.watcherDebounce) {
|
|
625
|
-
clearTimeout(run.watcherDebounce);
|
|
626
|
-
run.watcherDebounce = null;
|
|
627
|
-
}
|
|
628
|
-
if (run.timeoutTimer) {
|
|
629
|
-
clearTimeout(run.timeoutTimer);
|
|
630
|
-
run.timeoutTimer = null;
|
|
631
|
-
}
|
|
632
|
-
const r = run;
|
|
633
|
-
if (r._poll) {
|
|
634
|
-
clearInterval(r._poll);
|
|
635
|
-
r._poll = undefined;
|
|
636
|
-
}
|
|
637
|
-
run.waitResolve = null;
|
|
638
|
-
}
|
|
639
|
-
persist(run) {
|
|
640
|
-
(0, store_1.saveFlowState)(run.folderPath, run.state);
|
|
641
|
-
this.emit('state', { projectId: run.projectId, state: run.state });
|
|
642
|
-
}
|
|
643
|
-
finalize(run, status, detail) {
|
|
644
|
-
run.state.status = status;
|
|
645
|
-
run.state.currentNodeId = null;
|
|
646
|
-
if (detail)
|
|
647
|
-
run.state.pauseDetail = detail;
|
|
648
|
-
this.clearWaiters(run);
|
|
649
|
-
(0, store_1.saveFlowState)(run.folderPath, run.state);
|
|
650
|
-
this.active.delete(run.projectId);
|
|
651
|
-
this.emit('state', { projectId: run.projectId, state: run.state });
|
|
652
|
-
log.info({ projectId: run.projectId, status, detail }, 'flow finalized');
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
exports.FlowRunner = FlowRunner;
|
|
656
|
-
// Suppress unused-export warnings — clearFlowState may be used by callers we
|
|
657
|
-
// don't see (e.g. project deletion cleanup).
|
|
658
|
-
void store_1.clearFlowState;
|
|
659
|
-
exports.flowRunner = new FlowRunner();
|
|
660
|
-
//# sourceMappingURL=runner.js.map
|