qlogicagent 2.12.11 → 2.12.13
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 +359 -341
- package/dist/index.js +358 -340
- 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/skills-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/cli/stdio-acp-request-host.d.ts +26 -1
- package/dist/types/cli/stdio-server.d.ts +8 -4
- 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 +65 -0
- package/dist/types/orchestration/workflow/qla-executor-host.d.ts +20 -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 +9 -0
- package/dist/types/orchestration/workflow/workflow-runtime.d.ts +21 -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 +9 -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,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability Catalog (spec D24) — THE single source of truth for "what can a workflow node
|
|
3
|
+
* actually reference". One catalog feeds five consumers: the AI graph builder's prompt +
|
|
4
|
+
* list_capabilities tool, the canvas "+" suggestions, the advanced node library, the node
|
|
5
|
+
* config dropdowns, and Hub bundle `requires` verification.
|
|
6
|
+
*
|
|
7
|
+
* Channels are deliberately ABSENT here: the engine is channel-opaque (delivery lives in the
|
|
8
|
+
* gateway), so the gateway merges its own channel registry into `workflow.capabilities` /
|
|
9
|
+
* `x/workflow.chat` responses. Everything else enumerates REAL runtime state — no static lists.
|
|
10
|
+
*/
|
|
11
|
+
import type { ToolDefinition } from "../../protocol/wire/chat-types.js";
|
|
12
|
+
export type CapabilityCategory = "agent" | "tool" | "skill" | "mcp" | "memory" | "channel" | "control";
|
|
13
|
+
export interface CapabilityEntry {
|
|
14
|
+
category: CapabilityCategory;
|
|
15
|
+
/** Workflow node kind that uses this capability. */
|
|
16
|
+
kind: string;
|
|
17
|
+
/** Concrete reference: tool name / agent id / "server.tool" / memory op / channel name. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Human-readable name for pickers and the AI prompt. */
|
|
20
|
+
label: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
/** JSON schema of the underlying callable's input (tools/mcp), when known. */
|
|
23
|
+
paramsSchema?: Record<string, unknown>;
|
|
24
|
+
/** Prefill for add_node params when the user picks this entry. */
|
|
25
|
+
paramsTemplate?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export interface CapabilityCatalogDeps {
|
|
28
|
+
getToolManifest(): ToolDefinition[];
|
|
29
|
+
/** Available agent ids a workflow `agent` node can target (self first). */
|
|
30
|
+
listAgentIds(): {
|
|
31
|
+
id: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
}[];
|
|
34
|
+
/** Project root for project-scoped skill resolution. */
|
|
35
|
+
projectRoot: string;
|
|
36
|
+
}
|
|
37
|
+
/** Enumerate every capability a workflow in `projectRoot` can reference right now. */
|
|
38
|
+
export declare function buildCapabilityCatalog(deps: CapabilityCatalogDeps): CapabilityEntry[];
|
|
39
|
+
/**
|
|
40
|
+
* Compact catalog text for the AI graph builder's system prompt: category headers + `id — desc`
|
|
41
|
+
* lines, hard-capped per category so huge tool sets don't blow the prompt (the agent uses the
|
|
42
|
+
* list_capabilities tool for the long tail).
|
|
43
|
+
*/
|
|
44
|
+
export declare function renderCatalogForPrompt(entries: CapabilityEntry[], perCategoryCap?: number): string;
|
|
45
|
+
/** Refs the validator extracts from a def to check against the catalog (anti-hallucination). */
|
|
46
|
+
export declare function collectCapabilityRefs(def: {
|
|
47
|
+
nodes: {
|
|
48
|
+
id: string;
|
|
49
|
+
kind: string;
|
|
50
|
+
params?: Record<string, unknown>;
|
|
51
|
+
}[];
|
|
52
|
+
}): {
|
|
53
|
+
category: CapabilityCategory;
|
|
54
|
+
ref: string;
|
|
55
|
+
nodeId: string;
|
|
56
|
+
}[];
|
|
57
|
+
/** Validate def refs against the catalog. Returns human-readable errors (empty = clean). */
|
|
58
|
+
export declare function validateCapabilityRefs(def: {
|
|
59
|
+
nodes: {
|
|
60
|
+
id: string;
|
|
61
|
+
kind: string;
|
|
62
|
+
params?: Record<string, unknown>;
|
|
63
|
+
}[];
|
|
64
|
+
}, entries: CapabilityEntry[]): string[];
|
|
@@ -37,6 +37,14 @@ export interface ExpressionContext {
|
|
|
37
37
|
* Returns the raw value (object/array/number/string/boolean/null/undefined).
|
|
38
38
|
*/
|
|
39
39
|
export declare function evaluateExpression(body: string, ctx: ExpressionContext): unknown;
|
|
40
|
+
/**
|
|
41
|
+
* Static syntax+semantics gate: is `body` a valid expression of THIS language (no evaluation)?
|
|
42
|
+
* Beyond the grammar, it statically enforces what evalNode would reject at run time anyway:
|
|
43
|
+
* known context roots, whitelist function names, and "$node(...) is the only callable".
|
|
44
|
+
* Returns null when valid, else the error message. The n8n expression converter uses this as
|
|
45
|
+
* its single-track output gate — anything it emits must pass here.
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateExpressionSyntax(body: string): string | null;
|
|
40
48
|
/**
|
|
41
49
|
* Resolve a binding template:
|
|
42
50
|
* - exactly `{{ expr }}` (whole string) → returns the raw evaluated value (keeps type)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* to the host. fail-loud — invoking a host-backed kind with no host throws (no silent fallback).
|
|
10
10
|
*/
|
|
11
11
|
import type { NodeExecutor } from "./node-schema.js";
|
|
12
|
-
/** The host-backed kinds. Register these into a NodeRegistry to enable agent/tool/http/mcp/channel. */
|
|
12
|
+
/** The host-backed kinds. Register these into a NodeRegistry to enable agent/tool/http/mcp/channel/skill/memory/subworkflow/approval. */
|
|
13
13
|
export declare const hostExecutors: Record<string, NodeExecutor>;
|
|
14
14
|
/** Host-backed kind names (used by callers to know which kinds need a host). */
|
|
15
15
|
export declare const HOST_BACKED_KINDS: string[];
|
|
@@ -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,20 @@ 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";
|
|
28
42
|
}
|
|
29
43
|
/** A data edge (carries DataItem[]). fromPort/toPort default "default". */
|
|
30
44
|
export interface DataEdge {
|
|
@@ -44,6 +58,8 @@ export interface WorkflowDef {
|
|
|
44
58
|
export interface ExecutorContext {
|
|
45
59
|
nodeId: string;
|
|
46
60
|
kind: string;
|
|
61
|
+
/** Owning workflow id (def.id) — lets host-backed nodes (approval) tag outbound notifications. */
|
|
62
|
+
workflowId?: string;
|
|
47
63
|
/** 0-based; increments per loop iteration (drives the cache key, spec §A5). */
|
|
48
64
|
runIndex: number;
|
|
49
65
|
vars: Record<string, unknown>;
|
|
@@ -76,6 +92,20 @@ export interface ExecutorHost {
|
|
|
76
92
|
invokeMcp(req: McpHostRequest): Promise<DataItem[]>;
|
|
77
93
|
/** Send a message out through a channel (飞书/微信/…) via the openclaw channel layer. */
|
|
78
94
|
sendChannel(req: ChannelHostRequest): Promise<DataItem[]>;
|
|
95
|
+
/** Project-scoped memory (memdir) read/write/search for the `memory` kind (spec D25). */
|
|
96
|
+
memory?(req: MemoryHostRequest): Promise<DataItem[]>;
|
|
97
|
+
/** Run another workflow as a step (D34); the host enforces cycle guards. */
|
|
98
|
+
runSubworkflow?(req: SubworkflowHostRequest): Promise<DataItem[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Human-in-the-loop approval (D34): notify the owner and AWAIT the decision in-memory
|
|
101
|
+
* (the run stays "running"; the node shows pending). A restart loses pending approvals —
|
|
102
|
+
* the run then fails loud (durable suspended-run resumption is a tracked follow-up).
|
|
103
|
+
*/
|
|
104
|
+
approval?(req: ApprovalHostRequest): Promise<{
|
|
105
|
+
approved: boolean;
|
|
106
|
+
decidedAt: string;
|
|
107
|
+
note?: string;
|
|
108
|
+
}>;
|
|
79
109
|
}
|
|
80
110
|
export interface AgentHostRequest {
|
|
81
111
|
agentId?: string;
|
|
@@ -94,6 +124,8 @@ export interface HttpHostRequest {
|
|
|
94
124
|
url: string;
|
|
95
125
|
headers?: Record<string, string>;
|
|
96
126
|
body?: unknown;
|
|
127
|
+
/** Vault credential to inject as auth headers at execution time (D29; resolved host-side). */
|
|
128
|
+
credentialId?: string;
|
|
97
129
|
signal?: AbortSignal;
|
|
98
130
|
}
|
|
99
131
|
export interface McpHostRequest {
|
|
@@ -109,6 +141,39 @@ export interface ChannelHostRequest {
|
|
|
109
141
|
input: DataItem[];
|
|
110
142
|
signal?: AbortSignal;
|
|
111
143
|
}
|
|
144
|
+
export interface SubworkflowHostRequest {
|
|
145
|
+
workflowId: string;
|
|
146
|
+
/** The calling workflow's id — joins the cycle-guard chain so top-level runs count too. */
|
|
147
|
+
callerWorkflowId?: string;
|
|
148
|
+
/** Cross-project call target; absent = the calling workflow's own project root. */
|
|
149
|
+
projectId?: string;
|
|
150
|
+
/** Becomes the sub-run's $trigger payload (falls back to the upstream item's json). */
|
|
151
|
+
payload?: Record<string, unknown>;
|
|
152
|
+
input: DataItem[];
|
|
153
|
+
signal?: AbortSignal;
|
|
154
|
+
}
|
|
155
|
+
export interface ApprovalHostRequest {
|
|
156
|
+
nodeId: string;
|
|
157
|
+
/** Owning workflow id so the approval surface can name/locate the workflow. */
|
|
158
|
+
workflowId?: string;
|
|
159
|
+
/** Plain-language question shown to the owner (默认 "继续执行?"). */
|
|
160
|
+
prompt?: string;
|
|
161
|
+
/** Auto-deny deadline; default 24h. */
|
|
162
|
+
timeoutMs?: number;
|
|
163
|
+
input: DataItem[];
|
|
164
|
+
signal?: AbortSignal;
|
|
165
|
+
}
|
|
166
|
+
export interface MemoryHostRequest {
|
|
167
|
+
op: "read" | "write" | "search";
|
|
168
|
+
/** Topic-file name; absent → operate on the always-visible index. */
|
|
169
|
+
key?: string;
|
|
170
|
+
/** Content for `write` (falls back to the upstream item's json when omitted). */
|
|
171
|
+
value?: unknown;
|
|
172
|
+
/** Query for `search`. */
|
|
173
|
+
query?: string;
|
|
174
|
+
input: DataItem[];
|
|
175
|
+
signal?: AbortSignal;
|
|
176
|
+
}
|
|
112
177
|
export interface NodeExecResult {
|
|
113
178
|
/** outPort → DataItem[]. */
|
|
114
179
|
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, MemoryHostRequest, SubworkflowHostRequest, ApprovalHostRequest } 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,24 @@ 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
|
+
/**
|
|
54
|
+
* Vault credential → auth headers, resolved at EXECUTION time only (D29). Absent ⇒ http
|
|
55
|
+
* nodes carrying credentialId fail-loud (never silently unauthenticated).
|
|
56
|
+
*/
|
|
57
|
+
resolveCredentialHeaders?: (credentialId: string) => Record<string, string>;
|
|
39
58
|
/** fetch impl; defaults to global fetch. Injectable for tests. */
|
|
40
59
|
fetchImpl?: typeof fetch;
|
|
41
60
|
}
|
|
@@ -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,15 @@ 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;
|
|
67
76
|
};
|
|
68
77
|
/** A patch + concurrency/scope envelope (spec §C). `scope` present ⇒ scoped (框选) edit. */
|
|
69
78
|
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,6 +69,8 @@ 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;
|
|
@@ -95,6 +111,9 @@ export declare class WorkflowRuntime {
|
|
|
95
111
|
private readonly loopBackOf;
|
|
96
112
|
/** Cooperative pause request; honored before the next node executes. */
|
|
97
113
|
private pauseRequested;
|
|
114
|
+
/** Per-node trace (D33): seq/timing/attempts/cacheHit/disposition, keyed by nodeId. */
|
|
115
|
+
private readonly nodeStats;
|
|
116
|
+
private statsSeq;
|
|
98
117
|
/** True once the durable checkpoint has been loaded into `cache` (load once per instance). */
|
|
99
118
|
private checkpointLoaded;
|
|
100
119
|
constructor(def: WorkflowDef, opts?: WorkflowRuntimeOptions);
|
|
@@ -136,5 +155,7 @@ export declare class WorkflowRuntime {
|
|
|
136
155
|
private isCanceled;
|
|
137
156
|
private canceledError;
|
|
138
157
|
private throwIfCanceled;
|
|
158
|
+
/** Cancel-aware sleep for retry backoff: aborts immediately when the run is canceled. */
|
|
159
|
+
private cancellableDelay;
|
|
139
160
|
private buildDag;
|
|
140
161
|
}
|
|
@@ -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
|
}
|