qlogicagent 2.12.12 → 2.12.14
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/dist/cli.js +371 -352
- package/dist/index.js +370 -351
- package/dist/protocol.js +1 -1
- package/dist/types/cli/acp-extended-handlers.d.ts +7 -0
- package/dist/types/cli/community-resource-installer.d.ts +2 -1
- package/dist/types/cli/community-workflow-installer.d.ts +29 -0
- package/dist/types/cli/credential-vault.d.ts +62 -0
- package/dist/types/cli/handlers/community-handler.d.ts +3 -0
- package/dist/types/cli/handlers/project-handler.d.ts +1 -1
- package/dist/types/cli/handlers/workflow-handler.d.ts +138 -8
- package/dist/types/cli/project-command-service.d.ts +3 -1
- package/dist/types/orchestration/workflow/budget-permission-gate.d.ts +1 -1
- package/dist/types/orchestration/workflow/builtin-executors.d.ts +6 -0
- package/dist/types/orchestration/workflow/capability-catalog.d.ts +64 -0
- package/dist/types/orchestration/workflow/expression.d.ts +8 -0
- package/dist/types/orchestration/workflow/host-executors.d.ts +1 -1
- package/dist/types/orchestration/workflow/n8n-expression.d.ts +48 -0
- package/dist/types/orchestration/workflow/n8n-import.d.ts +45 -12
- package/dist/types/orchestration/workflow/node-schema.d.ts +100 -0
- package/dist/types/orchestration/workflow/qla-executor-host.d.ts +27 -1
- package/dist/types/orchestration/workflow/run-history-store.d.ts +8 -1
- package/dist/types/orchestration/workflow/workflow-audit-store.d.ts +31 -0
- package/dist/types/orchestration/workflow/workflow-bundle.d.ts +65 -0
- package/dist/types/orchestration/workflow/workflow-controller.d.ts +20 -3
- package/dist/types/orchestration/workflow/workflow-patch.d.ts +15 -0
- package/dist/types/orchestration/workflow/workflow-runtime.d.ts +24 -0
- package/dist/types/orchestration/workflow/workflow-scheduler.d.ts +21 -0
- package/dist/types/orchestration/workflow/workflow-store.d.ts +17 -0
- package/dist/types/orchestration/workflow/workflow-trigger.d.ts +21 -0
- package/dist/types/orchestration/workflow-chat-builder.d.ts +18 -0
- package/dist/types/protocol/wire/acp-agent-management.d.ts +5 -0
- package/dist/types/protocol/wire/acp-protocol.d.ts +1 -0
- package/dist/types/protocol/wire/agent-events.d.ts +2 -2
- package/dist/types/protocol/wire/gateway-rpc.d.ts +3 -1
- package/dist/types/runtime/community/community-consent-client.d.ts +4 -0
- package/dist/types/runtime/infra/project-store.d.ts +1 -1
- package/dist/types/transport/acp-server.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* n8n expression → restricted-language converter (IT-11, spec D35).
|
|
3
|
+
*
|
|
4
|
+
* n8n expressions are arbitrary JS inside `={{ }}`; our language is a fixed grammar
|
|
5
|
+
* (path access + literal + whitelist functions — see expression.ts, D6 hard line: NO eval).
|
|
6
|
+
* So conversion is a CURATED rewrite of the safe overlap, gated by our own parser:
|
|
7
|
+
* every rewritten body must pass `validateExpressionSyntax` or the whole parameter is
|
|
8
|
+
* flagged "manual" — we never emit anything our language can't parse, and we never
|
|
9
|
+
* approximate semantics silently.
|
|
10
|
+
*
|
|
11
|
+
* Convertible subset (n8n → ours):
|
|
12
|
+
* $json.path / $json["k"] → unchanged
|
|
13
|
+
* $node["X"].json.y → $node("X").output.default[0].json.y
|
|
14
|
+
* $('X').item.json.y / $('X').first().json.y → $node("X").output.default[0].json.y
|
|
15
|
+
* $items("X")[i].json.y → $node("X").output.default[i].json.y
|
|
16
|
+
* $input.item.json / $input.first().json → $json
|
|
17
|
+
* $now → $now (note: n8n is Luxon, ours is Date)
|
|
18
|
+
* Anything else ($today, $binary, $workflow, .method() calls, operators, …) → manual.
|
|
19
|
+
*/
|
|
20
|
+
export type ExpressionConversion = {
|
|
21
|
+
ok: true;
|
|
22
|
+
converted: string;
|
|
23
|
+
notes: string[];
|
|
24
|
+
} | {
|
|
25
|
+
ok: false;
|
|
26
|
+
reason: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Convert one n8n parameter STRING. n8n marks expression strings with a leading `=`;
|
|
30
|
+
* inside, `{{ }}` segments are evaluated and the rest is literal text. Strings without
|
|
31
|
+
* the `=` prefix are plain literals (returned unchanged, not counted as conversions).
|
|
32
|
+
*/
|
|
33
|
+
export declare function convertN8nExpression(raw: string): ExpressionConversion | null;
|
|
34
|
+
export interface ParamConversionStats {
|
|
35
|
+
converted: number;
|
|
36
|
+
manual: Array<{
|
|
37
|
+
path: string;
|
|
38
|
+
raw: string;
|
|
39
|
+
reason: string;
|
|
40
|
+
}>;
|
|
41
|
+
notes: string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Deep-walk a params object converting every n8n expression string in place (returns a new
|
|
45
|
+
* object). Unconvertible expressions KEEP their raw n8n text (visible, greppable) and are
|
|
46
|
+
* reported in `stats.manual` — explicit degradation, never silent loss.
|
|
47
|
+
*/
|
|
48
|
+
export declare function convertN8nParams(params: Record<string, unknown>, stats: ParamConversionStats, basePath?: string): Record<string, unknown>;
|
|
@@ -1,34 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* n8n workflow JSON import — read-format-only translator (plan M5 §2.1: "导入 n8n workflow JSON
|
|
3
|
-
* (读格式,不打包其代码)").
|
|
3
|
+
* (读格式,不打包其代码)"), deepened for IT-11 (spec D35).
|
|
4
4
|
*
|
|
5
5
|
* Converts an n8n workflow export (its public JSON shape: `nodes` + `connections`) into our own
|
|
6
6
|
* WorkflowDef. This reads n8n's data FORMAT only — it links/embeds NONE of n8n's runtime code, so
|
|
7
7
|
* the SUL licensing concern (plan §2.1 / risk register) does not apply.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Error model (IT-11): STRUCTURAL problems (malformed JSON, duplicate/missing node names, dangling
|
|
10
|
+
* connections) stay fail-loud — the graph identity itself is broken, nothing useful survives.
|
|
11
|
+
* SEMANTIC gaps (unmapped node type, inconvertible expression, Code-node JS) DEGRADE EXPLICITLY:
|
|
12
|
+
* the node lands as kind "manual" (its executor throws at run time — never silently passes
|
|
13
|
+
* through), the original n8n type/params are preserved for the human, and every degradation is
|
|
14
|
+
* itemized in the returned ConversionReport. Nothing is silently dropped; no JS is ever executed
|
|
15
|
+
* (D6 hard line — n8n Code nodes are flagged manual, not emulated).
|
|
14
16
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* set afterward through the validated set_trigger channel.
|
|
17
|
+
* Credentials: n8n nodes carry `credentials: {type: {id,name}}` references. The secrets are NOT
|
|
18
|
+
* in the export — we extract them as CredentialRequirement entries (mapped to vault types, D29)
|
|
19
|
+
* so the import UI can walk the user through re-binding.
|
|
19
20
|
*/
|
|
20
21
|
import type { WorkflowDef } from "./node-schema.js";
|
|
22
|
+
export interface N8nNodeReport {
|
|
23
|
+
id: string;
|
|
24
|
+
n8nType: string;
|
|
25
|
+
/** converted=faithful; partial=landed but params need attention; manual=placeholder node; skipped=annotation dropped. */
|
|
26
|
+
outcome: "converted" | "partial" | "manual" | "skipped";
|
|
27
|
+
notes: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface N8nCredentialRequirement {
|
|
30
|
+
nodeId: string;
|
|
31
|
+
n8nCredentialType: string;
|
|
32
|
+
/** n8n-side display name of the credential (helps the user recognize which secret to re-create). */
|
|
33
|
+
label?: string;
|
|
34
|
+
/** Suggested vault credential type (D29). */
|
|
35
|
+
suggestedVaultType: string;
|
|
36
|
+
}
|
|
37
|
+
export interface N8nConversionReport {
|
|
38
|
+
/** full=everything faithful; partial=some degradations; manual=at least one manual placeholder. */
|
|
39
|
+
status: "full" | "partial" | "manual";
|
|
40
|
+
nodes: N8nNodeReport[];
|
|
41
|
+
expressions: {
|
|
42
|
+
converted: number;
|
|
43
|
+
manual: Array<{
|
|
44
|
+
nodeId: string;
|
|
45
|
+
path: string;
|
|
46
|
+
raw: string;
|
|
47
|
+
reason: string;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
credentials: N8nCredentialRequirement[];
|
|
51
|
+
notes: string[];
|
|
52
|
+
}
|
|
21
53
|
export interface N8nImportResult {
|
|
22
54
|
name: string;
|
|
23
55
|
def: WorkflowDef;
|
|
56
|
+
report: N8nConversionReport;
|
|
24
57
|
}
|
|
25
|
-
/** Thrown when an n8n workflow
|
|
58
|
+
/** Thrown when an n8n workflow is STRUCTURALLY untranslatable. Carries every problem found. */
|
|
26
59
|
export declare class N8nImportError extends Error {
|
|
27
60
|
readonly errors: string[];
|
|
28
61
|
constructor(errors: string[]);
|
|
29
62
|
}
|
|
30
63
|
/**
|
|
31
|
-
* Convert an n8n workflow (a JSON string or already-parsed object) into a WorkflowDef.
|
|
64
|
+
* Convert an n8n workflow (a JSON string or already-parsed object) into a WorkflowDef + report.
|
|
32
65
|
* The returned def is NOT yet graph-validated — pass it to `validateWorkflowDef` / a controller
|
|
33
66
|
* write path, which is the single existing invariant gate.
|
|
34
67
|
*/
|
|
@@ -25,6 +25,29 @@ export interface WorkflowNodeDef {
|
|
|
25
25
|
inPorts?: PortDef[];
|
|
26
26
|
joinPolicy?: "all" | "any";
|
|
27
27
|
maxIterations?: number;
|
|
28
|
+
/** Retry the executor on failure: total attempts = maxTries (≥1); exponential backoff from backoffMs (default 1000). */
|
|
29
|
+
retry?: {
|
|
30
|
+
maxTries: number;
|
|
31
|
+
backoffMs?: number;
|
|
32
|
+
};
|
|
33
|
+
/** Per-attempt deadline; the executor's signal aborts when exceeded (counts as a failure → retry applies). */
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
/**
|
|
36
|
+
* What a FINAL failure (after retries) does to the run:
|
|
37
|
+
* "stop"(default)= run fails loud; "continue" = node marked failed, fires no ports
|
|
38
|
+
* (downstream skips), other branches keep running; "errorPort" = a DataItem with the
|
|
39
|
+
* error routes out the "error" port (wire an error-handler/notify subgraph to it).
|
|
40
|
+
*/
|
|
41
|
+
onError?: "stop" | "continue" | "errorPort";
|
|
42
|
+
/**
|
|
43
|
+
* Canvas position pinned by a manual drag (set_node_ui patch op). Layout-only — the
|
|
44
|
+
* engine never reads it; absent ⇒ the canvas auto-layouts the node. Travels with the
|
|
45
|
+
* def (Bundle/export/Hub/n8n-import) so a hand-arranged graph stays arranged.
|
|
46
|
+
*/
|
|
47
|
+
ui?: {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
};
|
|
28
51
|
}
|
|
29
52
|
/** A data edge (carries DataItem[]). fromPort/toPort default "default". */
|
|
30
53
|
export interface DataEdge {
|
|
@@ -41,9 +64,20 @@ export interface WorkflowDef {
|
|
|
41
64
|
/** Workflow-level variables exposed to expressions as $vars. */
|
|
42
65
|
vars?: Record<string, unknown>;
|
|
43
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* SINGLE source of the attended/unattended split. Attended = a person is present right now:
|
|
69
|
+
* manual (pressed 运行), intent (just SAID it, confirmed by default D22), form (just SUBMITTED
|
|
70
|
+
* it, D34). Everything else (schedule / im-message / webhook) fires with nobody watching.
|
|
71
|
+
* Lives here (leaf module) so both the gate and host-executors can import it cycle-free.
|
|
72
|
+
*/
|
|
73
|
+
export declare function isUnattendedTrigger(kind: string): boolean;
|
|
44
74
|
export interface ExecutorContext {
|
|
45
75
|
nodeId: string;
|
|
46
76
|
kind: string;
|
|
77
|
+
/** Owning workflow id (def.id) — lets host-backed nodes (approval) tag outbound notifications. */
|
|
78
|
+
workflowId?: string;
|
|
79
|
+
/** This run's trigger kind (manual/schedule/…) — drives fine-grained unattended audits (IT-7b). */
|
|
80
|
+
triggerKind?: string;
|
|
47
81
|
/** 0-based; increments per loop iteration (drives the cache key, spec §A5). */
|
|
48
82
|
runIndex: number;
|
|
49
83
|
vars: Record<string, unknown>;
|
|
@@ -76,6 +110,26 @@ export interface ExecutorHost {
|
|
|
76
110
|
invokeMcp(req: McpHostRequest): Promise<DataItem[]>;
|
|
77
111
|
/** Send a message out through a channel (飞书/微信/…) via the openclaw channel layer. */
|
|
78
112
|
sendChannel(req: ChannelHostRequest): Promise<DataItem[]>;
|
|
113
|
+
/** Project-scoped memory (memdir) read/write/search for the `memory` kind (spec D25). */
|
|
114
|
+
memory?(req: MemoryHostRequest): Promise<DataItem[]>;
|
|
115
|
+
/** Run another workflow as a step (D34); the host enforces cycle guards. */
|
|
116
|
+
runSubworkflow?(req: SubworkflowHostRequest): Promise<DataItem[]>;
|
|
117
|
+
/**
|
|
118
|
+
* Human-in-the-loop approval (D34): notify the owner and AWAIT the decision in-memory
|
|
119
|
+
* (the run stays "running"; the node shows pending). A restart loses pending approvals —
|
|
120
|
+
* the run then fails loud (durable suspended-run resumption is a tracked follow-up).
|
|
121
|
+
*/
|
|
122
|
+
approval?(req: ApprovalHostRequest): Promise<{
|
|
123
|
+
approved: boolean;
|
|
124
|
+
decidedAt: string;
|
|
125
|
+
note?: string;
|
|
126
|
+
}>;
|
|
127
|
+
/**
|
|
128
|
+
* D34 wait-until-webhook: suspend the branch until the given webhook path is called
|
|
129
|
+
* (await-in-memory, same posture as approval — the run stays "running"; a restart loses the
|
|
130
|
+
* wait and the run fails loud; checkpoint replay recovers prior steps on re-run).
|
|
131
|
+
*/
|
|
132
|
+
waitWebhook?(req: WebhookWaitHostRequest): Promise<Record<string, unknown>>;
|
|
79
133
|
}
|
|
80
134
|
export interface AgentHostRequest {
|
|
81
135
|
agentId?: string;
|
|
@@ -86,6 +140,8 @@ export interface AgentHostRequest {
|
|
|
86
140
|
export interface ToolHostRequest {
|
|
87
141
|
tool: string;
|
|
88
142
|
args: Record<string, unknown>;
|
|
143
|
+
/** True when this run's trigger is unattended — the host applies fine-grained audits (IT-7b). */
|
|
144
|
+
unattended?: boolean;
|
|
89
145
|
input: DataItem[];
|
|
90
146
|
signal?: AbortSignal;
|
|
91
147
|
}
|
|
@@ -94,6 +150,8 @@ export interface HttpHostRequest {
|
|
|
94
150
|
url: string;
|
|
95
151
|
headers?: Record<string, string>;
|
|
96
152
|
body?: unknown;
|
|
153
|
+
/** Vault credential to inject as auth headers at execution time (D29; resolved host-side). */
|
|
154
|
+
credentialId?: string;
|
|
97
155
|
signal?: AbortSignal;
|
|
98
156
|
}
|
|
99
157
|
export interface McpHostRequest {
|
|
@@ -109,6 +167,48 @@ export interface ChannelHostRequest {
|
|
|
109
167
|
input: DataItem[];
|
|
110
168
|
signal?: AbortSignal;
|
|
111
169
|
}
|
|
170
|
+
export interface SubworkflowHostRequest {
|
|
171
|
+
workflowId: string;
|
|
172
|
+
/** The calling workflow's id — joins the cycle-guard chain so top-level runs count too. */
|
|
173
|
+
callerWorkflowId?: string;
|
|
174
|
+
/** Cross-project call target; absent = the calling workflow's own project root. */
|
|
175
|
+
projectId?: string;
|
|
176
|
+
/** Becomes the sub-run's $trigger payload (falls back to the upstream item's json). */
|
|
177
|
+
payload?: Record<string, unknown>;
|
|
178
|
+
input: DataItem[];
|
|
179
|
+
signal?: AbortSignal;
|
|
180
|
+
}
|
|
181
|
+
export interface ApprovalHostRequest {
|
|
182
|
+
nodeId: string;
|
|
183
|
+
/** Owning workflow id so the approval surface can name/locate the workflow. */
|
|
184
|
+
workflowId?: string;
|
|
185
|
+
/** Plain-language question shown to the owner (默认 "继续执行?"). */
|
|
186
|
+
prompt?: string;
|
|
187
|
+
/** Auto-deny deadline; default 24h. */
|
|
188
|
+
timeoutMs?: number;
|
|
189
|
+
input: DataItem[];
|
|
190
|
+
signal?: AbortSignal;
|
|
191
|
+
}
|
|
192
|
+
export interface WebhookWaitHostRequest {
|
|
193
|
+
/** Webhook path to wait for (normalized with a leading "/"). */
|
|
194
|
+
path: string;
|
|
195
|
+
nodeId: string;
|
|
196
|
+
workflowId?: string;
|
|
197
|
+
/** Auto-fail deadline; default 24h (a forever-pending branch is a leak, not patience). */
|
|
198
|
+
timeoutMs?: number;
|
|
199
|
+
signal?: AbortSignal;
|
|
200
|
+
}
|
|
201
|
+
export interface MemoryHostRequest {
|
|
202
|
+
op: "read" | "write" | "search";
|
|
203
|
+
/** Topic-file name; absent → operate on the always-visible index. */
|
|
204
|
+
key?: string;
|
|
205
|
+
/** Content for `write` (falls back to the upstream item's json when omitted). */
|
|
206
|
+
value?: unknown;
|
|
207
|
+
/** Query for `search`. */
|
|
208
|
+
query?: string;
|
|
209
|
+
input: DataItem[];
|
|
210
|
+
signal?: AbortSignal;
|
|
211
|
+
}
|
|
112
212
|
export interface NodeExecResult {
|
|
113
213
|
/** outPort → DataItem[]. */
|
|
114
214
|
outputs: PortMap;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* (`findTool`). fail-loud everywhere: unknown tool, missing channel host, or any rejection throws.
|
|
9
9
|
*/
|
|
10
10
|
import type { RuntimeToolContract } from "../../runtime/ports/index.js";
|
|
11
|
-
import type
|
|
11
|
+
import { type DataItem } from "./data-item.js";
|
|
12
|
+
import type { ExecutorHost, ToolHostRequest, MemoryHostRequest, SubworkflowHostRequest, ApprovalHostRequest, WebhookWaitHostRequest } from "./node-schema.js";
|
|
12
13
|
/** Injected boundary capabilities — supplied by the handler host that owns the real runners. */
|
|
13
14
|
export interface QlaExecutorHostDeps {
|
|
14
15
|
/** Run one agent turn (spawn external ACP agent + sendTask). Returns the agent's text output. */
|
|
@@ -36,6 +37,31 @@ export interface QlaExecutorHostDeps {
|
|
|
36
37
|
}) => Promise<unknown>;
|
|
37
38
|
/** Tool lookup supplied by the runtime host. */
|
|
38
39
|
findTool: (name: string) => RuntimeToolContract | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Project-scoped memory backend for the `memory` kind (spec D25). The handler host wires
|
|
42
|
+
* the owning project root's memdir; absent → memory nodes fail-loud.
|
|
43
|
+
*/
|
|
44
|
+
memory?: (req: MemoryHostRequest) => Promise<DataItem[]>;
|
|
45
|
+
/** Run another workflow as a step (D34); the handler enforces cycle guards. */
|
|
46
|
+
runSubworkflow?: (req: SubworkflowHostRequest) => Promise<DataItem[]>;
|
|
47
|
+
/** Human-in-the-loop approval (D34): notify + await the owner's decision. */
|
|
48
|
+
approval?: (req: ApprovalHostRequest) => Promise<{
|
|
49
|
+
approved: boolean;
|
|
50
|
+
decidedAt: string;
|
|
51
|
+
note?: string;
|
|
52
|
+
}>;
|
|
53
|
+
/** D34 wait-until-webhook: suspend until the path is hit (handler owns the wake registry). */
|
|
54
|
+
waitWebhook?: (req: WebhookWaitHostRequest) => Promise<Record<string, unknown>>;
|
|
55
|
+
/**
|
|
56
|
+
* IT-7b fine-grained unattended audit (unified permission engine's operation classifier).
|
|
57
|
+
* Called for every tool invocation in an UNATTENDED run; throw to refuse (fail-loud).
|
|
58
|
+
*/
|
|
59
|
+
auditUnattendedTool?: (req: ToolHostRequest) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Vault credential → auth headers, resolved at EXECUTION time only (D29). Absent ⇒ http
|
|
62
|
+
* nodes carrying credentialId fail-loud (never silently unauthenticated).
|
|
63
|
+
*/
|
|
64
|
+
resolveCredentialHeaders?: (credentialId: string) => Record<string, string>;
|
|
39
65
|
/** fetch impl; defaults to global fetch. Injectable for tests. */
|
|
40
66
|
fetchImpl?: typeof fetch;
|
|
41
67
|
}
|
|
@@ -26,6 +26,13 @@ export interface RunNodeRecord {
|
|
|
26
26
|
executed: boolean;
|
|
27
27
|
/** Truncated JSON preview of the node's output items (port → items[].json). */
|
|
28
28
|
preview: string;
|
|
29
|
+
seq?: number;
|
|
30
|
+
startedAt?: string;
|
|
31
|
+
durationMs?: number;
|
|
32
|
+
attempts?: number;
|
|
33
|
+
cacheHit?: boolean;
|
|
34
|
+
/** Per-node disposition; "failed" with a surviving run = onError errorPort/continue (D32). */
|
|
35
|
+
nodeStatus?: "completed" | "failed";
|
|
29
36
|
}
|
|
30
37
|
export interface RunHistoryEntry {
|
|
31
38
|
runId: string;
|
|
@@ -40,7 +47,7 @@ export interface RunHistoryEntry {
|
|
|
40
47
|
}
|
|
41
48
|
/** Summary row for list views — full node outputs stay behind get(). */
|
|
42
49
|
export type RunHistorySummary = Omit<RunHistoryEntry, "nodes">;
|
|
43
|
-
/** Build per-node output previews from a settled RunResult. */
|
|
50
|
+
/** Build per-node output previews (+ D33 trace stats) from a settled RunResult. */
|
|
44
51
|
export declare function buildNodeRecords(result: RunResult): Record<string, RunNodeRecord>;
|
|
45
52
|
export declare class WorkflowRunHistoryStore {
|
|
46
53
|
private readonly cwd;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow audit log (IT-12, spec D38): WHO did WHAT to WHICH workflow WHEN —
|
|
3
|
+
* configuration/lifecycle changes only. Runs are deliberately NOT duplicated here:
|
|
4
|
+
* WorkflowRunHistoryStore is already the complete run audit (trigger/timing/status/per-node);
|
|
5
|
+
* a timeline UI merges both sources.
|
|
6
|
+
*
|
|
7
|
+
* Storage: append-only JSONL at `<project>/.qlogicagent/workflows/audit.jsonl` (project-level
|
|
8
|
+
* single file — the natural unit for a cross-workflow timeline). Entries are never rewritten.
|
|
9
|
+
*/
|
|
10
|
+
export interface WorkflowAuditEntry {
|
|
11
|
+
ts: string;
|
|
12
|
+
/** Owning user id (resolveActiveOwnerUserId) at the time of the action. */
|
|
13
|
+
actor: string;
|
|
14
|
+
/** e.g. created / updated / patched / activated / deactivated / published / archived /
|
|
15
|
+
* reverted / deleted / imported / duplicated / permissionGranted / publishedToHub */
|
|
16
|
+
action: string;
|
|
17
|
+
workflowId: string;
|
|
18
|
+
detail?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export declare class WorkflowAuditStore {
|
|
21
|
+
private readonly file;
|
|
22
|
+
/** `rootDir` = the project's workflows dir (same root the WorkflowStore uses). */
|
|
23
|
+
constructor(rootDir: string);
|
|
24
|
+
/**
|
|
25
|
+
* Append one entry. Failures are LOUD on stderr but never break the business write that
|
|
26
|
+
* triggered them (an audit-log disk error must not corrupt a successful state change).
|
|
27
|
+
*/
|
|
28
|
+
append(entry: WorkflowAuditEntry): void;
|
|
29
|
+
/** Newest-first entries, optionally filtered to one workflow. Malformed lines are skipped loud. */
|
|
30
|
+
list(workflowId?: string, limit?: number): WorkflowAuditEntry[];
|
|
31
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable workflow bundle (spec D23) — THE single serialization format shared by
|
|
3
|
+
* export / import / save-as(duplicate) / Hub publish / Hub apply. Two invariants:
|
|
4
|
+
*
|
|
5
|
+
* 1. SANITIZED: credentials never leave the machine inside a bundle. Any params key that
|
|
6
|
+
* looks credential-shaped has its VALUE stripped and is recorded as a CredentialRequirement
|
|
7
|
+
* the importer must re-bind. Unknown-but-suspicious shapes fail toward stripping (§1.5:
|
|
8
|
+
* never silently carry a secret out).
|
|
9
|
+
* 2. SELF-DESCRIBING: `requires` lists the capabilities (tool/agent/mcp/channel/skill) the
|
|
10
|
+
* graph references, so an importer (or the Hub apply checklist) can verify bindings
|
|
11
|
+
* BEFORE activating instead of failing mid-run.
|
|
12
|
+
*/
|
|
13
|
+
import type { WorkflowDef } from "./node-schema.js";
|
|
14
|
+
import type { TriggerDef } from "./workflow-trigger.js";
|
|
15
|
+
import type { ConcurrencyPolicy, WorkflowRecord } from "./workflow-store.js";
|
|
16
|
+
export declare const WORKFLOW_BUNDLE_SCHEMA_VERSION = 2;
|
|
17
|
+
export interface CapabilityRequirement {
|
|
18
|
+
category: "agent" | "tool" | "skill" | "mcp" | "memory" | "channel";
|
|
19
|
+
kind: string;
|
|
20
|
+
/** Concrete capability reference (tool name / agent id / "server.tool" / channel name). */
|
|
21
|
+
ref: string;
|
|
22
|
+
/** Human-readable label shown in the apply/bind checklist. */
|
|
23
|
+
label: string;
|
|
24
|
+
nodeIds: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface CredentialRequirement {
|
|
27
|
+
/** Best-effort type hint; v1 marks all stripped values "custom" (vault types land in IT-7). */
|
|
28
|
+
type: "apiKey" | "bearer" | "basic" | "oauth2" | "custom";
|
|
29
|
+
/** Where the value was stripped from, e.g. `节点「发飞书」 params.headers.Authorization`. */
|
|
30
|
+
label: string;
|
|
31
|
+
nodeIds: string[];
|
|
32
|
+
}
|
|
33
|
+
export interface WorkflowBundle {
|
|
34
|
+
schemaVersion: typeof WORKFLOW_BUNDLE_SCHEMA_VERSION;
|
|
35
|
+
name: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
def: WorkflowDef;
|
|
38
|
+
trigger?: TriggerDef;
|
|
39
|
+
concurrency?: ConcurrencyPolicy;
|
|
40
|
+
requires: CapabilityRequirement[];
|
|
41
|
+
credentialsRequired: CredentialRequirement[];
|
|
42
|
+
meta?: {
|
|
43
|
+
author?: string;
|
|
44
|
+
tags?: string[];
|
|
45
|
+
category?: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
source?: "native" | "n8n";
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Placeholder left where a credential value was stripped (visibly broken > silently leaked). */
|
|
51
|
+
export declare const STRIPPED_CREDENTIAL_PLACEHOLDER = "__CREDENTIAL_REQUIRED__";
|
|
52
|
+
/** Derive the capability requirements a graph references (host-backed kinds only). */
|
|
53
|
+
export declare function deriveRequirements(def: WorkflowDef): CapabilityRequirement[];
|
|
54
|
+
/** Serialize a stored record into a sanitized, self-describing portable bundle. */
|
|
55
|
+
export declare function toWorkflowBundle(record: WorkflowRecord): WorkflowBundle;
|
|
56
|
+
export interface ParsedBundle {
|
|
57
|
+
name: string;
|
|
58
|
+
def: WorkflowDef;
|
|
59
|
+
trigger?: TriggerDef;
|
|
60
|
+
concurrency?: ConcurrencyPolicy;
|
|
61
|
+
requires: CapabilityRequirement[];
|
|
62
|
+
credentialsRequired: CredentialRequirement[];
|
|
63
|
+
}
|
|
64
|
+
/** Parse + validate an incoming bundle payload. fail-loud on malformed shapes (§1.5). */
|
|
65
|
+
export declare function fromWorkflowBundle(payload: unknown): ParsedBundle;
|
|
@@ -42,6 +42,8 @@ export interface RunContext {
|
|
|
42
42
|
payload?: Record<string, unknown>;
|
|
43
43
|
/** Cancellation propagated to the runtime and host-backed node executors. */
|
|
44
44
|
signal?: AbortSignal;
|
|
45
|
+
/** The record's unattended capability grant at run start (D30; feeds the permission gate). */
|
|
46
|
+
permissionScope?: WorkflowRecord["permissionScope"];
|
|
45
47
|
}
|
|
46
48
|
/** Per-node lifecycle status surfaced for live canvas coloring (not durable; fire-and-forget). */
|
|
47
49
|
export type NodeRunStatus = "running" | "completed" | "failed";
|
|
@@ -61,6 +63,8 @@ export interface CreateInput {
|
|
|
61
63
|
def: WorkflowDef;
|
|
62
64
|
concurrency?: ConcurrencyPolicy;
|
|
63
65
|
active?: boolean;
|
|
66
|
+
/** Owning user (D38); the handler stamps resolveActiveOwnerUserId(). */
|
|
67
|
+
owner?: string;
|
|
64
68
|
}
|
|
65
69
|
/** Result of a `patch()` call (spec §C `WorkflowToolResult`). */
|
|
66
70
|
export type PatchOutcome = {
|
|
@@ -88,8 +92,15 @@ export declare class WorkflowController {
|
|
|
88
92
|
* trigger path — manual RPC, cron scheduler, IM, webhook — is captured alike. */
|
|
89
93
|
runHistory?: WorkflowRunHistoryStore | undefined);
|
|
90
94
|
create(input: CreateInput): Promise<WorkflowRecord>;
|
|
91
|
-
update(id: string, patch: Partial<Pick<WorkflowRecord, "name" | "def" | "concurrency" | "active">>): Promise<WorkflowRecord>;
|
|
95
|
+
update(id: string, patch: Partial<Pick<WorkflowRecord, "name" | "def" | "concurrency" | "active" | "permissionScope">>): Promise<WorkflowRecord>;
|
|
92
96
|
setActive(id: string, active: boolean): Promise<WorkflowRecord>;
|
|
97
|
+
/**
|
|
98
|
+
* Publish (D31): snapshot the current draft (`def`) as the version triggers execute.
|
|
99
|
+
* Editing the draft afterwards never touches the running version until the next publish.
|
|
100
|
+
*/
|
|
101
|
+
publish(id: string): Promise<WorkflowRecord>;
|
|
102
|
+
/** Archive (D31): keep the data, kill the lifecycle (deactivates; activation/publish refuse). */
|
|
103
|
+
archive(id: string, archived: boolean): Promise<WorkflowRecord>;
|
|
93
104
|
/**
|
|
94
105
|
* Revert a workflow to a stored revision snapshot (undo 基座). Git-revert semantics:
|
|
95
106
|
* the snapshot's def/name/trigger are re-applied as a NEW revision (rev bumps), so
|
|
@@ -104,7 +115,11 @@ export declare class WorkflowController {
|
|
|
104
115
|
*/
|
|
105
116
|
importN8n(id: string, json: string | unknown, opts?: {
|
|
106
117
|
concurrency?: ConcurrencyPolicy;
|
|
107
|
-
|
|
118
|
+
owner?: string;
|
|
119
|
+
}): Promise<{
|
|
120
|
+
record: WorkflowRecord;
|
|
121
|
+
report: import("./n8n-import.js").N8nConversionReport;
|
|
122
|
+
}>;
|
|
108
123
|
/** Load a single record or throw (fail-loud lookup; used by the scheduler to re-read triggers). */
|
|
109
124
|
get(id: string): Promise<WorkflowRecord>;
|
|
110
125
|
/**
|
|
@@ -125,7 +140,9 @@ export declare class WorkflowController {
|
|
|
125
140
|
*/
|
|
126
141
|
patch(id: string, env: PatchEnvelope): Promise<PatchOutcome>;
|
|
127
142
|
/** Trigger a run, honoring the workflow's concurrency policy (spec §B5). */
|
|
128
|
-
run(id: string, trigger?: TriggerEvent
|
|
143
|
+
run(id: string, trigger?: TriggerEvent, opts?: {
|
|
144
|
+
recordHistory?: boolean;
|
|
145
|
+
}): Promise<RunOutcome>;
|
|
129
146
|
/**
|
|
130
147
|
* Wrap a run with durable history recording (when wired). Lives at this choke point —
|
|
131
148
|
* not in the RPC handler — so cron/IM/webhook runs are recorded identically to manual
|
|
@@ -64,6 +64,21 @@ export type WorkflowPatch = {
|
|
|
64
64
|
name?: string;
|
|
65
65
|
active?: boolean;
|
|
66
66
|
};
|
|
67
|
+
} | {
|
|
68
|
+
op: "set_node_options";
|
|
69
|
+
nodeId: string;
|
|
70
|
+
retry?: {
|
|
71
|
+
maxTries: number;
|
|
72
|
+
backoffMs?: number;
|
|
73
|
+
} | null;
|
|
74
|
+
timeoutMs?: number | null;
|
|
75
|
+
onError?: "stop" | "continue" | "errorPort" | null;
|
|
76
|
+
} | {
|
|
77
|
+
op: "set_node_ui";
|
|
78
|
+
positions: Record<string, {
|
|
79
|
+
x: number;
|
|
80
|
+
y: number;
|
|
81
|
+
} | null>;
|
|
67
82
|
};
|
|
68
83
|
/** A patch + concurrency/scope envelope (spec §C). `scope` present ⇒ scoped (框选) edit. */
|
|
69
84
|
export interface PatchEnvelope {
|
|
@@ -46,6 +46,20 @@ export declare class WorkflowPauseSignal extends Error {
|
|
|
46
46
|
export declare class WorkflowRunCanceled extends Error {
|
|
47
47
|
constructor(message?: string);
|
|
48
48
|
}
|
|
49
|
+
/** Per-node execution stats for the run trace (D33, IT-9). */
|
|
50
|
+
export interface NodeRunStats {
|
|
51
|
+
/** Execution order (1-based; cache hits count too — they advance the dag). */
|
|
52
|
+
seq: number;
|
|
53
|
+
startedAt: string;
|
|
54
|
+
finishedAt: string;
|
|
55
|
+
durationMs: number;
|
|
56
|
+
/** Executor attempts actually made (≥1; >1 means retries happened). */
|
|
57
|
+
attempts: number;
|
|
58
|
+
/** True when the content-addressed cache satisfied this node (no executor invocation). */
|
|
59
|
+
cacheHit: boolean;
|
|
60
|
+
/** Final per-node disposition (errorPort/continue keep the run alive but mark failed). */
|
|
61
|
+
status: "completed" | "failed";
|
|
62
|
+
}
|
|
49
63
|
export interface RunResult {
|
|
50
64
|
status: "completed" | "failed" | "paused";
|
|
51
65
|
/** nodeId → its output PortMap. */
|
|
@@ -55,12 +69,16 @@ export interface RunResult {
|
|
|
55
69
|
progress: ReturnType<DagScheduler["getProgress"]>;
|
|
56
70
|
/** Set when status === "paused": the node the run suspended at. */
|
|
57
71
|
pausedAt?: string;
|
|
72
|
+
/** Ordered per-node trace (D33): timing, attempts, cache hits, disposition. */
|
|
73
|
+
nodeStats: Record<string, NodeRunStats>;
|
|
58
74
|
}
|
|
59
75
|
export interface WorkflowRuntimeOptions {
|
|
60
76
|
registry?: NodeRegistry;
|
|
61
77
|
gate?: ExecutionGate;
|
|
62
78
|
now?: () => Date;
|
|
63
79
|
trigger?: Record<string, unknown>;
|
|
80
|
+
/** This run's trigger KIND (manual/schedule/…) — exposed on ExecutorContext for IT-7b audits. */
|
|
81
|
+
triggerKind?: string;
|
|
64
82
|
log?: {
|
|
65
83
|
info(m: string): void;
|
|
66
84
|
warn(m: string): void;
|
|
@@ -80,6 +98,7 @@ export declare class WorkflowRuntime {
|
|
|
80
98
|
private readonly gate?;
|
|
81
99
|
private readonly now;
|
|
82
100
|
private readonly trigger?;
|
|
101
|
+
private readonly triggerKind?;
|
|
83
102
|
private readonly log?;
|
|
84
103
|
private readonly host?;
|
|
85
104
|
private readonly abortController?;
|
|
@@ -95,6 +114,9 @@ export declare class WorkflowRuntime {
|
|
|
95
114
|
private readonly loopBackOf;
|
|
96
115
|
/** Cooperative pause request; honored before the next node executes. */
|
|
97
116
|
private pauseRequested;
|
|
117
|
+
/** Per-node trace (D33): seq/timing/attempts/cacheHit/disposition, keyed by nodeId. */
|
|
118
|
+
private readonly nodeStats;
|
|
119
|
+
private statsSeq;
|
|
98
120
|
/** True once the durable checkpoint has been loaded into `cache` (load once per instance). */
|
|
99
121
|
private checkpointLoaded;
|
|
100
122
|
constructor(def: WorkflowDef, opts?: WorkflowRuntimeOptions);
|
|
@@ -136,5 +158,7 @@ export declare class WorkflowRuntime {
|
|
|
136
158
|
private isCanceled;
|
|
137
159
|
private canceledError;
|
|
138
160
|
private throwIfCanceled;
|
|
161
|
+
/** Cancel-aware sleep for retry backoff: aborts immediately when the run is canceled. */
|
|
162
|
+
private cancellableDelay;
|
|
139
163
|
private buildDag;
|
|
140
164
|
}
|
|
@@ -38,6 +38,18 @@ export interface SchedulerDeps {
|
|
|
38
38
|
onRunStarted?: (workflowId: string, triggerType: string) => void;
|
|
39
39
|
onRunCompleted?: (outcome: RunOutcome, triggerType: string) => void;
|
|
40
40
|
onRunFailed?: (workflowId: string, triggerType: string, err: unknown) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Trigger rate-limit (D32/§B5 防洪): minimum ms between UNATTENDED fires (webhook/IM/cron) of
|
|
43
|
+
* the same workflow. A fire inside the window is DROPPED — loudly, via onTriggerDropped (dead
|
|
44
|
+
* letter), never silently. Manual/intent (attended) fires are exempt. Default 0 (off) — the
|
|
45
|
+
* production assembly (ensureWorkflowRoot) opts in explicitly; rate policy is the host's call.
|
|
46
|
+
*/
|
|
47
|
+
minTriggerIntervalMs?: number;
|
|
48
|
+
/** Dead-letter surface for rate-limited drops. Required when minTriggerIntervalMs > 0. */
|
|
49
|
+
onTriggerDropped?: (workflowId: string, triggerType: string, detail: {
|
|
50
|
+
sinceLastMs: number;
|
|
51
|
+
minIntervalMs: number;
|
|
52
|
+
}) => void;
|
|
41
53
|
}
|
|
42
54
|
export declare class WorkflowScheduler {
|
|
43
55
|
private readonly controller;
|
|
@@ -50,6 +62,10 @@ export declare class WorkflowScheduler {
|
|
|
50
62
|
private readonly triggers;
|
|
51
63
|
/** Active cron arms keyed by workflow id. */
|
|
52
64
|
private readonly cronArms;
|
|
65
|
+
/** Last unattended fire per workflow (rate-limit window anchor, D32). */
|
|
66
|
+
private readonly lastFireAt;
|
|
67
|
+
private readonly minTriggerIntervalMs;
|
|
68
|
+
private readonly onTriggerDropped?;
|
|
53
69
|
private started;
|
|
54
70
|
constructor(deps: SchedulerDeps);
|
|
55
71
|
/** Load every active workflow and arm its trigger (spec §B4 restart re-registration). */
|
|
@@ -86,5 +102,10 @@ export declare class WorkflowScheduler {
|
|
|
86
102
|
private registerTrigger;
|
|
87
103
|
private armCron;
|
|
88
104
|
private fireCron;
|
|
105
|
+
/**
|
|
106
|
+
* Unattended-fire rate limit (D32): true ⇒ proceed (and anchor the window); false ⇒ DROPPED,
|
|
107
|
+
* surfaced via onTriggerDropped (dead letter) — never silent.
|
|
108
|
+
*/
|
|
109
|
+
private passesRateLimit;
|
|
89
110
|
private runWithLifecycle;
|
|
90
111
|
}
|
|
@@ -15,12 +15,29 @@ export type ConcurrencyPolicy = "queue" | "skip" | "parallel";
|
|
|
15
15
|
export interface WorkflowRecord {
|
|
16
16
|
id: string;
|
|
17
17
|
name: string;
|
|
18
|
+
/** Owning user (llmrouter id via resolveActiveOwnerUserId, D38). Absent on legacy records. */
|
|
19
|
+
owner?: string;
|
|
18
20
|
def: WorkflowDef;
|
|
19
21
|
/** Active workflows have their triggers registered by the gateway (spec §B4). */
|
|
20
22
|
active: boolean;
|
|
21
23
|
concurrency: ConcurrencyPolicy;
|
|
22
24
|
/** The workflow's trigger (set via a WorkflowPatch set_trigger op; spec §C/D7). */
|
|
23
25
|
trigger?: TriggerDef | null;
|
|
26
|
+
/**
|
|
27
|
+
* Unattended-run capability grant (spec D30, IT-7): sensitive node kinds this workflow may
|
|
28
|
+
* execute under unattended triggers (schedule/im/webhook). Granted explicitly by the owner
|
|
29
|
+
* at activation time; absent ⇒ unattended runs of sensitive kinds are denied by the gate.
|
|
30
|
+
*/
|
|
31
|
+
permissionScope?: {
|
|
32
|
+
allowUnattendedKinds: string[];
|
|
33
|
+
grantedAt: string;
|
|
34
|
+
} | null;
|
|
35
|
+
/** The published (run-by-triggers) snapshot. Absent ⇒ never published ⇒ cannot activate. */
|
|
36
|
+
publishedDef?: WorkflowDef | null;
|
|
37
|
+
/** The draft rev that was published (UI: draft-ahead badge when rev > publishedRev). */
|
|
38
|
+
publishedRev?: number;
|
|
39
|
+
/** Archived workflows keep their data but cannot run or activate (D31 lifecycle). */
|
|
40
|
+
archived?: boolean;
|
|
24
41
|
/** Monotonic revision for optimistic concurrency (LWW); bumped on every patch (spec §C). */
|
|
25
42
|
rev: number;
|
|
26
43
|
createdAt: string;
|