@ashlr/hub 2.2.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +95 -8
- package/dist/api/plugin.d.ts +1 -0
- package/dist/cli/backlog.js +2 -0
- package/dist/cli/backlog.js.map +1 -1
- package/dist/cli/digest.d.ts +6 -0
- package/dist/cli/digest.js +127 -2
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/eval-fixtures.d.ts +18 -0
- package/dist/cli/eval-fixtures.js +38 -0
- package/dist/cli/eval-fixtures.js.map +1 -0
- package/dist/cli/eval.d.ts +56 -0
- package/dist/cli/eval.js +283 -0
- package/dist/cli/eval.js.map +1 -0
- package/dist/cli/fleet.d.ts +27 -0
- package/dist/cli/fleet.js +222 -0
- package/dist/cli/fleet.js.map +1 -0
- package/dist/cli/goal.d.ts +15 -0
- package/dist/cli/goal.js +91 -0
- package/dist/cli/goal.js.map +1 -0
- package/dist/cli/help.js +10 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inbox.d.ts +6 -0
- package/dist/cli/inbox.js +183 -8
- package/dist/cli/inbox.js.map +1 -1
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/loop.d.ts +16 -0
- package/dist/cli/loop.js +66 -0
- package/dist/cli/loop.js.map +1 -0
- package/dist/cli/mcp.d.ts +12 -0
- package/dist/cli/mcp.js +148 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/onboard.d.ts +16 -0
- package/dist/cli/onboard.js +71 -0
- package/dist/cli/onboard.js.map +1 -1
- package/dist/cli/plugins.js +9 -8
- package/dist/cli/plugins.js.map +1 -1
- package/dist/cli/pulse.d.ts +10 -0
- package/dist/cli/pulse.js +261 -0
- package/dist/cli/pulse.js.map +1 -1
- package/dist/cli/run.js +21 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/stack.d.ts +34 -0
- package/dist/cli/stack.js +221 -0
- package/dist/cli/stack.js.map +1 -0
- package/dist/cli/update.js +12 -5
- package/dist/cli/update.js.map +1 -1
- package/dist/core/daemon/loop.d.ts +10 -5
- package/dist/core/daemon/loop.js +167 -37
- package/dist/core/daemon/loop.js.map +1 -1
- package/dist/core/env-bridge.js +3 -2
- package/dist/core/env-bridge.js.map +1 -1
- package/dist/core/fleet/automerge-pass.d.ts +33 -0
- package/dist/core/fleet/automerge-pass.js +61 -0
- package/dist/core/fleet/automerge-pass.js.map +1 -0
- package/dist/core/fleet/quota.d.ts +70 -0
- package/dist/core/fleet/quota.js +198 -0
- package/dist/core/fleet/quota.js.map +1 -0
- package/dist/core/fleet/router.d.ts +46 -0
- package/dist/core/fleet/router.js +121 -0
- package/dist/core/fleet/router.js.map +1 -0
- package/dist/core/fleet/self.d.ts +58 -0
- package/dist/core/fleet/self.js +187 -0
- package/dist/core/fleet/self.js.map +1 -0
- package/dist/core/fleet/status.d.ts +54 -0
- package/dist/core/fleet/status.js +157 -0
- package/dist/core/fleet/status.js.map +1 -0
- package/dist/core/foundry/provenance.d.ts +78 -0
- package/dist/core/foundry/provenance.js +172 -0
- package/dist/core/foundry/provenance.js.map +1 -0
- package/dist/core/git.d.ts +13 -0
- package/dist/core/git.js +26 -0
- package/dist/core/git.js.map +1 -1
- package/dist/core/inbox/merge.d.ts +151 -0
- package/dist/core/inbox/merge.js +858 -0
- package/dist/core/inbox/merge.js.map +1 -0
- package/dist/core/inbox/store.js +10 -1
- package/dist/core/inbox/store.js.map +1 -1
- package/dist/core/integrations/markdown.d.ts +66 -0
- package/dist/core/integrations/markdown.js +132 -0
- package/dist/core/integrations/markdown.js.map +1 -0
- package/dist/core/integrations/secrets.d.ts +37 -0
- package/dist/core/integrations/secrets.js +67 -0
- package/dist/core/integrations/secrets.js.map +1 -0
- package/dist/core/integrations/stack.d.ts +47 -0
- package/dist/core/integrations/stack.js +107 -0
- package/dist/core/integrations/stack.js.map +1 -0
- package/dist/core/mcp-native-engineer.d.ts +123 -0
- package/dist/core/mcp-native-engineer.js +936 -0
- package/dist/core/mcp-native-engineer.js.map +1 -0
- package/dist/core/mcp-native.js +20 -42
- package/dist/core/mcp-native.js.map +1 -1
- package/dist/core/mcp-registry.js +2 -0
- package/dist/core/mcp-registry.js.map +1 -1
- package/dist/core/observability/codex-source.d.ts +53 -0
- package/dist/core/observability/codex-source.js +390 -0
- package/dist/core/observability/codex-source.js.map +1 -0
- package/dist/core/observability/estimate.d.ts +12 -0
- package/dist/core/observability/estimate.js +26 -0
- package/dist/core/observability/estimate.js.map +1 -1
- package/dist/core/observability/limits.d.ts +52 -0
- package/dist/core/observability/limits.js +316 -0
- package/dist/core/observability/limits.js.map +1 -0
- package/dist/core/observability/usage-source.js +3 -1
- package/dist/core/observability/usage-source.js.map +1 -1
- package/dist/core/portfolio/scanners.d.ts +1 -0
- package/dist/core/portfolio/scanners.js +40 -1
- package/dist/core/portfolio/scanners.js.map +1 -1
- package/dist/core/providers.js +4 -0
- package/dist/core/providers.js.map +1 -1
- package/dist/core/quality/health.js +1 -0
- package/dist/core/quality/health.js.map +1 -1
- package/dist/core/run/agent-loop.d.ts +8 -0
- package/dist/core/run/agent-loop.js +24 -14
- package/dist/core/run/agent-loop.js.map +1 -1
- package/dist/core/run/engine-registry.d.ts +48 -0
- package/dist/core/run/engine-registry.js +190 -0
- package/dist/core/run/engine-registry.js.map +1 -0
- package/dist/core/run/engines.d.ts +17 -3
- package/dist/core/run/engines.js +63 -44
- package/dist/core/run/engines.js.map +1 -1
- package/dist/core/run/learned-router.d.ts +121 -0
- package/dist/core/run/learned-router.js +374 -0
- package/dist/core/run/learned-router.js.map +1 -0
- package/dist/core/run/model-manager.js +3 -3
- package/dist/core/run/model-manager.js.map +1 -1
- package/dist/core/run/model-profile.d.ts +67 -0
- package/dist/core/run/model-profile.js +132 -0
- package/dist/core/run/model-profile.js.map +1 -0
- package/dist/core/run/orchestrator.d.ts +1 -1
- package/dist/core/run/orchestrator.js +642 -510
- package/dist/core/run/orchestrator.js.map +1 -1
- package/dist/core/run/prompts/budget.d.ts +25 -0
- package/dist/core/run/prompts/budget.js +51 -0
- package/dist/core/run/prompts/budget.js.map +1 -0
- package/dist/core/run/prompts/index.d.ts +14 -0
- package/dist/core/run/prompts/index.js +59 -0
- package/dist/core/run/prompts/index.js.map +1 -0
- package/dist/core/run/prompts/layers.d.ts +26 -0
- package/dist/core/run/prompts/layers.js +73 -0
- package/dist/core/run/prompts/layers.js.map +1 -0
- package/dist/core/run/prompts/roles.d.ts +23 -0
- package/dist/core/run/prompts/roles.js +48 -0
- package/dist/core/run/prompts/roles.js.map +1 -0
- package/dist/core/run/prompts/types.d.ts +38 -0
- package/dist/core/run/prompts/types.js +5 -0
- package/dist/core/run/prompts/types.js.map +1 -0
- package/dist/core/run/provider-client.d.ts +6 -24
- package/dist/core/run/provider-client.js +125 -61
- package/dist/core/run/provider-client.js.map +1 -1
- package/dist/core/run/router.js +8 -0
- package/dist/core/run/router.js.map +1 -1
- package/dist/core/run/sandboxed-engine.d.ts +66 -0
- package/dist/core/run/sandboxed-engine.js +262 -0
- package/dist/core/run/sandboxed-engine.js.map +1 -0
- package/dist/core/run/verify-commands.d.ts +83 -0
- package/dist/core/run/verify-commands.js +234 -0
- package/dist/core/run/verify-commands.js.map +1 -0
- package/dist/core/run/verify.d.ts +31 -1
- package/dist/core/run/verify.js +48 -0
- package/dist/core/run/verify.js.map +1 -1
- package/dist/core/sandbox/audit.d.ts +27 -0
- package/dist/core/sandbox/audit.js +34 -0
- package/dist/core/sandbox/audit.js.map +1 -1
- package/dist/core/sandbox/confine.d.ts +134 -0
- package/dist/core/sandbox/confine.js +370 -0
- package/dist/core/sandbox/confine.js.map +1 -0
- package/dist/core/types.d.ts +227 -5
- package/dist/core/web/api.js +77 -28
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/control.d.ts +116 -0
- package/dist/core/web/control.js +307 -0
- package/dist/core/web/control.js.map +1 -0
- package/dist/core/web/public/app.js +512 -6
- package/dist/core/web/public/styles.css +468 -0
- package/package.json +3 -2
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quota.ts — M46: per-backend rolling-window rate/quota ledger.
|
|
3
|
+
*
|
|
4
|
+
* Subscription frontier backends (Claude / Codex) are flat-fee but RATE-LIMITED
|
|
5
|
+
* (not token-billed), so the fleet must throttle dispatches per backend. This
|
|
6
|
+
* ledger records each dispatch and answers "how many in the last window?" and
|
|
7
|
+
* "are we still under the configured cap?".
|
|
8
|
+
*
|
|
9
|
+
* Persistence discipline mirrors daemon/state.ts EXACTLY:
|
|
10
|
+
* - Atomic writes (tmp file + POSIX rename).
|
|
11
|
+
* - NEVER throws — load returns a fresh empty ledger on missing/corrupt file;
|
|
12
|
+
* record swallows any persistence error.
|
|
13
|
+
* - mkdir -p the parent dir.
|
|
14
|
+
* - Bounded history (last ~2000 events).
|
|
15
|
+
* - Homedir re-resolved at call time so tests can relocate HOME.
|
|
16
|
+
*
|
|
17
|
+
* No new runtime deps; node builtins only. evalQuota mirrors evalBudget's
|
|
18
|
+
* three-level (ok/warn/over) semantics with a null-cap-disabled path.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Constants
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/** Maximum number of dispatch events retained in quota.json. */
|
|
27
|
+
const MAX_EVENTS = 2000;
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// windowToMs — EXTENDS rollup's vocabulary with sub-day windows (local copy)
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/**
|
|
32
|
+
* Convert a window label to milliseconds. Extends rollup.ts's day-granularity
|
|
33
|
+
* vocabulary with sub-day windows used by rate limits. Unknown labels fall back
|
|
34
|
+
* to 1h (the most conservative useful default for rate caps).
|
|
35
|
+
*/
|
|
36
|
+
export function windowToMs(window) {
|
|
37
|
+
switch (window) {
|
|
38
|
+
case '1m': return 60_000;
|
|
39
|
+
case '5m': return 5 * 60_000;
|
|
40
|
+
case '15m': return 15 * 60_000;
|
|
41
|
+
case '1h': return 3_600_000;
|
|
42
|
+
case '1d': return 86_400_000;
|
|
43
|
+
case '7d': return 7 * 86_400_000;
|
|
44
|
+
case '30d': return 30 * 86_400_000;
|
|
45
|
+
default: return 3_600_000;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Path helpers (re-resolved at call time so tests can relocate HOME)
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
function fleetDir() {
|
|
52
|
+
return join(homedir(), '.ashlr', 'fleet');
|
|
53
|
+
}
|
|
54
|
+
/** Absolute path to the fleet quota ledger file. */
|
|
55
|
+
export function fleetQuotaPath() {
|
|
56
|
+
return join(fleetDir(), 'quota.json');
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Fresh default
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
function freshQuota() {
|
|
62
|
+
return { events: [] };
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Load
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
/**
|
|
68
|
+
* Read and parse fleetQuotaPath(). NEVER throws.
|
|
69
|
+
* Returns a fresh empty ledger when the file is missing or malformed.
|
|
70
|
+
*/
|
|
71
|
+
export function loadFleetQuota() {
|
|
72
|
+
const p = fleetQuotaPath();
|
|
73
|
+
if (!existsSync(p))
|
|
74
|
+
return freshQuota();
|
|
75
|
+
try {
|
|
76
|
+
const raw = readFileSync(p, 'utf8');
|
|
77
|
+
const parsed = JSON.parse(raw);
|
|
78
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
79
|
+
return freshQuota();
|
|
80
|
+
}
|
|
81
|
+
const obj = parsed;
|
|
82
|
+
const events = Array.isArray(obj['events'])
|
|
83
|
+
? obj['events'].filter((e) => typeof e === 'object' &&
|
|
84
|
+
e !== null &&
|
|
85
|
+
!Array.isArray(e) &&
|
|
86
|
+
typeof e['backend'] === 'string' &&
|
|
87
|
+
typeof e['ts'] === 'string')
|
|
88
|
+
: [];
|
|
89
|
+
return { events };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Corrupt JSON or any other read error — return a fresh empty ledger.
|
|
93
|
+
return freshQuota();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Save (atomic) — internal
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Atomically write the ledger via tmp-file + rename (POSIX-atomic).
|
|
101
|
+
* Creates ~/.ashlr/fleet recursively. Bounds events. Never throws.
|
|
102
|
+
*/
|
|
103
|
+
function saveFleetQuota(q) {
|
|
104
|
+
try {
|
|
105
|
+
const dir = fleetDir();
|
|
106
|
+
if (!existsSync(dir)) {
|
|
107
|
+
mkdirSync(dir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
const bounded = {
|
|
110
|
+
events: q.events.slice(-MAX_EVENTS),
|
|
111
|
+
};
|
|
112
|
+
const dest = fleetQuotaPath();
|
|
113
|
+
const tmp = dest + '.tmp';
|
|
114
|
+
writeFileSync(tmp, JSON.stringify(bounded, null, 2) + '\n', 'utf8');
|
|
115
|
+
renameSync(tmp, dest);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Persistence failure must not crash the fleet — swallow silently.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Record a dispatch
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
/**
|
|
125
|
+
* Append a dispatch event for `backend` (ts = now) and persist. Never throws.
|
|
126
|
+
*/
|
|
127
|
+
export function recordUse(backend) {
|
|
128
|
+
try {
|
|
129
|
+
const q = loadFleetQuota();
|
|
130
|
+
q.events.push({ backend, ts: new Date().toISOString() });
|
|
131
|
+
saveFleetQuota(q);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Never throws.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Window counting
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
/**
|
|
141
|
+
* Count events for `backend` whose ts is within the last `windowMs`
|
|
142
|
+
* (ts >= now - windowMs). Reads the ledger internally; `now` is injectable for
|
|
143
|
+
* deterministic tests (defaults to Date.now()).
|
|
144
|
+
*/
|
|
145
|
+
export function usesInWindow(backend, windowMs, now) {
|
|
146
|
+
const nowMs = now ?? Date.now();
|
|
147
|
+
const cutoff = nowMs - windowMs;
|
|
148
|
+
const q = loadFleetQuota();
|
|
149
|
+
let count = 0;
|
|
150
|
+
for (const ev of q.events) {
|
|
151
|
+
if (ev.backend !== backend)
|
|
152
|
+
continue;
|
|
153
|
+
const t = Date.parse(ev.ts);
|
|
154
|
+
if (Number.isNaN(t))
|
|
155
|
+
continue;
|
|
156
|
+
if (t >= cutoff)
|
|
157
|
+
count++;
|
|
158
|
+
}
|
|
159
|
+
return count;
|
|
160
|
+
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Limit check
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
/**
|
|
165
|
+
* True when `backend` is still UNDER its configured rate limit (or unlimited).
|
|
166
|
+
*
|
|
167
|
+
* Mirrors evalBudget's null-cap-disabled logic: when no
|
|
168
|
+
* cfg.foundry.limits[backend] is configured, the backend is unlimited ⇒ always
|
|
169
|
+
* within limit. Otherwise true iff usesInWindow(...) < limit.max. `now` is
|
|
170
|
+
* injectable for tests.
|
|
171
|
+
*/
|
|
172
|
+
export function withinLimit(backend, cfg, now) {
|
|
173
|
+
const limit = cfg.foundry?.limits?.[backend];
|
|
174
|
+
if (!limit)
|
|
175
|
+
return true; // no cap configured ⇒ unlimited
|
|
176
|
+
const used = usesInWindow(backend, windowToMs(limit.window), now);
|
|
177
|
+
return used < limit.max;
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Three-level quota evaluation (mirrors evalBudget)
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
/**
|
|
183
|
+
* Evaluate a backend's rate-limit status as 'ok' | 'warn' | 'over', mirroring
|
|
184
|
+
* evalBudget: 'over' at >= 100% of the cap, 'warn' at >= 80%, else 'ok'. When no
|
|
185
|
+
* cap is configured the backend is unlimited ⇒ always 'ok'. `now` is injectable.
|
|
186
|
+
*/
|
|
187
|
+
export function evalQuota(backend, cfg, now) {
|
|
188
|
+
const limit = cfg.foundry?.limits?.[backend];
|
|
189
|
+
if (!limit || limit.max <= 0)
|
|
190
|
+
return 'ok'; // unlimited (or nonsensical cap)
|
|
191
|
+
const used = usesInWindow(backend, windowToMs(limit.window), now);
|
|
192
|
+
if (used >= limit.max)
|
|
193
|
+
return 'over';
|
|
194
|
+
if (used >= limit.max * 0.8)
|
|
195
|
+
return 'warn';
|
|
196
|
+
return 'ok';
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=quota.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota.js","sourceRoot":"","sources":["../../../src/core/fleet/quota.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* router.ts — M46: capability-tiered backend router for the autonomous fleet.
|
|
3
|
+
*
|
|
4
|
+
* Maps a discovered WorkItem to the backend (EngineId) that should attempt it,
|
|
5
|
+
* honoring cfg.foundry.allowedBackends and PATH availability. The policy is a
|
|
6
|
+
* pure, DETERMINISTIC heuristic (no Math.random, no clock): the same item +
|
|
7
|
+
* config always routes the same way, so the decision is unit-testable and the
|
|
8
|
+
* senior load is split predictably across frontier backends.
|
|
9
|
+
*
|
|
10
|
+
* GUARDRAILS:
|
|
11
|
+
* - NEVER returns a backend not in allowedBackends (default ['builtin']).
|
|
12
|
+
* - NEVER returns an external backend that fails engineInstalled() — falls back
|
|
13
|
+
* to 'builtin' (which is always available).
|
|
14
|
+
* - Pure except for engineInstalled()'s PATH probe (read-only, never throws).
|
|
15
|
+
*
|
|
16
|
+
* ROUTING HEURISTIC (documented):
|
|
17
|
+
* - LOW-effort / bulk classes — source 'doc'|'dep'|'todo'|'test', OR effort<=2
|
|
18
|
+
* — go to 'builtin' (local loop, unbounded, $0). No reason to spend a
|
|
19
|
+
* rate-limited frontier seat on cheap, mechanical work.
|
|
20
|
+
* - HIGH-difficulty / important classes — source 'security'|'issue', OR
|
|
21
|
+
* effort>=4, OR a high score — go to a FRONTIER backend (prefer 'claude',
|
|
22
|
+
* else 'codex') when it is allowed AND installed, else fall back to 'builtin'.
|
|
23
|
+
* - MEDIUM (everything else) stays on 'builtin' UNLESS a frontier backend is
|
|
24
|
+
* allowed+installed AND the item is src-touching (a 'test' or 'todo' that
|
|
25
|
+
* is not low-effort, or any non-bulk source) — then it gets a frontier seat.
|
|
26
|
+
* - When MULTIPLE frontier backends are allowed+installed, alternate
|
|
27
|
+
* DETERMINISTICALLY by a stable hash of item.id so 'claude' and 'codex'
|
|
28
|
+
* share the senior load evenly.
|
|
29
|
+
*/
|
|
30
|
+
import type { AshlrConfig, EngineId, EngineTier, WorkItem } from '../types.js';
|
|
31
|
+
/** The outcome of routing a WorkItem to a backend. */
|
|
32
|
+
export interface RouteDecision {
|
|
33
|
+
/** Backend chosen to attempt the item. Always allowed + (if external) installed. */
|
|
34
|
+
backend: EngineId;
|
|
35
|
+
/** Tier of the chosen backend ('local' | 'frontier'). */
|
|
36
|
+
tier: EngineTier;
|
|
37
|
+
/** Short human-readable rationale. */
|
|
38
|
+
reason: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Route a WorkItem to the backend that should attempt it.
|
|
42
|
+
*
|
|
43
|
+
* PURE + DETERMINISTIC (modulo engineInstalled's read-only PATH probe). Never
|
|
44
|
+
* throws. Honors allowedBackends and PATH availability — see module heuristic.
|
|
45
|
+
*/
|
|
46
|
+
export declare function routeBackend(item: WorkItem, cfg: AshlrConfig): RouteDecision;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* router.ts — M46: capability-tiered backend router for the autonomous fleet.
|
|
3
|
+
*
|
|
4
|
+
* Maps a discovered WorkItem to the backend (EngineId) that should attempt it,
|
|
5
|
+
* honoring cfg.foundry.allowedBackends and PATH availability. The policy is a
|
|
6
|
+
* pure, DETERMINISTIC heuristic (no Math.random, no clock): the same item +
|
|
7
|
+
* config always routes the same way, so the decision is unit-testable and the
|
|
8
|
+
* senior load is split predictably across frontier backends.
|
|
9
|
+
*
|
|
10
|
+
* GUARDRAILS:
|
|
11
|
+
* - NEVER returns a backend not in allowedBackends (default ['builtin']).
|
|
12
|
+
* - NEVER returns an external backend that fails engineInstalled() — falls back
|
|
13
|
+
* to 'builtin' (which is always available).
|
|
14
|
+
* - Pure except for engineInstalled()'s PATH probe (read-only, never throws).
|
|
15
|
+
*
|
|
16
|
+
* ROUTING HEURISTIC (documented):
|
|
17
|
+
* - LOW-effort / bulk classes — source 'doc'|'dep'|'todo'|'test', OR effort<=2
|
|
18
|
+
* — go to 'builtin' (local loop, unbounded, $0). No reason to spend a
|
|
19
|
+
* rate-limited frontier seat on cheap, mechanical work.
|
|
20
|
+
* - HIGH-difficulty / important classes — source 'security'|'issue', OR
|
|
21
|
+
* effort>=4, OR a high score — go to a FRONTIER backend (prefer 'claude',
|
|
22
|
+
* else 'codex') when it is allowed AND installed, else fall back to 'builtin'.
|
|
23
|
+
* - MEDIUM (everything else) stays on 'builtin' UNLESS a frontier backend is
|
|
24
|
+
* allowed+installed AND the item is src-touching (a 'test' or 'todo' that
|
|
25
|
+
* is not low-effort, or any non-bulk source) — then it gets a frontier seat.
|
|
26
|
+
* - When MULTIPLE frontier backends are allowed+installed, alternate
|
|
27
|
+
* DETERMINISTICALLY by a stable hash of item.id so 'claude' and 'codex'
|
|
28
|
+
* share the senior load evenly.
|
|
29
|
+
*/
|
|
30
|
+
import { engineInstalled } from '../run/engines.js';
|
|
31
|
+
import { engineTierOf } from '../run/sandboxed-engine.js';
|
|
32
|
+
/** Frontier backends in preference order — first allowed+installed wins ties. */
|
|
33
|
+
const FRONTIER_PREFERENCE = ['claude', 'codex'];
|
|
34
|
+
/** Bulk / low-difficulty sources that route to the local builtin loop. */
|
|
35
|
+
const BULK_SOURCES = new Set(['doc', 'dep', 'todo', 'test']);
|
|
36
|
+
/** High-difficulty / important sources that warrant a frontier seat. */
|
|
37
|
+
const SENIOR_SOURCES = new Set(['security', 'issue']);
|
|
38
|
+
/** Score at/above which an item is considered "important" (frontier-worthy). */
|
|
39
|
+
const HIGH_SCORE = 7;
|
|
40
|
+
/**
|
|
41
|
+
* Stable, deterministic 32-bit FNV-1a hash of a string. Used only to pick which
|
|
42
|
+
* frontier backend handles an item — NOT a security primitive.
|
|
43
|
+
*/
|
|
44
|
+
function stableHash(s) {
|
|
45
|
+
let h = 0x811c9dc5;
|
|
46
|
+
for (let i = 0; i < s.length; i++) {
|
|
47
|
+
h ^= s.charCodeAt(i);
|
|
48
|
+
// 32-bit FNV prime multiply (kept in unsigned 32-bit range).
|
|
49
|
+
h = Math.imul(h, 0x01000193);
|
|
50
|
+
}
|
|
51
|
+
return h >>> 0;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The allowed backends, defaulting to ['builtin'] when foundry is absent.
|
|
55
|
+
* Always contains at least 'builtin' as a guaranteed fallback.
|
|
56
|
+
*/
|
|
57
|
+
function allowedSet(cfg) {
|
|
58
|
+
const allowed = cfg.foundry?.allowedBackends ?? ['builtin'];
|
|
59
|
+
const set = new Set(allowed);
|
|
60
|
+
set.add('builtin'); // builtin is always a valid fallback target
|
|
61
|
+
return set;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The frontier backends that are BOTH allowed and installed, in preference
|
|
65
|
+
* order. Empty when none qualify (caller then falls back to 'builtin').
|
|
66
|
+
*/
|
|
67
|
+
function availableFrontiers(cfg) {
|
|
68
|
+
const allowed = allowedSet(cfg);
|
|
69
|
+
return FRONTIER_PREFERENCE.filter((e) => allowed.has(e) && engineInstalled(e));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Pick a frontier backend deterministically. With one candidate, returns it;
|
|
73
|
+
* with multiple, alternates by a stable hash of item.id so the senior load is
|
|
74
|
+
* split predictably (no clock, no randomness). Returns null when none qualify.
|
|
75
|
+
*/
|
|
76
|
+
function pickFrontier(item, cfg) {
|
|
77
|
+
const frontiers = availableFrontiers(cfg);
|
|
78
|
+
if (frontiers.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
if (frontiers.length === 1)
|
|
81
|
+
return frontiers[0];
|
|
82
|
+
const idx = stableHash(item.id) % frontiers.length;
|
|
83
|
+
return frontiers[idx];
|
|
84
|
+
}
|
|
85
|
+
/** Build a decision for a concrete backend, deriving its tier. */
|
|
86
|
+
function decide(backend, reason) {
|
|
87
|
+
return { backend, tier: engineTierOf(backend), reason };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Route a WorkItem to the backend that should attempt it.
|
|
91
|
+
*
|
|
92
|
+
* PURE + DETERMINISTIC (modulo engineInstalled's read-only PATH probe). Never
|
|
93
|
+
* throws. Honors allowedBackends and PATH availability — see module heuristic.
|
|
94
|
+
*/
|
|
95
|
+
export function routeBackend(item, cfg) {
|
|
96
|
+
const source = item.source;
|
|
97
|
+
const effort = typeof item.effort === 'number' ? item.effort : 3;
|
|
98
|
+
const score = typeof item.score === 'number' ? item.score : 0;
|
|
99
|
+
// ── LOW-effort / bulk → builtin (local, unbounded, $0) ───────────────────
|
|
100
|
+
if (BULK_SOURCES.has(source) || effort <= 2) {
|
|
101
|
+
return decide('builtin', `bulk/low-effort (${source}, effort ${effort}) → local builtin`);
|
|
102
|
+
}
|
|
103
|
+
// ── HIGH-difficulty / important → frontier when available, else builtin ──
|
|
104
|
+
const isSenior = SENIOR_SOURCES.has(source) || effort >= 4 || score >= HIGH_SCORE;
|
|
105
|
+
if (isSenior) {
|
|
106
|
+
const frontier = pickFrontier(item, cfg);
|
|
107
|
+
if (frontier) {
|
|
108
|
+
return decide(frontier, `senior (${source}, effort ${effort}, score ${score}) → frontier ${frontier}`);
|
|
109
|
+
}
|
|
110
|
+
return decide('builtin', `senior (${source}) but no frontier allowed+installed → local builtin`);
|
|
111
|
+
}
|
|
112
|
+
// ── MEDIUM → builtin unless a frontier is available (src-touching) ───────
|
|
113
|
+
// Anything that reached here is a non-bulk source with effort 3 — treat it as
|
|
114
|
+
// src-touching and give it a frontier seat when one is allowed+installed.
|
|
115
|
+
const frontier = pickFrontier(item, cfg);
|
|
116
|
+
if (frontier) {
|
|
117
|
+
return decide(frontier, `medium src-touching (${source}, effort ${effort}) → frontier ${frontier}`);
|
|
118
|
+
}
|
|
119
|
+
return decide('builtin', `medium (${source}, effort ${effort}) → local builtin`);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/core/fleet/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* self.ts — M54 (v5 Open Fleet): the self-improving fleet's safety harness.
|
|
3
|
+
*
|
|
4
|
+
* The fleet may modify ashlr-hub's OWN source, but it may NEVER weaken its own
|
|
5
|
+
* safety guarantees. Two pure mechanisms enforce that:
|
|
6
|
+
*
|
|
7
|
+
* 1. `guardSafetyTests(diff)` — the never-weaken guard. Refuses, BY CONSTRUCTION
|
|
8
|
+
* and BEFORE any verification runs, any diff that deletes a safety/invariant
|
|
9
|
+
* test file or removes net assertions from one. A self-authored change can
|
|
10
|
+
* fix bugs and add tests, but can never disarm the gates that keep the fleet
|
|
11
|
+
* safe.
|
|
12
|
+
* 2. `selfEvalParity(runSuite)` — the self-eval harness contract: a self-target
|
|
13
|
+
* change is only eligible when the invariant suite is green with the foundry
|
|
14
|
+
* flag OFF *and* ON. Higher-order + pure so it is unit-testable with a stub
|
|
15
|
+
* runner; the gated auto-merge pass supplies the real suite runner.
|
|
16
|
+
*
|
|
17
|
+
* Plus `isSelfTargetProposal` — detect when a proposal targets ashlr-hub itself
|
|
18
|
+
* (by package name, not a brittle absolute path).
|
|
19
|
+
*
|
|
20
|
+
* All functions are PURE (except the bounded fs read in isSelfTargetProposal) and
|
|
21
|
+
* NEVER throw.
|
|
22
|
+
*/
|
|
23
|
+
import type { AshlrConfig, Proposal } from '../types.js';
|
|
24
|
+
/**
|
|
25
|
+
* True when `proposal` targets ashlr-hub's own source tree — detected by reading
|
|
26
|
+
* the repo's package.json `name` (=== '@ashlr/hub'), never a hardcoded path.
|
|
27
|
+
* Bounded read; never throws; false on any doubt.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isSelfTargetProposal(proposal: Proposal, _cfg?: AshlrConfig): boolean;
|
|
30
|
+
/** Whether a repo-relative path names a safety/invariant test file. */
|
|
31
|
+
export declare function isSafetyTestFile(path: string): boolean;
|
|
32
|
+
export interface SafetyGuardVerdict {
|
|
33
|
+
/** True ⇒ the diff weakens a safety guarantee and MUST be refused. */
|
|
34
|
+
weakened: boolean;
|
|
35
|
+
reason: string;
|
|
36
|
+
/** Safety files the diff touched (for the audit trail). */
|
|
37
|
+
files: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The never-weaken guard. Refuses (weakened:true) when the diff:
|
|
41
|
+
* - deletes a safety/invariant test file, OR
|
|
42
|
+
* - removes more assertions than it adds from a safety/invariant test file.
|
|
43
|
+
* Conservative by design: a diff that touches a safety file and nets out
|
|
44
|
+
* assertions is refused even if it also adds unrelated code. PURE; never throws.
|
|
45
|
+
*/
|
|
46
|
+
export declare function guardSafetyTests(diff: string): SafetyGuardVerdict;
|
|
47
|
+
export interface SelfEvalVerdict {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
reason: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The self-eval harness contract: a self-target change is eligible only when the
|
|
53
|
+
* invariant suite is green with the foundry flag OFF *and* ON. `runSuite(flagOn)`
|
|
54
|
+
* runs the suite in the (already diff-applied) sandbox worktree and returns
|
|
55
|
+
* whether it passed. PURE over its injected runner; never throws — a runner that
|
|
56
|
+
* throws counts as a failing suite (fail-closed).
|
|
57
|
+
*/
|
|
58
|
+
export declare function selfEvalParity(runSuite: (flagOn: boolean) => boolean): SelfEvalVerdict;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* self.ts — M54 (v5 Open Fleet): the self-improving fleet's safety harness.
|
|
3
|
+
*
|
|
4
|
+
* The fleet may modify ashlr-hub's OWN source, but it may NEVER weaken its own
|
|
5
|
+
* safety guarantees. Two pure mechanisms enforce that:
|
|
6
|
+
*
|
|
7
|
+
* 1. `guardSafetyTests(diff)` — the never-weaken guard. Refuses, BY CONSTRUCTION
|
|
8
|
+
* and BEFORE any verification runs, any diff that deletes a safety/invariant
|
|
9
|
+
* test file or removes net assertions from one. A self-authored change can
|
|
10
|
+
* fix bugs and add tests, but can never disarm the gates that keep the fleet
|
|
11
|
+
* safe.
|
|
12
|
+
* 2. `selfEvalParity(runSuite)` — the self-eval harness contract: a self-target
|
|
13
|
+
* change is only eligible when the invariant suite is green with the foundry
|
|
14
|
+
* flag OFF *and* ON. Higher-order + pure so it is unit-testable with a stub
|
|
15
|
+
* runner; the gated auto-merge pass supplies the real suite runner.
|
|
16
|
+
*
|
|
17
|
+
* Plus `isSelfTargetProposal` — detect when a proposal targets ashlr-hub itself
|
|
18
|
+
* (by package name, not a brittle absolute path).
|
|
19
|
+
*
|
|
20
|
+
* All functions are PURE (except the bounded fs read in isSelfTargetProposal) and
|
|
21
|
+
* NEVER throw.
|
|
22
|
+
*/
|
|
23
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Self-target detection
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/** The package name that identifies ashlr-hub's own repo. */
|
|
29
|
+
const SELF_PACKAGE_NAME = '@ashlr/hub';
|
|
30
|
+
/**
|
|
31
|
+
* True when `proposal` targets ashlr-hub's own source tree — detected by reading
|
|
32
|
+
* the repo's package.json `name` (=== '@ashlr/hub'), never a hardcoded path.
|
|
33
|
+
* Bounded read; never throws; false on any doubt.
|
|
34
|
+
*/
|
|
35
|
+
export function isSelfTargetProposal(proposal, _cfg) {
|
|
36
|
+
const repo = proposal.repo;
|
|
37
|
+
if (!repo)
|
|
38
|
+
return false;
|
|
39
|
+
try {
|
|
40
|
+
const pkgPath = join(repo, 'package.json');
|
|
41
|
+
if (!existsSync(pkgPath))
|
|
42
|
+
return false;
|
|
43
|
+
const raw = readFileSync(pkgPath, 'utf8');
|
|
44
|
+
if (raw.length > 256 * 1024)
|
|
45
|
+
return false; // bounded
|
|
46
|
+
const pkg = JSON.parse(raw);
|
|
47
|
+
return pkg.name === SELF_PACKAGE_NAME;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// The never-weaken guard
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Patterns for test files whose weakening would disarm a safety invariant. A
|
|
58
|
+
* diff that deletes one of these, or nets out assertions from one, is refused.
|
|
59
|
+
*/
|
|
60
|
+
const SAFETY_FILE_PATTERNS = [
|
|
61
|
+
/^test\/h\d+[.-].*\.test\.ts$/, // h1..h8 hardening / invariant suites
|
|
62
|
+
/^test\/m45\.foundry\.test\.ts$/, // sandboxed-engine containment
|
|
63
|
+
/^test\/m47[._].*\.test\.ts$/, // merge gate + provenance
|
|
64
|
+
/^test\/m51\.trust\.test\.ts$/, // tri-tier trust
|
|
65
|
+
/^test\/m52\..*\.test\.ts$/, // OS confinement
|
|
66
|
+
/^test\/m54\..*\.test\.ts$/, // the self-improvement guard itself
|
|
67
|
+
/daemon-gates/, // daemon-no-primitive source grep-guard
|
|
68
|
+
/proposal-only/,
|
|
69
|
+
/\.safety\./,
|
|
70
|
+
];
|
|
71
|
+
/** Whether a repo-relative path names a safety/invariant test file. */
|
|
72
|
+
export function isSafetyTestFile(path) {
|
|
73
|
+
const p = path.replace(/^\.\//, '');
|
|
74
|
+
return SAFETY_FILE_PATTERNS.some((re) => re.test(p));
|
|
75
|
+
}
|
|
76
|
+
/** Lines that represent a test assertion or block — the protected substance. */
|
|
77
|
+
const ASSERTION_RE = /^\s*(it|test|describe|expect|assert|toBe|toEqual|toThrow|toMatch)\b/;
|
|
78
|
+
/** Split a unified diff into per-file sections and tally assertion deltas. */
|
|
79
|
+
function parseDiff(diff) {
|
|
80
|
+
const files = [];
|
|
81
|
+
let cur = null;
|
|
82
|
+
for (const line of diff.split('\n')) {
|
|
83
|
+
if (line.startsWith('diff --git')) {
|
|
84
|
+
if (cur)
|
|
85
|
+
files.push(cur);
|
|
86
|
+
// `diff --git a/<path> b/<path>` — take the b/ path.
|
|
87
|
+
const m = line.match(/ b\/(.+)$/);
|
|
88
|
+
cur = { path: m ? m[1] : '', deleted: false, addedAssertions: 0, removedAssertions: 0 };
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!cur)
|
|
92
|
+
continue;
|
|
93
|
+
if (line.startsWith('deleted file mode')) {
|
|
94
|
+
cur.deleted = true;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (line.startsWith('+++ /dev/null')) {
|
|
98
|
+
cur.deleted = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// `--- a/path` / `+++ b/path` headers are not content; refine the path from +++.
|
|
102
|
+
if (line.startsWith('+++ b/')) {
|
|
103
|
+
cur.path = line.slice('+++ b/'.length);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (line.startsWith('---') || line.startsWith('+++'))
|
|
107
|
+
continue;
|
|
108
|
+
if (line.startsWith('+') && ASSERTION_RE.test(line.slice(1)))
|
|
109
|
+
cur.addedAssertions++;
|
|
110
|
+
else if (line.startsWith('-') && ASSERTION_RE.test(line.slice(1)))
|
|
111
|
+
cur.removedAssertions++;
|
|
112
|
+
}
|
|
113
|
+
if (cur)
|
|
114
|
+
files.push(cur);
|
|
115
|
+
return files;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The never-weaken guard. Refuses (weakened:true) when the diff:
|
|
119
|
+
* - deletes a safety/invariant test file, OR
|
|
120
|
+
* - removes more assertions than it adds from a safety/invariant test file.
|
|
121
|
+
* Conservative by design: a diff that touches a safety file and nets out
|
|
122
|
+
* assertions is refused even if it also adds unrelated code. PURE; never throws.
|
|
123
|
+
*/
|
|
124
|
+
export function guardSafetyTests(diff) {
|
|
125
|
+
const touched = [];
|
|
126
|
+
if (!diff || !diff.trim())
|
|
127
|
+
return { weakened: false, reason: 'empty diff', files: [] };
|
|
128
|
+
let parsed;
|
|
129
|
+
try {
|
|
130
|
+
parsed = parseDiff(diff);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Unparseable diff over self ⇒ refuse (ambiguous = unsafe).
|
|
134
|
+
return { weakened: true, reason: 'unparseable diff over self-target — refused', files: [] };
|
|
135
|
+
}
|
|
136
|
+
for (const f of parsed) {
|
|
137
|
+
if (!f.path || !isSafetyTestFile(f.path))
|
|
138
|
+
continue;
|
|
139
|
+
touched.push(f.path);
|
|
140
|
+
if (f.deleted) {
|
|
141
|
+
return {
|
|
142
|
+
weakened: true,
|
|
143
|
+
reason: `diff deletes safety/invariant test file '${f.path}' — refused`,
|
|
144
|
+
files: touched,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (f.removedAssertions > f.addedAssertions) {
|
|
148
|
+
return {
|
|
149
|
+
weakened: true,
|
|
150
|
+
reason: `diff removes ${f.removedAssertions - f.addedAssertions} net assertion(s) from safety test '${f.path}' — refused`,
|
|
151
|
+
files: touched,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { weakened: false, reason: 'no safety test weakened', files: touched };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The self-eval harness contract: a self-target change is eligible only when the
|
|
159
|
+
* invariant suite is green with the foundry flag OFF *and* ON. `runSuite(flagOn)`
|
|
160
|
+
* runs the suite in the (already diff-applied) sandbox worktree and returns
|
|
161
|
+
* whether it passed. PURE over its injected runner; never throws — a runner that
|
|
162
|
+
* throws counts as a failing suite (fail-closed).
|
|
163
|
+
*/
|
|
164
|
+
export function selfEvalParity(runSuite) {
|
|
165
|
+
let offGreen;
|
|
166
|
+
let onGreen;
|
|
167
|
+
try {
|
|
168
|
+
offGreen = runSuite(false);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
offGreen = false;
|
|
172
|
+
}
|
|
173
|
+
if (!offGreen) {
|
|
174
|
+
return { ok: false, reason: 'invariant suite not green with foundry flag OFF' };
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
onGreen = runSuite(true);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
onGreen = false;
|
|
181
|
+
}
|
|
182
|
+
if (!onGreen) {
|
|
183
|
+
return { ok: false, reason: 'invariant suite not green with foundry flag ON' };
|
|
184
|
+
}
|
|
185
|
+
return { ok: true, reason: 'invariant suite green flag-off AND flag-on' };
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=self.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self.js","sourceRoot":"","sources":["../../../src/core/fleet/self.ts"],"names":[],"mappings":"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"}
|