@passioncode-ai/fabric-agent-adapter 0.82.0 → 0.84.1
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 +169 -3
- package/README.md +7 -3
- package/SECURITY.md +4 -3
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +7 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +4 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +18 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +16 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_identifiers.py +1 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +31 -15
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +38 -12
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +21 -148
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check_cli.py +164 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +15 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +4 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +14 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/README.md +76 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/config.example.json +28 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/data_agent.py +366 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +32 -17
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support-agent.mjs +115 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +63 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/a2a.md +98 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +28 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +13 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +11 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/ops-digest.md +75 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +25 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +10 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +70 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_a2a.py +158 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a-server.mjs +275 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a.mjs +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +5 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +55 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +47 -31
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +25 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-connections.mjs +27 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +19 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate-rules.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +7 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +22 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +4 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +7 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-identifiers.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +21 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +16 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +25 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ops-digest.mjs +456 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package-schema.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +18 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-page-links.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +19 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +56 -19
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +16 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-socket-proof.mjs +181 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +20 -15
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet-identity.mjs +469 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a.py +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a_server.py +405 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +7 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +17 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +98 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +37 -48
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +25 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +28 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +19 -13
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +24 -14
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate_rules.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +4 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +32 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +19 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +10 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_identifiers.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +56 -96
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +99 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +16 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_mandate.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +44 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ops_digest.py +648 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +17 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package_schema.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_page_links.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +21 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +108 -108
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +38 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_socket_proof.py +391 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +19 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet_identity.py +493 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +632 -104
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample-remote-service.mjs +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +17 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/tailnet-groups.schema.json +69 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +6 -5
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agent-templates.md +16 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +14 -9
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +4 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +18 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +4 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/admit.py +182 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +14 -9
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +7 -4
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +10 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +2 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +16 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +79 -8
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +147 -45
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// Work a service holds by its own limits, said to its caller and its operator (FAA-298): a held job's statusMessage in
|
|
2
3
|
// the contract's pause grammar (`quota-exhausted[ until <until>]: waiting <reason> since <since>[: <message>]`; the
|
|
3
4
|
// pinned interop-job schema is closed, so `waiting {reason, since, until, message}` rides statusMessage, never a field
|
|
4
5
|
// of the job), one push per hold, and the operator's scoped lift. The twin of fabric_holds.py, held to the same
|
|
5
|
-
// vectors (test/fixtures/holds/waiting-vectors.json).
|
|
6
|
+
// vectors (test/fixtures/holds/waiting-vectors.json). A record written since 0.83.0 carries `squash: 'ecmascript'`; one
|
|
7
|
+
// without it is matched under both squashes, as 0.81.0's Python twin squashed names with str.split() (FAA-320,
|
|
8
|
+
// test/fixtures/holds/legacy-squash-vectors.json). Part of the Node service kit (layer L3, after fabric-interop).
|
|
6
9
|
// Node 20+, no dependency.
|
|
7
10
|
import crypto from 'node:crypto';
|
|
8
11
|
import fs from 'node:fs';
|
|
@@ -40,6 +43,17 @@ const reasonOk = (reason, what) => {
|
|
|
40
43
|
// (FAA-316): Python's str.split() also takes U+001C to U+001F and U+0085 and not U+FEFF.
|
|
41
44
|
const SPACES = /[\t\n\v\f\r \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/g;
|
|
42
45
|
const squash = (text) => String(text).replace(SPACES, ' ').replace(/^ | $/g, '');
|
|
46
|
+
/** What a record written since 0.83.0 carries: its names were squashed with ECMAScript's whitespace (FAA-320). */
|
|
47
|
+
export const SQUASH = 'ecmascript';
|
|
48
|
+
// Python's str.isspace() class, the one 0.81.0's Python twin squashed a name with (`" ".join(text.split())`).
|
|
49
|
+
const PY_SPACES = /[\t\n\v\f\r\u001c-\u001f \u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+/g;
|
|
50
|
+
const legacySquash = (text) => String(text).replace(PY_SPACES, ' ').replace(/^ | $/g, '');
|
|
51
|
+
// The stored forms `caller` matches in `record`: the ECMAScript squash, and for a record with no `squash` mark (written
|
|
52
|
+
// before 0.83.0) also the squash 0.81.0's Python twin wrote (FAA-320). null matches only null.
|
|
53
|
+
const names = (caller, record) => {
|
|
54
|
+
if (typeof caller !== 'string') return [caller];
|
|
55
|
+
return record.squash === SQUASH ? [squash(caller)] : [squash(caller), legacySquash(caller)];
|
|
56
|
+
};
|
|
43
57
|
|
|
44
58
|
/** A held job's statusMessage; the message is shortened, never refused, to fit 500 characters. */
|
|
45
59
|
export function waitingMessage(reason, since, until = null, message = null) {
|
|
@@ -149,6 +163,7 @@ export class HoldBook {
|
|
|
149
163
|
|
|
150
164
|
/** Hold `work` for `caller` by `reason`: {hold, statusMessage, push}; push only when this call opened the hold. */
|
|
151
165
|
hold({ caller, work, reason, until = null, message = null, jobId = null, now = null }) {
|
|
166
|
+
const rawCaller = caller;
|
|
152
167
|
caller = text(caller, 'caller');
|
|
153
168
|
work = text(work, 'work');
|
|
154
169
|
reasonOk(reason, 'hold');
|
|
@@ -158,10 +173,10 @@ export class HoldBook {
|
|
|
158
173
|
}
|
|
159
174
|
const data = this.load();
|
|
160
175
|
HoldBook.expire(data, utcTime(at));
|
|
161
|
-
let hold = data.holds.find((h) => h.closed === null && h.reason === reason && h.caller
|
|
176
|
+
let hold = data.holds.find((h) => h.closed === null && h.reason === reason && names(rawCaller, h).includes(h.caller));
|
|
162
177
|
const opened = hold === undefined;
|
|
163
178
|
if (opened) {
|
|
164
|
-
hold = { id: newId(), reason, caller, work, since: at, until, jobs: [], closed: null };
|
|
179
|
+
hold = { id: newId(), reason, caller, work, since: at, until, jobs: [], closed: null, squash: SQUASH };
|
|
165
180
|
data.holds.push(hold);
|
|
166
181
|
}
|
|
167
182
|
if (jobId !== null && !hold.jobs.includes(String(jobId))) hold.jobs.push(String(jobId));
|
|
@@ -176,7 +191,7 @@ export class HoldBook {
|
|
|
176
191
|
release({ reason, caller, now = null }) {
|
|
177
192
|
const at = this.now(now);
|
|
178
193
|
const data = this.load();
|
|
179
|
-
const hold = data.holds.find((h) => h.closed === null && h.reason === reason && h.caller
|
|
194
|
+
const hold = data.holds.find((h) => h.closed === null && h.reason === reason && names(caller, h).includes(h.caller));
|
|
180
195
|
if (!hold) return null;
|
|
181
196
|
hold.closed = 'released';
|
|
182
197
|
hold.closedAt = at;
|
|
@@ -211,12 +226,12 @@ export class HoldBook {
|
|
|
211
226
|
throw new ServiceError('A raised limit needs its window\'s end (until): it falls back when the window ends.');
|
|
212
227
|
}
|
|
213
228
|
const record = { id: newId(), reason, caller: caller === null ? null : text(caller, 'caller'),
|
|
214
|
-
job: jobId === null ? null : String(jobId), from: at, until, [kind]: size, decision: { ...decision }, at };
|
|
229
|
+
job: jobId === null ? null : String(jobId), from: at, until, [kind]: size, decision: { ...decision }, at, squash: SQUASH };
|
|
215
230
|
if (kind !== 'limit') record.left = size;
|
|
216
231
|
const data = this.load();
|
|
217
232
|
HoldBook.expire(data, utcTime(at));
|
|
218
233
|
for (const hold of data.holds) {
|
|
219
|
-
if (hold.closed === null && hold.reason === reason && (
|
|
234
|
+
if (hold.closed === null && hold.reason === reason && (caller === null || names(caller, hold).includes(hold.caller))
|
|
220
235
|
&& (jobId === null || hold.jobs.includes(String(jobId)))) {
|
|
221
236
|
hold.closed = 'lifted';
|
|
222
237
|
hold.closedAt = at;
|
|
@@ -234,7 +249,7 @@ export class HoldBook {
|
|
|
234
249
|
|
|
235
250
|
static matching(data, reason, caller, jobId, at, kind) {
|
|
236
251
|
return data.lifts.filter((l) => l.reason === reason && kind in l && HoldBook.live(l, at)
|
|
237
|
-
&& (l.caller === null || l.caller
|
|
252
|
+
&& (l.caller === null || names(caller, l).includes(l.caller)) && (l.job === null || l.job === jobId));
|
|
238
253
|
}
|
|
239
254
|
|
|
240
255
|
/** Whether a lift lets work past the limit: {admitted: true, lift, left} spends one job or `costUsd`, else {admitted: false}. */
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// An outbound HTTP client for a service: one deadline across every attempt, bounded retries with jittered backoff,
|
|
2
3
|
// `Retry-After` honoured up to a cap, a response size cap, and no secret in an error. The twin of fabric_http.py:
|
|
3
4
|
// the same rules, the same cases (test/fixtures/kit-http-cases.json).
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
import http from 'node:http';
|
|
23
24
|
import net from 'node:net';
|
|
24
25
|
import { CheckedAgent, ListenerRefused, TABLE_RESTRICTED, UNKNOWN, method as tableMethod, owner as ownerOf, refusal, serverEnd } from './fabric-peer-owner.mjs';
|
|
26
|
+
import { loopbackHost } from './fabric-socket-proof.mjs';
|
|
25
27
|
|
|
26
28
|
export const RETRY_STATUSES = Object.freeze([408, 425, 429, 500, 502, 503, 504]);
|
|
27
29
|
export const IDEMPOTENT_METHODS = Object.freeze(['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE']);
|
|
@@ -78,11 +80,8 @@ export function redactUrl(url) {
|
|
|
78
80
|
return `${parts.protocol}//${parts.host}${parts.pathname}`;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (h === 'localhost' || h.endsWith('.localhost') || h === '::1') return true;
|
|
84
|
-
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
|
|
85
|
-
}
|
|
83
|
+
// Loopback as new URL leaves the host: the kit's one rule, shared with fabric-delegation (FAA-187 (15)).
|
|
84
|
+
const isLoopback = loopbackHost;
|
|
86
85
|
|
|
87
86
|
// Refuse what this client never sends: another scheme, no host, credentials in the URL, plain http off loopback.
|
|
88
87
|
export function checkUrl(url, { insecure = false } = {}) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// The Fabric hub's door, as a service agent reaches it (fabric-hub/0.1; Fabric ADR-0115 §1, ADR-0149 §4; FAA-12b).
|
|
2
3
|
// The twin of fabric_hub.py, same rules and the same answers; Node built-ins only.
|
|
3
4
|
//
|
|
@@ -79,7 +80,8 @@ export function fabricRoot(platform = process.platform, env = process.env) {
|
|
|
79
80
|
/**
|
|
80
81
|
* true: a process of this OS account runs under pid; false: none does. null (falsy): a process of another account does
|
|
81
82
|
* (EPERM), as when the id hub.json names was reused: not verified, never called running (FAA-304, the session hooks'
|
|
82
|
-
* answer since FAA-254). Never signals it.
|
|
83
|
+
* answer since FAA-254). Never signals it. On Windows libuv opens the process for terminate, query and synchronize, so
|
|
84
|
+
* an elevated process of the same user answers EPERM too; the Python twin asks OpenProcess for the same access (FAA-323).
|
|
83
85
|
*/
|
|
84
86
|
export function pidAlive(pid) {
|
|
85
87
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -192,7 +194,7 @@ export function readCredential(file) {
|
|
|
192
194
|
* is HubError hub-unavailable (retryable); a listener that is not this OS account's process is HubError foreign-user
|
|
193
195
|
* (retryable) before the bearer is sent (DEC-0154 HA-2): the connection is checked from the socket table first.
|
|
194
196
|
*/
|
|
195
|
-
export function postMcp(origin, body, bearer, { session, timeout = 15000, method = 'POST', protocolVersion, agent = HUB_AGENT } = {}) {
|
|
197
|
+
export function postMcp(origin, body, bearer, { session, timeout = 15000, method = 'POST', protocolVersion, agent = HUB_AGENT, deadline = 0 } = {}) {
|
|
196
198
|
const m = LOOPBACK_ORIGIN.exec(origin);
|
|
197
199
|
if (!m) return Promise.reject(new HubError('not-a-hub', 'Only the hub\'s loopback origin is used.'));
|
|
198
200
|
const payload = method === 'DELETE' ? null : Buffer.from(JSON.stringify(body));
|
|
@@ -224,6 +226,9 @@ export function postMcp(origin, body, bearer, { session, timeout = 15000, method
|
|
|
224
226
|
res.on('error', () => reject(new HubError('hub-unavailable', 'Fabric\'s hub did not answer (the answer broke off).', true)));
|
|
225
227
|
});
|
|
226
228
|
req.on('timeout', () => req.destroy(new Error('timeout')));
|
|
229
|
+
// `deadline` (ms) bounds the whole request, where `timeout` bounds each socket read (FAA-187 (12))
|
|
230
|
+
const timer = deadline > 0 ? setTimeout(() => req.destroy(new Error('deadline')), deadline) : null;
|
|
231
|
+
if (timer) req.on('close', () => clearTimeout(timer));
|
|
227
232
|
req.on('error', (e) => reject(e instanceof ListenerRefused
|
|
228
233
|
? listenerRefusal(e)
|
|
229
234
|
: new HubError('hub-unavailable', `Fabric's hub did not answer (${e.code ?? e.message}).`, true)));
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-identifiers.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// Identifier equality (contract DEC-0076): two identifiers are the same when their normal forms are equal. A port of the
|
|
2
3
|
// contract's src/identifiers.ts (types removed), held to its fixtures/vectors/identifiers.json; the twin of
|
|
3
4
|
// fabric_identifiers.py. Nothing here throws.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// fabric-interop/0.1, the rule layer: the extension's names and limits, its errors, the trace context (C3.4), a usage
|
|
2
3
|
// object's reading (C3.2) and the job limits and uncertain effects (DEC-0087). Every function answers by a rule and
|
|
3
4
|
// touches no file; fabric-interop.mjs builds the tools, the envelope and the job store on it and re-exports every public
|
|
@@ -7,7 +8,7 @@ import crypto from 'node:crypto';
|
|
|
7
8
|
import { ServiceError } from './fabric-base.mjs';
|
|
8
9
|
import { jcs, refusalFromAction, refusalToolResult } from './fabric-delegation.mjs';
|
|
9
10
|
import { actionDecision, decisionShape, jobCapability } from './fabric-actions.mjs'; // DEC-0121
|
|
10
|
-
import { EFFECT_CLASSES, OPERATOR_SOURCE_PROTOCOL, mandateCovers, mandateProblem, withoutSource } from './fabric-mandate.mjs'; // DEC-0125, DEC-0126
|
|
11
|
+
import { EFFECT_CLASSES, OPERATOR_SOURCE_PROTOCOL, isAgentId, mandateCovers, mandateProblem, withoutSource } from './fabric-mandate.mjs'; // DEC-0125, DEC-0126
|
|
11
12
|
import { isString } from './fabric-jsvalue.mjs'; // the core's (FAA-65)
|
|
12
13
|
import { isObject } from './fabric-jsvalue.mjs'; // the core's (FAA-65)
|
|
13
14
|
|
|
@@ -277,6 +278,25 @@ export function jobStep({ mandate, tier, now, self, capability, key, effect, cal
|
|
|
277
278
|
/** The project of a call whose project could not be read: never in a mandate's projects (FAA-139). */
|
|
278
279
|
export const UNREADABLE_PROJECT = '\u0000unreadable';
|
|
279
280
|
|
|
281
|
+
/**
|
|
282
|
+
* The caller's agent id when a service's own hook answered something no chain can hold (an id the registry refuses, not
|
|
283
|
+
* a string, or the hook threw): passed as callerAgent it matches no agent, so every mandate reads
|
|
284
|
+
* mandate-not-for-this-call (fail closed, FAA-135). Never an agent id (isAgentId refuses it).
|
|
285
|
+
*/
|
|
286
|
+
export const UNREADABLE_AGENT = '\u0000unreadable-agent';
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* FAA-135: the caller's agent id as a service's own hook named it from a per-agent credential (contract delegation.md,
|
|
290
|
+
* `surfaces.mcp.auth: own`), read as the mandate chain binds it: pass the answer as actionGate's and jobStep's
|
|
291
|
+
* `callerAgent`. null or undefined (no per-agent credential) is null, and the chain's end alone binds the mandate, as
|
|
292
|
+
* before; an agent id the registry accepts is that id; anything else is UNREADABLE_AGENT. A hook that throws: pass
|
|
293
|
+
* UNREADABLE_AGENT. The Python twin's McpToolServer(identify_agent=) reads it so; never a name the call carries.
|
|
294
|
+
*/
|
|
295
|
+
export function identifiedAgent(value) {
|
|
296
|
+
if (value === null || value === undefined) return null;
|
|
297
|
+
return isAgentId(value) ? value : UNREADABLE_AGENT;
|
|
298
|
+
}
|
|
299
|
+
|
|
280
300
|
/** A spend estimate as a mandate reads it (FAA-139): a finite number stays, anything else is none (a ceiling refuses it). */
|
|
281
301
|
export function mandateSpend(value) {
|
|
282
302
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// Reference kit for the fabric-interop/0.1 extension — Node.js 20+, no dependencies.
|
|
2
3
|
// The Node twin of fabric_interop.py: the same rules and the same job file format, so a
|
|
3
4
|
// job written by one kit is read by the other. The minimal MCP dispatcher is Python-only;
|
|
@@ -43,6 +44,8 @@ export {
|
|
|
43
44
|
mandateSpend,
|
|
44
45
|
keptStep,
|
|
45
46
|
UNREADABLE_PROJECT,
|
|
47
|
+
UNREADABLE_AGENT,
|
|
48
|
+
identifiedAgent,
|
|
46
49
|
actionRefusalToolResult,
|
|
47
50
|
usageFindings,
|
|
48
51
|
usageToward,
|
|
@@ -271,6 +274,9 @@ export class JobStore {
|
|
|
271
274
|
requestInput(jobId, inputRequests, statusMessage, { steps } = {}) {
|
|
272
275
|
const entries = Object.entries(inputRequests ?? {});
|
|
273
276
|
if (!entries.length || Array.isArray(inputRequests) || typeof inputRequests !== 'object') throw new InteropError('input_required needs a JSON object of at least one input request.'); // a JSON object of requests, first (unit 38, 40)
|
|
277
|
+
if (steps !== null && steps !== undefined && (typeof steps !== 'object' || Array.isArray(steps))) { // FAA-196 (26)
|
|
278
|
+
throw new InteropError('steps maps an input request\'s key to its approval step: a JSON object, or none.');
|
|
279
|
+
}
|
|
274
280
|
const kept = Object.create(null); // the value checked is the value kept (seam 34); a key __proto__ stays a key (35)
|
|
275
281
|
for (const [key, step] of Object.entries(steps ?? {})) {
|
|
276
282
|
kept[key] = Object.hasOwn(inputRequests, key) && step !== null && step !== undefined ? keptStep(step) : {};
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// JavaScript's view of JSON values, once for the whole Node kit (FAA-65, layer L0); the twin of fabric_jsvalue.py.
|
|
2
3
|
// The contract's answers are computed by TypeScript; every reader in the kit takes these helpers from here:
|
|
3
4
|
// isObject, isString, objects (an array's object items), indexed (with their index as written), show (the
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// The call queue and the load report of DEC-0104 (contract src/agent-network.ts, FAC-SEM-131 to FAC-SEM-133), as the
|
|
2
3
|
// contract's reference answers them: a node that hosts a job-backed agent queues its jobs per (agentRef, context) with
|
|
3
4
|
// these and reports its load as service-load/1 under extensions/load/0.1. Part of the Node service kit (FAA-289, layer
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// One copy of a service, locked before any side effect (LC-01, DEC-0057): the instance lock and the supervisor marker.
|
|
2
3
|
// Part of the Node service kit (FAA-65, layer L3); the twin of fabric_lock.py. Node 20+ (22+ for the Linux lock), no dependency.
|
|
3
4
|
//
|
|
@@ -172,13 +173,26 @@ export class InstanceLock {
|
|
|
172
173
|
return this;
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Let the lock go. Never throws, so a release in a `finally` never replaces the caller's own outcome (FAA-196 (11)):
|
|
178
|
+
* the descriptor and the name's server are forgotten before they are closed, so a retry never closes a descriptor
|
|
179
|
+
* reused since, and a close that fails still closes the server.
|
|
180
|
+
*/
|
|
175
181
|
release() {
|
|
176
182
|
if (this.timer !== null) clearInterval(this.timer);
|
|
177
|
-
|
|
178
|
-
if (this.server) this.server.close();
|
|
183
|
+
const { fd, server } = this;
|
|
179
184
|
this.timer = null;
|
|
180
185
|
this.fd = null;
|
|
181
186
|
this.server = null;
|
|
187
|
+
try {
|
|
188
|
+
if (fd !== null) fs.closeSync(fd);
|
|
189
|
+
} catch { // the descriptor is gone either way; the kernel frees the lock with it
|
|
190
|
+
} finally {
|
|
191
|
+
try {
|
|
192
|
+
server?.close();
|
|
193
|
+
} catch { // nothing to add to the caller's own outcome
|
|
194
|
+
}
|
|
195
|
+
}
|
|
182
196
|
}
|
|
183
197
|
}
|
|
184
198
|
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// Logs and stopping (LC-01, LC-12): a rotating log, a capped stdout log, the stop signal and the drain.
|
|
2
3
|
// Part of the Node service kit (FAA-65, layer L3); the twin of fabric_logs.py. Node 20+, no dependency.
|
|
3
4
|
import fs from 'node:fs';
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// The operator's mandate, mandate/1 (DEC-0125, FAC-SEM-144): the operator's instruction for one task travels with the
|
|
2
3
|
// task, narrowed at each hand-over. Part of the Node service kit (layer L2); the twin of fabric_mandate.py.
|
|
3
4
|
//
|
|
@@ -193,6 +194,11 @@ const invalid = (detail) => ({ ok: false, code: MANDATE_INVALID, data: { detail
|
|
|
193
194
|
* being where the operator said the words when the mandate carries it (DEC-0126 OA-6). `callerAgent` is
|
|
194
195
|
* passed only when the callee authenticated the caller's agent id; without one the chain's end alone binds it. A missing
|
|
195
196
|
* or non-object `call` is read as a call that names nothing (the reference would throw).
|
|
197
|
+
*
|
|
198
|
+
* A call is a plain object of its own data properties (FAA-196 (19)): its members are read as own members only, where
|
|
199
|
+
* the contract's reference reads plain properties, so an inherited or getter `project`, `action` or `spendUsd` is none
|
|
200
|
+
* here and may be covered where the reference refuses it. Build the call as an object literal of the values the work
|
|
201
|
+
* names, as the kit's own readers do (the board's `covers`, actionGate, jobStep).
|
|
196
202
|
*/
|
|
197
203
|
export function mandateCovers(input) {
|
|
198
204
|
const field = (key) => member(input, key);
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
// OpenRouter chat completions for a service: its own key, a spend ceiling checked before every attempt, a usage receipt
|
|
2
3
|
// for every answered call, and a fallback model of another vendor. The twin of fabric_openrouter.py: the same rules, the
|
|
3
4
|
// same cases (test/fixtures/kit-openrouter-cases.json); the rules are written out in that file's docstring.
|
|
@@ -86,6 +87,26 @@ function checkAttribution(projectId, taskRunId) {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
// Why a base URL is not sent the same by both twins, read from the URL as sent (its ends trimmed), or null: a backslash
|
|
91
|
+
// anywhere (new URL reads it as a slash), and after a `//` authority a path holding anything but URL path characters
|
|
92
|
+
// and %XX escapes (Python refused a space or a character outside ASCII where new URL percent-encoded it, and new URL
|
|
93
|
+
// removes a tab or a line break) or a dot segment, escaped or not (new URL collapses it). The Python twin's
|
|
94
|
+
// base_url_problem is the same rule (FAA-187 (18), kit-openrouter-cases.json).
|
|
95
|
+
export function baseUrlProblem(base) {
|
|
96
|
+
const text = String(base).replace(/^[\x00-\x20]+|[\x00-\x20]+$/g, '');
|
|
97
|
+
if (text.includes('\\')) return 'the base URL holds a backslash';
|
|
98
|
+
if (/[\t\n\r]/.test(text)) return 'the base URL holds a tab or a line break'; // new URL removes them and sends the rest
|
|
99
|
+
const rest = text.slice(text.indexOf(':') + 1);
|
|
100
|
+
if (text.indexOf(':') < 0 || !rest.startsWith('//')) return null; // no authority as written: checkUrl reads it
|
|
101
|
+
const after = rest.slice(2);
|
|
102
|
+
const path = after.includes('/') ? after.slice(after.indexOf('/')) : '';
|
|
103
|
+
if (!/^(?:\/(?:[A-Za-z0-9._~!$&'()*+,;=:@-]|%[0-9A-Fa-f]{2})*)*$/.test(path)) {
|
|
104
|
+
return 'the base URL\'s path holds a character outside URL path characters and %XX escapes';
|
|
105
|
+
}
|
|
106
|
+
if (path.split('/').slice(1).some((seg) => ['.', '..'].includes(seg.replace(/%2e/gi, '.')))) return 'the base URL\'s path holds a dot segment';
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
export class Client {
|
|
90
111
|
constructor({ ceilingUsd, ledger, prices, keyEnv = 'OPENROUTER_API_KEY', window = 'day', baseUrl = BASE_URL,
|
|
91
112
|
attribution = null, projectId, unmeasuredCallUsd = null, deadlineS = 120, retries = 1, env = process.env,
|
|
@@ -108,13 +129,14 @@ export class Client {
|
|
|
108
129
|
if (/[?#]/.test(baseUrl)) {
|
|
109
130
|
throw new OpenRouterError('the base URL holds no query or fragment', { kind: 'config' });
|
|
110
131
|
}
|
|
132
|
+
const problem = baseUrlProblem(baseUrl);
|
|
133
|
+
if (problem) throw new OpenRouterError(`${problem} (both kits would not send it alike)`, { kind: 'config' });
|
|
111
134
|
http.checkUrl(baseUrl);
|
|
112
135
|
checkAttribution(projectId, undefined);
|
|
113
136
|
Object.assign(this, { ceilingUsd, ledger, keyEnv, window, attribution, projectId, unmeasuredCallUsd, deadlineS, retries });
|
|
114
137
|
this.prices = Object.fromEntries(Object.entries(prices ?? {}).map(([k, v]) => [k, { ...v }]));
|
|
115
|
-
// The base URL as checkUrl read it:
|
|
116
|
-
//
|
|
117
|
-
// composed them in 0.55.0 (0.56.0 product r20, unit r22, seam r22; the Python twin refuses what urllib cannot send).
|
|
138
|
+
// The base URL as checkUrl read it: its ends trimmed of C0 controls and spaces, so `.../v1/ ` is composed as
|
|
139
|
+
// `.../v1/chat/completions` (0.56.0 product r20); a tab or line break inside is refused above since FAA-187 (18).
|
|
118
140
|
this.baseUrl = baseUrl.replace(/[\t\n\r]/g, '').replace(/^[\x00-\x20]+|[\x00-\x20]+$/g, '').replace(/\/+$/, '');
|
|
119
141
|
this.env = env;
|
|
120
142
|
this.transport = transport;
|