@pipefy/pipefy-process-coder 0.1.0 → 0.1.2
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/.agents/skills/ppc-pipefy-flow-authoring/SKILL.md +262 -262
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-danfe-consulta.json +247 -247
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-webhook-retorno-consulta.json +589 -589
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-4-recebimento-barramento.json +1391 -1391
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-2-danfe-retorno.json +623 -623
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-4-criacao-operacao.json +636 -636
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/03-subflow-4-criacao-titulo.json +3642 -3642
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-cedente.json +863 -863
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-sacado.json +799 -799
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/README.md +42 -42
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-acompanhamento-cobranca.json +581 -581
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-nfe-monitoramento.json +503 -503
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-retorno-bancario.json +562 -562
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-cedente.json +557 -557
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-sacado.json +609 -609
- package/.agents/skills/ppc-pipefy-pipe-authoring/SKILL.md +146 -146
- package/.agents/skills/ppc-pipefy-process-design/SKILL.md +127 -127
- package/.agents/skills/ppc-pipefy-workspace/SKILL.md +91 -91
- package/AGENTS.md +476 -456
- package/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +830 -808
- package/bin/pipe.js +25 -4
- package/dist/apply/adopt.js +128 -0
- package/dist/apply/agentops.js +42 -0
- package/dist/apply/compile.js +722 -0
- package/dist/apply/execute.js +277 -0
- package/dist/apply/flowops.js +293 -0
- package/dist/apply/idmap.js +233 -0
- package/dist/apply/mutations.js +704 -0
- package/dist/apply/registry.js +348 -0
- package/dist/apply/types.js +18 -0
- package/dist/cli/args.js +79 -0
- package/dist/cli.js +196 -0
- package/dist/codec/flow.js +164 -0
- package/dist/codec/pack.js +88 -0
- package/dist/codec/roundtrip.js +86 -0
- package/dist/codec/unpack.js +181 -0
- package/dist/commands/agents.js +145 -0
- package/dist/commands/apply.js +982 -0
- package/dist/commands/context.js +79 -0
- package/dist/commands/create.js +63 -0
- package/dist/commands/diff.js +294 -0
- package/dist/commands/flows.js +348 -0
- package/dist/commands/misc.js +562 -0
- package/dist/commands/plan.js +301 -0
- package/dist/commands/pull.js +549 -0
- package/dist/commands/runs.js +74 -0
- package/dist/commands/skills.js +131 -0
- package/dist/commands/verify.js +205 -0
- package/dist/config.js +125 -0
- package/dist/diff/agents.js +80 -0
- package/dist/diff/diff.js +983 -0
- package/dist/diff/flow.js +258 -0
- package/{src/diff/html.ts → dist/diff/html.js} +274 -322
- package/dist/diff/render.js +90 -0
- package/dist/model/payload.js +84 -0
- package/dist/model/tree.js +7 -0
- package/dist/model/volatile.js +54 -0
- package/dist/pipefy/agents.js +131 -0
- package/dist/pipefy/automations.js +155 -0
- package/dist/pipefy/capability.js +84 -0
- package/dist/pipefy/client.js +204 -0
- package/dist/pipefy/discovery.js +148 -0
- package/dist/pipefy/internal.js +406 -0
- package/dist/pipefy/ipaas.js +269 -0
- package/dist/pipefy/reconstruct.js +676 -0
- package/dist/pipefy/reference.js +172 -0
- package/dist/pipefy/snapshot.js +185 -0
- package/dist/pipefy/toolkit.js +174 -0
- package/{src → dist}/pipefy/toolkit_bearer.py +60 -14
- package/dist/report/integrations.js +165 -0
- package/dist/report/run.js +356 -0
- package/dist/util/fsx.js +50 -0
- package/dist/util/git.js +25 -0
- package/dist/util/json.js +56 -0
- package/dist/util/log.js +69 -0
- package/dist/util/pool.js +42 -0
- package/dist/util/slug.js +23 -0
- package/dist/util/tui.js +287 -0
- package/dist/validate/index.js +86 -0
- package/dist/validate/integrity.js +370 -0
- package/dist/validate/reference.js +120 -0
- package/dist/validate/schema.js +278 -0
- package/dist/version.js +18 -0
- package/dist/workspace/agents.js +245 -0
- package/{src/workspace/docs.ts → dist/workspace/docs.js} +367 -407
- package/dist/workspace/flows.js +149 -0
- package/dist/workspace/layout.js +146 -0
- package/dist/workspace/lock.js +83 -0
- package/dist/workspace/read.js +168 -0
- package/dist/workspace/reference.js +24 -0
- package/dist/workspace/stamp.js +234 -0
- package/dist/workspace/write.js +194 -0
- package/package.json +38 -35
- package/src/apply/adopt.ts +0 -150
- package/src/apply/agentops.ts +0 -71
- package/src/apply/compile.ts +0 -875
- package/src/apply/execute.ts +0 -336
- package/src/apply/flowops.ts +0 -399
- package/src/apply/idmap.ts +0 -241
- package/src/apply/mutations.ts +0 -955
- package/src/apply/registry.ts +0 -430
- package/src/apply/types.ts +0 -134
- package/src/cli/args.ts +0 -88
- package/src/cli.ts +0 -211
- package/src/codec/flow.ts +0 -199
- package/src/codec/pack.ts +0 -103
- package/src/codec/roundtrip.ts +0 -94
- package/src/codec/unpack.ts +0 -198
- package/src/commands/agents.ts +0 -184
- package/src/commands/apply.ts +0 -1144
- package/src/commands/context.ts +0 -119
- package/src/commands/create.ts +0 -79
- package/src/commands/diff.ts +0 -314
- package/src/commands/flows.ts +0 -414
- package/src/commands/misc.ts +0 -644
- package/src/commands/plan.ts +0 -331
- package/src/commands/pull.ts +0 -567
- package/src/commands/runs.ts +0 -83
- package/src/commands/skills.ts +0 -137
- package/src/commands/verify.ts +0 -253
- package/src/config.ts +0 -168
- package/src/diff/agents.ts +0 -122
- package/src/diff/diff.ts +0 -1130
- package/src/diff/flow.ts +0 -318
- package/src/diff/render.ts +0 -101
- package/src/model/payload.ts +0 -154
- package/src/model/tree.ts +0 -99
- package/src/model/volatile.ts +0 -55
- package/src/pipefy/agents.ts +0 -165
- package/src/pipefy/automations.ts +0 -219
- package/src/pipefy/capability.ts +0 -119
- package/src/pipefy/client.ts +0 -267
- package/src/pipefy/discovery.ts +0 -209
- package/src/pipefy/internal.ts +0 -380
- package/src/pipefy/ipaas.ts +0 -365
- package/src/pipefy/reconstruct.ts +0 -775
- package/src/pipefy/reference.ts +0 -251
- package/src/pipefy/snapshot.ts +0 -245
- package/src/pipefy/toolkit.ts +0 -200
- package/src/report/integrations.ts +0 -231
- package/src/report/run.ts +0 -475
- package/src/util/fsx.ts +0 -45
- package/src/util/git.ts +0 -32
- package/src/util/json.ts +0 -55
- package/src/util/log.ts +0 -76
- package/src/util/pool.ts +0 -48
- package/src/util/slug.ts +0 -26
- package/src/util/tui.ts +0 -335
- package/src/validate/index.ts +0 -123
- package/src/validate/integrity.ts +0 -387
- package/src/validate/reference.ts +0 -136
- package/src/validate/schema.ts +0 -328
- package/src/workspace/agents.ts +0 -290
- package/src/workspace/flows.ts +0 -191
- package/src/workspace/layout.ts +0 -165
- package/src/workspace/lock.ts +0 -148
- package/src/workspace/read.ts +0 -165
- package/src/workspace/reference.ts +0 -24
- package/src/workspace/stamp.ts +0 -301
- package/src/workspace/write.ts +0 -225
package/src/model/volatile.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PLAN.md 9: "Volatile fields will make every diff pure noise." These change
|
|
3
|
-
* with no structural edit, so the codec strips them on unpack and the
|
|
4
|
-
* comparator ignores them. Without this, `pipe diff` is unusable on day one.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export const VOLATILE_KEYS = new Set([
|
|
8
|
-
'created_at',
|
|
9
|
-
'updated_at',
|
|
10
|
-
'deleted_at',
|
|
11
|
-
'created_by_id',
|
|
12
|
-
'updated_by_id',
|
|
13
|
-
'run_count',
|
|
14
|
-
'cards_count',
|
|
15
|
-
'users_count',
|
|
16
|
-
'opened_cards_count',
|
|
17
|
-
'last_updated_by_card',
|
|
18
|
-
'cloning',
|
|
19
|
-
'sequential_id',
|
|
20
|
-
'clone_from_id',
|
|
21
|
-
'cloned_from_id',
|
|
22
|
-
'cloned_from_phase_uuid',
|
|
23
|
-
'cloned_from_field_uuid',
|
|
24
|
-
'migrated_from_repo_uuid',
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Server-owned identity and ownership columns. Kept in the files (Claude needs
|
|
29
|
-
* to read them for a field reference to mean anything) but never diffed as
|
|
30
|
-
* content, because they are consequences of structure rather than structure.
|
|
31
|
-
*/
|
|
32
|
-
export const SERVER_OWNED_KEYS = new Set([
|
|
33
|
-
'id',
|
|
34
|
-
'repo_id',
|
|
35
|
-
'organization_id',
|
|
36
|
-
'organization_uuid',
|
|
37
|
-
'suid',
|
|
38
|
-
'slug',
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
export const stripVolatile = <T extends Record<string, unknown>>(row: T): T => {
|
|
42
|
-
const out: Record<string, unknown> = {};
|
|
43
|
-
for (const [k, v] of Object.entries(row)) {
|
|
44
|
-
if (VOLATILE_KEYS.has(k)) continue;
|
|
45
|
-
out[k] = v;
|
|
46
|
-
}
|
|
47
|
-
return out as T;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/** The volatile values we drop, kept aside so the UI can still show them. */
|
|
51
|
-
export const takeVolatile = (row: Record<string, unknown>): Record<string, unknown> => {
|
|
52
|
-
const out: Record<string, unknown> = {};
|
|
53
|
-
for (const [k, v] of Object.entries(row)) if (VOLATILE_KEYS.has(k)) out[k] = v;
|
|
54
|
-
return out;
|
|
55
|
-
};
|
package/src/pipefy/agents.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { debug } from '../util/log.ts';
|
|
2
|
-
import type { Row } from '../model/payload.ts';
|
|
3
|
-
import type { PipefyClient } from './client.ts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* AI agents — not in the snapshot payload at all (ROUTE-B-V2.md ask 5), so read
|
|
7
|
-
* separately over the public GraphQL API, keyed by the pipe's **uuid**, not its
|
|
8
|
-
* numeric id.
|
|
9
|
-
*
|
|
10
|
-
* `AiAgent.behaviors` answers with full `Automation` rows — a behavior is not a
|
|
11
|
-
* new kind of thing, it is an automation with `action_id: "ai_behavior"`. But
|
|
12
|
-
* the *snapshot* payload's row for one is a stub: `behavior_uuid` plus a couple
|
|
13
|
-
* of field-id lists, no `instruction`, no `actionsAttributes` — the actual
|
|
14
|
-
* configuration only exists behind this query. So this is the one source for a
|
|
15
|
-
* behavior's real content; the snapshot's copy of it is never used.
|
|
16
|
-
*
|
|
17
|
-
* The query aliases every field to the snake_case name the rest of this tool
|
|
18
|
-
* already uses on disk (matching `automations/*.json`), so nothing downstream
|
|
19
|
-
* has to know these rows came from a different endpoint.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export type AgentRow = Row & {
|
|
23
|
-
uuid: string;
|
|
24
|
-
name: string;
|
|
25
|
-
/** Full automation-shaped rows — same content `workspace/agents.ts` files under `behaviors/`. */
|
|
26
|
-
behaviors: Row[];
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type AgentsRead = {
|
|
30
|
-
agents: AgentRow[];
|
|
31
|
-
/** `complete` means this pull read them; `unknown` means it could not. */
|
|
32
|
-
status: 'complete' | 'unknown';
|
|
33
|
-
note?: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type Edge = { node: Row & { behaviors?: Row[] | null } };
|
|
37
|
-
type Page = { edges: Edge[]; pageInfo: { hasNextPage: boolean; endCursor: string | null } };
|
|
38
|
-
|
|
39
|
-
const QUERY = `
|
|
40
|
-
query($repoUuid: ID!, $after: String) {
|
|
41
|
-
aiAgents(repoUuid: $repoUuid, first: 50, after: $after) {
|
|
42
|
-
edges {
|
|
43
|
-
node {
|
|
44
|
-
uuid
|
|
45
|
-
name
|
|
46
|
-
instruction
|
|
47
|
-
repoUuid
|
|
48
|
-
disabledAt
|
|
49
|
-
needReview
|
|
50
|
-
dataSourceIds
|
|
51
|
-
createdAt
|
|
52
|
-
updatedAt
|
|
53
|
-
behaviors {
|
|
54
|
-
id
|
|
55
|
-
uuid
|
|
56
|
-
name
|
|
57
|
-
active
|
|
58
|
-
event_id
|
|
59
|
-
action_id
|
|
60
|
-
event_params {
|
|
61
|
-
to_phase_id
|
|
62
|
-
from_phase_id: fromPhaseId
|
|
63
|
-
in_phase_id: inPhaseId
|
|
64
|
-
trigger_field_ids: triggerFieldIds
|
|
65
|
-
kind_of_sla: kindOfSla
|
|
66
|
-
trigger_automation_id: triggerAutomationId
|
|
67
|
-
}
|
|
68
|
-
condition {
|
|
69
|
-
id
|
|
70
|
-
expressions_structure
|
|
71
|
-
expressions { id field_address operation value structure_id }
|
|
72
|
-
}
|
|
73
|
-
action_params {
|
|
74
|
-
ai_behavior_params: aiBehaviorParams {
|
|
75
|
-
data_source_ids: dataSourceIds
|
|
76
|
-
instruction
|
|
77
|
-
referenced_field_ids: referencedFieldIds
|
|
78
|
-
provider_id: providerId
|
|
79
|
-
system_provider_id: systemProviderId
|
|
80
|
-
actions_attributes: actionsAttributes {
|
|
81
|
-
id
|
|
82
|
-
name
|
|
83
|
-
action_type: actionType
|
|
84
|
-
reference_id: referenceId
|
|
85
|
-
metadata {
|
|
86
|
-
destination_phase_id: destinationPhaseId
|
|
87
|
-
pipe_id: pipeId
|
|
88
|
-
table_id: tableId
|
|
89
|
-
email_template_id: emailTemplateId
|
|
90
|
-
allow_template_modifications: allowTemplateModifications
|
|
91
|
-
mcp_server_id: mcpServerId
|
|
92
|
-
tool_name: toolName
|
|
93
|
-
title
|
|
94
|
-
emails
|
|
95
|
-
tool_inputs: toolInputs { name source value field_id: fieldId }
|
|
96
|
-
fields_attributes: fieldsAttributes { field_id: fieldId input_mode: inputMode value }
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
capabilities_attributes: capabilitiesAttributes { capability_type: capabilityType enabled }
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
pageInfo { hasNextPage endCursor }
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
`;
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* `event_params` always comes back with every key this query asked for, most
|
|
112
|
-
* of them null for any given event — `card_created` genuinely accepts none of
|
|
113
|
-
* them. An ordinary automation's `event_params` only ever has the keys its
|
|
114
|
-
* own event actually uses, and `pipe validate` checks a present key against
|
|
115
|
-
* the event's catalogue regardless of its value, so a null one reads as one
|
|
116
|
-
* more thing this event does not accept. Dropped to match.
|
|
117
|
-
*/
|
|
118
|
-
const pruneNulls = (o: Row): Row => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== null && v !== undefined));
|
|
119
|
-
|
|
120
|
-
/** A behavior as the query returns it, given the shape `tree.automations` (AutomationNode) needs. */
|
|
121
|
-
const normaliseBehavior = (b: Row): Row => ({
|
|
122
|
-
...b,
|
|
123
|
-
event_params: pruneNulls((b['event_params'] as Row) ?? {}),
|
|
124
|
-
condition: b['condition'] ?? null,
|
|
125
|
-
field_maps: [],
|
|
126
|
-
distribute_assignments: [],
|
|
127
|
-
response_schemas: [],
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
export const fetchAgents = async (client: PipefyClient, repoUuid: string): Promise<AgentsRead> => {
|
|
131
|
-
const queryFields = await client.typeFields('Query');
|
|
132
|
-
if (!queryFields?.has('aiAgents')) {
|
|
133
|
-
debug('no aiAgents query on this schema');
|
|
134
|
-
return { agents: [], status: 'unknown', note: 'agents: no aiAgents query on this endpoint — AI agents cannot be read or checked' };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const agents: AgentRow[] = [];
|
|
138
|
-
let after: string | null = null;
|
|
139
|
-
for (let page = 0; page < 50; page++) {
|
|
140
|
-
const data: { aiAgents: Page | null } | null = await client.tryRaw(QUERY, { repoUuid, after }, 'ai agents');
|
|
141
|
-
const result: Page | null | undefined = data?.aiAgents;
|
|
142
|
-
if (!result) {
|
|
143
|
-
return {
|
|
144
|
-
agents: [],
|
|
145
|
-
status: 'unknown',
|
|
146
|
-
note: 'agents: aiAgents exists but did not answer — AI agent configuration is unverified',
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
for (const { node } of result.edges) {
|
|
151
|
-
const { behaviors, ...rest } = node;
|
|
152
|
-
agents.push({
|
|
153
|
-
...rest,
|
|
154
|
-
uuid: String(rest['uuid']),
|
|
155
|
-
name: String(rest['name'] ?? ''),
|
|
156
|
-
behaviors: (behaviors ?? []).map(normaliseBehavior),
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (!result.pageInfo.hasNextPage) break;
|
|
161
|
-
after = result.pageInfo.endCursor;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return { agents, status: 'complete' };
|
|
165
|
-
};
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { debug } from '../util/log.ts';
|
|
2
|
-
import type { PipefyClient } from './client.ts';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The automation event and action catalogue, read from the **public** API.
|
|
6
|
-
*
|
|
7
|
-
* Pipefy publishes exactly what each event and each action accepts —
|
|
8
|
-
* `automationEvents.acceptedParameters`, `automationActions.acceptedParameters` —
|
|
9
|
-
* and which combinations are refused. Without reading it, a wrong parameter name
|
|
10
|
-
* is only discovered by sending the mutation, and the answer is the single word
|
|
11
|
-
* `is invalid`, from both the public and the internal endpoint. Nothing names the
|
|
12
|
-
* parameter, the event, or the action.
|
|
13
|
-
*
|
|
14
|
-
* The case that motivated this: an automation meaning "when a card lands in
|
|
15
|
-
* New Request, move it to Archived" was written as
|
|
16
|
-
*
|
|
17
|
-
* event_id: 'card_moved', event_params: { inPhaseId: '<phaseId>' }
|
|
18
|
-
*
|
|
19
|
-
* `inPhaseId` is real — it belongs to `card_inbox_received_email`. `card_moved`
|
|
20
|
-
* accepts only `to_phase_id`. Both endpoints answered `is invalid` and the apply
|
|
21
|
-
* failed at step one with nothing to act on.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
export type AutomationEventSpec = {
|
|
25
|
-
id: string;
|
|
26
|
-
/** Parameter names this event accepts, in the API's own snake_case. */
|
|
27
|
-
acceptedParameters: string[];
|
|
28
|
-
/** Actions this event cannot be paired with. */
|
|
29
|
-
actionsBlacklist: string[];
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type AutomationActionSpec = {
|
|
33
|
-
id: string;
|
|
34
|
-
acceptedParameters: string[];
|
|
35
|
-
eventsBlacklist: string[];
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type AutomationCatalogue = {
|
|
39
|
-
events: Map<string, AutomationEventSpec>;
|
|
40
|
-
actions: Map<string, AutomationActionSpec>;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const EVENTS_QUERY = `query AutomationEvents {
|
|
44
|
-
automationEvents { id acceptedParameters actionsBlacklist }
|
|
45
|
-
}`;
|
|
46
|
-
|
|
47
|
-
const ACTIONS_QUERY = `query AutomationActions($repoId: ID!) {
|
|
48
|
-
automationActions(repoId: $repoId) { id acceptedParameters eventsBlacklist }
|
|
49
|
-
}`;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Read the catalogue for one pipe. Actions are per-repo (a pipe without AI
|
|
53
|
-
* enabled offers fewer), events are global.
|
|
54
|
-
*/
|
|
55
|
-
export const readAutomationCatalogue = async (
|
|
56
|
-
client: PipefyClient,
|
|
57
|
-
repoId: number | string,
|
|
58
|
-
): Promise<AutomationCatalogue> => {
|
|
59
|
-
const [ev, ac] = await Promise.all([
|
|
60
|
-
client.raw<{ automationEvents: AutomationEventSpec[] }>(EVENTS_QUERY),
|
|
61
|
-
client.raw<{ automationActions: AutomationActionSpec[] }>(ACTIONS_QUERY, { repoId: String(repoId) }),
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
const events = new Map<string, AutomationEventSpec>();
|
|
65
|
-
for (const e of ev?.automationEvents ?? []) {
|
|
66
|
-
events.set(e.id, {
|
|
67
|
-
id: e.id,
|
|
68
|
-
acceptedParameters: e.acceptedParameters ?? [],
|
|
69
|
-
actionsBlacklist: e.actionsBlacklist ?? [],
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
const actions = new Map<string, AutomationActionSpec>();
|
|
73
|
-
for (const a of ac?.automationActions ?? []) {
|
|
74
|
-
actions.set(a.id, {
|
|
75
|
-
id: a.id,
|
|
76
|
-
acceptedParameters: a.acceptedParameters ?? [],
|
|
77
|
-
eventsBlacklist: a.eventsBlacklist ?? [],
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
debug(`automation catalogue: ${events.size} events, ${actions.size} actions for repo ${repoId}`);
|
|
81
|
-
return { events, actions };
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Parameter names the payload spells differently from the API.
|
|
86
|
-
*
|
|
87
|
-
* The stored row and the mutation input disagree in both directions, so a check
|
|
88
|
-
* against `acceptedParameters` has to normalise first or it reports correct
|
|
89
|
-
* automations as broken.
|
|
90
|
-
*/
|
|
91
|
-
const PARAM_ALIASES: Record<string, string> = {
|
|
92
|
-
// event params
|
|
93
|
-
inPhaseId: 'in_phase_id',
|
|
94
|
-
fromPhaseId: 'from_phase_id',
|
|
95
|
-
toPhaseId: 'to_phase_id',
|
|
96
|
-
triggerFieldIds: 'trigger_field_ids',
|
|
97
|
-
triggerAutomationId: 'trigger_automation_id',
|
|
98
|
-
kindOfSla: 'kind_of_sla',
|
|
99
|
-
// action params
|
|
100
|
-
fieldsMapOrder: 'fields_map_order',
|
|
101
|
-
cardId: 'card_id',
|
|
102
|
-
toPhaseIdAction: 'to_phase_id',
|
|
103
|
-
taskParams: 'task_params',
|
|
104
|
-
aiParams: 'ai_params',
|
|
105
|
-
aiBehaviorParams: 'ai_behavior_params',
|
|
106
|
-
slaParams: 'sla_params',
|
|
107
|
-
httpMethod: 'http_method',
|
|
108
|
-
emailTemplateId: 'email_template_id',
|
|
109
|
-
authenticationKey: 'authentication_key',
|
|
110
|
-
authenticationValue: 'authentication_value',
|
|
111
|
-
authenticationAddTo: 'authentication_add_to',
|
|
112
|
-
authenticationType: 'authentication_type',
|
|
113
|
-
oauth2Uuid: 'oauth2_uuid',
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const canonical = (name: string) => PARAM_ALIASES[name] ?? name;
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Params the API accepts on every action regardless of the catalogue.
|
|
120
|
-
*
|
|
121
|
-
* `field_map` carries the values an `update_card_field` or `create_card` writes
|
|
122
|
-
* and is not listed in `acceptedParameters` — the catalogue lists
|
|
123
|
-
* `fields_map_order` beside it and stops. Flagging `field_map` would refuse every
|
|
124
|
-
* automation that writes a field, which is most of them.
|
|
125
|
-
*/
|
|
126
|
-
const ALWAYS_ALLOWED_ACTION_PARAMS = new Set(['field_map', 'schema']);
|
|
127
|
-
|
|
128
|
-
export type ParamFinding = {
|
|
129
|
-
severity: 'error' | 'warning';
|
|
130
|
-
where: 'event' | 'action' | 'pairing';
|
|
131
|
-
message: string;
|
|
132
|
-
fix?: string;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Check one automation against the catalogue, before anything is sent.
|
|
137
|
-
*
|
|
138
|
-
* Returns findings rather than throwing, so the compiler can refuse the step and
|
|
139
|
-
* name the parameter — which is the whole point. An unknown event or action is an
|
|
140
|
-
* error; a catalogue this pipe does not offer is silence, not a guess.
|
|
141
|
-
*/
|
|
142
|
-
export const checkAutomationParams = (
|
|
143
|
-
auto: Record<string, unknown>,
|
|
144
|
-
catalogue: AutomationCatalogue,
|
|
145
|
-
): ParamFinding[] => {
|
|
146
|
-
const findings: ParamFinding[] = [];
|
|
147
|
-
const eventId = String(auto['event_id'] ?? '');
|
|
148
|
-
const actionId = String(auto['action_id'] ?? '');
|
|
149
|
-
|
|
150
|
-
const event = catalogue.events.get(eventId);
|
|
151
|
-
const action = catalogue.actions.get(actionId);
|
|
152
|
-
|
|
153
|
-
if (eventId && !event) {
|
|
154
|
-
findings.push({
|
|
155
|
-
severity: 'error',
|
|
156
|
-
where: 'event',
|
|
157
|
-
message: `event_id ${JSON.stringify(eventId)} is not an event this endpoint offers`,
|
|
158
|
-
fix: `available: ${[...catalogue.events.keys()].join(', ')}`,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
if (actionId && !action) {
|
|
162
|
-
findings.push({
|
|
163
|
-
severity: 'error',
|
|
164
|
-
where: 'action',
|
|
165
|
-
message: `action_id ${JSON.stringify(actionId)} is not an action this pipe offers`,
|
|
166
|
-
fix: `available: ${[...catalogue.actions.keys()].join(', ')}`,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (event && action) {
|
|
171
|
-
if (event.actionsBlacklist.includes(actionId)) {
|
|
172
|
-
findings.push({
|
|
173
|
-
severity: 'error',
|
|
174
|
-
where: 'pairing',
|
|
175
|
-
message: `event ${eventId} cannot be paired with action ${actionId}`,
|
|
176
|
-
});
|
|
177
|
-
} else if (action.eventsBlacklist.includes(eventId)) {
|
|
178
|
-
findings.push({
|
|
179
|
-
severity: 'error',
|
|
180
|
-
where: 'pairing',
|
|
181
|
-
message: `action ${actionId} cannot be triggered by event ${eventId}`,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (event) {
|
|
187
|
-
const accepted = new Set(event.acceptedParameters.map(canonical));
|
|
188
|
-
for (const key of Object.keys((auto['event_params'] ?? {}) as Record<string, unknown>)) {
|
|
189
|
-
const name = canonical(key);
|
|
190
|
-
if (accepted.has(name)) continue;
|
|
191
|
-
findings.push({
|
|
192
|
-
severity: 'error',
|
|
193
|
-
where: 'event',
|
|
194
|
-
message: `event ${eventId} does not accept event_params.${key}`,
|
|
195
|
-
fix: accepted.size
|
|
196
|
-
? `it accepts: ${[...accepted].join(', ')}`
|
|
197
|
-
: `it accepts no parameters — remove event_params.${key}`,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (action) {
|
|
203
|
-
const accepted = new Set(action.acceptedParameters.map(canonical));
|
|
204
|
-
for (const key of Object.keys((auto['action_params'] ?? {}) as Record<string, unknown>)) {
|
|
205
|
-
const name = canonical(key);
|
|
206
|
-
if (accepted.has(name) || ALWAYS_ALLOWED_ACTION_PARAMS.has(name)) continue;
|
|
207
|
-
findings.push({
|
|
208
|
-
severity: 'error',
|
|
209
|
-
where: 'action',
|
|
210
|
-
message: `action ${actionId} does not accept action_params.${key}`,
|
|
211
|
-
fix: accepted.size
|
|
212
|
-
? `it accepts: ${[...accepted].join(', ')}`
|
|
213
|
-
: `it accepts no parameters — remove action_params.${key}`,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return findings;
|
|
219
|
-
};
|
package/src/pipefy/capability.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { debug } from '../util/log.ts';
|
|
2
|
-
import type { PipefyClient } from './client.ts';
|
|
3
|
-
import type { InternalApi } from './internal.ts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Capability detection. PLAN.md §1.2: `restoreRepoSnapshot` and
|
|
7
|
-
* `createRepoDraft` carry the entire safety model but do not exist on every
|
|
8
|
-
* endpoint, so the app detects them at startup and degrades explicitly rather
|
|
9
|
-
* than failing mid-apply.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export type Capabilities = {
|
|
13
|
-
/** Read paths. */
|
|
14
|
-
snapshots: boolean;
|
|
15
|
-
/** Safety primitives. */
|
|
16
|
-
restoreRepoSnapshot: boolean;
|
|
17
|
-
/** Which spelling this endpoint has, so the caller does not guess. */
|
|
18
|
-
restoreMutation: 'restoreRepoToSnapshot' | 'restoreRepoSnapshot' | null;
|
|
19
|
-
/** Whether a restore is limited to the newest snapshot. Measured, not assumed. */
|
|
20
|
-
restoreLatestOnly: boolean;
|
|
21
|
-
createRepoDraft: boolean;
|
|
22
|
-
/** Route B, tracked so `pipe doctor` reports the day it lands. */
|
|
23
|
-
importRepoSnapshot: boolean;
|
|
24
|
-
/** Public write paths this build depends on. */
|
|
25
|
-
mutations: Record<string, boolean>;
|
|
26
|
-
/** The internal endpoint — phase jumps and automation details. */
|
|
27
|
-
internalApi: 'ok' | 'unavailable' | 'unknown';
|
|
28
|
-
checkedAt: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/** Mutations the plan compiler emits, checked as a set so gaps are visible up front. */
|
|
32
|
-
export const REQUIRED_MUTATIONS = [
|
|
33
|
-
'updatePipe',
|
|
34
|
-
'createPhase',
|
|
35
|
-
'updatePhase',
|
|
36
|
-
'deletePhase',
|
|
37
|
-
'createPhaseField',
|
|
38
|
-
'updatePhaseField',
|
|
39
|
-
'deletePhaseField',
|
|
40
|
-
'createLabel',
|
|
41
|
-
'updateLabel',
|
|
42
|
-
'deleteLabel',
|
|
43
|
-
'createWebhook',
|
|
44
|
-
'updateWebhook',
|
|
45
|
-
'deleteWebhook',
|
|
46
|
-
'createFieldCondition',
|
|
47
|
-
'updateFieldCondition',
|
|
48
|
-
'deleteFieldCondition',
|
|
49
|
-
'setFieldConditionOrder',
|
|
50
|
-
'createAutomation',
|
|
51
|
-
'updateAutomation',
|
|
52
|
-
'deleteAutomation',
|
|
53
|
-
'createPipeRelation',
|
|
54
|
-
'updatePipeRelation',
|
|
55
|
-
'deletePipeRelation',
|
|
56
|
-
'phaseSettings',
|
|
57
|
-
'updateRepoPreferences',
|
|
58
|
-
'archiveField',
|
|
59
|
-
'unarchiveField',
|
|
60
|
-
'moveCardToPhase',
|
|
61
|
-
] as const;
|
|
62
|
-
|
|
63
|
-
export const detect = async (client: PipefyClient, internal?: InternalApi): Promise<Capabilities> => {
|
|
64
|
-
const mutationFields = await client.typeFields('Mutation');
|
|
65
|
-
const pipeFields = await client.typeFields('Pipe');
|
|
66
|
-
const has = (n: string) => Boolean(mutationFields?.has(n));
|
|
67
|
-
|
|
68
|
-
const mutations: Record<string, boolean> = {};
|
|
69
|
-
for (const m of REQUIRED_MUTATIONS) mutations[m] = has(m);
|
|
70
|
-
|
|
71
|
-
const caps: Capabilities = {
|
|
72
|
-
snapshots: has('createRepoSnapshot') && Boolean(pipeFields?.has('snapshots') || pipeFields?.has('snapshot')),
|
|
73
|
-
/**
|
|
74
|
-
* Both spellings, because this build was looking for the wrong one.
|
|
75
|
-
*
|
|
76
|
-
* `restoreRepoSnapshot` does not exist on this endpoint and never did.
|
|
77
|
-
* **`restoreRepoToSnapshot`** does — "Restores a Pipe structure to its latest
|
|
78
|
-
* uploaded snapshot" — so every apply has printed "no one-command rollback"
|
|
79
|
-
* beside a snapshot that could in fact be restored, and `pipe rollback` has
|
|
80
|
-
* refused to try. That is the same failure as automations sitting on the
|
|
81
|
-
* internal endpoint: a name assumed once and never re-checked.
|
|
82
|
-
*
|
|
83
|
-
* The capability is still narrower than it sounds, and `restoreLatestOnly`
|
|
84
|
-
* below is what says so.
|
|
85
|
-
*/
|
|
86
|
-
restoreRepoSnapshot: has('restoreRepoSnapshot') || has('restoreRepoToSnapshot'),
|
|
87
|
-
restoreMutation: has('restoreRepoToSnapshot')
|
|
88
|
-
? 'restoreRepoToSnapshot'
|
|
89
|
-
: has('restoreRepoSnapshot')
|
|
90
|
-
? 'restoreRepoSnapshot'
|
|
91
|
-
: null,
|
|
92
|
-
/**
|
|
93
|
-
* A restore only accepts the **latest** uploaded snapshot. Measured: an
|
|
94
|
-
* older versionId is refused with "Version_id must reference the latest
|
|
95
|
-
* uploaded snapshot of the Pipe".
|
|
96
|
-
*
|
|
97
|
-
* That is the whole story for rollback on this endpoint, and it is why the
|
|
98
|
-
* pre-apply snapshot is usually not restorable: `pipe apply` reads the repo
|
|
99
|
-
* back to verify, and that read takes a snapshot of its own, which becomes
|
|
100
|
-
* the latest. Recovery stays forward-first.
|
|
101
|
-
*/
|
|
102
|
-
restoreLatestOnly: has('restoreRepoToSnapshot'),
|
|
103
|
-
createRepoDraft: has('createRepoDraft'),
|
|
104
|
-
importRepoSnapshot: has('importRepoSnapshot') || has('createRepoSnapshotFromPayload'),
|
|
105
|
-
mutations,
|
|
106
|
-
internalApi: internal?.status ?? 'unknown',
|
|
107
|
-
checkedAt: new Date().toISOString(),
|
|
108
|
-
};
|
|
109
|
-
debug(
|
|
110
|
-
`capabilities: snapshots=${caps.snapshots} restore=${caps.restoreRepoSnapshot} ` +
|
|
111
|
-
`draft=${caps.createRepoDraft} import=${caps.importRepoSnapshot}`,
|
|
112
|
-
);
|
|
113
|
-
return caps;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
export const missingMutations = (c: Capabilities): string[] =>
|
|
117
|
-
Object.entries(c.mutations)
|
|
118
|
-
.filter(([, present]) => !present)
|
|
119
|
-
.map(([name]) => name);
|