@bongos/core 1.19.246 → 1.19.248
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/.bongos-core.json +17 -12
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/cli-lib.js +49 -2
- package/scripts/gds/start.js +5 -1
- package/src/module-api.js +5 -1
- package/tests/cli_dispatcher.mjs +133 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.248",
|
|
6
|
+
"core_contract": "1.19.248",
|
|
7
|
+
"source_commit": "bae0bacddfbacf9522b52c969f70eba97a404999",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-18T23:
|
|
9
|
+
"built_at": "2026-08-18T23:41:54.166Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 312,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1330,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1663,
|
|
20
|
+
"tree_sha256": "e28385efcc78d3c913d04bce79e44e95394413c2e484ed96abffa7e8269d377c",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -4347,12 +4347,12 @@
|
|
|
4347
4347
|
{
|
|
4348
4348
|
"path": "package-lock.json",
|
|
4349
4349
|
"mode": "0000644",
|
|
4350
|
-
"sha256": "
|
|
4350
|
+
"sha256": "16750ea8ecab7425183daae2d05d8f4aceb3769a03651b812ca520b5e1323801"
|
|
4351
4351
|
},
|
|
4352
4352
|
{
|
|
4353
4353
|
"path": "package.json",
|
|
4354
4354
|
"mode": "0000644",
|
|
4355
|
-
"sha256": "
|
|
4355
|
+
"sha256": "2dcfaaa9223a22671c43afcc5f0d3abac0db6d73193fc4a402e6d46bd04c519c"
|
|
4356
4356
|
},
|
|
4357
4357
|
{
|
|
4358
4358
|
"path": "public-docs/index.html",
|
|
@@ -4847,7 +4847,7 @@
|
|
|
4847
4847
|
{
|
|
4848
4848
|
"path": "scripts/gds/cli-lib.js",
|
|
4849
4849
|
"mode": "0000644",
|
|
4850
|
-
"sha256": "
|
|
4850
|
+
"sha256": "95574e210b1fd215b10c7664da109b0698b18aa7724daa736bade6d752318f63"
|
|
4851
4851
|
},
|
|
4852
4852
|
{
|
|
4853
4853
|
"path": "scripts/gds/code.js",
|
|
@@ -5572,7 +5572,7 @@
|
|
|
5572
5572
|
{
|
|
5573
5573
|
"path": "scripts/gds/start.js",
|
|
5574
5574
|
"mode": "0000644",
|
|
5575
|
-
"sha256": "
|
|
5575
|
+
"sha256": "cb37801c6ef35a64734af422c9651b287236c4d67f7ed72a63e1cd75a94d8b38"
|
|
5576
5576
|
},
|
|
5577
5577
|
{
|
|
5578
5578
|
"path": "scripts/gds/status.js",
|
|
@@ -5972,7 +5972,7 @@
|
|
|
5972
5972
|
{
|
|
5973
5973
|
"path": "src/module-api.js",
|
|
5974
5974
|
"mode": "0000644",
|
|
5975
|
-
"sha256": "
|
|
5975
|
+
"sha256": "63a931fbe395426e0eaa09e2bb10cbbbdc247de66fcbdd836226dceaa45cac22"
|
|
5976
5976
|
},
|
|
5977
5977
|
{
|
|
5978
5978
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6439,6 +6439,11 @@
|
|
|
6439
6439
|
"mode": "0000644",
|
|
6440
6440
|
"sha256": "b9258f690d5dc7c87bcbb12d510b3071a38669777ae9a92a99e683e76ea15e83"
|
|
6441
6441
|
},
|
|
6442
|
+
{
|
|
6443
|
+
"path": "tests/cli_dispatcher.mjs",
|
|
6444
|
+
"mode": "0000644",
|
|
6445
|
+
"sha256": "3e3e17428a6c8f50ce0512cfacc4e18e0b6a2c506d185fa5f2adf21c9feffe9f"
|
|
6446
|
+
},
|
|
6442
6447
|
{
|
|
6443
6448
|
"path": "tests/cli_exit_no_abort.mjs",
|
|
6444
6449
|
"mode": "0000644",
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.248",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.248",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.248",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/scripts/gds/cli-lib.js
CHANGED
|
@@ -141,6 +141,49 @@ function noKeepAliveDispatcher() {
|
|
|
141
141
|
return _noKeepAliveDispatcher;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// task 1002670 — the vendored Agent above is only valid as a `dispatcher` for
|
|
145
|
+
// Node's built-in fetch when the two undici copies speak the same internal
|
|
146
|
+
// handler protocol. Node 26 bundles undici 8 while package.json overrides pin
|
|
147
|
+
// the vendored copy at ^6, so built-in fetch rejects the v6 Agent with
|
|
148
|
+
// InvalidArgumentError('invalid onError method') / UND_ERR_INVALID_ARG *before
|
|
149
|
+
// any network I/O* — and every CLI then misreported a pure client-side bug as
|
|
150
|
+
// "this instance isn't reachable yet". The pairing can't be predicted from
|
|
151
|
+
// version numbers alone (a v6 Agent works fine under Node 24's undici 7), so
|
|
152
|
+
// the selection is empirical: try the vendored Agent, and on the first
|
|
153
|
+
// incompat rejection drop it for the rest of the process and retry the request
|
|
154
|
+
// once on the default dispatcher.
|
|
155
|
+
function isDispatcherIncompatError(err) {
|
|
156
|
+
const seen = new Set();
|
|
157
|
+
for (let e = err; e && typeof e === 'object' && !seen.has(e); e = e.cause) {
|
|
158
|
+
seen.add(e);
|
|
159
|
+
if (e.code === 'UND_ERR_INVALID_ARG' || e.name === 'InvalidArgumentError') return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Test-only: reset the tried-once singleton so tests/cli_dispatcher.mjs can
|
|
165
|
+
// exercise the selection/fallback from a clean slate regardless of what earlier
|
|
166
|
+
// (real) requests did to it on this Node version.
|
|
167
|
+
function _noKeepAliveResetForTest() {
|
|
168
|
+
_noKeepAliveDispatcher = undefined;
|
|
169
|
+
_noKeepAliveTried = false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function dispatchedFetch(url, init = {}, fetchImpl = fetch) {
|
|
173
|
+
const injected = init.dispatcher !== undefined && init.dispatcher === _noKeepAliveDispatcher;
|
|
174
|
+
try {
|
|
175
|
+
return await fetchImpl(url, init);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (!injected || !isDispatcherIncompatError(err)) throw err;
|
|
178
|
+
// The vendored Agent is incompatible with this Node's built-in fetch.
|
|
179
|
+
// Stop offering it process-wide, then retry on the default dispatcher.
|
|
180
|
+
_noKeepAliveDispatcher = undefined;
|
|
181
|
+
const retry = { ...init };
|
|
182
|
+
delete retry.dispatcher;
|
|
183
|
+
return fetchImpl(url, retry);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
144
187
|
// cliExit(code) — the post-request replacement for process.exit(code). Records the
|
|
145
188
|
// exit status and lets node exit naturally once the loop drains, which is what
|
|
146
189
|
// keeps the UV_HANDLE_CLOSING abort above from firing. Callers must `return` after
|
|
@@ -463,7 +506,7 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
|
|
|
463
506
|
// account billing, so identity is not an auth boundary. anthropic_user_id is
|
|
464
507
|
// still kept in the session as cosmetic attribution; it is simply not
|
|
465
508
|
// transmitted as a header.
|
|
466
|
-
const res = await
|
|
509
|
+
const res = await dispatchedFetch(`${base}${urlPath}`, {
|
|
467
510
|
method,
|
|
468
511
|
headers,
|
|
469
512
|
body: body ? JSON.stringify(body) : null,
|
|
@@ -531,7 +574,7 @@ async function cliClient(opts = {}) {
|
|
|
531
574
|
init = init || {};
|
|
532
575
|
init.headers = Object.assign({ 'User-Agent': CLI_USER_AGENT }, init.headers);
|
|
533
576
|
if (init.dispatcher === undefined) init.dispatcher = noKeepAliveDispatcher();
|
|
534
|
-
const res = await
|
|
577
|
+
const res = await dispatchedFetch(url, init);
|
|
535
578
|
if (res.status === 401 && !allowUnauthenticated && !_unauthExitInFlight) {
|
|
536
579
|
_unauthExitInFlight = true;
|
|
537
580
|
printUnauthGuidanceAndExit();
|
|
@@ -817,6 +860,10 @@ module.exports = {
|
|
|
817
860
|
readSessionToken,
|
|
818
861
|
apiCall,
|
|
819
862
|
cliExit,
|
|
863
|
+
isDispatcherIncompatError,
|
|
864
|
+
dispatchedFetch,
|
|
865
|
+
noKeepAliveDispatcher,
|
|
866
|
+
_noKeepAliveResetForTest,
|
|
820
867
|
cliClient,
|
|
821
868
|
requireSession,
|
|
822
869
|
printUnauthGuidanceAndExit,
|
package/scripts/gds/start.js
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
//
|
|
53
53
|
// Returns JSON if --json is passed (for the slash command to parse cleanly).
|
|
54
54
|
|
|
55
|
-
const { cliClient, requireSession, fmtUsd, printOnboardingNudge, printCostWarning, printNeedsNudge, hallBase, API_BASE, rewardPolicy, costPlusOnlyNote } = require('./cli-lib');
|
|
55
|
+
const { cliClient, requireSession, fmtUsd, printOnboardingNudge, printCostWarning, printNeedsNudge, hallBase, API_BASE, rewardPolicy, costPlusOnlyNote, isDispatcherIncompatError } = require('./cli-lib');
|
|
56
56
|
const { contextPack, formatContextPackCompact } = require('./context-pack');
|
|
57
57
|
const { depsFinding } = require('./preflight');
|
|
58
58
|
|
|
@@ -507,6 +507,10 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
|
|
|
507
507
|
// builder-start skill routes those + the pre-install "bongos not found" case.
|
|
508
508
|
function isConnectionError(err) {
|
|
509
509
|
if (!err) return false;
|
|
510
|
+
// task 1002670: a dispatcher/argument bug (e.g. a vendored undici Agent the
|
|
511
|
+
// built-in fetch rejects with UND_ERR_INVALID_ARG) throws before any network
|
|
512
|
+
// I/O. That is a CLIENT bug — never present it as "instance isn't reachable".
|
|
513
|
+
if (isDispatcherIncompatError(err)) return false;
|
|
510
514
|
const codes = ['ECONNREFUSED', 'ENOTFOUND', 'ECONNRESET', 'EAI_AGAIN', 'ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET'];
|
|
511
515
|
const code = err.code || (err.cause && err.cause.code) || '';
|
|
512
516
|
if (codes.includes(code)) return true;
|
package/src/module-api.js
CHANGED
|
@@ -919,8 +919,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
919
919
|
// landed since 1.19.244 with no explicit bump. run 32196805853. (task 1002620)
|
|
920
920
|
// 1.19.246 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
921
921
|
// landed since 1.19.245 with no explicit bump. run 32197129803. (task 1002620)
|
|
922
|
+
// 1.19.247 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
923
|
+
// landed since 1.19.246 with no explicit bump. run 32197488065. (task 1002620)
|
|
924
|
+
// 1.19.248 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
925
|
+
// landed since 1.19.247 with no explicit bump. run 32198208766. (task 1002620)
|
|
922
926
|
// ---------------------------------------------------------------------------
|
|
923
|
-
const CORE_VERSION = '1.19.
|
|
927
|
+
const CORE_VERSION = '1.19.248'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
924
928
|
|
|
925
929
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
926
930
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// tests/cli_dispatcher.mjs — dispatcher selection/fallback for the CLI fetch path
|
|
2
|
+
// (task 1002670).
|
|
3
|
+
//
|
|
4
|
+
// THE BUG. cli-lib's noKeepAliveDispatcher() builds an Agent from the VENDORED
|
|
5
|
+
// undici (package.json overrides pin ^6) and hands it to Node's BUILT-IN fetch as
|
|
6
|
+
// `dispatcher`. Whether that pairing works is an internal-protocol accident of the
|
|
7
|
+
// two undici versions: it works on Node 24 (built-in undici 7), and on Node 26
|
|
8
|
+
// (built-in undici 8) it throws InvalidArgumentError('invalid onError method') /
|
|
9
|
+
// UND_ERR_INVALID_ARG before any network I/O — and every CLI then misreported the
|
|
10
|
+
// client-side bug as "this instance isn't reachable yet".
|
|
11
|
+
//
|
|
12
|
+
// THE CONTRACT PINNED HERE:
|
|
13
|
+
// 1. isDispatcherIncompatError() recognizes the incompat shapes (top-level and
|
|
14
|
+
// wrapped in `cause`, per built-in fetch's TypeError('fetch failed')) and
|
|
15
|
+
// rejects genuine network-error shapes.
|
|
16
|
+
// 2. dispatchedFetch() retries exactly once on the default dispatcher when the
|
|
17
|
+
// injected vendored Agent is rejected — and does NOT mask other failures or
|
|
18
|
+
// caller-supplied dispatchers.
|
|
19
|
+
// 3. start.js's isConnectionError() returns false for the incompat shapes, so a
|
|
20
|
+
// dispatcher bug can never render the "instance isn't reachable" card.
|
|
21
|
+
// 4. End-to-end on THIS Node: the real built-in fetch + the real vendored Agent
|
|
22
|
+
// reach a live local server (on Node 26 this is the case that used to die).
|
|
23
|
+
//
|
|
24
|
+
// Run: node --test tests/cli_dispatcher.mjs
|
|
25
|
+
|
|
26
|
+
import assert from 'node:assert/strict';
|
|
27
|
+
import { test } from 'node:test';
|
|
28
|
+
import http from 'node:http';
|
|
29
|
+
import { createRequire } from 'node:module';
|
|
30
|
+
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
const cli = require('../scripts/gds/cli-lib.js');
|
|
33
|
+
const { isConnectionError } = require('../scripts/gds/start.js');
|
|
34
|
+
|
|
35
|
+
function incompatError() {
|
|
36
|
+
const cause = new TypeError('invalid onError method');
|
|
37
|
+
cause.name = 'InvalidArgumentError';
|
|
38
|
+
cause.code = 'UND_ERR_INVALID_ARG';
|
|
39
|
+
return new TypeError('fetch failed', { cause });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function connRefusedError() {
|
|
43
|
+
const cause = new Error('connect ECONNREFUSED 127.0.0.1:1');
|
|
44
|
+
cause.code = 'ECONNREFUSED';
|
|
45
|
+
return new TypeError('fetch failed', { cause });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test('isDispatcherIncompatError: recognizes UND_ERR_INVALID_ARG wrapped in cause', () => {
|
|
49
|
+
assert.equal(cli.isDispatcherIncompatError(incompatError()), true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('isDispatcherIncompatError: recognizes a bare InvalidArgumentError (no wrapper)', () => {
|
|
53
|
+
const err = new TypeError('invalid onError method');
|
|
54
|
+
err.name = 'InvalidArgumentError';
|
|
55
|
+
assert.equal(cli.isDispatcherIncompatError(err), true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('isDispatcherIncompatError: rejects genuine network errors', () => {
|
|
59
|
+
assert.equal(cli.isDispatcherIncompatError(connRefusedError()), false);
|
|
60
|
+
assert.equal(cli.isDispatcherIncompatError(new Error('socket hang up')), false);
|
|
61
|
+
assert.equal(cli.isDispatcherIncompatError(null), false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('isDispatcherIncompatError: survives a self-referential cause chain', () => {
|
|
65
|
+
const err = new Error('loop');
|
|
66
|
+
err.cause = err;
|
|
67
|
+
assert.equal(cli.isDispatcherIncompatError(err), false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('end-to-end on this Node: real fetch + vendored Agent reach a live server', async () => {
|
|
71
|
+
const server = http.createServer((req, res) => {
|
|
72
|
+
res.setHeader('content-type', 'application/json');
|
|
73
|
+
res.end(JSON.stringify({ ok: true, path: req.url }));
|
|
74
|
+
});
|
|
75
|
+
await new Promise((r) => server.listen(0, '127.0.0.1', r));
|
|
76
|
+
const { port } = server.address();
|
|
77
|
+
try {
|
|
78
|
+
// Exactly what apiCall does: built-in fetch, vendored no-keep-alive Agent
|
|
79
|
+
// injected. On Node 26 without the fallback this rejected before any I/O.
|
|
80
|
+
const res = await cli.dispatchedFetch(`http://127.0.0.1:${port}/api/gds/me`, {
|
|
81
|
+
method: 'GET',
|
|
82
|
+
headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
dispatcher: cli.noKeepAliveDispatcher(),
|
|
84
|
+
});
|
|
85
|
+
assert.equal(res.status, 200);
|
|
86
|
+
const data = await res.json();
|
|
87
|
+
assert.equal(data.ok, true);
|
|
88
|
+
} finally {
|
|
89
|
+
server.close();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// NOTE: on Node 26 the end-to-end test above already fired the fallback and
|
|
94
|
+
// dropped the singleton, so each test below resets it to a clean slate.
|
|
95
|
+
|
|
96
|
+
test('dispatchedFetch: retries once without dispatcher when the vendored Agent is rejected', async () => {
|
|
97
|
+
cli._noKeepAliveResetForTest();
|
|
98
|
+
const vendored = cli.noKeepAliveDispatcher();
|
|
99
|
+
assert.ok(vendored !== undefined, 'vendored undici Agent should resolve in this repo');
|
|
100
|
+
|
|
101
|
+
const calls = [];
|
|
102
|
+
const fakeFetch = async (url, init) => {
|
|
103
|
+
calls.push(init.dispatcher);
|
|
104
|
+
if (init.dispatcher !== undefined) throw incompatError();
|
|
105
|
+
return { ok: true, status: 200 };
|
|
106
|
+
};
|
|
107
|
+
const res = await cli.dispatchedFetch('http://x/', { dispatcher: vendored }, fakeFetch);
|
|
108
|
+
assert.equal(res.status, 200);
|
|
109
|
+
assert.deepEqual(calls, [vendored, undefined]);
|
|
110
|
+
|
|
111
|
+
// After one incompat rejection the broken Agent is dropped process-wide:
|
|
112
|
+
// later calls get the default dispatcher instead of retrying the broken one.
|
|
113
|
+
assert.equal(cli.noKeepAliveDispatcher(), undefined);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('dispatchedFetch: does not retry a caller-supplied dispatcher, or non-incompat errors', async () => {
|
|
117
|
+
const foreign = { name: 'someone-elses-dispatcher' };
|
|
118
|
+
let calls = 0;
|
|
119
|
+
const rejectingFetch = async () => { calls++; throw incompatError(); };
|
|
120
|
+
await assert.rejects(() => cli.dispatchedFetch('http://x/', { dispatcher: foreign }, rejectingFetch));
|
|
121
|
+
assert.equal(calls, 1);
|
|
122
|
+
|
|
123
|
+
calls = 0;
|
|
124
|
+
const networkFail = async () => { calls++; throw connRefusedError(); };
|
|
125
|
+
await assert.rejects(() => cli.dispatchedFetch('http://x/', {}, networkFail), /fetch failed/);
|
|
126
|
+
assert.equal(calls, 1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('start.js isConnectionError: dispatcher bug is NOT a connection error', () => {
|
|
130
|
+
assert.equal(isConnectionError(incompatError()), false);
|
|
131
|
+
assert.equal(isConnectionError(connRefusedError()), true);
|
|
132
|
+
assert.equal(isConnectionError(new Error('getaddrinfo ENOTFOUND nope.example')), true);
|
|
133
|
+
});
|