@yemi33/minions 0.1.2446 → 0.1.2447
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/dashboard.js +39 -10
- package/engine/api-contracts/orchestration.js +23 -3
- package/engine/api-contracts/paging.js +74 -0
- package/engine/api-contracts/pull-requests.js +10 -0
- package/engine/api-contracts/work-plan-prd.js +32 -5
- package/engine/plan-prd-validation.js +12 -3
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -101,6 +101,7 @@ const { getAgents, getAgentDetail, getPrdInfo, getWorkItems, getDispatchQueue,
|
|
|
101
101
|
getEngineLog, getMetrics, getKnowledgeBaseEntries, getKnowledgeBaseEntriesSnapshot, getProjectGitStatus, timeSince,
|
|
102
102
|
MINIONS_DIR, AGENTS_DIR, ENGINE_DIR, INBOX_DIR, PRD_DIR } = queries;
|
|
103
103
|
const apiContracts = require('./engine/api-contracts');
|
|
104
|
+
const listPaging = require('./engine/api-contracts/paging');
|
|
104
105
|
|
|
105
106
|
// Dev vs binary differentiation. When two dashboards run side-by-side (npm
|
|
106
107
|
// install on 7331, local checkout on 7332), the favicon and title need to
|
|
@@ -3384,7 +3385,11 @@ async function _maxInputMtimeMs(inputs) {
|
|
|
3384
3385
|
// cross-scope display order is post-enrichment (central + every project by
|
|
3385
3386
|
// rowid), which per-scope SQL LIMIT/OFFSET cannot compose, and (c) slicing the
|
|
3386
3387
|
// deterministic cached array is O(page) to serialize — the real transfer win.
|
|
3387
|
-
|
|
3388
|
+
// The bounds come from engine/api-contracts/paging.js — the SAME constants the
|
|
3389
|
+
// route catalog publishes on GET /api/routes, so the clamp and the advertised
|
|
3390
|
+
// contract can never drift (review of PR #1062).
|
|
3391
|
+
const _LIST_PAGE_MAX = listPaging.LIST_PAGE_MAX_LIMIT;
|
|
3392
|
+
const _LIST_PAGE_DEFAULT_LIMIT = listPaging.LIST_PAGE_DEFAULT_LIMIT;
|
|
3388
3393
|
function _parsePageParams(req) {
|
|
3389
3394
|
const params = new URL((req && req.url) || '/', 'http://localhost').searchParams;
|
|
3390
3395
|
const hasLimit = params.has('limit');
|
|
@@ -3394,7 +3399,7 @@ function _parsePageParams(req) {
|
|
|
3394
3399
|
const rawOffset = Number(params.get('offset'));
|
|
3395
3400
|
const limit = hasLimit && Number.isFinite(rawLimit)
|
|
3396
3401
|
? Math.max(1, Math.min(_LIST_PAGE_MAX, Math.floor(rawLimit)))
|
|
3397
|
-
:
|
|
3402
|
+
: _LIST_PAGE_DEFAULT_LIMIT;
|
|
3398
3403
|
const offset = hasOffset && Number.isFinite(rawOffset) ? Math.max(0, Math.floor(rawOffset)) : 0;
|
|
3399
3404
|
return { limit, offset };
|
|
3400
3405
|
}
|
|
@@ -9164,14 +9169,23 @@ const server = http.createServer(async (req, res) => {
|
|
|
9164
9169
|
}
|
|
9165
9170
|
|
|
9166
9171
|
async function handlePlansList(req, res) {
|
|
9172
|
+
let page;
|
|
9167
9173
|
try {
|
|
9168
|
-
|
|
9174
|
+
// Only the optional limit/offset paging params are accepted; every other
|
|
9175
|
+
// query param is still rejected with the canonical 400, and the error
|
|
9176
|
+
// blames the REJECTED param rather than the accepted paging one
|
|
9177
|
+
// (W-ms3ovk83000rb60f). _parsePageParams returns null on the legacy
|
|
9178
|
+
// no-param path so the response stays byte-identical to legacy then.
|
|
9179
|
+
planPrdValidation.validateNoQuery(req, { allow: listPaging.LIST_PAGING_PARAMS });
|
|
9180
|
+
page = _parsePageParams(req);
|
|
9169
9181
|
} catch (e) {
|
|
9170
9182
|
return apiErrorReply(res, e, req);
|
|
9171
9183
|
}
|
|
9172
9184
|
const now = Date.now();
|
|
9173
9185
|
if (_plansCache && (now - _plansCacheTs) < PLANS_CACHE_TTL_MS) {
|
|
9174
|
-
|
|
9186
|
+
// Keep caching the full array and slice per-request AFTER the cache read
|
|
9187
|
+
// so paging never corrupts the cache and `total` reflects the full set.
|
|
9188
|
+
return jsonReply(res, 200, page ? _paginateList(_plansCache, page) : _plansCache);
|
|
9175
9189
|
}
|
|
9176
9190
|
const fsp = fs.promises;
|
|
9177
9191
|
// W-mrffmjaf002f08f0 — PRD JSON is SQL-authoritative (engine/prd-store.js);
|
|
@@ -9325,7 +9339,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
9325
9339
|
plans.sort((a, b) => (b.generatedAt || '').localeCompare(a.generatedAt || ''));
|
|
9326
9340
|
_plansCache = plans;
|
|
9327
9341
|
_plansCacheTs = Date.now();
|
|
9328
|
-
return jsonReply(res, 200, plans);
|
|
9342
|
+
return jsonReply(res, 200, page ? _paginateList(plans, page) : plans);
|
|
9329
9343
|
}
|
|
9330
9344
|
|
|
9331
9345
|
async function handlePlansArchiveRead(req, res, match) {
|
|
@@ -12551,7 +12565,16 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
12551
12565
|
// ── Watches API Handlers ─────────────────────────────────────────────────
|
|
12552
12566
|
|
|
12553
12567
|
async function handleWatchesList(req, res) {
|
|
12554
|
-
|
|
12568
|
+
const all = watchesMod.getWatches();
|
|
12569
|
+
// Optional progressive-load paging (W-ms3ovk83000rb60f). With no paging
|
|
12570
|
+
// params the response is byte-identical to the legacy `{ watches: [...] }`
|
|
12571
|
+
// shape; when a param is present we slice the SAME array and keep the
|
|
12572
|
+
// `watches` key (mapped from _paginateList's `items`) so existing consumers
|
|
12573
|
+
// keep working, adding total/hasMore/offset/limit alongside it.
|
|
12574
|
+
const page = _parsePageParams(req);
|
|
12575
|
+
if (!page) return jsonReply(res, 200, { watches: all });
|
|
12576
|
+
const { items, total, offset, limit, hasMore } = _paginateList(all, page);
|
|
12577
|
+
return jsonReply(res, 200, { watches: items, total, hasMore, offset, limit });
|
|
12555
12578
|
}
|
|
12556
12579
|
|
|
12557
12580
|
async function handleWatchesTargetTypes(req, res) {
|
|
@@ -14829,10 +14852,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
14829
14852
|
},
|
|
14830
14853
|
});
|
|
14831
14854
|
}},
|
|
14832
|
-
{ method: 'GET', path: '/api/schedules', desc: 'Schedule definitions merged with SQL schedule-run state (_lastRun/_lastResult/_lastCompletedAt)', handler: (req, res) => {
|
|
14855
|
+
{ method: 'GET', path: '/api/schedules', desc: 'Schedule definitions merged with SQL schedule-run state (_lastRun/_lastResult/_lastCompletedAt). Optional ?limit=&offset= returns a { items, total, hasMore, offset, limit } page (progressive load, W-ms3bbzry000i6612); with no paging params the full array is byte-identical to legacy.', handler: (req, res) => {
|
|
14856
|
+
const page = _parsePageParams(req);
|
|
14833
14857
|
return serveFreshJson(req, res, {
|
|
14834
14858
|
tag: 'schedules',
|
|
14835
14859
|
inputs: [CONFIG_PATH],
|
|
14860
|
+
variant: page ? ('p' + page.offset + '.' + page.limit) : '',
|
|
14861
|
+
transform: page ? (list) => _paginateList(list, page) : null,
|
|
14836
14862
|
builder: () => {
|
|
14837
14863
|
// Read config.json directly via queries.getConfig() rather than
|
|
14838
14864
|
// calling reloadConfig() — the latter cascades into
|
|
@@ -14857,12 +14883,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
14857
14883
|
},
|
|
14858
14884
|
});
|
|
14859
14885
|
}},
|
|
14860
|
-
{ method: 'GET', path: '/api/pipelines', desc: 'Pipeline definitions merged with last-5 SQL-backed runs', handler: (req, res) => {
|
|
14886
|
+
{ method: 'GET', path: '/api/pipelines', desc: 'Pipeline definitions merged with last-5 SQL-backed runs. Optional ?limit=&offset= returns a { items, total, hasMore, offset, limit } page (progressive load, W-ms3bbzry000i6612); with no paging params the full array is byte-identical to legacy.', handler: (req, res) => {
|
|
14887
|
+
const page = _parsePageParams(req);
|
|
14861
14888
|
return serveFreshJson(req, res, {
|
|
14862
14889
|
tag: 'pipelines',
|
|
14863
14890
|
inputs: [
|
|
14864
14891
|
path.join(MINIONS_DIR, 'pipelines'),
|
|
14865
14892
|
],
|
|
14893
|
+
variant: page ? ('p' + page.offset + '.' + page.limit) : '',
|
|
14894
|
+
transform: page ? (list) => _paginateList(list, page) : null,
|
|
14866
14895
|
builder: () => {
|
|
14867
14896
|
try {
|
|
14868
14897
|
const pl = require('./engine/pipeline');
|
|
@@ -15100,7 +15129,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
15100
15129
|
|
|
15101
15130
|
// Plans
|
|
15102
15131
|
{ method: 'POST', path: '/api/plan', desc: 'Create a plan work item that chains to PRD on completion', params: `title, description?, ${WORK_ITEM_PRIORITY_PARAM_HINT}, project? (string OR array for cross-repo plans), agent?, branch_strategy? or branchStrategy?`, handler: handlePlanCreate },
|
|
15103
|
-
{ method: 'GET', path: '/api/plans', desc: 'List plan files (.md drafts + .json PRDs)', handler: handlePlansList },
|
|
15132
|
+
{ method: 'GET', path: '/api/plans', desc: 'List plan files (.md drafts + .json PRDs). Optional ?limit=&offset= returns a { items, total, hasMore, offset, limit } page (progressive load, W-ms3bbzry000i6612); with no paging params the full array is byte-identical to legacy.', handler: handlePlansList },
|
|
15104
15133
|
{ method: 'POST', path: '/api/plans/trigger-verify', desc: 'Manually trigger verification for a completed plan', params: 'file', handler: handlePlansTriggerVerify },
|
|
15105
15134
|
{ method: 'POST', path: '/api/plans/approve', desc: 'Approve a plan for execution', params: 'file, approvedBy?, forceRegen?, skipRegen?', handler: handlePlansApprove },
|
|
15106
15135
|
{ method: 'POST', path: '/api/plans/pause', desc: 'Pause a plan (stops materialization + resets active items)', params: 'file', handler: handlePlansPause },
|
|
@@ -15996,7 +16025,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
15996
16025
|
{ method: 'POST', path: '/api/schedules/run-now', desc: 'Manually enqueue the work item for a schedule', params: 'id', handler: handleSchedulesRunNow },
|
|
15997
16026
|
|
|
15998
16027
|
// Watches
|
|
15999
|
-
{ method: 'GET', path: '/api/watches', desc: 'List all watches', handler: handleWatchesList },
|
|
16028
|
+
{ method: 'GET', path: '/api/watches', desc: 'List all watches. Optional ?limit=&offset= returns a wrapped { watches, total, hasMore, offset, limit } page (progressive load, W-ms3bbzry000i6612); with no paging params the response is byte-identical to legacy { watches: [...] }.', handler: handleWatchesList },
|
|
16000
16029
|
{ method: 'GET', path: '/api/watches/target-types', desc: 'List registered watch target types and their valid conditions', handler: handleWatchesTargetTypes },
|
|
16001
16030
|
{ method: 'GET', path: '/api/watches/action-types', desc: 'List registered follow-up action types (notify, dispatch-work-item, webhook, ...)', handler: handleWatchesActionTypes },
|
|
16002
16031
|
{ method: 'GET', path: /^\/api\/watches\/([\w-]+)\/history$/, template: '/api/watches/:id/history', desc: 'Read the persisted evaluation history (last 25 checks) for a watch', handler: handleWatchHistory },
|
|
@@ -4,6 +4,23 @@ const keepProcesses = require('../keep-process-sweep');
|
|
|
4
4
|
const managedSpawn = require('../managed-spawn');
|
|
5
5
|
const managedSpecNameMax = managedSpawn.validateManagedSpecName('x').maxLength;
|
|
6
6
|
const agentApiValidation = require('../agent-api-validation');
|
|
7
|
+
const paging = require('./paging');
|
|
8
|
+
|
|
9
|
+
// The optional progressive-load paging window is the ONLY caller-supplied input
|
|
10
|
+
// these list endpoints read; unknown params are ignored (not rejected) because
|
|
11
|
+
// they never reach a validator.
|
|
12
|
+
function pagedListContract() {
|
|
13
|
+
return {
|
|
14
|
+
audit: 'audited',
|
|
15
|
+
query: paging.listPagingQuery(
|
|
16
|
+
'Optional progressive-load paging window; any other query parameter is ignored, not rejected.',
|
|
17
|
+
),
|
|
18
|
+
constraints: [
|
|
19
|
+
...paging.listPagingClampConstraints(),
|
|
20
|
+
{ kind: 'ignores-unknown-query-params', location: 'query' },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
7
24
|
|
|
8
25
|
function absentOrEmptyOperatorBody() {
|
|
9
26
|
return {
|
|
@@ -74,9 +91,12 @@ module.exports = {
|
|
|
74
91
|
],
|
|
75
92
|
overrides: {
|
|
76
93
|
'GET /api/keep-processes': { noInput: true },
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
// These three consume the optional ?limit=&offset= paging window
|
|
95
|
+
// (W-ms3ovk83000rb60f) — they are NOT input-less, so the catalog must publish
|
|
96
|
+
// the real parameters instead of certifying them noInput.
|
|
97
|
+
'GET /api/schedules': pagedListContract(),
|
|
98
|
+
'GET /api/pipelines': pagedListContract(),
|
|
99
|
+
'GET /api/watches': pagedListContract(),
|
|
80
100
|
'GET /api/watches/target-types': { noInput: true },
|
|
81
101
|
'GET /api/watches/action-types': { noInput: true },
|
|
82
102
|
'GET /api/watches/<id>/history': {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Single source of truth for the OPTIONAL progressive-load paging query surface
|
|
4
|
+
// (`?limit=&offset=`) shared by the large list endpoints.
|
|
5
|
+
//
|
|
6
|
+
// dashboard.js's `_parsePageParams` clamps incoming values with these bounds and
|
|
7
|
+
// the api-contract owner modules publish the same bounds on `GET /api/routes`,
|
|
8
|
+
// so the runtime behavior and the advertised contract cannot drift. Adding
|
|
9
|
+
// paging to another list endpoint means declaring `listPagingQuery()` on its
|
|
10
|
+
// contract — otherwise the route keeps certifying itself input-less while
|
|
11
|
+
// silently consuming caller input (review of PR #1062).
|
|
12
|
+
|
|
13
|
+
const LIST_PAGE_MAX_LIMIT = 1000;
|
|
14
|
+
const LIST_PAGE_DEFAULT_LIMIT = 50;
|
|
15
|
+
const LIST_PAGING_PARAMS = Object.freeze(['limit', 'offset']);
|
|
16
|
+
|
|
17
|
+
function listPagingQueryFields() {
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
name: 'limit',
|
|
21
|
+
type: 'integer',
|
|
22
|
+
required: false,
|
|
23
|
+
min: 1,
|
|
24
|
+
max: LIST_PAGE_MAX_LIMIT,
|
|
25
|
+
default: LIST_PAGE_DEFAULT_LIMIT,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'offset',
|
|
29
|
+
type: 'integer',
|
|
30
|
+
required: false,
|
|
31
|
+
min: 0,
|
|
32
|
+
default: 0,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// `rationale` documents the per-route behavior for the params this contract does
|
|
38
|
+
// NOT list (rejected on /api/plans, ignored on the serveFreshJson endpoints).
|
|
39
|
+
function listPagingQuery(rationale) {
|
|
40
|
+
const query = { policy: 'optional', fields: listPagingQueryFields() };
|
|
41
|
+
if (rationale) query.rationale = String(rationale);
|
|
42
|
+
return query;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Out-of-range/non-numeric paging values are clamped to the published bounds
|
|
46
|
+
// rather than rejected, so the clamp is part of the contract, not a 400 path.
|
|
47
|
+
function listPagingClampConstraints() {
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
kind: 'clamped-limit',
|
|
51
|
+
field: 'limit',
|
|
52
|
+
location: 'query',
|
|
53
|
+
min: 1,
|
|
54
|
+
max: LIST_PAGE_MAX_LIMIT,
|
|
55
|
+
default: LIST_PAGE_DEFAULT_LIMIT,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
kind: 'clamped-offset',
|
|
59
|
+
field: 'offset',
|
|
60
|
+
location: 'query',
|
|
61
|
+
min: 0,
|
|
62
|
+
default: 0,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
LIST_PAGE_MAX_LIMIT,
|
|
69
|
+
LIST_PAGE_DEFAULT_LIMIT,
|
|
70
|
+
LIST_PAGING_PARAMS,
|
|
71
|
+
listPagingQueryFields,
|
|
72
|
+
listPagingQuery,
|
|
73
|
+
listPagingClampConstraints,
|
|
74
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { PR_ACTIONS } = require('../pr-action');
|
|
4
4
|
const { EXECUTION_TARGET_IDS } = require('../pr-fix-target');
|
|
5
5
|
const { PR_FIX_CAUSE } = require('../shared');
|
|
6
|
+
const paging = require('./paging');
|
|
6
7
|
|
|
7
8
|
const PR_REFERENCE_FORMATS = Object.freeze([
|
|
8
9
|
'GitHub PR URL',
|
|
@@ -52,6 +53,15 @@ module.exports = {
|
|
|
52
53
|
overrides: {
|
|
53
54
|
'GET /api/pull-requests': {
|
|
54
55
|
audit: 'audited',
|
|
56
|
+
// Consumes the optional ?limit=&offset= paging window (W-ms3bbzry000i6612),
|
|
57
|
+
// clamped rather than rejected — declared so the catalog publishes it.
|
|
58
|
+
query: paging.listPagingQuery(
|
|
59
|
+
'Optional progressive-load paging window; any other query parameter is ignored, not rejected.',
|
|
60
|
+
),
|
|
61
|
+
constraints: [
|
|
62
|
+
...paging.listPagingClampConstraints(),
|
|
63
|
+
{ kind: 'ignores-unknown-query-params', location: 'query' },
|
|
64
|
+
],
|
|
55
65
|
},
|
|
56
66
|
'GET /api/prs/<id>': {
|
|
57
67
|
audit: 'audited',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const paging = require('./paging');
|
|
4
|
+
|
|
3
5
|
const AUDITED_NEGATIVE_TESTS = Object.freeze([{
|
|
4
6
|
strategy: 'isolated-http-invalid-input-state-snapshot',
|
|
5
7
|
expectedStatus: '400-or-409',
|
|
@@ -57,7 +59,24 @@ module.exports = {
|
|
|
57
59
|
query: { policy: 'none', rationale: 'Handler rejects any query string (planPrdValidation.validateNoQuery).' },
|
|
58
60
|
},
|
|
59
61
|
'GET /api/plans': {
|
|
60
|
-
|
|
62
|
+
audit: 'audited',
|
|
63
|
+
// Accepts the optional ?limit=&offset= paging window (W-ms3ovk83000rb60f);
|
|
64
|
+
// every OTHER query parameter is still rejected with the canonical 400 via
|
|
65
|
+
// planPrdValidation.validateNoQuery(req, { allow: LIST_PAGING_PARAMS }).
|
|
66
|
+
query: paging.listPagingQuery(
|
|
67
|
+
'Optional progressive-load paging window; any other query parameter is rejected (planPrdValidation.validateNoQuery).',
|
|
68
|
+
),
|
|
69
|
+
constraints: [
|
|
70
|
+
...paging.listPagingClampConstraints(),
|
|
71
|
+
{
|
|
72
|
+
kind: 'reject-unknown-query-params',
|
|
73
|
+
location: 'query',
|
|
74
|
+
allowed: [...paging.LIST_PAGING_PARAMS],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
negativeTests: [
|
|
78
|
+
{ strategy: 'reject-unknown-query-param-blaming-the-rejected-name', expectedStatus: 400 },
|
|
79
|
+
],
|
|
61
80
|
},
|
|
62
81
|
'POST /api/work-items': {
|
|
63
82
|
audit: 'audited',
|
|
@@ -248,10 +267,18 @@ module.exports = {
|
|
|
248
267
|
},
|
|
249
268
|
},
|
|
250
269
|
'GET /api/work-items': {
|
|
251
|
-
// Polled list endpoint
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
|
|
270
|
+
// Polled list endpoint. Reads no body/headers; the only caller-supplied
|
|
271
|
+
// input is the optional ?limit=&offset= paging window (W-ms3bbzry000i6612),
|
|
272
|
+
// which is clamped rather than rejected. Declared explicitly so the catalog
|
|
273
|
+
// publishes the real parameters instead of certifying the route input-less.
|
|
274
|
+
audit: 'audited',
|
|
275
|
+
query: paging.listPagingQuery(
|
|
276
|
+
'Optional progressive-load paging window; any other query parameter is ignored, not rejected.',
|
|
277
|
+
),
|
|
278
|
+
constraints: [
|
|
279
|
+
...paging.listPagingClampConstraints(),
|
|
280
|
+
{ kind: 'ignores-unknown-query-params', location: 'query' },
|
|
281
|
+
],
|
|
255
282
|
},
|
|
256
283
|
'GET /api/work-items/archive': {
|
|
257
284
|
// Archived-item listing: reads no query/body. Affirmed input-less.
|
|
@@ -65,11 +65,20 @@ function validateBody(body) {
|
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// Reject caller-supplied query params. `options.allow` lists the params the
|
|
69
|
+
// endpoint genuinely accepts (e.g. the optional `limit`/`offset` paging window);
|
|
70
|
+
// those are filtered out BEFORE the offending name is chosen, so the message and
|
|
71
|
+
// the machine-readable `field`/`path` always blame a rejected param rather than
|
|
72
|
+
// an accepted one (review of PR #1062).
|
|
73
|
+
function validateNoQuery(req, options = {}) {
|
|
74
|
+
const allowed = new Set(Array.isArray(options.allow) ? options.allow : []);
|
|
69
75
|
const parsed = new URL(req?.url || '/', 'http://localhost');
|
|
70
|
-
const names = [...new Set(parsed.searchParams.keys())];
|
|
76
|
+
const names = [...new Set(parsed.searchParams.keys())].filter(name => !allowed.has(name));
|
|
71
77
|
if (names.length > 0) {
|
|
72
|
-
|
|
78
|
+
const message = allowed.size > 0
|
|
79
|
+
? `This endpoint only accepts the query parameters ${[...allowed].join(', ')}: ${names.join(', ')}`
|
|
80
|
+
: `This endpoint does not accept query parameters: ${names.join(', ')}`;
|
|
81
|
+
inputError(message, {
|
|
73
82
|
code: 'unexpected-query',
|
|
74
83
|
field: names[0],
|
|
75
84
|
path: `query.${names[0]}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2447",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|