@ashlr/hub 3.5.0 → 3.7.0
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 +160 -0
- package/README.md +3 -0
- package/dist/build-identity.json +2 -2
- package/dist/cli/activation.d.ts +41 -0
- package/dist/cli/activation.js +485 -0
- package/dist/cli/activation.js.map +1 -0
- package/dist/core/daemon/cutoff-checkpoint-scheduler.js +7 -0
- package/dist/core/daemon/cutoff-checkpoint-scheduler.js.map +1 -1
- package/dist/core/daemon/loop.d.ts +19 -0
- package/dist/core/daemon/loop.js +235 -5
- package/dist/core/daemon/loop.js.map +1 -1
- package/dist/core/daemon/overnight-status.d.ts +152 -0
- package/dist/core/daemon/overnight-status.js +275 -0
- package/dist/core/daemon/overnight-status.js.map +1 -0
- package/dist/core/daemon/post-merge-halt.d.ts +212 -0
- package/dist/core/daemon/post-merge-halt.js +503 -0
- package/dist/core/daemon/post-merge-halt.js.map +1 -0
- package/dist/core/daemon/run-window.d.ts +218 -0
- package/dist/core/daemon/run-window.js +420 -0
- package/dist/core/daemon/run-window.js.map +1 -0
- package/dist/core/fleet/external-skill-audit.js +1 -1
- package/dist/core/fleet/external-skill-audit.js.map +1 -1
- package/dist/core/fleet/generated-repair-resolution.d.ts +36 -0
- package/dist/core/fleet/generated-repair-resolution.js +245 -0
- package/dist/core/fleet/generated-repair-resolution.js.map +1 -0
- package/dist/core/local-runtime/llama/config.d.ts +28 -6
- package/dist/core/local-runtime/llama/config.js +28 -6
- package/dist/core/local-runtime/llama/config.js.map +1 -1
- package/dist/core/local-runtime/llama/port.d.ts +17 -0
- package/dist/core/local-runtime/llama/port.js +47 -0
- package/dist/core/local-runtime/llama/port.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-host.d.ts +54 -0
- package/dist/core/local-runtime/llama/proxy-host.js +159 -0
- package/dist/core/local-runtime/llama/proxy-host.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-process.d.ts +148 -0
- package/dist/core/local-runtime/llama/proxy-process.js +250 -0
- package/dist/core/local-runtime/llama/proxy-process.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-record.d.ts +40 -0
- package/dist/core/local-runtime/llama/proxy-record.js +133 -0
- package/dist/core/local-runtime/llama/proxy-record.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.d.ts +118 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.js +511 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.js.map +1 -0
- package/dist/core/web/public/next/assets/App-Cgi6j1Gk.css +1 -0
- package/dist/core/web/public/next/assets/App-vtaHB9mp.js +7 -0
- package/dist/core/web/public/next/assets/App-vtaHB9mp.js.map +1 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-B-bFJ0I9.js +2 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-B-bFJ0I9.js.map +1 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-Su-NUTis.css +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-CHjxJt2z.css +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-DoqmsCjw.js +2 -0
- package/dist/core/web/public/next/assets/AutonomySection-DoqmsCjw.js.map +1 -0
- package/dist/core/web/public/next/assets/ChatSection-BfnLe5dT.css +1 -0
- package/dist/core/web/public/next/assets/ChatSection-vNPIJffd.js +80 -0
- package/dist/core/web/public/next/assets/ChatSection-vNPIJffd.js.map +1 -0
- package/dist/core/web/public/next/assets/ConfirmDialog-Dd2APQhB.js +2 -0
- package/dist/core/web/public/next/assets/ConfirmDialog-Dd2APQhB.js.map +1 -0
- package/dist/core/web/public/next/assets/{ConfirmDialog-cYuivhCX.css → ConfirmDialog-Dx15x0Ho.css} +1 -1
- package/dist/core/web/public/next/assets/Dialog-CPM9JxBc.js +2 -0
- package/dist/core/web/public/next/assets/Dialog-CPM9JxBc.js.map +1 -0
- package/dist/core/web/public/next/assets/Dialog-tQ8kFulD.css +1 -0
- package/dist/core/web/public/next/assets/DiffViewer-B84qVef9.js +7 -0
- package/dist/core/web/public/next/assets/DiffViewer-B84qVef9.js.map +1 -0
- package/dist/core/web/public/next/assets/DiffViewer-C8HHTnVE.css +1 -0
- package/dist/core/web/public/next/assets/McpSection-CEUpV_5X.js +2 -0
- package/dist/core/web/public/next/assets/McpSection-CEUpV_5X.js.map +1 -0
- package/dist/core/web/public/next/assets/McpSection-D88QlDpG.css +1 -0
- package/dist/core/web/public/next/assets/Meter--h3MmZS-.js +2 -0
- package/dist/core/web/public/next/assets/Meter--h3MmZS-.js.map +1 -0
- package/dist/core/web/public/next/assets/Meter-CLcr-4wS.css +1 -0
- package/dist/core/web/public/next/assets/{MutationTokenDialog-jWLLCxWn.css → MutationTokenDialog-CrfJ0PdD.css} +1 -1
- package/dist/core/web/public/next/assets/MutationTokenDialog-DA8t97XT.js +2 -0
- package/dist/core/web/public/next/assets/MutationTokenDialog-DA8t97XT.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-Bq7Vyud0.js +7 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-Bq7Vyud0.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-CM_8pJs6.css +1 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-Badio_uO.js +2 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-Badio_uO.js.map +1 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-ekyMXAK8.css +1 -0
- package/dist/core/web/public/next/assets/SettingsSection-BII7TTSD.css +1 -0
- package/dist/core/web/public/next/assets/SettingsSection-BwrpQlpP.js +2 -0
- package/dist/core/web/public/next/assets/SettingsSection-BwrpQlpP.js.map +1 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Dh7A-hLD.css +1 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-_FSohAdN.js +2 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-_FSohAdN.js.map +1 -0
- package/dist/core/web/public/next/assets/UniverseView-Bqa8DH57.css +1 -0
- package/dist/core/web/public/next/assets/UniverseView-DqcXbed4.js +9 -0
- package/dist/core/web/public/next/assets/UniverseView-DqcXbed4.js.map +1 -0
- package/dist/core/web/public/next/assets/UsageSection-Bq6Daaby.js +2 -0
- package/dist/core/web/public/next/assets/UsageSection-Bq6Daaby.js.map +1 -0
- package/dist/core/web/public/next/assets/UsageSection-DwgNOYvi.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CMkCizvL.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CsIQv9id.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CsIQv9id.js.map +1 -0
- package/dist/core/web/public/next/assets/charts-CTZJ-JrH.js +2 -0
- package/dist/core/web/public/next/assets/charts-CTZJ-JrH.js.map +1 -0
- package/dist/core/web/public/next/assets/charts-CsrtT4rk.css +1 -0
- package/dist/core/web/public/next/assets/fleet-model-ClBbQ0Uj.js +2 -0
- package/dist/core/web/public/next/assets/fleet-model-ClBbQ0Uj.js.map +1 -0
- package/dist/core/web/public/next/assets/format-BNYWGJat.js +2 -0
- package/dist/core/web/public/next/assets/format-BNYWGJat.js.map +1 -0
- package/dist/core/web/public/next/assets/global-D6bTxIRn.js +2 -0
- package/dist/core/web/public/next/assets/global-D6bTxIRn.js.map +1 -0
- package/dist/core/web/public/next/assets/global-z-JfD3ow.css +1 -0
- package/dist/core/web/public/next/assets/icons-D9vlf85u.js +2 -0
- package/dist/core/web/public/next/assets/icons-D9vlf85u.js.map +1 -0
- package/dist/core/web/public/next/assets/index-CZamKRUg.js +11 -0
- package/dist/core/web/public/next/assets/index-CZamKRUg.js.map +1 -0
- package/dist/core/web/public/next/assets/queries-B8eBnFox.js +2 -0
- package/dist/core/web/public/next/assets/queries-B8eBnFox.js.map +1 -0
- package/dist/core/web/public/next/assets/use-guarded-action-bzrVVAYq.js +2 -0
- package/dist/core/web/public/next/assets/use-guarded-action-bzrVVAYq.js.map +1 -0
- package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js +2 -0
- package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js.map +1 -0
- package/dist/core/web/public/next/assets/workspace-model-v0a1gl-C.js +2 -0
- package/dist/core/web/public/next/assets/workspace-model-v0a1gl-C.js.map +1 -0
- package/dist/core/web/public/next/index.html +1 -1
- package/dist/release-dependency-inventory.json +1 -1
- package/node_modules/marked/README.md +8 -0
- package/node_modules/marked/bin/main.js +12 -11
- package/node_modules/marked/lib/marked.d.ts +88 -25
- package/node_modules/marked/lib/marked.esm.js +50 -44
- package/node_modules/marked/lib/marked.esm.js.map +3 -3
- package/node_modules/marked/lib/marked.umd.js +50 -44
- package/node_modules/marked/lib/marked.umd.js.map +3 -3
- package/node_modules/marked/man/marked.1 +1 -1
- package/node_modules/marked/package.json +21 -21
- package/package.json +13 -8
- package/dist/core/web/public/next/assets/App-BCKqSppK.css +0 -1
- package/dist/core/web/public/next/assets/App-CsbPYQqy.js +0 -16
- package/dist/core/web/public/next/assets/App-CsbPYQqy.js.map +0 -1
- package/dist/core/web/public/next/assets/ApprovalsSection-CBZquyLK.js +0 -2
- package/dist/core/web/public/next/assets/ApprovalsSection-CBZquyLK.js.map +0 -1
- package/dist/core/web/public/next/assets/ApprovalsSection-CRP59wSb.css +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-CQVpEdCE.js +0 -2
- package/dist/core/web/public/next/assets/AutonomySection-CQVpEdCE.js.map +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-DTN6m6QE.css +0 -1
- package/dist/core/web/public/next/assets/ChatSection-DiRvNX3Q.css +0 -1
- package/dist/core/web/public/next/assets/ChatSection-DmE9K0Tg.js +0 -77
- package/dist/core/web/public/next/assets/ChatSection-DmE9K0Tg.js.map +0 -1
- package/dist/core/web/public/next/assets/ConfirmDialog-deO0C0iY.js +0 -2
- package/dist/core/web/public/next/assets/ConfirmDialog-deO0C0iY.js.map +0 -1
- package/dist/core/web/public/next/assets/Dialog-CNd7q89m.js +0 -2
- package/dist/core/web/public/next/assets/Dialog-CNd7q89m.js.map +0 -1
- package/dist/core/web/public/next/assets/Dialog-DxN7RgB5.css +0 -1
- package/dist/core/web/public/next/assets/DiffViewer-DTO0KRuG.js +0 -7
- package/dist/core/web/public/next/assets/DiffViewer-DTO0KRuG.js.map +0 -1
- package/dist/core/web/public/next/assets/DiffViewer-hN7X6wGT.css +0 -1
- package/dist/core/web/public/next/assets/Epistemic-53PikaUI.js +0 -2
- package/dist/core/web/public/next/assets/Epistemic-53PikaUI.js.map +0 -1
- package/dist/core/web/public/next/assets/Epistemic-DiyMyeaN.css +0 -1
- package/dist/core/web/public/next/assets/Meter-CGBsbXUX.css +0 -1
- package/dist/core/web/public/next/assets/Meter-DRWZQgr1.js +0 -2
- package/dist/core/web/public/next/assets/Meter-DRWZQgr1.js.map +0 -1
- package/dist/core/web/public/next/assets/MutationTokenDialog-DYF8n_RD.js +0 -2
- package/dist/core/web/public/next/assets/MutationTokenDialog-DYF8n_RD.js.map +0 -1
- package/dist/core/web/public/next/assets/RefreshIndicator-DXJQXKyA.js +0 -2
- package/dist/core/web/public/next/assets/RefreshIndicator-DXJQXKyA.js.map +0 -1
- package/dist/core/web/public/next/assets/RefreshIndicator-O7jUBOmX.css +0 -1
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-DLl4DbGG.css +0 -1
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-hDSEFdLU.js +0 -7
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-hDSEFdLU.js.map +0 -1
- package/dist/core/web/public/next/assets/RouteErrorBoundary-BIpdnxp-.css +0 -1
- package/dist/core/web/public/next/assets/RouteErrorBoundary-CZd7ln8v.js +0 -2
- package/dist/core/web/public/next/assets/RouteErrorBoundary-CZd7ln8v.js.map +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-C9Qs9JWW.js +0 -2
- package/dist/core/web/public/next/assets/SettingsSection-C9Qs9JWW.js.map +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-DGJMdUh2.css +0 -1
- package/dist/core/web/public/next/assets/Skeleton-D2JaTXyv.css +0 -1
- package/dist/core/web/public/next/assets/Skeleton-YkcOpgFb.js +0 -2
- package/dist/core/web/public/next/assets/Skeleton-YkcOpgFb.js.map +0 -1
- package/dist/core/web/public/next/assets/StatusBadge-DJw0LSPg.js +0 -2
- package/dist/core/web/public/next/assets/StatusBadge-DJw0LSPg.js.map +0 -1
- package/dist/core/web/public/next/assets/StatusBadge-TpDqZf6K.css +0 -1
- package/dist/core/web/public/next/assets/TableView-Bj-w1O6o.js +0 -2
- package/dist/core/web/public/next/assets/TableView-Bj-w1O6o.js.map +0 -1
- package/dist/core/web/public/next/assets/TableView-R6Yeztp1.css +0 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Bs2fxaen.js +0 -2
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Bs2fxaen.js.map +0 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-CiZZxRPV.css +0 -1
- package/dist/core/web/public/next/assets/UniverseView-B6b2hw-8.js +0 -9
- package/dist/core/web/public/next/assets/UniverseView-B6b2hw-8.js.map +0 -1
- package/dist/core/web/public/next/assets/UniverseView-gBxl6yum.css +0 -1
- package/dist/core/web/public/next/assets/UsageSection-C__ziNkV.js +0 -2
- package/dist/core/web/public/next/assets/UsageSection-C__ziNkV.js.map +0 -1
- package/dist/core/web/public/next/assets/UsageSection-UUYDidOg.css +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-Cu5aG6J6.js +0 -3
- package/dist/core/web/public/next/assets/VerseConsoleApp-Cu5aG6J6.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-mOAXvWa6.css +0 -1
- package/dist/core/web/public/next/assets/fleet-model-DXCfVtNW.js +0 -2
- package/dist/core/web/public/next/assets/fleet-model-DXCfVtNW.js.map +0 -1
- package/dist/core/web/public/next/assets/format-fggwu_Rs.js +0 -2
- package/dist/core/web/public/next/assets/format-fggwu_Rs.js.map +0 -1
- package/dist/core/web/public/next/assets/global-BHjd6fVZ.js +0 -2
- package/dist/core/web/public/next/assets/global-BHjd6fVZ.js.map +0 -1
- package/dist/core/web/public/next/assets/global-BeRYd1Bu.css +0 -1
- package/dist/core/web/public/next/assets/index-CJ-e8YA9.js +0 -2
- package/dist/core/web/public/next/assets/index-CJ-e8YA9.js.map +0 -1
- package/dist/core/web/public/next/assets/index-CpgkRaTi.js +0 -51
- package/dist/core/web/public/next/assets/index-CpgkRaTi.js.map +0 -1
- package/dist/core/web/public/next/assets/queries-856qUN3Y.js +0 -2
- package/dist/core/web/public/next/assets/queries-856qUN3Y.js.map +0 -1
- package/dist/core/web/public/next/assets/use-guarded-action-Bqa2v0a-.css +0 -1
- package/dist/core/web/public/next/assets/use-guarded-action-DwaESKVy.js +0 -2
- package/dist/core/web/public/next/assets/use-guarded-action-DwaESKVy.js.map +0 -1
- package/dist/core/web/public/next/assets/verse-model-Dllo8rRt.js +0 -2
- package/dist/core/web/public/next/assets/verse-model-Dllo8rRt.js.map +0 -1
- package/dist/core/web/public/next/assets/workspace-model-JgmWDKt4.js +0 -2
- package/dist/core/web/public/next/assets/workspace-model-JgmWDKt4.js.map +0 -1
- /package/node_modules/marked/{LICENSE.md → LICENSE} +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity and invocation for the DETACHED Anthropic proxy host process.
|
|
3
|
+
*
|
|
4
|
+
* Two jobs, both of which are "a pid is not an identity" problems:
|
|
5
|
+
*
|
|
6
|
+
* 1. HOW TO START ONE. The proxy runs as a child of the hub's own CLI, so
|
|
7
|
+
* something has to answer "what argv re-enters this program?" — and the
|
|
8
|
+
* answer differs in all three shipping runtimes. See
|
|
9
|
+
* {@link anthropicProxyHostInvocation}.
|
|
10
|
+
*
|
|
11
|
+
* 2. HOW TO RECOGNISE ONE. `proxy-supervisor.ts` must be able to adopt a proxy
|
|
12
|
+
* it did not spawn, refuse a listener it does not own, and terminate only
|
|
13
|
+
* what is genuinely ours after a pid has possibly been recycled. That is
|
|
14
|
+
* the same discipline process.ts applies to llama-server, with the same
|
|
15
|
+
* rule: EVERY condition must hold, because any one of them alone is
|
|
16
|
+
* forgeable by an unrelated command line.
|
|
17
|
+
*
|
|
18
|
+
* WHY THE ENTRY POINT IS A SUBCOMMAND AND NOT A TOP-LEVEL FLAG: the hub already
|
|
19
|
+
* has internal re-entry flags (`--_cutoff-checkpoint-supervisor`, the probe
|
|
20
|
+
* helpers), all dispatched from `src/cli/index.ts`. This one is reached as
|
|
21
|
+
* `local-runtime --_proxy-host` instead, so the whole dispatch lives inside the
|
|
22
|
+
* `local-runtime` command that owns it. The flag is operand-free apart from two
|
|
23
|
+
* integers, absent from help and completions, and — the part that matters —
|
|
24
|
+
* CANNOT carry a bind host. The child re-derives its host from the persisted
|
|
25
|
+
* config and re-applies `gateBindHost`, so no argv anywhere can move the
|
|
26
|
+
* listener off loopback. See proxy-host.ts.
|
|
27
|
+
*/
|
|
28
|
+
import { createRequire } from 'node:module';
|
|
29
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
30
|
+
import { pathToFileURL } from 'node:url';
|
|
31
|
+
import { bundledIntoSingleFileBinary, insideSingleFileBinaryRoot, } from '../../resources/probe-helper-invocation.js';
|
|
32
|
+
import { processTable } from './process.js';
|
|
33
|
+
/** The `local-runtime` subcommand word that re-enters this program as a proxy. */
|
|
34
|
+
export const ANTHROPIC_PROXY_HOST_FLAG = '--_proxy-host';
|
|
35
|
+
/** The command word the internal flag lives under. */
|
|
36
|
+
export const ANTHROPIC_PROXY_COMMAND = 'local-runtime';
|
|
37
|
+
/** Flag naming the port the PROXY binds. */
|
|
38
|
+
export const ANTHROPIC_PROXY_PORT_FLAG = '--anthropic-port';
|
|
39
|
+
/** Flag naming the llama-server port the proxy forwards to. */
|
|
40
|
+
export const ANTHROPIC_PROXY_UPSTREAM_PORT_FLAG = '--upstream-port';
|
|
41
|
+
/**
|
|
42
|
+
* Argv (after argv[0]) for a proxy host serving `port` from `upstreamPort`.
|
|
43
|
+
*
|
|
44
|
+
* PURE, so the exact spelling is pinned by unit tests rather than discovered
|
|
45
|
+
* from a running process — and so {@link argvMatchesProxyRecord} and this
|
|
46
|
+
* function cannot drift apart into a supervisor that can start a proxy it can
|
|
47
|
+
* never recognise again.
|
|
48
|
+
*/
|
|
49
|
+
export function anthropicProxyHostArgs(port, upstreamPort) {
|
|
50
|
+
return [
|
|
51
|
+
ANTHROPIC_PROXY_COMMAND,
|
|
52
|
+
ANTHROPIC_PROXY_HOST_FLAG,
|
|
53
|
+
ANTHROPIC_PROXY_PORT_FLAG,
|
|
54
|
+
String(port),
|
|
55
|
+
ANTHROPIC_PROXY_UPSTREAM_PORT_FLAG,
|
|
56
|
+
String(upstreamPort),
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Pin a loader specifier to an absolute file URL.
|
|
61
|
+
*
|
|
62
|
+
* The child is spawned with `cwd: homedir()` — deliberately, because a detached
|
|
63
|
+
* process must not hold a working directory that can be deleted out from under
|
|
64
|
+
* it. That makes a BARE specifier a live bug: `node --import tsx/esm
|
|
65
|
+
* src/cli/index.ts …` puts the literal string `tsx/esm` in `execArgv`, the
|
|
66
|
+
* child resolves it from the home directory, finds no `node_modules/tsx`
|
|
67
|
+
* there, and dies with ERR_MODULE_NOT_FOUND before it can even log why.
|
|
68
|
+
*
|
|
69
|
+
* (Not hypothetical: the supervisor test spawns the CLI exactly that way and
|
|
70
|
+
* caught this. Running `tsx` through its own launcher happens to put ABSOLUTE
|
|
71
|
+
* paths in execArgv, so the defect is invisible until someone uses the plain
|
|
72
|
+
* `--import` spelling — a fine way to ship something that works on the
|
|
73
|
+
* maintainer's machine and nowhere else.)
|
|
74
|
+
*
|
|
75
|
+
* Resolution failure returns the specifier untouched: a loader we could not
|
|
76
|
+
* resolve is still more likely to work than one we dropped, and the child's
|
|
77
|
+
* own error message is more useful than a silent omission.
|
|
78
|
+
*/
|
|
79
|
+
function absoluteLoaderSpecifier(specifier, cwd) {
|
|
80
|
+
if (specifier.startsWith('file://') || isAbsolute(specifier))
|
|
81
|
+
return specifier;
|
|
82
|
+
try {
|
|
83
|
+
// The filename is a resolution anchor, not a file that has to exist.
|
|
84
|
+
return pathToFileURL(createRequire(join(cwd, 'noop.js')).resolve(specifier)).href;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return specifier;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Forward only the loader flags a child genuinely needs to run our source.
|
|
92
|
+
*
|
|
93
|
+
* `--import` / `--require` is how tsx registers its TypeScript loader; without
|
|
94
|
+
* them a child spawned in dev would be handed a `.ts` entry point that plain
|
|
95
|
+
* Node refuses to parse. Everything else in `execArgv` (inspector ports above
|
|
96
|
+
* all) is deliberately dropped: inheriting `--inspect` would make the child
|
|
97
|
+
* fight the parent for the debug port and die.
|
|
98
|
+
*
|
|
99
|
+
* Every forwarded value is made absolute first — see
|
|
100
|
+
* {@link absoluteLoaderSpecifier}.
|
|
101
|
+
*/
|
|
102
|
+
function childRuntimeArgs(execArgv, cwd) {
|
|
103
|
+
const args = [];
|
|
104
|
+
for (let index = 0; index < execArgv.length; index += 1) {
|
|
105
|
+
const arg = execArgv[index];
|
|
106
|
+
for (const flag of ['--import', '--require']) {
|
|
107
|
+
if (arg.startsWith(`${flag}=`)) {
|
|
108
|
+
args.push(`${flag}=${absoluteLoaderSpecifier(arg.slice(flag.length + 1), cwd)}`);
|
|
109
|
+
}
|
|
110
|
+
else if (arg === flag) {
|
|
111
|
+
const value = execArgv[index + 1];
|
|
112
|
+
if (value !== undefined)
|
|
113
|
+
args.push(flag, absoluteLoaderSpecifier(value, cwd));
|
|
114
|
+
index += 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return args;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Build the command + argv that re-enters this program as a proxy host.
|
|
122
|
+
*
|
|
123
|
+
* THREE SHIPPING RUNTIMES, and the middle one is the trap:
|
|
124
|
+
*
|
|
125
|
+
* dev / tsx → node <tsx --require/--import> src/cli/index.ts local-runtime …
|
|
126
|
+
* npm dist → node dist/cli/index.js local-runtime …
|
|
127
|
+
* Bun binary → <the binary itself> local-runtime …
|
|
128
|
+
*
|
|
129
|
+
* "Compiled" means: re-invoking `execPath` alone re-enters this program, so the
|
|
130
|
+
* entry script must NOT be passed as a command word. Four independent signals
|
|
131
|
+
* decide it, because each covers a case the others miss — this is the same
|
|
132
|
+
* reasoning as `cutoffCaptureCliInvocation` in the daemon, and it is written
|
|
133
|
+
* out again here rather than imported because that function's name, flag
|
|
134
|
+
* argument and failure mode all belong to cutoff capture:
|
|
135
|
+
*
|
|
136
|
+
* 1. `bundledIntoSingleFileBinary(moduleUrl)` — INTRINSIC. Inside a Bun
|
|
137
|
+
* single-file binary every bundled module's URL collapses onto the virtual
|
|
138
|
+
* root, and that is true no matter how the process was invoked. This is
|
|
139
|
+
* the signal argv cannot fool.
|
|
140
|
+
* 2. no `entry` at all — a packaged binary invoked with no script operand.
|
|
141
|
+
* 3. `insideSingleFileBinaryRoot(entry)` — Bun sets argv[1] to
|
|
142
|
+
* `/$bunfs/root/_entry.js`, a path that does not exist on disk. Spawning
|
|
143
|
+
* it would hand the CLI a command word it parses as an unknown command and
|
|
144
|
+
* rejects with exit 2 — a proxy that never starts and never says why. This
|
|
145
|
+
* is also the only form a unit test can simulate, since `import.meta.url`
|
|
146
|
+
* cannot be stubbed.
|
|
147
|
+
* 4. `entry === execPath` — a Node SEA, where argv[1] IS the executable and
|
|
148
|
+
* `import.meta.url` is an ordinary on-disk path.
|
|
149
|
+
*
|
|
150
|
+
* This repository has shipped the /$bunfs/root defect three times (both account
|
|
151
|
+
* probes, then cutoff capture). Signals 1 and 3 are precisely what
|
|
152
|
+
* probe-helper-invocation.ts exports to stop it happening a fourth.
|
|
153
|
+
*/
|
|
154
|
+
export function anthropicProxyHostInvocation(port, upstreamPort, context) {
|
|
155
|
+
const hostArgs = anthropicProxyHostArgs(port, upstreamPort);
|
|
156
|
+
const { entry } = context;
|
|
157
|
+
const compiled = bundledIntoSingleFileBinary(context.moduleUrl) ||
|
|
158
|
+
entry === undefined ||
|
|
159
|
+
entry.length === 0 ||
|
|
160
|
+
insideSingleFileBinaryRoot(entry) ||
|
|
161
|
+
resolve(entry) === resolve(context.execPath);
|
|
162
|
+
return {
|
|
163
|
+
command: context.execPath,
|
|
164
|
+
args: compiled
|
|
165
|
+
? hostArgs
|
|
166
|
+
: [...childRuntimeArgs(context.execArgv, context.cwd), entry, ...hostArgs],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** This process's own facts, for the non-test call site. */
|
|
170
|
+
export function currentHostInvocationContext(moduleUrl) {
|
|
171
|
+
return {
|
|
172
|
+
execPath: process.execPath,
|
|
173
|
+
entry: process.argv[1],
|
|
174
|
+
execArgv: process.execArgv,
|
|
175
|
+
moduleUrl,
|
|
176
|
+
cwd: process.cwd(),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Does an argv line carry `--anthropic-port <n>` for exactly this port?
|
|
181
|
+
*
|
|
182
|
+
* Matches the `=` spelling too, so a hand-typed invocation is still recognised
|
|
183
|
+
* as ours rather than becoming an unkillable stranger on our own port.
|
|
184
|
+
*/
|
|
185
|
+
export function argvBindsAnthropicPort(argv, port) {
|
|
186
|
+
const pattern = new RegExp(`(?:^|\\s)${ANTHROPIC_PROXY_PORT_FLAG}(?:=|\\s+)${port}(?:\\s|$)`);
|
|
187
|
+
return pattern.test(argv);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Is this argv line a proxy host invocation (and not, say, a grep for one)?
|
|
191
|
+
*
|
|
192
|
+
* Requires BOTH the command word and the internal flag as standalone tokens.
|
|
193
|
+
* The flag is undocumented and absent from help, parsing and completions, so an
|
|
194
|
+
* argv carrying it is one this program wrote — but requiring the command word
|
|
195
|
+
* as well keeps a stray mention in some other tool's arguments from being
|
|
196
|
+
* mistaken for a process we may terminate.
|
|
197
|
+
*/
|
|
198
|
+
export function isAnthropicProxyHostArgv(argv) {
|
|
199
|
+
const tokens = argv.trim().split(/\s+/);
|
|
200
|
+
return tokens.includes(ANTHROPIC_PROXY_COMMAND) && tokens.includes(ANTHROPIC_PROXY_HOST_FLAG);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Does this argv line belong to the proxy host described by the record?
|
|
204
|
+
*
|
|
205
|
+
* Pure, so the rule is testable without a live process. Three independent facts
|
|
206
|
+
* must all hold before the recorded pid may be signalled — the direct analogue
|
|
207
|
+
* of {@link import('./process.js').argvMatchesRecord}:
|
|
208
|
+
*
|
|
209
|
+
* 1. argv[0] is the recorded executable,
|
|
210
|
+
* 2. it is a proxy host invocation at all, and
|
|
211
|
+
* 3. it binds the recorded proxy port.
|
|
212
|
+
*
|
|
213
|
+
* A recycled pid belongs to some other program, whose argv will not be our CLI
|
|
214
|
+
* re-entering itself on our port — which is what makes recycling harmless.
|
|
215
|
+
*/
|
|
216
|
+
export function argvMatchesProxyRecord(argv, record) {
|
|
217
|
+
const line = argv.trim();
|
|
218
|
+
if (!line || !record.execPath)
|
|
219
|
+
return false;
|
|
220
|
+
if (line !== record.execPath && !line.startsWith(`${record.execPath} `))
|
|
221
|
+
return false;
|
|
222
|
+
if (!isAnthropicProxyHostArgv(line))
|
|
223
|
+
return false;
|
|
224
|
+
return argvBindsAnthropicPort(line, record.port);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Find every live proxy host bound to `port`.
|
|
228
|
+
*
|
|
229
|
+
* Used to ADOPT a proxy this process did not spawn — the ordinary case, since
|
|
230
|
+
* `ashlr local-runtime start` exits and the next invocation is a different
|
|
231
|
+
* process entirely — and to let `stop` release the port afterwards.
|
|
232
|
+
*/
|
|
233
|
+
export function findAnthropicProxiesOnPort(port) {
|
|
234
|
+
const found = [];
|
|
235
|
+
for (const { pid, argv } of processTable()) {
|
|
236
|
+
if (!isAnthropicProxyHostArgv(argv))
|
|
237
|
+
continue;
|
|
238
|
+
if (!argvBindsAnthropicPort(argv, port))
|
|
239
|
+
continue;
|
|
240
|
+
const execPath = argv.split(/\s+/)[0];
|
|
241
|
+
// An argv[0] that is not an absolute path cannot be compared against a
|
|
242
|
+
// record, and cannot be re-derived either; treat it as unidentifiable
|
|
243
|
+
// rather than guessing, exactly as the llama-server scan does.
|
|
244
|
+
if (!isAbsolute(execPath))
|
|
245
|
+
continue;
|
|
246
|
+
found.push({ pid, argv, execPath });
|
|
247
|
+
}
|
|
248
|
+
return found;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=proxy-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-process.js","sourceRoot":"","sources":["../../../../src/core/local-runtime/llama/proxy-process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Anthropic proxy's ownership record — record.ts, for the second process.
|
|
3
|
+
*
|
|
4
|
+
* Written when the detached proxy host is spawned, read on the next start,
|
|
5
|
+
* deleted on a clean stop. Everything record.ts says applies here verbatim: it
|
|
6
|
+
* is the only thing that survives a crash, it is deliberately minimal, there is
|
|
7
|
+
* no field a credential could hide in, and reading NEVER throws — a missing,
|
|
8
|
+
* truncated, foreign or hand-edited record is simply "no record", which
|
|
9
|
+
* degrades the supervisor to the unmanaged path rather than taking the CLI
|
|
10
|
+
* down.
|
|
11
|
+
*
|
|
12
|
+
* It is a sibling of record.ts rather than an extension of it because the two
|
|
13
|
+
* describe processes with independent lifetimes. Folding the proxy into
|
|
14
|
+
* llama-server's record would make every field optional, and an optional field
|
|
15
|
+
* in a shape check that gates a kill decision is exactly the wrong place for
|
|
16
|
+
* ambiguity.
|
|
17
|
+
*/
|
|
18
|
+
import type { AnthropicProxyOwnershipRecord } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Validate an untrusted parsed value into a proxy record.
|
|
21
|
+
*
|
|
22
|
+
* Exported because the shape check is the security boundary: a record that
|
|
23
|
+
* fails here is discarded, and a discarded record can never become a kill
|
|
24
|
+
* decision. Pure, so it is unit-testable without touching the filesystem.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseAnthropicProxyRecord(value: unknown): AnthropicProxyOwnershipRecord | null;
|
|
27
|
+
/** Read the proxy record, or null when there is not a valid one. Never throws. */
|
|
28
|
+
export declare function readAnthropicProxyRecord(path?: string): AnthropicProxyOwnershipRecord | null;
|
|
29
|
+
/**
|
|
30
|
+
* Persist the proxy record atomically, 0600, fsynced.
|
|
31
|
+
*
|
|
32
|
+
* Returns false rather than throwing: failing to record ownership is a
|
|
33
|
+
* degradation (the next start cannot reclaim automatically), not a reason to
|
|
34
|
+
* kill a proxy that is already forwarding correctly.
|
|
35
|
+
*/
|
|
36
|
+
export declare function writeAnthropicProxyRecord(record: AnthropicProxyOwnershipRecord, path?: string): boolean;
|
|
37
|
+
/** Forget the recorded proxy. Idempotent; never throws. */
|
|
38
|
+
export declare function clearAnthropicProxyRecord(path?: string): void;
|
|
39
|
+
/** Move a record aside without destroying it, for a corrupt-state postmortem. */
|
|
40
|
+
export declare function quarantineAnthropicProxyRecord(path?: string): string | null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Anthropic proxy's ownership record — record.ts, for the second process.
|
|
3
|
+
*
|
|
4
|
+
* Written when the detached proxy host is spawned, read on the next start,
|
|
5
|
+
* deleted on a clean stop. Everything record.ts says applies here verbatim: it
|
|
6
|
+
* is the only thing that survives a crash, it is deliberately minimal, there is
|
|
7
|
+
* no field a credential could hide in, and reading NEVER throws — a missing,
|
|
8
|
+
* truncated, foreign or hand-edited record is simply "no record", which
|
|
9
|
+
* degrades the supervisor to the unmanaged path rather than taking the CLI
|
|
10
|
+
* down.
|
|
11
|
+
*
|
|
12
|
+
* It is a sibling of record.ts rather than an extension of it because the two
|
|
13
|
+
* describe processes with independent lifetimes. Folding the proxy into
|
|
14
|
+
* llama-server's record would make every field optional, and an optional field
|
|
15
|
+
* in a shape check that gates a kill decision is exactly the wrong place for
|
|
16
|
+
* ambiguity.
|
|
17
|
+
*/
|
|
18
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
19
|
+
import { randomUUID } from 'node:crypto';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { writePrivateFileAtomically } from '../../util/private-file-write.js';
|
|
22
|
+
import { anthropicProxyRecordPath, localRuntimeDir } from './paths.js';
|
|
23
|
+
/** A record larger than this is not one of ours; refuse to parse it. */
|
|
24
|
+
const MAX_RECORD_BYTES = 64 * 1024;
|
|
25
|
+
const OWNERS = ['cli', 'launchd', 'adopted'];
|
|
26
|
+
/**
|
|
27
|
+
* Validate an untrusted parsed value into a proxy record.
|
|
28
|
+
*
|
|
29
|
+
* Exported because the shape check is the security boundary: a record that
|
|
30
|
+
* fails here is discarded, and a discarded record can never become a kill
|
|
31
|
+
* decision. Pure, so it is unit-testable without touching the filesystem.
|
|
32
|
+
*/
|
|
33
|
+
export function parseAnthropicProxyRecord(value) {
|
|
34
|
+
if (typeof value !== 'object' || value === null)
|
|
35
|
+
return null;
|
|
36
|
+
const raw = value;
|
|
37
|
+
if (raw['schemaVersion'] !== 1)
|
|
38
|
+
return null;
|
|
39
|
+
const pid = raw['pid'];
|
|
40
|
+
const port = raw['port'];
|
|
41
|
+
const upstreamPort = raw['upstreamPort'];
|
|
42
|
+
if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 1)
|
|
43
|
+
return null;
|
|
44
|
+
if (typeof port !== 'number' || !Number.isInteger(port) || port < 1 || port > 65_535)
|
|
45
|
+
return null;
|
|
46
|
+
if (typeof upstreamPort !== 'number' ||
|
|
47
|
+
!Number.isInteger(upstreamPort) ||
|
|
48
|
+
upstreamPort < 1 ||
|
|
49
|
+
upstreamPort > 65_535) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const host = raw['host'];
|
|
53
|
+
const execPath = raw['execPath'];
|
|
54
|
+
const upstreamOrigin = raw['upstreamOrigin'];
|
|
55
|
+
const startedAt = raw['startedAt'];
|
|
56
|
+
if (typeof host !== 'string' || host.length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
if (typeof execPath !== 'string' || execPath.length === 0)
|
|
59
|
+
return null;
|
|
60
|
+
if (typeof upstreamOrigin !== 'string' || upstreamOrigin.length === 0)
|
|
61
|
+
return null;
|
|
62
|
+
if (typeof startedAt !== 'string' || Number.isNaN(Date.parse(startedAt)))
|
|
63
|
+
return null;
|
|
64
|
+
const owner = raw['owner'];
|
|
65
|
+
if (typeof owner !== 'string' || !OWNERS.includes(owner))
|
|
66
|
+
return null;
|
|
67
|
+
return {
|
|
68
|
+
schemaVersion: 1,
|
|
69
|
+
pid,
|
|
70
|
+
port,
|
|
71
|
+
host,
|
|
72
|
+
execPath,
|
|
73
|
+
upstreamPort,
|
|
74
|
+
upstreamOrigin,
|
|
75
|
+
startedAt,
|
|
76
|
+
owner: owner,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Read the proxy record, or null when there is not a valid one. Never throws. */
|
|
80
|
+
export function readAnthropicProxyRecord(path = anthropicProxyRecordPath()) {
|
|
81
|
+
try {
|
|
82
|
+
const stat = statSync(path);
|
|
83
|
+
if (!stat.isFile() || stat.size > MAX_RECORD_BYTES)
|
|
84
|
+
return null;
|
|
85
|
+
return parseAnthropicProxyRecord(JSON.parse(readFileSync(path, 'utf8')));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Persist the proxy record atomically, 0600, fsynced.
|
|
93
|
+
*
|
|
94
|
+
* Returns false rather than throwing: failing to record ownership is a
|
|
95
|
+
* degradation (the next start cannot reclaim automatically), not a reason to
|
|
96
|
+
* kill a proxy that is already forwarding correctly.
|
|
97
|
+
*/
|
|
98
|
+
export function writeAnthropicProxyRecord(record, path = anthropicProxyRecordPath()) {
|
|
99
|
+
try {
|
|
100
|
+
const dir = localRuntimeDir();
|
|
101
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
102
|
+
const temporaryPath = join(dir, `.anthropic-proxy.${randomUUID()}.tmp`);
|
|
103
|
+
writePrivateFileAtomically(temporaryPath, path, `${JSON.stringify(record, null, 2)}\n`, {
|
|
104
|
+
anchorPath: dir,
|
|
105
|
+
label: 'local-runtime anthropic proxy ownership record',
|
|
106
|
+
});
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Forget the recorded proxy. Idempotent; never throws. */
|
|
114
|
+
export function clearAnthropicProxyRecord(path = anthropicProxyRecordPath()) {
|
|
115
|
+
try {
|
|
116
|
+
rmSync(path, { force: true });
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Nothing to forget, or the file is not ours to remove.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Move a record aside without destroying it, for a corrupt-state postmortem. */
|
|
123
|
+
export function quarantineAnthropicProxyRecord(path = anthropicProxyRecordPath()) {
|
|
124
|
+
try {
|
|
125
|
+
const target = `${path}.stale-${new Date().toISOString().replace(/[:.]/g, '')}`;
|
|
126
|
+
renameSync(path, target);
|
|
127
|
+
return target;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=proxy-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-record.js","sourceRoot":"","sources":["../../../../src/core/local-runtime/llama/proxy-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The proxy supervisor: start, adopt, stop and inspect the Anthropic lane.
|
|
3
|
+
*
|
|
4
|
+
* supervisor.ts, for the second process. The lifecycle is deliberately the same
|
|
5
|
+
* boring, idempotent shape, because an operator who has learned how
|
|
6
|
+
* `local-runtime start` treats llama-server should not have to learn a second
|
|
7
|
+
* set of rules for the listener in front of it:
|
|
8
|
+
*
|
|
9
|
+
* * `ensure` on a healthy proxy of ours is a no-op that says so;
|
|
10
|
+
* * a proxy already on the port that we can PROVE is ours is ADOPTED, never
|
|
11
|
+
* duplicated — the ordinary case, since every CLI invocation is a new
|
|
12
|
+
* process that spawned none of them;
|
|
13
|
+
* * a listener we cannot prove is ours is REFUSED, never killed;
|
|
14
|
+
* * `stop` with nothing running is a success, not an error;
|
|
15
|
+
* * a record that no longer describes a live process of ours is discarded
|
|
16
|
+
* rather than acted on, so a recycled pid can never become a kill.
|
|
17
|
+
*
|
|
18
|
+
* ONE DEPARTURE from llama-server's lifecycle, and it is deliberate: when we
|
|
19
|
+
* own the proxy but it is forwarding to a DIFFERENT llama-server than the one
|
|
20
|
+
* being started, it is restarted rather than reported. llama-server cannot be
|
|
21
|
+
* repointed cheaply — it holds 27 GB of mapped weights — so `start` there can
|
|
22
|
+
* only describe the drift. A proxy is a socket and a pipe; leaving one quietly
|
|
23
|
+
* forwarding to a runtime nobody is using is the two-endpoints failure this
|
|
24
|
+
* module family exists to make impossible, and here we can simply fix it.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here consults `~/.ashlr/KILL`, creates it, or removes it.
|
|
27
|
+
*/
|
|
28
|
+
import type { AshlrConfig } from '../../types.js';
|
|
29
|
+
import type { AnthropicProxyLifecycleResult } from './types.js';
|
|
30
|
+
/**
|
|
31
|
+
* How long `ensure` waits for a freshly spawned host to answer.
|
|
32
|
+
*
|
|
33
|
+
* Unlike llama-server's five minutes this is short on purpose: the child binds
|
|
34
|
+
* a socket and opens a pipe. It loads no weights, so anything past a couple of
|
|
35
|
+
* seconds is a failure to launch, not a slow start, and a generous deadline
|
|
36
|
+
* would only delay the operator learning that.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_PROXY_START_TIMEOUT_MS = 10000;
|
|
39
|
+
export interface DetachedProxyOptions {
|
|
40
|
+
/** Config the ports, bind host and loopback opt-in are resolved from. */
|
|
41
|
+
cfg?: AshlrConfig;
|
|
42
|
+
/** Override the proxy's own port. */
|
|
43
|
+
port?: number;
|
|
44
|
+
/** Override llama-server's port — what the proxy forwards to. */
|
|
45
|
+
upstreamPort?: number;
|
|
46
|
+
/** Deadline for a freshly spawned host to start answering. */
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
/** Terminate a listener on our port that we have no record for. */
|
|
49
|
+
force?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* How to build the child's command line. Injected ONLY by tests, which run
|
|
52
|
+
* under a test runner whose `process.argv[1]` is the runner rather than this
|
|
53
|
+
* CLI, so the real resolution would produce an argv that re-enters vitest.
|
|
54
|
+
*/
|
|
55
|
+
invocation?: (port: number, upstreamPort: number) => {
|
|
56
|
+
command: string;
|
|
57
|
+
args: string[];
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Does this port accept a TCP connection?
|
|
62
|
+
*
|
|
63
|
+
* This — not an HTTP round trip — is what "the child we just spawned has bound
|
|
64
|
+
* its socket" actually means, and it is deliberately the weaker question.
|
|
65
|
+
*
|
|
66
|
+
* The proxy pipes every path but `/v1/messages` byte-for-byte to llama-server
|
|
67
|
+
* and answers NOTHING for itself; that invariant is load-bearing (see the
|
|
68
|
+
* anthropic-proxy.ts header) and is not worth breaking for a health endpoint.
|
|
69
|
+
* The consequence is that an HTTP probe through the proxy measures llama-server
|
|
70
|
+
* too: a wedged upstream that accepts connections and never replies would make
|
|
71
|
+
* a perfectly healthy proxy look dead, and `start` would then report failure
|
|
72
|
+
* for a lane that was working. Asking the narrower question keeps the two
|
|
73
|
+
* processes' health independent, which is the whole reason they are two
|
|
74
|
+
* processes.
|
|
75
|
+
*
|
|
76
|
+
* Paired with `processAlive(pid)` on a pid we spawned, onto a port `isPortFree`
|
|
77
|
+
* said was free moments earlier, this is a sound proof of ownership.
|
|
78
|
+
*
|
|
79
|
+
* Never throws; an unreachable port is `false`, not an exception.
|
|
80
|
+
*/
|
|
81
|
+
export declare function anthropicProxyAccepts(host: string, port: number, timeoutMs?: number): Promise<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* Is a proxy answering HTTP on this host/port, end to end?
|
|
84
|
+
*
|
|
85
|
+
* ANY HTTP response counts, including a 502. That is not laziness: a 502 from
|
|
86
|
+
* this listener means the proxy is up and llama-server is not, which is exactly
|
|
87
|
+
* the proxy being healthy at the only job it has. Requiring a 200 would make
|
|
88
|
+
* the Anthropic lane un-reportable whenever the runtime behind it was still
|
|
89
|
+
* loading.
|
|
90
|
+
*
|
|
91
|
+
* Used for REPORTING (`status`), not for lifecycle decisions — see
|
|
92
|
+
* {@link anthropicProxyAccepts} for why the two are separate questions.
|
|
93
|
+
*
|
|
94
|
+
* Never throws; an unreachable port is `false`, not an exception.
|
|
95
|
+
*/
|
|
96
|
+
export declare function anthropicProxyResponds(host: string, port: number, timeoutMs?: number): Promise<boolean>;
|
|
97
|
+
/** Recover `--upstream-port` from an argv line. Pure; null when absent. */
|
|
98
|
+
export declare function upstreamPortFromArgv(argv: string): number | null;
|
|
99
|
+
/**
|
|
100
|
+
* Bring up the detached proxy, or prove one is already up.
|
|
101
|
+
*
|
|
102
|
+
* Never throws and never fails loudly for an expected state: llama-server
|
|
103
|
+
* serving correctly is still a success even when the Anthropic lane in front of
|
|
104
|
+
* it could not bind, so the caller gets a value it can fold into its own
|
|
105
|
+
* sentence rather than an exception it can forget to catch.
|
|
106
|
+
*/
|
|
107
|
+
export declare function ensureDetachedAnthropicProxy(options?: DetachedProxyOptions): Promise<AnthropicProxyLifecycleResult>;
|
|
108
|
+
/**
|
|
109
|
+
* Stop the detached proxy and release the port.
|
|
110
|
+
*
|
|
111
|
+
* Idempotent: stopping what is not running is `not-running` and `ok: true`, not
|
|
112
|
+
* an error. A listener we cannot prove is ours is refused rather than killed,
|
|
113
|
+
* unless `force` is given — the same bargain `local-runtime stop --force`
|
|
114
|
+
* offers for llama-server.
|
|
115
|
+
*/
|
|
116
|
+
export declare function stopDetachedAnthropicProxy(options?: DetachedProxyOptions): Promise<AnthropicProxyLifecycleResult>;
|
|
117
|
+
/** Read the proxy's state without changing anything. Never throws. */
|
|
118
|
+
export declare function statusDetachedAnthropicProxy(options?: DetachedProxyOptions): Promise<AnthropicProxyLifecycleResult>;
|