@com55/node-red-ai-access 0.2.0-beta.7 → 0.2.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/README.md +67 -21
- package/ai-access.html +1 -1
- package/dist/plugin.cjs +551 -334
- package/dist/plugin.d.cts +35 -1
- package/docs/api/openapi.yaml +189 -33
- package/docs/errors.md +4 -3
- package/docs/mcp-tools.md +9 -7
- package/package.json +2 -1
package/dist/plugin.d.cts
CHANGED
|
@@ -218,7 +218,7 @@ declare class AuditLog {
|
|
|
218
218
|
*/
|
|
219
219
|
|
|
220
220
|
type ProposalStatus = 'pending' | 'conflict' | 'approved' | 'rejected' | 'failed' | 'withdrawn' | 'expired' | 'superseded';
|
|
221
|
-
/** One stored rebase conflict (field
|
|
221
|
+
/** One stored rebase conflict (field paths only, including env names / credential keys; never values). */
|
|
222
222
|
interface StoredRebaseConflict {
|
|
223
223
|
kind: string;
|
|
224
224
|
nodeId?: string;
|
|
@@ -228,6 +228,11 @@ interface StoredRebaseConflict {
|
|
|
228
228
|
message: string;
|
|
229
229
|
choices: ('live' | 'proposed')[];
|
|
230
230
|
}
|
|
231
|
+
/** Paths where an AI conflict choice replaced a live editor change. */
|
|
232
|
+
interface AiConflictOverride {
|
|
233
|
+
nodeId: string;
|
|
234
|
+
fields: string[];
|
|
235
|
+
}
|
|
231
236
|
/** Result of the last auto-rebase check against the live flows. */
|
|
232
237
|
interface ProposalRebaseInfo {
|
|
233
238
|
/** Live rev the check ran against. */
|
|
@@ -295,14 +300,33 @@ interface Proposal {
|
|
|
295
300
|
nodeId: string;
|
|
296
301
|
property: string;
|
|
297
302
|
}[];
|
|
303
|
+
deploymentAlert?: string;
|
|
304
|
+
landedAfterWithdraw?: boolean;
|
|
305
|
+
rejectedWhileDeployUnknown?: boolean;
|
|
298
306
|
/** Persisted while a timed-out runtime deployment has an unknown outcome. */
|
|
299
307
|
deployPending?: {
|
|
300
308
|
mergedHash: string;
|
|
309
|
+
bootId?: string;
|
|
301
310
|
reason: string;
|
|
302
311
|
queueReleased?: boolean;
|
|
312
|
+
attemptId?: string;
|
|
303
313
|
};
|
|
304
314
|
/** Last auto-rebase result (pending / conflict proposals). */
|
|
305
315
|
rebase?: ProposalRebaseInfo;
|
|
316
|
+
/** AI resolutions that replace editor changes and still require reviewer acknowledgement. */
|
|
317
|
+
aiConflictOverrides?: AiConflictOverride[];
|
|
318
|
+
/** Latest manual resolution, retained for admin undo until the next body edit. */
|
|
319
|
+
conflictChoice?: {
|
|
320
|
+
nodeId: string;
|
|
321
|
+
choice: 'live' | 'proposed';
|
|
322
|
+
chooser?: 'ai' | 'human';
|
|
323
|
+
undo?: {
|
|
324
|
+
baseFlow: Partial<FlowTab>;
|
|
325
|
+
flow: Partial<FlowTab>;
|
|
326
|
+
warnings: ProposalWarning[];
|
|
327
|
+
aiConflictOverrides?: AiConflictOverride[];
|
|
328
|
+
};
|
|
329
|
+
};
|
|
306
330
|
/**
|
|
307
331
|
* Short human title (≤80 chars). Set at submit (AI-supplied or auto-generated).
|
|
308
332
|
* Optional on legacy proposals; API responses always resolve a title.
|
|
@@ -347,6 +371,7 @@ declare class ProposalStore {
|
|
|
347
371
|
private toDiskRecord;
|
|
348
372
|
private writeAtomic;
|
|
349
373
|
create(input: CreateProposalInput): Proposal;
|
|
374
|
+
private currentProcessProposal;
|
|
350
375
|
get(id: string): Proposal;
|
|
351
376
|
list(filter?: {
|
|
352
377
|
linkId?: string;
|
|
@@ -360,6 +385,9 @@ declare class ProposalStore {
|
|
|
360
385
|
beginApprove(id: string): Proposal;
|
|
361
386
|
releaseLock(id: string): void;
|
|
362
387
|
recordDeployPending(id: string, pending: Proposal['deployPending']): Proposal;
|
|
388
|
+
flagRejectedWhileDeployUnknown(id: string): Proposal;
|
|
389
|
+
flagLandedAfterWithdraw(id: string): void;
|
|
390
|
+
flagDeployment(id: string, message: string): void;
|
|
363
391
|
markApproved(id: string, decidedBy: ProposalDecisionBy, resultRev: string): Proposal;
|
|
364
392
|
markRejected(id: string, decidedBy: ProposalDecisionBy | undefined, reason: string): Proposal;
|
|
365
393
|
markFailed(id: string, decidedBy: ProposalDecisionBy | undefined, reason: string): Proposal;
|
|
@@ -388,6 +416,8 @@ declare class ProposalStore {
|
|
|
388
416
|
property: string;
|
|
389
417
|
}[];
|
|
390
418
|
warnings?: ProposalWarning[];
|
|
419
|
+
conflictChoice?: Proposal['conflictChoice'];
|
|
420
|
+
aiConflictOverrides?: AiConflictOverride[];
|
|
391
421
|
}): Proposal;
|
|
392
422
|
/** True while an approve holds this proposal's lock. */
|
|
393
423
|
isApproving(id: string): boolean;
|
|
@@ -527,6 +557,7 @@ interface ProposalServiceOptions {
|
|
|
527
557
|
deployHardCapMs?: number;
|
|
528
558
|
}
|
|
529
559
|
interface ApproveOptions {
|
|
560
|
+
overrideDeployOutcomeUnknown?: boolean;
|
|
530
561
|
deploymentType?: DeploymentType;
|
|
531
562
|
/**
|
|
532
563
|
* `mergedHash` the reviewer last saw. Required when the rebase merged live
|
|
@@ -541,6 +572,9 @@ declare class ProposalService {
|
|
|
541
572
|
private readonly getDeploymentType;
|
|
542
573
|
private readonly serviceOptions;
|
|
543
574
|
constructor(proposals: ProposalStore, audit: AuditLog, adapter: FlowAdapter, getDeploymentType: () => DeploymentType, serviceOptions?: ProposalServiceOptions);
|
|
575
|
+
unknownDeployBlocks(): Proposal[];
|
|
576
|
+
confirmLiveChecked(id: string, actor: ProposalDecisionBy, expectedAttemptId: unknown): Proposal;
|
|
577
|
+
private guardUnknown;
|
|
544
578
|
approve(id: string, decidedBy: ProposalDecisionBy, options?: ApproveOptions): Promise<Proposal>;
|
|
545
579
|
private approveLocked;
|
|
546
580
|
private withDeployDeadline;
|
package/docs/api/openapi.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.1.0
|
|
|
2
2
|
|
|
3
3
|
info:
|
|
4
4
|
title: Node-RED AI Access Plugin API
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.2.0-beta.9
|
|
6
6
|
summary: Temporary, owner-approved Node-RED access for AI clients
|
|
7
7
|
description: |
|
|
8
8
|
REST surface exposed by the `@com55/node-red-ai-access` Node-RED plugin
|
|
@@ -10,8 +10,8 @@ info:
|
|
|
10
10
|
|
|
11
11
|
**Git mental model:** a proposal is like a pull request. Human approve =
|
|
12
12
|
merge; reject = close; withdraw = the author closes their own PR. If `rev` /
|
|
13
|
-
`expectedRev` does not match,
|
|
14
|
-
|
|
13
|
+
`expectedRev` does not match, open proposals auto-rebase; only overlapping
|
|
14
|
+
field changes block approval. Read `rebase.state` to assess readiness. Every access link can see all proposals
|
|
15
15
|
(`mine: true|false`).
|
|
16
16
|
|
|
17
17
|
## Base path
|
|
@@ -185,6 +185,10 @@ info:
|
|
|
185
185
|
full tab or batch return `duplicate_node_id`.
|
|
186
186
|
|
|
187
187
|
Open proposals (`pending` or `conflict`) auto-rebase when live revisions change.
|
|
188
|
+
Disjoint node and tab fields merge automatically. Only fields changed on both
|
|
189
|
+
sides conflict (even identical edits); x/y never conflict and proposed positions
|
|
190
|
+
win ties. Env merges by unique name and entry field, credentials by key.
|
|
191
|
+
Duplicate env names fall back to atomic comparison with ambiguity warnings.
|
|
188
192
|
Conflicts block approval until resolved. Proposals expire only after the
|
|
189
193
|
configured `proposalTtlDays` (default 7). Approval rebases other open proposals;
|
|
190
194
|
legacy expired, rejected and superseded proposals stay closed.
|
|
@@ -194,7 +198,11 @@ info:
|
|
|
194
198
|
The creating access link may withdraw its own pending proposal
|
|
195
199
|
(`POST /proposals/{id}/withdraw`, GET `…/proposals/{id}/withdraw?confirm=1`,
|
|
196
200
|
or MCP `withdraw_proposal`). GET withdraw requires `confirm=1` so prefetchers
|
|
197
|
-
cannot trigger it.
|
|
201
|
+
cannot trigger it. If the token is lost, a full (`*`) admin session may use
|
|
202
|
+
POST `/proposals/{id}/withdraw`. Other tokens remain blocked. Recovery is logged
|
|
203
|
+
as `proposal.withdrawn` with the admin identity, originalOwnerId, reason and
|
|
204
|
+
withdrawalAuthority=`admin-recovery` (owner-token withdrawals use `owner-token`).
|
|
205
|
+
Status becomes `withdrawn` (separate from admin
|
|
198
206
|
`rejected`).
|
|
199
207
|
|
|
200
208
|
## Known issues
|
|
@@ -942,6 +950,21 @@ paths:
|
|
|
942
950
|
type: array
|
|
943
951
|
items:
|
|
944
952
|
$ref: '#/components/schemas/Proposal'
|
|
953
|
+
deployUnknownBlocks:
|
|
954
|
+
type: array
|
|
955
|
+
description: Admin-only active process blocks, independent of status filters and pagination. Each deployment submits a full flow snapshot, so these blocks apply to all approvals, including after proposal closure.
|
|
956
|
+
items:
|
|
957
|
+
type: object
|
|
958
|
+
required: [proposalId, flowIds]
|
|
959
|
+
properties:
|
|
960
|
+
proposalId:
|
|
961
|
+
type: string
|
|
962
|
+
attemptId:
|
|
963
|
+
type: string
|
|
964
|
+
flowIds:
|
|
965
|
+
type: array
|
|
966
|
+
items:
|
|
967
|
+
type: string
|
|
945
968
|
total:
|
|
946
969
|
type: integer
|
|
947
970
|
description: Count after filters, before limit/offset
|
|
@@ -1054,9 +1077,13 @@ paths:
|
|
|
1054
1077
|
(long text fields listed for expandable UI). Modified nodes include
|
|
1055
1078
|
line-level diffs for text fields such as `func` and `template`.
|
|
1056
1079
|
|
|
1057
|
-
`
|
|
1080
|
+
`baseRev` aliases `expectedRev`, the stored proposal base revision.
|
|
1081
|
+
`baseRevChanged` is true only for **open** proposals when
|
|
1058
1082
|
`expectedRev` does not match the live revision (informational).
|
|
1059
|
-
|
|
1083
|
+
This is informational after a successful rebase, not an approval error.
|
|
1084
|
+
`rebase.state` (`clean`, `merged`, `conflict`) reports merge readiness and
|
|
1085
|
+
`rebase.rev` identifies the checked live rev. Closed proposals never set
|
|
1086
|
+
`baseRevChanged` as true. Open proposals include
|
|
1060
1087
|
`rebase` and `liveChanges` (editor changes since the stored base).
|
|
1061
1088
|
Without conflicts, `vsCurrent` compares the merged deployment to live.
|
|
1062
1089
|
Merges use raw stored proposals and live flows; AI diff sections are redacted.
|
|
@@ -1087,7 +1114,7 @@ paths:
|
|
|
1087
1114
|
/proposals/{id}/resolve-conflict:
|
|
1088
1115
|
post:
|
|
1089
1116
|
tags: [Proposals]
|
|
1090
|
-
summary: Choose live or proposed for one current conflict
|
|
1117
|
+
summary: Choose live or proposed for one current conflict, or undo the latest choice
|
|
1091
1118
|
operationId: resolveProposalConflict
|
|
1092
1119
|
security:
|
|
1093
1120
|
- AdminSession: []
|
|
@@ -1145,7 +1172,12 @@ paths:
|
|
|
1145
1172
|
/proposals/{id}/resolve:
|
|
1146
1173
|
post:
|
|
1147
1174
|
tags: [Proposals]
|
|
1148
|
-
summary: Resolve your own open proposal with a
|
|
1175
|
+
summary: Resolve your own open proposal with a choice or replacement payload
|
|
1176
|
+
description: |
|
|
1177
|
+
Use nodeId + choice for one current conflict (overlapping fields only;
|
|
1178
|
+
independent live and proposed edits survive). Or use expectedRev + payload:
|
|
1179
|
+
operations apply to the flow snapshot at expectedRev, not the stored
|
|
1180
|
+
proposal or merged result. Include every intended edit. Do not mix modes.
|
|
1149
1181
|
operationId: resolveProposal
|
|
1150
1182
|
security:
|
|
1151
1183
|
- AccessLinkBearer: []
|
|
@@ -1227,6 +1259,42 @@ paths:
|
|
|
1227
1259
|
'415':
|
|
1228
1260
|
$ref: '#/components/responses/UnsupportedMediaType'
|
|
1229
1261
|
|
|
1262
|
+
/proposals/{id}/confirm-live-checked:
|
|
1263
|
+
post:
|
|
1264
|
+
tags: [Proposals]
|
|
1265
|
+
summary: Confirm live flows checked and clear the unknown deployment block
|
|
1266
|
+
description: Requires full admin permission (*). Records the actor and attempt in proposal.live_checked audit. Requires a post-cap deployPending.queueReleased flag and its matching attemptId. Does not cancel a detached deploy; a later landing is still monitored on every changed tab.
|
|
1267
|
+
operationId: confirmProposalLiveChecked
|
|
1268
|
+
security:
|
|
1269
|
+
- AdminSession: []
|
|
1270
|
+
parameters:
|
|
1271
|
+
- $ref: '#/components/parameters/ProposalId'
|
|
1272
|
+
requestBody:
|
|
1273
|
+
required: true
|
|
1274
|
+
content:
|
|
1275
|
+
application/json:
|
|
1276
|
+
schema:
|
|
1277
|
+
type: object
|
|
1278
|
+
required: [attemptId]
|
|
1279
|
+
properties:
|
|
1280
|
+
attemptId:
|
|
1281
|
+
type: string
|
|
1282
|
+
responses:
|
|
1283
|
+
'200':
|
|
1284
|
+
description: Block cleared
|
|
1285
|
+
content:
|
|
1286
|
+
application/json:
|
|
1287
|
+
schema:
|
|
1288
|
+
$ref: '#/components/schemas/Proposal'
|
|
1289
|
+
'401':
|
|
1290
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1291
|
+
'403':
|
|
1292
|
+
$ref: '#/components/responses/Forbidden'
|
|
1293
|
+
'409':
|
|
1294
|
+
description: deploy_outcome_unknown — no post-cap deployment or attemptId does not match
|
|
1295
|
+
'404':
|
|
1296
|
+
$ref: '#/components/responses/NotFound'
|
|
1297
|
+
|
|
1230
1298
|
/proposals/{id}/reject:
|
|
1231
1299
|
post:
|
|
1232
1300
|
tags: [Proposals]
|
|
@@ -1272,14 +1340,16 @@ paths:
|
|
|
1272
1340
|
/proposals/{id}/withdraw:
|
|
1273
1341
|
post:
|
|
1274
1342
|
tags: [Proposals]
|
|
1275
|
-
summary: Withdraw
|
|
1343
|
+
summary: Withdraw an open proposal (owner token or full admin recovery)
|
|
1276
1344
|
description: |
|
|
1277
|
-
|
|
1278
|
-
|
|
1345
|
+
Accepts the creating access-link Bearer, or a full (`*`) admin session
|
|
1346
|
+
for lost-token recovery. Other tokens and read-only admins are forbidden.
|
|
1347
|
+
Withdraws an **open** (pending or conflict) proposal. Status becomes `withdrawn` (not `rejected`). Optional
|
|
1279
1348
|
JSON body `{ "reason": "…" }`.
|
|
1280
1349
|
operationId: withdrawProposal
|
|
1281
1350
|
security:
|
|
1282
1351
|
- AccessLinkBearer: []
|
|
1352
|
+
- AdminSession: []
|
|
1283
1353
|
parameters:
|
|
1284
1354
|
- $ref: '#/components/parameters/ProposalId'
|
|
1285
1355
|
requestBody:
|
|
@@ -1301,7 +1371,7 @@ paths:
|
|
|
1301
1371
|
'401':
|
|
1302
1372
|
$ref: '#/components/responses/Unauthorized'
|
|
1303
1373
|
'403':
|
|
1304
|
-
description: Not owned by this access link
|
|
1374
|
+
description: Not owned by this access link or admin lacks full permission
|
|
1305
1375
|
content:
|
|
1306
1376
|
application/json:
|
|
1307
1377
|
schema:
|
|
@@ -1550,6 +1620,7 @@ components:
|
|
|
1550
1620
|
Machine-readable `code` on JSON error bodies. See docs/errors.md.
|
|
1551
1621
|
|
|
1552
1622
|
- `approve_queue_timeout` (504): Approval exceeded its queue-wait deadline (default 60 seconds); no deployment was started for this request
|
|
1623
|
+
- `deploy_outcome_unknown` (409): A past-cap deployment can still change live flows
|
|
1553
1624
|
- `deploy_timeout` (504): Deployment exceeded its deadline (default 60 seconds); approvals wait for settlement or the hard cap (default 5 minutes)
|
|
1554
1625
|
- `proposal_conflict` (409): Editor and proposal changes conflict (conflicts contains field names only)
|
|
1555
1626
|
- `rebase_ack_required` (409): The merged result needs review (mergedHash included)
|
|
@@ -1585,7 +1656,7 @@ components:
|
|
|
1585
1656
|
- `version_mismatch` (409): Unknown or unavailable `rev` / `expectedRev` snapshot
|
|
1586
1657
|
- `proposal_not_found` (404): Unknown proposal id
|
|
1587
1658
|
- `proposal_not_pending` (409): Action needs an open proposal
|
|
1588
|
-
- `proposal_not_owned` (403):
|
|
1659
|
+
- `proposal_not_owned` (403): Resolve or withdraw another token’s proposal
|
|
1589
1660
|
- `validation_failed` (400): Several validation errors at once (message `N validation errors`)
|
|
1590
1661
|
- `duplicate_node_id` (400): The same node id appears more than once in a full tab or batch
|
|
1591
1662
|
- `missing_node_id` (400): Node / op without an id
|
|
@@ -1611,6 +1682,7 @@ components:
|
|
|
1611
1682
|
- `internal_error` (500): Unexpected server error
|
|
1612
1683
|
enum:
|
|
1613
1684
|
- approve_queue_timeout
|
|
1685
|
+
- deploy_outcome_unknown
|
|
1614
1686
|
- deploy_timeout
|
|
1615
1687
|
- proposal_conflict
|
|
1616
1688
|
- rebase_ack_required
|
|
@@ -2346,6 +2418,7 @@ components:
|
|
|
2346
2418
|
- type
|
|
2347
2419
|
- status
|
|
2348
2420
|
- expectedRev
|
|
2421
|
+
- baseRev
|
|
2349
2422
|
- payload
|
|
2350
2423
|
- createdBy
|
|
2351
2424
|
- createdAt
|
|
@@ -2355,18 +2428,42 @@ components:
|
|
|
2355
2428
|
- reviewUrl
|
|
2356
2429
|
- reviewPath
|
|
2357
2430
|
properties:
|
|
2431
|
+
conflictChoice:
|
|
2432
|
+
type: object
|
|
2433
|
+
description: Latest manual resolution; admins can undo it until the next body edit or choice. Undo snapshots are never returned.
|
|
2434
|
+
required: [nodeId, choice]
|
|
2435
|
+
properties:
|
|
2436
|
+
nodeId:
|
|
2437
|
+
type: string
|
|
2438
|
+
choice:
|
|
2439
|
+
type: string
|
|
2440
|
+
enum: [live, proposed]
|
|
2441
|
+
rejectedWhileDeployUnknown:
|
|
2442
|
+
type: boolean
|
|
2443
|
+
description: Rejected while a full-snapshot deploy could still land. Check live flows; late landing triggers an alert and audit entry.
|
|
2444
|
+
landedAfterWithdraw:
|
|
2445
|
+
type: boolean
|
|
2446
|
+
description: The withdrawn proposal’s outstanding deployment landed later; shown as a dashboard badge.
|
|
2447
|
+
deploymentAlert:
|
|
2448
|
+
type: string
|
|
2449
|
+
description: Warning that a late deployment may have overwritten an intervening decision.
|
|
2358
2450
|
deployPending:
|
|
2359
2451
|
type: object
|
|
2360
2452
|
description: Timed-out deployment has an unknown outcome; approvals wait until settlement or the hard cap. Check live flows before approving again.
|
|
2361
2453
|
required: [mergedHash, reason]
|
|
2362
2454
|
properties:
|
|
2455
|
+
attemptId:
|
|
2456
|
+
type: string
|
|
2457
|
+
bootId:
|
|
2458
|
+
type: string
|
|
2459
|
+
description: Process identity; flags from previous processes are ignored.
|
|
2363
2460
|
mergedHash:
|
|
2364
2461
|
type: string
|
|
2365
2462
|
reason:
|
|
2366
2463
|
type: string
|
|
2367
2464
|
queueReleased:
|
|
2368
2465
|
type: boolean
|
|
2369
|
-
description: True when the deploy hard cap released the queue.
|
|
2466
|
+
description: True when the deploy hard cap released the queue. All approvals are blocked until settlement, restart, or confirm-live-checked with a matching attemptId; closing the proposal does not clear the block; approval may proceed with an audited override. Rejection is allowed with rejectedWhileDeployUnknown warning. Late settlement updates the proposal or flags intervening decisions.
|
|
2370
2467
|
rebase:
|
|
2371
2468
|
$ref: '#/components/schemas/ProposalRebase'
|
|
2372
2469
|
id:
|
|
@@ -2378,6 +2475,12 @@ components:
|
|
|
2378
2475
|
$ref: '#/components/schemas/ProposalStatus'
|
|
2379
2476
|
expectedRev:
|
|
2380
2477
|
type: string
|
|
2478
|
+
baseRev:
|
|
2479
|
+
type: string
|
|
2480
|
+
description: Revision of the stored base snapshot; alias of expectedRev.
|
|
2481
|
+
baseRevChanged:
|
|
2482
|
+
type: boolean
|
|
2483
|
+
description: Informational revision difference for open proposals; use rebase.state for merge readiness.
|
|
2381
2484
|
payload:
|
|
2382
2485
|
$ref: '#/components/schemas/ReplaceFlowPayload'
|
|
2383
2486
|
baseFlow:
|
|
@@ -2536,7 +2639,7 @@ components:
|
|
|
2536
2639
|
type: object
|
|
2537
2640
|
required: [kind]
|
|
2538
2641
|
description: |
|
|
2539
|
-
Admin for approve/reject/fail; access-link for
|
|
2642
|
+
Admin for approve/reject/fail and recovery withdrawal; access-link for owner withdrawal.
|
|
2540
2643
|
properties:
|
|
2541
2644
|
kind:
|
|
2542
2645
|
type: string
|
|
@@ -2596,28 +2699,52 @@ components:
|
|
|
2596
2699
|
type: string
|
|
2597
2700
|
choice:
|
|
2598
2701
|
type: string
|
|
2599
|
-
enum: [live, proposed]
|
|
2702
|
+
enum: [live, proposed, undo]
|
|
2703
|
+
description: Undo restores the proposal before the latest choice for nodeId and rechecks against live. Available until the next body edit or choice; admin only. Nothing is deployed.
|
|
2600
2704
|
|
|
2601
2705
|
ResolveProposalRequest:
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2706
|
+
oneOf:
|
|
2707
|
+
- type: object
|
|
2708
|
+
additionalProperties: false
|
|
2709
|
+
required: [nodeId, choice]
|
|
2710
|
+
properties:
|
|
2711
|
+
nodeId:
|
|
2712
|
+
type: string
|
|
2713
|
+
description: Current conflict node id; flow id for tab/env conflicts.
|
|
2714
|
+
choice:
|
|
2715
|
+
type: string
|
|
2716
|
+
enum: [live, proposed]
|
|
2717
|
+
description: Keep this side for overlapping fields only; preserve independent changes and live secrets.
|
|
2718
|
+
- type: object
|
|
2719
|
+
required: [expectedRev]
|
|
2720
|
+
properties:
|
|
2721
|
+
expectedRev:
|
|
2722
|
+
type: string
|
|
2723
|
+
description: Flow snapshot revision to which replacement operations apply.
|
|
2724
|
+
title:
|
|
2725
|
+
type: string
|
|
2726
|
+
maxLength: 80
|
|
2727
|
+
payload:
|
|
2728
|
+
$ref: '#/components/schemas/ReplaceFlowPayload'
|
|
2729
|
+
description: Complete intended edits based on expectedRev; not applied to the stored proposal or merged result.
|
|
2730
|
+
nodes:
|
|
2731
|
+
type: array
|
|
2732
|
+
description: Legacy alias for replacement payload nodes, applied to expectedRev.
|
|
2733
|
+
items:
|
|
2734
|
+
type: object
|
|
2735
|
+
additionalProperties: true
|
|
2736
|
+
not:
|
|
2737
|
+
anyOf:
|
|
2738
|
+
- required: [nodeId]
|
|
2739
|
+
- required: [choice]
|
|
2617
2740
|
|
|
2618
2741
|
ApproveProposalRequest:
|
|
2619
2742
|
type: object
|
|
2620
2743
|
properties:
|
|
2744
|
+
overrideDeployOutcomeUnknown:
|
|
2745
|
+
type: boolean
|
|
2746
|
+
default: false
|
|
2747
|
+
description: Explicitly allow approval while a past-cap deploy can still overwrite live flows.
|
|
2621
2748
|
mergedHash:
|
|
2622
2749
|
type: string
|
|
2623
2750
|
description: Hash of the reviewed merged result; required when editor changes are merged.
|
|
@@ -2730,6 +2857,11 @@ components:
|
|
|
2730
2857
|
type: object
|
|
2731
2858
|
required: [summary, added, removed, modified]
|
|
2732
2859
|
properties:
|
|
2860
|
+
tabProperties:
|
|
2861
|
+
type: array
|
|
2862
|
+
description: Before and after values of changed flow settings, including env. Uses the caller's redacted view.
|
|
2863
|
+
items:
|
|
2864
|
+
$ref: '#/components/schemas/ProposalDiffPropertyChange'
|
|
2733
2865
|
summary:
|
|
2734
2866
|
type: object
|
|
2735
2867
|
required: [added, removed, modified, tabChanges]
|
|
@@ -2766,7 +2898,8 @@ components:
|
|
|
2766
2898
|
- flowId
|
|
2767
2899
|
- expectedRev
|
|
2768
2900
|
- currentRev
|
|
2769
|
-
-
|
|
2901
|
+
- baseRevChanged
|
|
2902
|
+
- baseRev
|
|
2770
2903
|
- compareAgainst
|
|
2771
2904
|
properties:
|
|
2772
2905
|
proposalId:
|
|
@@ -2775,9 +2908,12 @@ components:
|
|
|
2775
2908
|
type: string
|
|
2776
2909
|
expectedRev:
|
|
2777
2910
|
type: string
|
|
2911
|
+
baseRev:
|
|
2912
|
+
type: string
|
|
2913
|
+
description: Alias of expectedRev, the stored base revision.
|
|
2778
2914
|
currentRev:
|
|
2779
2915
|
type: string
|
|
2780
|
-
|
|
2916
|
+
baseRevChanged:
|
|
2781
2917
|
type: boolean
|
|
2782
2918
|
description: |
|
|
2783
2919
|
Informational: true for open proposals when expectedRev ≠ live rev.
|
|
@@ -2790,6 +2926,26 @@ components:
|
|
|
2790
2926
|
description: True for legacy proposals without a stored baseFlow
|
|
2791
2927
|
rebase:
|
|
2792
2928
|
$ref: '#/components/schemas/ProposalRebase'
|
|
2929
|
+
conflictValues:
|
|
2930
|
+
type: array
|
|
2931
|
+
description: Base, live and proposed values for each conflicting path. AI callers receive redacted values.
|
|
2932
|
+
items:
|
|
2933
|
+
type: object
|
|
2934
|
+
required: [nodeId, fields]
|
|
2935
|
+
properties:
|
|
2936
|
+
nodeId:
|
|
2937
|
+
type: string
|
|
2938
|
+
fields:
|
|
2939
|
+
type: array
|
|
2940
|
+
items:
|
|
2941
|
+
type: object
|
|
2942
|
+
required: [field]
|
|
2943
|
+
properties:
|
|
2944
|
+
field:
|
|
2945
|
+
type: string
|
|
2946
|
+
base: {}
|
|
2947
|
+
live: {}
|
|
2948
|
+
proposed: {}
|
|
2793
2949
|
liveChanges:
|
|
2794
2950
|
$ref: '#/components/schemas/ProposalDiffSections'
|
|
2795
2951
|
vsCurrent:
|
package/docs/errors.md
CHANGED
|
@@ -14,12 +14,13 @@ GET-link `401` responses include a `WWW-Authenticate: Bearer …` header.
|
|
|
14
14
|
| Code | HTTP | Meaning | Hint / next step | Channels |
|
|
15
15
|
| --- | --- | --- | --- | --- |
|
|
16
16
|
| `approve_queue_timeout` | 504 | Approval exceeded its queue-wait deadline (default 60 seconds); no deployment was started for this request | Check the live flow and retry approval | admin |
|
|
17
|
+
| `deploy_outcome_unknown` | 409 | A past-cap deployment can still change live flows | All approvals require settlement, confirm live checked with the matching attempt, restart, or an audited override; rejection is allowed with a warning | admin |
|
|
17
18
|
| `deploy_timeout` | 504 | Deployment exceeded its deadline (default 60 seconds); approvals wait for settlement or the hard cap (default 5 minutes) | Before the cap, late settlement updates the proposal; after the cap, deployPending.queueReleased is true and the outcome stays unknown. Check the live flow before approving again; approval always re-reads live | admin |
|
|
18
19
|
| `proposal_conflict` | 409 | Editor and proposal changes conflict (conflicts contains field names only) | Read the current flow and resolve the proposal before approval | GET, REST, MCP, admin |
|
|
19
20
|
| `rebase_ack_required` | 409 | The merged result needs review (mergedHash included) | Review the diff and approve with its mergedHash | admin |
|
|
20
21
|
| `rebase_race` | 409 | Flows kept changing during approval | Recheck the proposal and retry approval | admin |
|
|
21
|
-
| `conflict_not_found` | 409 | The requested conflict no longer exists | Reload the proposal and its conflicts | admin |
|
|
22
|
-
| `invalid_choice` | 400 | Invalid manual conflict choice | Use an available live or proposed choice | admin |
|
|
22
|
+
| `conflict_not_found` | 409 | The requested conflict no longer exists | Reload the proposal and its conflicts | REST, MCP, admin |
|
|
23
|
+
| `invalid_choice` | 400 | Invalid manual conflict choice | Use nodeId plus an available live or proposed choice, without replacement payload or revision | REST, MCP, admin |
|
|
23
24
|
| `flow_id_mismatch` | 400 | Resolution tried to change the proposal flow id | Keep the same flowId or submit a new proposal | REST, MCP |
|
|
24
25
|
| `access_link_missing` | 401 | No Bearer / path token | Ask the user for a new access link of the right kind | GET, REST, MCP |
|
|
25
26
|
| `bearer_scheme_required` | 401 | Auth used Basic (or non-Bearer) | Send `Authorization: Bearer <token>` | REST, MCP |
|
|
@@ -49,7 +50,7 @@ GET-link `401` responses include a `WWW-Authenticate: Bearer …` header.
|
|
|
49
50
|
| `version_mismatch` | 409 | Unknown or unavailable `rev` / `expectedRev` snapshot | Re-read flows and resubmit | GET, REST, MCP |
|
|
50
51
|
| `proposal_not_found` | 404 | Unknown proposal id | List proposals and use an existing id | GET, REST, MCP |
|
|
51
52
|
| `proposal_not_pending` | 409 | Action needs an open proposal | Only pending or conflict proposals can withdraw / decide | GET, REST, MCP |
|
|
52
|
-
| `proposal_not_owned` | 403 |
|
|
53
|
+
| `proposal_not_owned` | 403 | Resolve or withdraw another token’s proposal | Use the creating token; lost-token withdrawal requires a full admin session | GET, REST, MCP |
|
|
53
54
|
| `validation_failed` | 400 | Several validation errors at once (message `N validation errors`) | Fix every entry in errors[] and resubmit. | GET, REST, MCP |
|
|
54
55
|
| `duplicate_node_id` | 400 | The same node id appears more than once in a full tab or batch | Give every node a unique id | GET, REST, MCP |
|
|
55
56
|
| `missing_node_id` | 400 | Node / op without an id | Include a non-empty string id | GET, REST, MCP |
|
package/docs/mcp-tools.md
CHANGED
|
@@ -45,7 +45,7 @@ Get detail for one installed node type: defaults (JSON), inputs/outputs (always
|
|
|
45
45
|
|
|
46
46
|
## `get_proposal`
|
|
47
47
|
|
|
48
|
-
Get any proposal by id (all tokens can read). Response includes mine:true|false and, for open proposals (pending or conflict), conflictingProposals when other open work touches the same flow. Lean by default (no payload/baseFlow); pass include_payload=true for full bodies. Cite displayName + reviewUrl when talking to the user.
|
|
48
|
+
Get any proposal by id (all tokens can read). Response includes mine:true|false and, for open proposals (pending or conflict), conflictingProposals when other open work touches the same flow. baseRev aliases expectedRev; baseRevChanged is informational. rebase.state=clean|merged means mergeable; conflict blocks approval. Lean by default (no payload/baseFlow); pass include_payload=true for full bodies. Cite displayName + reviewUrl when talking to the user.
|
|
49
49
|
|
|
50
50
|
| Parameter | Required | Type | Description |
|
|
51
51
|
| --- | --- | --- | --- |
|
|
@@ -109,14 +109,16 @@ List proposals visible to this access link (all proposals by default). Each item
|
|
|
109
109
|
|
|
110
110
|
## `resolve_proposal_conflict`
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
Resolve your own open proposal. Different node/tab fields auto-merge; only fields changed on both sides conflict (x/y never conflict). Choose one current conflict using nodeId + choice, preserving all non-conflicting fields. Alternatively replace the proposal using expectedRev + payload: nodes ops apply to the flow snapshot at expectedRev, NOT the stored proposal or merged result; include every intended proposal edit. Read get_proposal_diff for conflicts and get_flow for a fresh rev. These two modes cannot be mixed.
|
|
113
113
|
|
|
114
114
|
| Parameter | Required | Type | Description |
|
|
115
115
|
| --- | --- | --- | --- |
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
116
|
+
| `choice` | no | "live" \| "proposed" | For this conflict, keep live or proposed values of overlapping fields only. Disjoint edits survive. Use with nodeId, without expectedRev/title/payload. |
|
|
117
|
+
| `expectedRev` | no | string | Revision of the flow snapshot on which replacement payload ops run. Required with payload; normally the current get_flow rev. |
|
|
118
|
+
| `id` | yes | string | Your open proposal id (prop-…) |
|
|
119
|
+
| `nodeId` | no | string | Conflict nodeId from rebase.conflicts; use the flow id for tab/env conflicts. Required with choice. |
|
|
120
|
+
| `payload` | no | object | Replacement body; required with expectedRev, omitted with nodeId + choice |
|
|
121
|
+
| `title` | no | string | Optional replacement title (body replacement mode only) |
|
|
120
122
|
|
|
121
123
|
## `search_flows`
|
|
122
124
|
|
|
@@ -151,7 +153,7 @@ Submit a replace_flow proposal for owner approval (does not deploy). Preferred f
|
|
|
151
153
|
|
|
152
154
|
## `withdraw_proposal`
|
|
153
155
|
|
|
154
|
-
Withdraw one of YOUR open (pending or conflict) proposals (status becomes withdrawn, not rejected). Optional reason. Only the creating access link can withdraw; other tokens get 403. Cite displayName + reviewUrl when telling the user.
|
|
156
|
+
Withdraw one of YOUR open (pending or conflict) proposals (status becomes withdrawn, not rejected). Optional reason. Only the creating access link can withdraw via MCP; other tokens get 403. If it is lost, a full admin session can recover via POST /proposals/{id}/withdraw. Cite displayName + reviewUrl when telling the user.
|
|
155
157
|
|
|
156
158
|
| Parameter | Required | Type | Description |
|
|
157
159
|
| --- | --- | --- | --- |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@com55/node-red-ai-access",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.9",
|
|
4
4
|
"description": "Node-RED plugin that gives AI agents temporary, owner-approved access via REST and MCP",
|
|
5
5
|
"main": "dist/plugin.cjs",
|
|
6
6
|
"types": "dist/plugin.d.cts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
26
|
"!dist/**/*.map",
|
|
27
|
+
"!dist/.production-build-hash",
|
|
27
28
|
"docs",
|
|
28
29
|
"ai-access.cjs",
|
|
29
30
|
"ai-access.html",
|