@passioncode-ai/fabric-agent-adapter 0.78.0 → 0.80.2
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 +256 -0
- package/README.md +14 -7
- package/bin/fabric-agent-adapter.js +26 -4
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +6 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +13 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +3 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +23 -8
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +6 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +6 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +34 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/README.md +67 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/dashboard_service.py +252 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.mjs +168 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.py +179 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/README.md +57 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.mjs +181 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.py +237 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +11 -6
- 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/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +26 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +14 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +44 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +10 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +15 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +37 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +30 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +48 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +9 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +16 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +344 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +54 -20
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +8 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +4 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +55 -14
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +8 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +3 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +16 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +97 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +50 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +49 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +9 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +358 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +41 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +17 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +24 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +110 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +543 -37
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +4 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +12 -5
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +6 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +13 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +13 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +32 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/working-rules.md +54 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +27 -26
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/cleanup.md +30 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +59 -38
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +52 -26
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/remote-sessions.md +75 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +28 -13
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/task-context.md +58 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/waits.md +36 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +14 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +169 -46
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +146 -19
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +78 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +9 -2
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py
CHANGED
|
@@ -4,6 +4,7 @@ removed without touching what the operator turned off; release pruning (LC-15).
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import http.client
|
|
7
|
+
import ipaddress
|
|
7
8
|
import json
|
|
8
9
|
import os
|
|
9
10
|
import plistlib
|
|
@@ -140,12 +141,40 @@ def _check(result: Any, what: str) -> None:
|
|
|
140
141
|
raise ServiceError("%s failed: %s" % (what, (getattr(result, "stderr", "") or getattr(result, "stdout", "") or "").strip()[:200]))
|
|
141
142
|
|
|
142
143
|
|
|
144
|
+
def _poll_installed(origin: str, service_id: Optional[str], instance: str, timeout: float,
|
|
145
|
+
read: Optional[Callable[[str], Any]], sleep: Callable[[float], None], clock: Callable[[], float]) -> Any:
|
|
146
|
+
"""Poll the well-known answer at `origin` until this OS account's listener answers as `service_id`.`instance`, as
|
|
147
|
+
launchd_install does (the pinned contract's Install row: every supervisor polls, FAA-253). Another account's listener,
|
|
148
|
+
or one whose owner cannot be told, is never a successful install (DEC-0154 HA-2)."""
|
|
149
|
+
if not service_id:
|
|
150
|
+
raise ServiceError("polling the install at %s needs the service_id it must answer as" % origin)
|
|
151
|
+
read = read or well_known_of
|
|
152
|
+
deadline = clock() + max(0.0, timeout)
|
|
153
|
+
while True:
|
|
154
|
+
doc = read(origin)
|
|
155
|
+
foreign = doc.reason if isinstance(doc, ForeignUserAnswer) else None
|
|
156
|
+
if not foreign and _answers(doc, service_id, instance):
|
|
157
|
+
return doc
|
|
158
|
+
if clock() >= deadline:
|
|
159
|
+
break
|
|
160
|
+
sleep(0.5)
|
|
161
|
+
if foreign:
|
|
162
|
+
raise ServiceError("%s is not this service's: %s (DEC-0154 HA-2); the port is another OS account's or its owner "
|
|
163
|
+
"cannot be told." % (origin, foreign))
|
|
164
|
+
raise ServiceError("%s did not answer as %s.%s within %.0f s; read its log." % (origin, service_id, instance, timeout))
|
|
165
|
+
|
|
166
|
+
|
|
143
167
|
def systemd_install(unit: str, text: str, *, unit_dir: Optional[Path] = None, run: Callable[..., Any] = subprocess.run,
|
|
144
|
-
system: str = sys.platform, force_enable: bool = False
|
|
168
|
+
system: str = sys.platform, force_enable: bool = False, origin: Optional[str] = None,
|
|
169
|
+
service_id: Optional[str] = None, instance: str = "default", timeout: float = 40.0,
|
|
170
|
+
fetch_well_known: Optional[Callable[[str], Any]] = None, sleep: Callable[[float], None] = time.sleep,
|
|
171
|
+
clock: Callable[[], float] = time.monotonic) -> Path:
|
|
145
172
|
"""Write the user unit, enable it so it comes back after a login, and (re)start it so the new unit is what runs.
|
|
146
173
|
|
|
147
174
|
The operator's intent wins (LC-14): a unit already installed and disabled stays off — the file is rewritten, so
|
|
148
|
-
the new version is what starts once they enable it — unless ``force_enable`` (the operator asked for it on).
|
|
175
|
+
the new version is what starts once they enable it — unless ``force_enable`` (the operator asked for it on).
|
|
176
|
+
With ``origin`` (and ``service_id``), it then polls the well-known answer as ``launchd_install`` does, until this
|
|
177
|
+
account's listener answers within ``timeout`` (FAA-253); ``fetch_well_known(origin)`` reads it as ``well_known_of``."""
|
|
149
178
|
if not system.startswith("linux"):
|
|
150
179
|
raise ServiceError("systemd is the supervisor on Linux only")
|
|
151
180
|
if not _UNIT.match(unit):
|
|
@@ -162,6 +191,8 @@ def systemd_install(unit: str, text: str, *, unit_dir: Optional[Path] = None, ru
|
|
|
162
191
|
return path
|
|
163
192
|
_check(run(["systemctl", "--user", "enable", unit], capture_output=True, text=True), "systemctl --user enable")
|
|
164
193
|
_check(run(["systemctl", "--user", "restart", unit], capture_output=True, text=True), "systemctl --user restart")
|
|
194
|
+
if origin is not None:
|
|
195
|
+
_poll_installed(origin, service_id, instance, timeout, fetch_well_known, sleep, clock)
|
|
165
196
|
return path
|
|
166
197
|
|
|
167
198
|
|
|
@@ -226,9 +257,13 @@ _TASK_DISABLED = re.compile(r"<Settings>(?:(?!</Settings>).)*<Enabled>false</Ena
|
|
|
226
257
|
|
|
227
258
|
|
|
228
259
|
def task_install(task: str, xml_text: str, *, run: Callable[..., Any] = subprocess.run, system: str = sys.platform,
|
|
229
|
-
force_enable: bool = False
|
|
260
|
+
force_enable: bool = False, origin: Optional[str] = None, service_id: Optional[str] = None,
|
|
261
|
+
instance: str = "default", timeout: float = 40.0, fetch_well_known: Optional[Callable[[str], Any]] = None,
|
|
262
|
+
sleep: Callable[[float], None] = time.sleep, clock: Callable[[], float] = time.monotonic) -> Any:
|
|
230
263
|
"""Create (or replace) the task from its XML without administrator rights, then (re)start it so the new
|
|
231
|
-
definition is what runs. A task already installed and disabled stays off (LC-14) unless ``force_enable``.
|
|
264
|
+
definition is what runs. A task already installed and disabled stays off (LC-14) unless ``force_enable``.
|
|
265
|
+
With ``origin`` (and ``service_id``), it then polls the well-known answer as ``launchd_install`` does and returns it
|
|
266
|
+
(FAA-253); without, it returns None as before."""
|
|
232
267
|
if system != "win32":
|
|
233
268
|
raise ServiceError("Task Scheduler is the supervisor on Windows only")
|
|
234
269
|
query = run(["schtasks", "/Query", "/TN", task, "/XML"], capture_output=True, text=True)
|
|
@@ -247,6 +282,9 @@ def task_install(task: str, xml_text: str, *, run: Callable[..., Any] = subproce
|
|
|
247
282
|
if existed:
|
|
248
283
|
run(["schtasks", "/End", "/TN", task], capture_output=True, text=True)
|
|
249
284
|
_check(run(["schtasks", "/Run", "/TN", task], capture_output=True, text=True), "schtasks /Run")
|
|
285
|
+
if origin is not None:
|
|
286
|
+
return _poll_installed(origin, service_id, instance, timeout, fetch_well_known, sleep, clock)
|
|
287
|
+
return None
|
|
250
288
|
|
|
251
289
|
|
|
252
290
|
def task_uninstall(task: str, *, run: Callable[..., Any] = subprocess.run, system: str = sys.platform) -> None:
|
|
@@ -367,13 +405,16 @@ def launchd_loaded(label: str, domain: Optional[str] = None) -> bool:
|
|
|
367
405
|
return _launchctl("print", "%s/%s" % (launchd_domain(domain=domain), label)).returncode == 0
|
|
368
406
|
|
|
369
407
|
|
|
370
|
-
_PID_ROW = re.compile(r"^\s*pid\s*=\s*(
|
|
408
|
+
_PID_ROW = re.compile(r"^\s*pid\s*=\s*([0-9]+)\s*\Z") # ASCII digits, as Node reads them (FAA-273)
|
|
371
409
|
|
|
372
410
|
|
|
373
411
|
def launchd_pid(label: str, domain: Optional[str] = None) -> Optional[int]:
|
|
374
412
|
"""The running pid of this account's launchd job ``label`` (with ``domain="system"``, of the LaunchDaemon), or
|
|
375
413
|
None (not running, or not readable). Reading needs no root."""
|
|
376
|
-
|
|
414
|
+
try:
|
|
415
|
+
result = _launchctl("print", "%s/%s" % (launchd_domain(domain=domain), label))
|
|
416
|
+
except subprocess.TimeoutExpired:
|
|
417
|
+
return None # a launchctl that hangs is a pid not read, never an error out of an install's poll (FAA-273)
|
|
377
418
|
if result.returncode != 0:
|
|
378
419
|
return None
|
|
379
420
|
for line in (result.stdout or "").splitlines():
|
|
@@ -383,6 +424,29 @@ def launchd_pid(label: str, domain: Optional[str] = None) -> Optional[int]:
|
|
|
383
424
|
return None
|
|
384
425
|
|
|
385
426
|
|
|
427
|
+
def _loopback_host(host: Optional[str]) -> bool:
|
|
428
|
+
"""Whether `host` (urlsplit's hostname: lower-case, no brackets) names this machine: localhost, 127/8 or ::1."""
|
|
429
|
+
if not host:
|
|
430
|
+
return False
|
|
431
|
+
if host == "localhost":
|
|
432
|
+
return True
|
|
433
|
+
try:
|
|
434
|
+
return ipaddress.ip_address(host).is_loopback
|
|
435
|
+
except ValueError:
|
|
436
|
+
return False
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class ForeignUserAnswer:
|
|
440
|
+
"""A refused listener (DEC-0154 HA-2), as `well_known_of` answers it: never a document, whatever keys a service's JSON
|
|
441
|
+
carries. `reason` starts with `foreign-user` or `table-restricted` (the Node twin's class, FAA-259)."""
|
|
442
|
+
|
|
443
|
+
def __init__(self, reason: str) -> None:
|
|
444
|
+
self.reason = reason
|
|
445
|
+
|
|
446
|
+
def __repr__(self) -> str:
|
|
447
|
+
return "ForeignUserAnswer(%r)" % self.reason
|
|
448
|
+
|
|
449
|
+
|
|
386
450
|
def _read_well_known(origin: str, timeout: float = 2.0,
|
|
387
451
|
vouch_pid: Optional[int] = None) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
|
|
388
452
|
"""(the well-known answer or None, why the listener was refused or None). A loopback origin is read only on a
|
|
@@ -391,10 +455,16 @@ def _read_well_known(origin: str, timeout: float = 2.0,
|
|
|
391
455
|
table hides other processes. `vouch_pid`, the pid of a launchd job this
|
|
392
456
|
caller started, proves the listener when the socket table hides other processes (FAA-267)."""
|
|
393
457
|
parts = urllib.parse.urlsplit(origin)
|
|
394
|
-
|
|
395
|
-
|
|
458
|
+
try:
|
|
459
|
+
port = parts.port or (80 if parts.scheme == "http" else None) # no port is http's 80, as in the Node twin
|
|
460
|
+
except ValueError: # a port out of range
|
|
461
|
+
port = None
|
|
462
|
+
if parts.scheme == "http" and port and _loopback_host(parts.hostname):
|
|
463
|
+
# every loopback spelling (127.0.0.1, another 127/8 address, localhost, [::1]) goes on a checked connection,
|
|
464
|
+
# never the unchecked way (FAA-257)
|
|
465
|
+
conn = _po.CheckedHTTPConnection(parts.hostname, port, timeout=timeout, vouch_pid=vouch_pid)
|
|
396
466
|
try:
|
|
397
|
-
conn.request("GET", "/.well-known/fabric-service", headers={"Host":
|
|
467
|
+
conn.request("GET", "/.well-known/fabric-service", headers={"Host": parts.netloc.rpartition("@")[2]})
|
|
398
468
|
response = conn.getresponse()
|
|
399
469
|
return (json.loads(response.read().decode()) if response.status == 200 else None), None
|
|
400
470
|
except _po.ListenerRefused as exc:
|
|
@@ -514,10 +584,24 @@ def _read_job_well_known(origin: str, job_pid: int, job_uid: Optional[int] = Non
|
|
|
514
584
|
|
|
515
585
|
def fetch_well_known(origin: str, timeout: float = 2.0) -> Optional[Dict[str, Any]]:
|
|
516
586
|
"""The well-known answer at `origin`, or None: nothing answers, the answer is not JSON, or the listener is not this
|
|
517
|
-
OS account's process (DEC-0154 HA-2)."""
|
|
587
|
+
OS account's process (DEC-0154 HA-2). `well_known_of` tells the last two apart."""
|
|
518
588
|
return _read_well_known(origin, timeout)[0]
|
|
519
589
|
|
|
520
590
|
|
|
591
|
+
def well_known_of(origin: str, timeout: float = 2.0, vouch_pid: Optional[int] = None) -> Any:
|
|
592
|
+
"""The well-known answer at `origin` as the Node `wellKnownOf` gives it (FAA-259): the document; None when nothing
|
|
593
|
+
answers or the answer is not JSON (down); a `ForeignUserAnswer` when the listener is another OS account's process or
|
|
594
|
+
its owner cannot be told. So a status reads HA-2's order: down, foreign-user, foreign, status."""
|
|
595
|
+
doc, why = _read_well_known(origin, timeout, vouch_pid=vouch_pid)
|
|
596
|
+
return ForeignUserAnswer(why) if why else doc
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def job_well_known_of(origin: str, job_pid: int, job_uid: Optional[int] = None, timeout: float = 2.0) -> Any:
|
|
600
|
+
"""As `well_known_of`, for a LaunchDaemon whose job `job_pid` (running as `job_uid`) must hold the listener."""
|
|
601
|
+
doc, why = _read_job_well_known(origin, job_pid, job_uid)
|
|
602
|
+
return ForeignUserAnswer(why) if why else doc
|
|
603
|
+
|
|
604
|
+
|
|
521
605
|
_OVERRIDE_ROW = re.compile(r'^\s*"([^"]+)"\s*=>\s*(\S+)\s*\Z')
|
|
522
606
|
|
|
523
607
|
|
|
@@ -593,7 +677,9 @@ def _refuse_held_label(label: str, plist_path: Path) -> None:
|
|
|
593
677
|
|
|
594
678
|
def launchd_install(label: str, plist_path: Path, plist_bytes: bytes, *, origin: str,
|
|
595
679
|
service_id: str, instance: str = "default", timeout: float = 40.0,
|
|
596
|
-
force_enable: bool = False, domain: Optional[str] = None
|
|
680
|
+
force_enable: bool = False, domain: Optional[str] = None,
|
|
681
|
+
fetch_well_known: Optional[Callable[..., Any]] = None,
|
|
682
|
+
fetch_job_well_known: Optional[Callable[..., Any]] = None) -> Dict[str, Any]:
|
|
597
683
|
"""Write + lint the plist, bootout and WAIT for unload, bootstrap (retry EIO 5), then
|
|
598
684
|
poll the well-known document until it answers with this identity.
|
|
599
685
|
|
|
@@ -606,7 +692,13 @@ def launchd_install(label: str, plist_path: Path, plist_bytes: bytes, *, origin:
|
|
|
606
692
|
A plist in ``/Library/LaunchDaemons`` (or ``domain="system"``) is a LaunchDaemon in launchd's system domain, started
|
|
607
693
|
at boot with no login (FAA-278): only root installs it, refused before anything is written otherwise, and the
|
|
608
694
|
answer counts only when launchd's own job accepted the connection (its ``UserName``, not the installer, owns it).
|
|
609
|
-
A ``gui/<uid>`` bootstrap with no GUI session (SSH, a headless Mac after a FileVault restart) says so.
|
|
695
|
+
A ``gui/<uid>`` bootstrap with no GUI session (SSH, a headless Mac after a FileVault restart) says so.
|
|
696
|
+
|
|
697
|
+
``fetch_well_known(origin, vouch_pid=...)`` and ``fetch_job_well_known(origin, job_pid, job_uid)`` read the answer as
|
|
698
|
+
``well_known_of`` and ``job_well_known_of`` do (the document, None, or a ``ForeignUserAnswer``); a caller may inject
|
|
699
|
+
its own, as the Node ``launchdInstall`` takes ``fetchWellKnown`` (FAA-259)."""
|
|
700
|
+
read_answer = fetch_well_known or (lambda origin_, vouch_pid=None: well_known_of(origin_, vouch_pid=vouch_pid))
|
|
701
|
+
read_job = fetch_job_well_known or job_well_known_of
|
|
610
702
|
plist_path = Path(plist_path)
|
|
611
703
|
dom = launchd_domain(plist_path, domain)
|
|
612
704
|
which = "system" if dom == "system" else None
|
|
@@ -685,9 +777,10 @@ def launchd_install(label: str, plist_path: Path, plist_bytes: bytes, *, origin:
|
|
|
685
777
|
foreign = None
|
|
686
778
|
while time.time() < deadline:
|
|
687
779
|
job = launchd_pid(label, "system")
|
|
688
|
-
doc
|
|
780
|
+
doc = read_job(origin, job, job_uid) if job else None
|
|
781
|
+
why = doc.reason if isinstance(doc, ForeignUserAnswer) else None
|
|
689
782
|
foreign = why or foreign # the last refusal stays the reason while launchd shows no pid
|
|
690
|
-
if _answers(doc, service_id, instance):
|
|
783
|
+
if not why and _answers(doc, service_id, instance):
|
|
691
784
|
return doc
|
|
692
785
|
time.sleep(0.5)
|
|
693
786
|
loaded = ("; the job stays loaded and its plist starts it again at boot: as root, `launchctl bootout system/%s` "
|
|
@@ -702,10 +795,11 @@ def launchd_install(label: str, plist_path: Path, plist_bytes: bytes, *, origin:
|
|
|
702
795
|
% (origin, service_id, instance, timeout, loaded))
|
|
703
796
|
foreign = vouch = None
|
|
704
797
|
while time.time() < deadline:
|
|
705
|
-
doc
|
|
798
|
+
doc = read_answer(origin, vouch_pid=vouch)
|
|
799
|
+
foreign = doc.reason if isinstance(doc, ForeignUserAnswer) else None
|
|
706
800
|
if foreign and foreign.startswith(_po.TABLE_RESTRICTED):
|
|
707
801
|
vouch = launchd_pid(label) # only a restricted table needs the job's pid (FAA-267)
|
|
708
|
-
if _answers(doc, service_id, instance):
|
|
802
|
+
if not foreign and _answers(doc, service_id, instance):
|
|
709
803
|
return doc
|
|
710
804
|
time.sleep(0.5)
|
|
711
805
|
if foreign:
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py
CHANGED
|
@@ -137,7 +137,7 @@ def _by_project(receipts: List[Dict[str, Any]], first: str, last: str) -> List[D
|
|
|
137
137
|
out = []
|
|
138
138
|
for key in sorted(projects):
|
|
139
139
|
row = projects[key]
|
|
140
|
-
runs = [
|
|
140
|
+
runs = [_close_run(run) for _, run in sorted(row.pop("_runs").items())]
|
|
141
141
|
row = _close_totals(row)
|
|
142
142
|
if runs:
|
|
143
143
|
row["taskRuns"] = runs
|
|
@@ -167,6 +167,17 @@ def _round_usd(value: float) -> float:
|
|
|
167
167
|
return whole / 1e6
|
|
168
168
|
|
|
169
169
|
|
|
170
|
+
def _close_run(run: Dict[str, Any]) -> Dict[str, Any]:
|
|
171
|
+
"""A task run's totals, its cost rounded as `_round_usd` rounds but never above the exact cost: each run and its
|
|
172
|
+
project round apart, so many runs rounding up summed past the project and failed FAC-SEM-025 (board request
|
|
173
|
+
f5753c5a). Rounded this way the runs' sum stays within the project's rounded cost by construction."""
|
|
174
|
+
exact = run["_cost"]
|
|
175
|
+
run = _close_totals(run)
|
|
176
|
+
if run["costUsd"] is not None and run["costUsd"] > exact:
|
|
177
|
+
run["costUsd"] = _round_usd(run["costUsd"] - 0.000001)
|
|
178
|
+
return run
|
|
179
|
+
|
|
180
|
+
|
|
170
181
|
def _close_totals(row: Dict[str, Any]) -> Dict[str, Any]:
|
|
171
182
|
cost = row.pop("_cost")
|
|
172
183
|
row["costUsd"] = None if row["calls"] and row["unpricedCalls"] == row["calls"] else _round_usd(cost)
|