@pipefy/pipefy-process-coder 0.1.1 → 0.1.3
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.md +33 -2
- package/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +6 -2
- package/dist/apply/adopt.js +52 -0
- package/dist/cli.js +14 -5
- package/dist/codec/pack.js +2 -0
- package/dist/codec/unpack.js +1 -0
- package/dist/commands/apply.js +25 -6
- package/dist/commands/context.js +40 -2
- package/dist/commands/diff.js +22 -20
- package/dist/commands/misc.js +55 -34
- package/dist/commands/plan.js +3 -1
- package/dist/commands/pull.js +54 -6
- package/dist/commands/verify.js +11 -32
- package/dist/diff/diff.js +28 -4
- package/dist/pipefy/agents.js +36 -27
- package/dist/pipefy/internal.js +201 -92
- package/dist/pipefy/ipaas.js +4 -3
- package/dist/pipefy/reconstruct.js +147 -39
- package/dist/pipefy/toolkit.js +27 -11
- package/dist/pipefy/toolkit_bearer.py +60 -14
- package/dist/report/run.js +2 -1
- package/dist/util/log.js +14 -4
- package/dist/util/tui.js +43 -2
- package/dist/version.js +18 -0
- package/dist/workspace/docs.js +1 -1
- package/dist/workspace/read.js +1 -0
- package/package.json +4 -3
package/dist/commands/misc.js
CHANGED
|
@@ -13,7 +13,7 @@ import { RECONSTRUCTION_GAPS } from "../pipefy/reconstruct.js";
|
|
|
13
13
|
import { readTree } from "../workspace/read.js";
|
|
14
14
|
import { registryRows } from "../apply/registry.js";
|
|
15
15
|
import { validateTree, renderFindings, blockingErrors } from "../validate/index.js";
|
|
16
|
-
import { connect, openWorkspace } from "./context.js";
|
|
16
|
+
import { connect, openWorkspace, readLive } from "./context.js";
|
|
17
17
|
// ── login ────────────────────────────────────────────────────────────────────
|
|
18
18
|
export const loginHelp = `pipe login [options]
|
|
19
19
|
|
|
@@ -36,11 +36,14 @@ export const login = async (p) => {
|
|
|
36
36
|
const token = str(p, 'token');
|
|
37
37
|
// ── the intended path: delegate to the toolkit's OAuth flow ───────────────
|
|
38
38
|
if (!token) {
|
|
39
|
-
const { inspectToolkit, runToolkitLogin, INSTALL_HINT } = await import("../pipefy/toolkit.js");
|
|
39
|
+
const { inspectToolkit, runToolkitLogin, INSTALL_HINT, UPGRADE_HINT } = await import("../pipefy/toolkit.js");
|
|
40
40
|
const toolkit = await inspectToolkit();
|
|
41
41
|
if (!toolkit.installed) {
|
|
42
42
|
throw new UserError('the Pipefy AI toolkit is not installed, so there is no browser login to run', `${INSTALL_HINT} — or use \`pipe login --token <token>\` for a personal API token`);
|
|
43
43
|
}
|
|
44
|
+
if (!toolkit.usable) {
|
|
45
|
+
throw new UserError(`the Pipefy AI toolkit is installed but this tool cannot use it: ${toolkit.error}`, `${UPGRADE_HINT} — or use \`pipe login --token <token>\` for a personal API token`);
|
|
46
|
+
}
|
|
44
47
|
step('running `pipefy auth login` (browser sign-in)');
|
|
45
48
|
const code = await runToolkitLogin(bool(p, 'device') ? ['--device'] : []);
|
|
46
49
|
if (code !== 0) {
|
|
@@ -91,7 +94,7 @@ export const doctorHelp = `pipe doctor [--repo <pipeId>]
|
|
|
91
94
|
With --repo it also probes that pipe's read paths.`;
|
|
92
95
|
export const doctor = async (p) => {
|
|
93
96
|
const { resolveCredential } = await import("../config.js");
|
|
94
|
-
const { inspectToolkit, toolkitAuthStatus, INSTALL_HINT } = await import("../pipefy/toolkit.js");
|
|
97
|
+
const { inspectToolkit, toolkitAuthStatus, INSTALL_HINT, UPGRADE_HINT } = await import("../pipefy/toolkit.js");
|
|
95
98
|
out(bold('endpoints'));
|
|
96
99
|
out(` graphql ${ENDPOINTS.graphql}`);
|
|
97
100
|
out(` internal ${ENDPOINTS.internal}`);
|
|
@@ -99,9 +102,18 @@ export const doctor = async (p) => {
|
|
|
99
102
|
out(bold('credential'));
|
|
100
103
|
const toolkit = await inspectToolkit();
|
|
101
104
|
const credential = await resolveCredential();
|
|
102
|
-
|
|
103
|
-
(
|
|
104
|
-
|
|
105
|
+
if (!toolkit.installed) {
|
|
106
|
+
out(` toolkit ${red('not installed')}${dim(` ${INSTALL_HINT}`)}`);
|
|
107
|
+
}
|
|
108
|
+
else if (!toolkit.usable) {
|
|
109
|
+
out(` toolkit ${yellow('installed, unusable')}${dim(` ${toolkit.cliPath ?? toolkit.interpreter}`)}`);
|
|
110
|
+
out(` ${dim(toolkit.error)}`);
|
|
111
|
+
out(` ${dim(UPGRADE_HINT)}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
out(` toolkit ${green('installed')}${dim(` ${toolkit.cliPath ?? toolkit.interpreter}`)}`);
|
|
115
|
+
}
|
|
116
|
+
if (toolkit.installed && toolkit.usable) {
|
|
105
117
|
const status = await toolkitAuthStatus();
|
|
106
118
|
if (status) {
|
|
107
119
|
const identity = status['identity'];
|
|
@@ -118,7 +130,11 @@ export const doctor = async (p) => {
|
|
|
118
130
|
dim(credential?.detail ? ` ${credential.detail}` : ''));
|
|
119
131
|
if (!credential) {
|
|
120
132
|
info('');
|
|
121
|
-
throw new UserError('no credential', toolkit.installed
|
|
133
|
+
throw new UserError('no credential', !toolkit.installed
|
|
134
|
+
? `${INSTALL_HINT}, or \`pipe login --token <token>\``
|
|
135
|
+
: !toolkit.usable
|
|
136
|
+
? `${UPGRADE_HINT}, or \`pipe login --token <token>\``
|
|
137
|
+
: 'run `pipe login` for the browser flow');
|
|
122
138
|
}
|
|
123
139
|
const net = await connect(p);
|
|
124
140
|
const caps = await net.capabilities();
|
|
@@ -151,14 +167,35 @@ export const doctor = async (p) => {
|
|
|
151
167
|
if (repoId) {
|
|
152
168
|
out('');
|
|
153
169
|
out(bold(`probing repo ${repoId}`));
|
|
154
|
-
const
|
|
170
|
+
const repo = await probeRepo(net, repoId);
|
|
171
|
+
const jumps = await net.internal.getPhaseSettings(repo.firstPhaseId);
|
|
155
172
|
out(` internal settings endpoint ${jumps ? green('answers') : red('no answer')}`);
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
// The repo's own organization, as a pull uses — not the saved default.
|
|
174
|
+
const orgId = repo.organizationId ?? net.organizationId;
|
|
175
|
+
const autos = orgId ? await net.internal.listAutomations(orgId, repoId) : null;
|
|
176
|
+
out(` internal automations query ${autos ? green(`answers (${autos.length} automations)`) : orgId ? red('no answer') : yellow('needs --org')}`);
|
|
158
177
|
}
|
|
159
178
|
out('');
|
|
160
179
|
out(bold('internal API'));
|
|
161
|
-
|
|
180
|
+
// Validates the query documents themselves, with ids that match nothing —
|
|
181
|
+
// "status unknown" used to be all this said, while every automation read in
|
|
182
|
+
// a pull was failing on a query the schema had stopped accepting.
|
|
183
|
+
const checks = await net.internal.checkQueries();
|
|
184
|
+
if (!checks)
|
|
185
|
+
out(` queries ${red('no answer')}${dim(' the endpoint could not be reached')}`);
|
|
186
|
+
else {
|
|
187
|
+
for (const c of checks) {
|
|
188
|
+
out(` ${c.ok ? green('ok ') : red('FAIL')} ${c.label.padEnd(18)}${c.ok ? '' : ` ${dim(c.message ?? '')}`}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const status = net.internal.status;
|
|
192
|
+
out(` status ${status === 'ok'
|
|
193
|
+
? green('ok')
|
|
194
|
+
: status === 'incompatible'
|
|
195
|
+
? red('incompatible') + dim(' a query this version of pipe sends is refused — a bug in pipe, worth reporting')
|
|
196
|
+
: status === 'unavailable'
|
|
197
|
+
? red('unavailable')
|
|
198
|
+
: dim('unknown')}`);
|
|
162
199
|
info(dim(' Unversioned, no compatibility contract (PLAN.md §2.1). When it breaks, automation\n' +
|
|
163
200
|
' detail and phase-jump editing degrade to unknown rather than failing an apply.'));
|
|
164
201
|
if (!caps.snapshots) {
|
|
@@ -169,9 +206,12 @@ export const doctor = async (p) => {
|
|
|
169
206
|
}
|
|
170
207
|
return 0;
|
|
171
208
|
};
|
|
172
|
-
const
|
|
173
|
-
const data = await net.client.tryRaw('query($id: ID!) { pipe(id: $id) { phases { id } } }', { id: repoId }, '
|
|
174
|
-
return
|
|
209
|
+
const probeRepo = async (net, repoId) => {
|
|
210
|
+
const data = await net.client.tryRaw('query($id: ID!) { pipe(id: $id) { organizationId phases { id } } }', { id: repoId }, 'probe repo');
|
|
211
|
+
return {
|
|
212
|
+
firstPhaseId: data?.pipe?.phases?.[0]?.id ?? '0',
|
|
213
|
+
organizationId: data?.pipe?.organizationId ? String(data.pipe.organizationId) : null,
|
|
214
|
+
};
|
|
175
215
|
};
|
|
176
216
|
// ── export ───────────────────────────────────────────────────────────────────
|
|
177
217
|
export const exportHelp = `pipe export <pipeId> [options]
|
|
@@ -191,26 +231,7 @@ export const exportCmd = async (p) => {
|
|
|
191
231
|
const net = await connect(p);
|
|
192
232
|
const caps = await net.capabilities();
|
|
193
233
|
const useSnapshot = caps.snapshots && !bool(p, 'no-snapshot');
|
|
194
|
-
|
|
195
|
-
if (useSnapshot) {
|
|
196
|
-
const { payload, versionId } = await net.snapshots.pull(repoId);
|
|
197
|
-
const { fullCoverage } = await import("../model/payload.js");
|
|
198
|
-
env = {
|
|
199
|
-
source: 'snapshot',
|
|
200
|
-
versionId,
|
|
201
|
-
readAt: new Date().toISOString(),
|
|
202
|
-
repoKind: 'pipe',
|
|
203
|
-
coverage: fullCoverage('complete'),
|
|
204
|
-
notes: [],
|
|
205
|
-
payload,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
env = await net.reconstructor.reconstruct(repoId, {
|
|
210
|
-
organizationId: net.organizationId,
|
|
211
|
-
noInternal: bool(p, 'no-internal'),
|
|
212
|
-
});
|
|
213
|
-
}
|
|
234
|
+
const env = await readLive(net, p, repoId, { preferSnapshot: useSnapshot, noInternal: bool(p, 'no-internal') });
|
|
214
235
|
/**
|
|
215
236
|
* A bare payload has no provenance, and for a reconstructed read that matters.
|
|
216
237
|
*
|
package/dist/commands/plan.js
CHANGED
|
@@ -10,7 +10,7 @@ import { compile } from "../apply/compile.js";
|
|
|
10
10
|
import { readAutomationCatalogue } from "../pipefy/automations.js";
|
|
11
11
|
import { validateTree, renderFindings, blockingErrors } from "../validate/index.js";
|
|
12
12
|
import { connect, openWorkspace } from "./context.js";
|
|
13
|
-
import { loadBaseline, writeHtml, openViewer } from "./diff.js";
|
|
13
|
+
import { loadBaseline, writeHtml, openViewer, refuseSuppressedEdits } from "./diff.js";
|
|
14
14
|
export const help = `pipe plan [workspace] [options]
|
|
15
15
|
|
|
16
16
|
--repo <id> which repo in the workspace (default: the root pipe)
|
|
@@ -115,6 +115,8 @@ export const run = async (p) => {
|
|
|
115
115
|
throw new UserError(`${blocking.length} validation error${blocking.length === 1 ? '' : 's'} — fix these before planning`);
|
|
116
116
|
}
|
|
117
117
|
const changeSet = diffTrees(baseline, edited);
|
|
118
|
+
// Before the empty check: edits that were all left out are not "nothing to do".
|
|
119
|
+
refuseSuppressedEdits(changeSet, bool(p, 'force'));
|
|
118
120
|
if (!changeSet.changes.length) {
|
|
119
121
|
/**
|
|
120
122
|
* The pipe half is clean, which is not the same as nothing to do: a flow-only
|
package/dist/commands/pull.js
CHANGED
|
@@ -19,6 +19,7 @@ import { baselineFile, paths } from "../workspace/layout.js";
|
|
|
19
19
|
import { connect } from "./context.js";
|
|
20
20
|
import { RunRecorder, invocation } from "../report/run.js";
|
|
21
21
|
import { pullAndWriteFlows } from "./flows.js";
|
|
22
|
+
import { TOOL } from "../version.js";
|
|
22
23
|
/**
|
|
23
24
|
* `pipe pull <pipeId>` — the read path.
|
|
24
25
|
*
|
|
@@ -39,6 +40,7 @@ export const help = `pipe pull <pipeId> [options]
|
|
|
39
40
|
--org <id> organization id, for the internal automations query
|
|
40
41
|
--reuse-snapshot use the newest complete snapshot instead of creating one
|
|
41
42
|
--no-snapshot force GraphQL reconstruction even if snapshots exist
|
|
43
|
+
--no-fallback if a snapshot read fails, fail rather than reconstruct over GraphQL
|
|
42
44
|
--no-internal skip the internal API (automations and jumps become unknown)
|
|
43
45
|
--label <text> label the snapshot this pull creates
|
|
44
46
|
--skip-roundtrip do not run the codec self-check (not recommended)
|
|
@@ -57,6 +59,7 @@ export const run = async (p) => {
|
|
|
57
59
|
const noInternal = bool(p, 'no-internal');
|
|
58
60
|
const reuseSnapshot = bool(p, 'reuse-snapshot');
|
|
59
61
|
const skipRoundTrip = bool(p, 'skip-roundtrip');
|
|
62
|
+
const noFallback = bool(p, 'no-fallback');
|
|
60
63
|
const label = str(p, 'label');
|
|
61
64
|
const startedAt = new Date();
|
|
62
65
|
const net = await connect(p);
|
|
@@ -114,7 +117,28 @@ export const run = async (p) => {
|
|
|
114
117
|
* relation arrays a pipe's does (VALIDATION.md §3g).
|
|
115
118
|
*/
|
|
116
119
|
if (useSnapshots) {
|
|
117
|
-
|
|
120
|
+
try {
|
|
121
|
+
env = await readViaSnapshot(net, ref, { reuseSnapshot, label });
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
/**
|
|
125
|
+
* A failed snapshot read falls back to reconstruction rather than
|
|
126
|
+
* leaving the repo out. Found live: Pipefy's storage answered every
|
|
127
|
+
* signed snapshot URL with HTTP 500 — the creation succeeded, the
|
|
128
|
+
* download did not, on every pipe tried — and the pull wrote a
|
|
129
|
+
* workspace with no pipe in it. Reconstruction is less complete, and
|
|
130
|
+
* says so per group: what it cannot see is `unknown`, never empty, so
|
|
131
|
+
* the fallback cannot license a delete.
|
|
132
|
+
*/
|
|
133
|
+
if (noFallback)
|
|
134
|
+
throw e;
|
|
135
|
+
warn(`snapshot read failed for ${ref.kind} ${ref.id} (${ref.name ?? '?'}): ${e.message} — ` +
|
|
136
|
+
'reading it over GraphQL instead (--no-fallback to stop here)');
|
|
137
|
+
env = await net.reconstructor.reconstruct(ref.id, {
|
|
138
|
+
organizationId: net.organizationId,
|
|
139
|
+
noInternal,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
118
142
|
}
|
|
119
143
|
else {
|
|
120
144
|
env = await net.reconstructor.reconstruct(ref.id, {
|
|
@@ -164,8 +188,31 @@ export const run = async (p) => {
|
|
|
164
188
|
});
|
|
165
189
|
const rootRef = discovery.repos.find((r) => r.id === rootRepoId);
|
|
166
190
|
const outDir = resolve(str(p, 'out') ?? `${slugName(rootRef.name ?? String(rootRepoId))}-workspace`);
|
|
191
|
+
/**
|
|
192
|
+
* The root repo is the workspace. Without it there is nothing to validate,
|
|
193
|
+
* diff or plan against — and writing lock.json and workspace.json anyway
|
|
194
|
+
* did worse than produce an empty directory: re-pulling into an existing
|
|
195
|
+
* workspace replaced its lock with one that no longer listed the pipe, so
|
|
196
|
+
* the next diff had no baseline. This used to finish with exit code 0 and a
|
|
197
|
+
* "next: pipe validate", which scripts and agents believed.
|
|
198
|
+
*/
|
|
199
|
+
const rootEnvelope = await readRepo(rootRef);
|
|
200
|
+
if (!rootEnvelope) {
|
|
201
|
+
ui.stop();
|
|
202
|
+
throw new UserError(`could not read the root repo ${rootRepoId}${rootRef.name ? ` (${rootRef.name})` : ''} — nothing was written to ${outDir}`, useSnapshots && noFallback
|
|
203
|
+
? 'drop --no-fallback to read it over GraphQL, or run with --verbose for the cause'
|
|
204
|
+
: 'run with --verbose for the cause');
|
|
205
|
+
}
|
|
167
206
|
await mkdirp(outDir);
|
|
168
|
-
|
|
207
|
+
/**
|
|
208
|
+
* The workspace's organization is the root pipe's, not the credential's
|
|
209
|
+
* default: `plan` hands this to the compiler, and a token can belong to
|
|
210
|
+
* several organizations. The default is only the fallback for a payload
|
|
211
|
+
* that does not say.
|
|
212
|
+
*/
|
|
213
|
+
const rootOrg = rootEnvelope.payload['organization_id'];
|
|
214
|
+
const organizationId = rootOrg ? String(rootOrg) : net.organizationId;
|
|
215
|
+
const lock = emptyLock(rootRepoId, organizationId, depth);
|
|
169
216
|
lock.edges = discovery.edges;
|
|
170
217
|
const trees = [];
|
|
171
218
|
const sources = new Set();
|
|
@@ -270,7 +317,8 @@ export const run = async (p) => {
|
|
|
270
317
|
const detail = ref.kind === 'table'
|
|
271
318
|
? `${fieldCount} fields, ${Math.max(0, tree.phases.length - 1)} record statuses, ${written.length} files`
|
|
272
319
|
: `${tree.phases.length} phases, ${fieldCount} fields, ` +
|
|
273
|
-
|
|
320
|
+
// "0 automations" beside an unknown group read as "this pipe has none".
|
|
321
|
+
`${env.coverage.automations === 'unknown' ? 'automations unknown' : `${tree.automations.length} automations`}, ${written.length} files`;
|
|
274
322
|
ui.update(task, {
|
|
275
323
|
state: 'done',
|
|
276
324
|
detail: detail + (gaps.length ? `, ${gaps.length} groups partial or unknown` : ''),
|
|
@@ -281,10 +329,10 @@ export const run = async (p) => {
|
|
|
281
329
|
name: rootRef.name ?? String(rootRepoId),
|
|
282
330
|
createdAt: new Date().toISOString(),
|
|
283
331
|
rootRepoId,
|
|
284
|
-
organizationId
|
|
332
|
+
organizationId,
|
|
285
333
|
depth,
|
|
286
334
|
lastPullSource: sources.size > 1 ? 'mixed' : (sources.values().next().value ?? 'reconstructed'),
|
|
287
|
-
tool: {
|
|
335
|
+
tool: { ...TOOL },
|
|
288
336
|
});
|
|
289
337
|
/**
|
|
290
338
|
* Read the closed sets while the client is open. Two queries, and it is what
|
|
@@ -492,7 +540,7 @@ const pullFromFile = async (p, fromFile) => {
|
|
|
492
540
|
organizationId: str(p, 'org') ?? null,
|
|
493
541
|
depth: 0,
|
|
494
542
|
lastPullSource: env.source,
|
|
495
|
-
tool: {
|
|
543
|
+
tool: { ...TOOL },
|
|
496
544
|
});
|
|
497
545
|
await writeDocs(outDir, lock, [tree], null, await loadReference(outDir));
|
|
498
546
|
ok(`${rootRef.kind} ${repoIdNum} ${green(rootRef.name ?? '')} — ${tree.phases.length} phases, ` +
|
package/dist/commands/verify.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { bool } from "../cli/args.js";
|
|
2
2
|
import { writeJson } from "../util/fsx.js";
|
|
3
3
|
import { dim, info, ok, out, stripAnsi, warn } from "../util/log.js";
|
|
4
|
-
import { fullCoverage } from "../model/payload.js";
|
|
5
4
|
import { unpack } from "../codec/unpack.js";
|
|
6
5
|
import { readTree } from "../workspace/read.js";
|
|
7
6
|
import { baselineFile } from "../workspace/layout.js";
|
|
8
7
|
import { diffTrees } from "../diff/diff.js";
|
|
9
8
|
import { renderChangeSet } from "../diff/render.js";
|
|
10
|
-
import { connect, openWorkspace } from "./context.js";
|
|
9
|
+
import { connect, openWorkspace, readLive } from "./context.js";
|
|
11
10
|
import { RunRecorder, invocation } from "../report/run.js";
|
|
12
11
|
export const help = `pipe verify [workspace] [options]
|
|
13
12
|
|
|
@@ -40,20 +39,7 @@ export const run = async (p) => {
|
|
|
40
39
|
const { writeTree } = await import("../workspace/write.js");
|
|
41
40
|
const caps = await net.capabilities();
|
|
42
41
|
info(dim(`reading pipe ${repo.id} to match against (${caps.snapshots ? 'snapshot' : 'GraphQL reconstruction'})`));
|
|
43
|
-
const envelope =
|
|
44
|
-
? await (async () => {
|
|
45
|
-
const { payload, versionId } = await net.snapshots.pull(repo.id);
|
|
46
|
-
return {
|
|
47
|
-
source: 'snapshot',
|
|
48
|
-
versionId,
|
|
49
|
-
readAt: new Date().toISOString(),
|
|
50
|
-
repoKind: repo.kind,
|
|
51
|
-
coverage: fullCoverage('complete'),
|
|
52
|
-
notes: [],
|
|
53
|
-
payload,
|
|
54
|
-
};
|
|
55
|
-
})()
|
|
56
|
-
: await net.reconstructor.reconstruct(repo.id, { organizationId: lock.organizationId });
|
|
42
|
+
const envelope = await readLive(net, p, repo.id, { kind: repo.kind, organizationId: lock.organizationId });
|
|
57
43
|
const authored = await readTree(dir);
|
|
58
44
|
const { loadBaselineEnvelope } = await import("./diff.js");
|
|
59
45
|
const previous = await loadBaselineEnvelope(root, repo.id);
|
|
@@ -88,7 +74,7 @@ export const run = async (p) => {
|
|
|
88
74
|
return 0;
|
|
89
75
|
}
|
|
90
76
|
const rec = recorderFor(p, root, repo, 'verify');
|
|
91
|
-
const result = await verifyRepo({ net, root, lock, repo, expectedTreeDir: dir });
|
|
77
|
+
const result = await verifyRepo({ net, parsed: p, root, lock, repo, expectedTreeDir: dir });
|
|
92
78
|
rec?.after(result.envelope);
|
|
93
79
|
rec?.verified(result.ok, result.ok ? undefined : `${result.changeCount} difference(s) remain`);
|
|
94
80
|
if (bool(p, 'json')) {
|
|
@@ -143,6 +129,8 @@ const write = async (rec, outcome, summary) => {
|
|
|
143
129
|
else if (res)
|
|
144
130
|
info(dim(` run recorded in runs/${rec.id}/`));
|
|
145
131
|
};
|
|
132
|
+
/** No flags: a caller that passes none gets the defaults, fallback included. */
|
|
133
|
+
const EMPTY_ARGS = { command: 'verify', positionals: [], flags: {} };
|
|
146
134
|
export const verifyRepo = async (args) => {
|
|
147
135
|
const { net, lock, repo, expectedTreeDir, idMap, preferReconstruct } = args;
|
|
148
136
|
const caps = await net.capabilities();
|
|
@@ -159,21 +147,12 @@ export const verifyRepo = async (args) => {
|
|
|
159
147
|
* comparison and a verified-clean apply proves less than it appears to.
|
|
160
148
|
* `preferReconstruct` trades that away deliberately — see its doc comment.
|
|
161
149
|
*/
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
repoKind: repo.kind,
|
|
169
|
-
coverage: fullCoverage('complete'),
|
|
170
|
-
notes: [],
|
|
171
|
-
payload,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
envelope = await net.reconstructor.reconstruct(repo.id, { organizationId: lock.organizationId });
|
|
176
|
-
}
|
|
150
|
+
envelope = await readLive(net, args.parsed ?? EMPTY_ARGS, repo.id, {
|
|
151
|
+
kind: repo.kind,
|
|
152
|
+
label: `post-apply/${new Date().toISOString()}`,
|
|
153
|
+
organizationId: lock.organizationId,
|
|
154
|
+
preferSnapshot: useSnapshot,
|
|
155
|
+
});
|
|
177
156
|
const live = unpack(envelope);
|
|
178
157
|
/**
|
|
179
158
|
* Direction matters: live -> expected. A change in this diff means "the pipe
|
package/dist/diff/diff.js
CHANGED
|
@@ -378,8 +378,19 @@ export const diffTrees = (before, after, opts = {}) => {
|
|
|
378
378
|
* second would be catastrophic.
|
|
379
379
|
*/
|
|
380
380
|
const skipSet = new Set(opts.skipEntities ?? []);
|
|
381
|
-
/**
|
|
381
|
+
/**
|
|
382
|
+
* Which sides read this group's *columns* only partially. See deltasOf.
|
|
383
|
+
* `meta.columns` when the read recorded it, otherwise the group's coverage —
|
|
384
|
+
* what a snapshot, and every workspace pulled before the two were split,
|
|
385
|
+
* carry.
|
|
386
|
+
*/
|
|
387
|
+
const columnCoverage = (tree, entity) => tree.meta.columns?.[entity] ?? tree.meta.coverage?.[entity] ?? 'unknown';
|
|
382
388
|
const partialSides = (entity) => ({
|
|
389
|
+
before: columnCoverage(before, entity) === 'partial',
|
|
390
|
+
after: columnCoverage(after, entity) === 'partial',
|
|
391
|
+
});
|
|
392
|
+
/** Which sides read this group's *rows* only partially — the row rule below. */
|
|
393
|
+
const partialRows = (entity) => ({
|
|
383
394
|
before: (before.meta.coverage?.[entity] ?? 'unknown') === 'partial',
|
|
384
395
|
after: (after.meta.coverage?.[entity] ?? 'unknown') === 'partial',
|
|
385
396
|
});
|
|
@@ -392,12 +403,14 @@ export const diffTrees = (before, after, opts = {}) => {
|
|
|
392
403
|
* "deletes" — and an apply would have deleted two live automations because one
|
|
393
404
|
* read path could not see them.
|
|
394
405
|
*
|
|
395
|
-
* So on a partial
|
|
396
|
-
* *after* side is partial, and a create when the *before*
|
|
406
|
+
* So on a side whose *rows* are partial, absence means unseen: a delete is
|
|
407
|
+
* suppressed when the *after* side is partial, and a create when the *before*
|
|
408
|
+
* side is partial. Missing columns say nothing about rows, so they do not
|
|
409
|
+
* enter into this (see Coverage on TreeMeta.columns).
|
|
397
410
|
*/
|
|
398
411
|
const suppressed = [];
|
|
399
412
|
const rowAbsenceIsUnknown = (entity, op, label) => {
|
|
400
|
-
const sides =
|
|
413
|
+
const sides = partialRows(entity);
|
|
401
414
|
const unknown = op === 'delete' ? sides.after : sides.before;
|
|
402
415
|
if (unknown)
|
|
403
416
|
suppressed.push({ entity, op, label });
|
|
@@ -963,6 +976,7 @@ export const diffTrees = (before, after, opts = {}) => {
|
|
|
963
976
|
entity: s.entity,
|
|
964
977
|
reason: `"${s.label}" would read as a ${s.op}, but that side's coverage is partial — ` +
|
|
965
978
|
`absence there is unseen, not gone. Re-read from a snapshot to decide.`,
|
|
979
|
+
suppressed: { op: s.op, label: s.label },
|
|
966
980
|
});
|
|
967
981
|
}
|
|
968
982
|
return {
|
|
@@ -975,6 +989,16 @@ export const diffTrees = (before, after, opts = {}) => {
|
|
|
975
989
|
};
|
|
976
990
|
};
|
|
977
991
|
export const isEmpty = (cs) => cs.changes.length === 0;
|
|
992
|
+
/**
|
|
993
|
+
* The creates and deletes this change set had to leave out.
|
|
994
|
+
*
|
|
995
|
+
* Between a workspace and its own baseline every one of these is an edit
|
|
996
|
+
* someone made, and planning the rest without them is applying half of what
|
|
997
|
+
* was asked for. Found in a field run: 20 updates would have gone through and
|
|
998
|
+
* the 2 phase creates they pointed at would not, leaving a pipe with no final
|
|
999
|
+
* phase. `plan` and `apply` refuse on these unless told otherwise.
|
|
1000
|
+
*/
|
|
1001
|
+
export const suppressedEdits = (cs) => cs.skipped.flatMap((s) => (s.suppressed ? [{ entity: s.entity, ...s.suppressed }] : []));
|
|
978
1002
|
export const countByOp = (cs) => {
|
|
979
1003
|
const out = { create: 0, update: 0, delete: 0, reorder: 0, archive: 0, unarchive: 0 };
|
|
980
1004
|
for (const c of cs.changes)
|
package/dist/pipefy/agents.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
import { debug } from "../util/log.js";
|
|
2
|
+
/**
|
|
3
|
+
* The selection under `aiBehaviorParams`, aliased to the snake_case the files
|
|
4
|
+
* use. Shared with `internal.ts`'s `loadAutomation`, because `AiBehaviorParams`
|
|
5
|
+
* is the same object type on both endpoints — and the internal query once
|
|
6
|
+
* selected it bare, as though it were a scalar. GraphQL rejects that before
|
|
7
|
+
* resolving anything, so every automation in the pipe failed to load, not only
|
|
8
|
+
* the AI ones. One selection, two call sites, so the two cannot drift apart.
|
|
9
|
+
*/
|
|
10
|
+
export const AI_BEHAVIOR_PARAMS_SELECTION = `{
|
|
11
|
+
data_source_ids: dataSourceIds
|
|
12
|
+
instruction
|
|
13
|
+
referenced_field_ids: referencedFieldIds
|
|
14
|
+
provider_id: providerId
|
|
15
|
+
system_provider_id: systemProviderId
|
|
16
|
+
actions_attributes: actionsAttributes {
|
|
17
|
+
id
|
|
18
|
+
name
|
|
19
|
+
action_type: actionType
|
|
20
|
+
reference_id: referenceId
|
|
21
|
+
metadata {
|
|
22
|
+
destination_phase_id: destinationPhaseId
|
|
23
|
+
pipe_id: pipeId
|
|
24
|
+
table_id: tableId
|
|
25
|
+
email_template_id: emailTemplateId
|
|
26
|
+
allow_template_modifications: allowTemplateModifications
|
|
27
|
+
mcp_server_id: mcpServerId
|
|
28
|
+
tool_name: toolName
|
|
29
|
+
title
|
|
30
|
+
emails
|
|
31
|
+
tool_inputs: toolInputs { name source value field_id: fieldId }
|
|
32
|
+
fields_attributes: fieldsAttributes { field_id: fieldId input_mode: inputMode value }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
capabilities_attributes: capabilitiesAttributes { capability_type: capabilityType enabled }
|
|
36
|
+
}`;
|
|
2
37
|
const QUERY = `
|
|
3
38
|
query($repoUuid: ID!, $after: String) {
|
|
4
39
|
aiAgents(repoUuid: $repoUuid, first: 50, after: $after) {
|
|
@@ -34,33 +69,7 @@ const QUERY = `
|
|
|
34
69
|
expressions { id field_address operation value structure_id }
|
|
35
70
|
}
|
|
36
71
|
action_params {
|
|
37
|
-
ai_behavior_params: aiBehaviorParams {
|
|
38
|
-
data_source_ids: dataSourceIds
|
|
39
|
-
instruction
|
|
40
|
-
referenced_field_ids: referencedFieldIds
|
|
41
|
-
provider_id: providerId
|
|
42
|
-
system_provider_id: systemProviderId
|
|
43
|
-
actions_attributes: actionsAttributes {
|
|
44
|
-
id
|
|
45
|
-
name
|
|
46
|
-
action_type: actionType
|
|
47
|
-
reference_id: referenceId
|
|
48
|
-
metadata {
|
|
49
|
-
destination_phase_id: destinationPhaseId
|
|
50
|
-
pipe_id: pipeId
|
|
51
|
-
table_id: tableId
|
|
52
|
-
email_template_id: emailTemplateId
|
|
53
|
-
allow_template_modifications: allowTemplateModifications
|
|
54
|
-
mcp_server_id: mcpServerId
|
|
55
|
-
tool_name: toolName
|
|
56
|
-
title
|
|
57
|
-
emails
|
|
58
|
-
tool_inputs: toolInputs { name source value field_id: fieldId }
|
|
59
|
-
fields_attributes: fieldsAttributes { field_id: fieldId input_mode: inputMode value }
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
capabilities_attributes: capabilitiesAttributes { capability_type: capabilityType enabled }
|
|
63
|
-
}
|
|
72
|
+
ai_behavior_params: aiBehaviorParams ${AI_BEHAVIOR_PARAMS_SELECTION}
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
}
|