@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.
Files changed (165) hide show
  1. package/CHANGELOG.md +169 -3
  2. package/README.md +7 -3
  3. package/SECURITY.md +4 -3
  4. package/package.json +1 -1
  5. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  6. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +7 -2
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +10 -0
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +4 -2
  9. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +18 -4
  10. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +16 -0
  11. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_identifiers.py +1 -0
  12. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +31 -15
  13. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +38 -12
  14. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +21 -148
  15. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check_cli.py +164 -0
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +15 -8
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +4 -3
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +2 -1
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +3 -2
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +14 -4
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/README.md +76 -0
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/config.example.json +28 -0
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/data_agent.py +366 -0
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +32 -17
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +4 -0
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support-agent.mjs +115 -0
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +63 -11
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/a2a.md +98 -0
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +10 -0
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +28 -12
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +14 -2
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +13 -1
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +11 -7
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/ops-digest.md +75 -0
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +7 -5
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +25 -12
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +10 -1
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +70 -0
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_a2a.py +158 -0
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a-server.mjs +275 -0
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a.mjs +2 -1
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +1 -0
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +5 -2
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +1 -0
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +1 -0
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +15 -2
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +55 -11
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +47 -31
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +1 -0
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +25 -7
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +1 -0
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +1 -0
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-connections.mjs +27 -5
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +1 -0
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +1 -0
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +4 -2
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +19 -12
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate-rules.mjs +1 -0
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +1 -0
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +7 -1
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +22 -7
  64. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +4 -5
  65. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +7 -2
  66. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-identifiers.mjs +1 -0
  67. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +21 -1
  68. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +6 -0
  69. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +1 -0
  70. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +1 -0
  71. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +16 -2
  72. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +1 -0
  73. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +6 -0
  74. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +25 -3
  75. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ops-digest.mjs +456 -0
  76. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package-schema.mjs +1 -0
  77. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +18 -2
  78. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-page-links.mjs +1 -0
  79. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +19 -5
  80. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +56 -19
  81. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +1 -0
  82. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -0
  83. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -0
  84. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +16 -2
  85. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +1 -0
  86. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +1 -0
  87. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-socket-proof.mjs +181 -0
  88. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +20 -15
  89. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet-identity.mjs +469 -0
  90. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet.mjs +1 -0
  91. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +1 -0
  92. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +1 -0
  93. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a.py +3 -1
  94. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a_server.py +405 -0
  95. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +1 -0
  96. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +7 -2
  97. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +1 -0
  98. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +1 -0
  99. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +1 -0
  100. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +17 -3
  101. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +98 -9
  102. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +37 -48
  103. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +1 -0
  104. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +25 -8
  105. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +1 -0
  106. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +28 -6
  107. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +1 -0
  108. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +1 -0
  109. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +19 -13
  110. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +24 -14
  111. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +1 -0
  112. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate_rules.py +1 -0
  113. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +4 -1
  114. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +32 -7
  115. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +19 -16
  116. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +10 -5
  117. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_identifiers.py +1 -0
  118. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +56 -96
  119. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +99 -1
  120. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +1 -0
  121. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +1 -0
  122. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +16 -6
  123. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +1 -0
  124. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_mandate.py +1 -0
  125. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +44 -1
  126. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ops_digest.py +648 -0
  127. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +17 -4
  128. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package_schema.py +1 -0
  129. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_page_links.py +1 -0
  130. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +21 -5
  131. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +108 -108
  132. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +1 -0
  133. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +1 -0
  134. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -0
  135. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +38 -12
  136. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +1 -0
  137. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +1 -0
  138. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_socket_proof.py +391 -0
  139. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +19 -16
  140. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet.py +1 -0
  141. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet_identity.py +493 -0
  142. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +1 -0
  143. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +1 -0
  144. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +632 -104
  145. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample-remote-service.mjs +2 -1
  146. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +17 -7
  147. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/tailnet-groups.schema.json +69 -0
  148. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +6 -5
  149. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agent-templates.md +16 -0
  150. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +10 -0
  151. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +14 -9
  152. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +4 -2
  153. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +18 -4
  154. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +4 -3
  155. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/admit.py +182 -0
  156. package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
  157. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
  158. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +10 -0
  159. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +14 -9
  160. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +7 -4
  161. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +10 -5
  162. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +2 -0
  163. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +16 -5
  164. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +79 -8
  165. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +147 -45
@@ -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). Part of the Node service kit (layer L3, after fabric-interop).
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 === 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 === 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 && (record.caller === null || record.caller === hold.caller)
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 === caller) && (l.job === null || l.job === jobId));
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}. */
@@ -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
- function isLoopback(host) {
82
- const h = host.replace(/^\[|\]$/g, '');
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)));
@@ -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;
@@ -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) : {};
@@ -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
@@ -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
@@ -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
- if (this.fd !== null) fs.closeSync(this.fd);
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
 
@@ -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';
@@ -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);
@@ -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: `new URL` removes tabs and line breaks anywhere and trims C0 controls and
116
- // spaces from its ends, so `.../v1/ ` and `.../v1\n/` are composed as `.../v1/chat/completions`, as `new URL`
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;