@mnemom/mnemom 0.17.34-next.1 → 0.17.34-next.3
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/dist/base-url-proxy.mjs +18 -4
- package/dist/commands/agent-config.js +5 -1
- package/dist/commands/agent-doctor.d.ts +9 -0
- package/dist/commands/agent-doctor.js +4 -0
- package/dist/commands/agent-run.d.ts +14 -6
- package/dist/commands/agent-run.js +44 -27
- package/dist/commands/agent-session.d.ts +1 -2
- package/dist/commands/agent-session.js +1 -2
- package/dist/commands/agent-setup.d.ts +6 -0
- package/dist/commands/agent-setup.js +12 -1
- package/dist/commands/agent-share-test-issuer.d.ts +67 -0
- package/dist/commands/agent-share-test-issuer.js +139 -0
- package/dist/commands/agent-share.d.ts +7 -1
- package/dist/commands/agent-share.js +14 -4
- package/dist/commands/agent.d.ts +18 -5
- package/dist/commands/agent.js +186 -59
- package/dist/commands/durable-doctor.d.ts +50 -0
- package/dist/commands/durable-doctor.js +186 -0
- package/dist/commands/durable-lock.d.ts +261 -0
- package/dist/commands/durable-lock.js +600 -0
- package/dist/commands/durable-sessions.d.ts +87 -0
- package/dist/commands/durable-sessions.js +303 -0
- package/dist/commands/durable-verbs.d.ts +116 -0
- package/dist/commands/durable-verbs.js +393 -0
- package/dist/commands/scan.d.ts +12 -1
- package/dist/commands/scan.js +22 -3
- package/dist/commands/wrap.js +1 -1
- package/dist/durable/adopt-live.js +5 -8
- package/dist/durable/adopt.d.ts +8 -0
- package/dist/durable/adopt.js +27 -6
- package/dist/durable/cwd-rule.d.ts +9 -1
- package/dist/durable/cwd-rule.js +18 -0
- package/dist/durable/flock.d.ts +26 -0
- package/dist/durable/flock.js +80 -0
- package/dist/durable/launch-spec.d.ts +9 -1
- package/dist/durable/launch-spec.js +11 -4
- package/dist/durable/seat-proxy-entry.d.ts +11 -0
- package/dist/durable/seat-proxy-entry.js +25 -0
- package/dist/durable/seat-proxy.d.ts +107 -0
- package/dist/durable/seat-proxy.js +599 -0
- package/dist/durable/seat-socket.d.ts +46 -0
- package/dist/durable/seat-socket.js +179 -0
- package/dist/durable/seat-spec-validator.d.ts +28 -0
- package/dist/durable/seat-spec-validator.js +59 -0
- package/dist/durable/test-mode.mjs +105 -0
- package/dist/durable/turn-chain.d.ts +55 -0
- package/dist/durable/turn-chain.js +178 -0
- package/dist/host/auto-continue.d.ts +67 -0
- package/dist/host/auto-continue.js +118 -0
- package/dist/host/bootid.d.ts +13 -0
- package/dist/host/bootid.js +41 -0
- package/dist/host/client.d.ts +125 -0
- package/dist/host/client.js +171 -0
- package/dist/host/commands.d.ts +2 -0
- package/dist/host/commands.js +122 -0
- package/dist/host/control.d.ts +33 -0
- package/dist/host/control.js +144 -0
- package/dist/host/daemon.d.ts +221 -0
- package/dist/host/daemon.js +616 -0
- package/dist/host/gate.d.ts +46 -0
- package/dist/host/gate.js +82 -0
- package/dist/host/grant.d.ts +24 -0
- package/dist/host/grant.js +24 -0
- package/dist/host/instance.d.ts +35 -0
- package/dist/host/instance.js +62 -0
- package/dist/host/integrity.d.ts +35 -0
- package/dist/host/integrity.js +115 -0
- package/dist/host/launchd.d.ts +16 -0
- package/dist/host/launchd.js +45 -0
- package/dist/host/lock.d.ts +20 -0
- package/dist/host/lock.js +63 -0
- package/dist/host/owner.d.ts +58 -0
- package/dist/host/owner.js +123 -0
- package/dist/host/plist.d.ts +16 -0
- package/dist/host/plist.js +85 -0
- package/dist/host/procs.d.ts +27 -0
- package/dist/host/procs.js +105 -0
- package/dist/host/run.d.ts +65 -0
- package/dist/host/run.js +299 -0
- package/dist/host/runtime.d.ts +8 -0
- package/dist/host/runtime.js +22 -0
- package/dist/host/s1-names.d.ts +23 -0
- package/dist/host/s1-names.js +22 -0
- package/dist/host/seat-credential.d.ts +27 -0
- package/dist/host/seat-credential.js +41 -0
- package/dist/host/seat-launch-main.d.ts +1 -0
- package/dist/host/seat-launch-main.js +59 -0
- package/dist/host/seat-launch.d.ts +116 -0
- package/dist/host/seat-launch.js +302 -0
- package/dist/host/seat.d.ts +48 -0
- package/dist/host/seat.js +149 -0
- package/dist/host/state.d.ts +33 -0
- package/dist/host/state.js +117 -0
- package/dist/host/status.d.ts +101 -0
- package/dist/host/status.js +307 -0
- package/dist/host/tmux.d.ts +56 -0
- package/dist/host/tmux.js +182 -0
- package/dist/host/transcript.d.ts +30 -0
- package/dist/host/transcript.js +130 -0
- package/dist/host/verbs.d.ts +52 -0
- package/dist/host/verbs.js +282 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +209 -43
- package/dist/lib/agent.d.ts +13 -29
- package/dist/lib/agent.js +17 -29
- package/dist/lib/claude-bin.d.ts +6 -0
- package/dist/lib/claude-bin.js +26 -0
- package/dist/lib/claude-plugin.d.ts +11 -2
- package/dist/lib/claude-plugin.js +10 -1
- package/dist/lib/commit-report.d.ts +5 -4
- package/dist/lib/commit-report.js +6 -9
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/config.js +28 -0
- package/dist/lib/durable/api.d.ts +136 -0
- package/dist/lib/durable/api.js +220 -0
- package/dist/lib/durable/codes.d.ts +8 -0
- package/dist/lib/durable/codes.js +2 -0
- package/dist/lib/durable/gate.d.ts +48 -0
- package/dist/lib/durable/gate.js +122 -0
- package/dist/lib/durable/handover.d.ts +101 -0
- package/dist/lib/durable/handover.js +175 -0
- package/dist/lib/durable/launch.d.ts +199 -0
- package/dist/lib/durable/launch.js +461 -0
- package/dist/lib/durable/launching.d.ts +15 -0
- package/dist/lib/durable/launching.js +70 -0
- package/dist/lib/durable/live.d.ts +190 -0
- package/dist/lib/durable/live.js +633 -0
- package/dist/lib/durable/refusal.d.ts +57 -0
- package/dist/lib/durable/refusal.js +78 -0
- package/dist/lib/durable/view.d.ts +83 -0
- package/dist/lib/durable/view.js +244 -0
- package/dist/lib/experimental.d.ts +0 -9
- package/dist/lib/experimental.js +18 -17
- package/dist/lib/launcher-config.d.ts +13 -0
- package/dist/lib/launcher-config.js +32 -0
- package/dist/lib/runner/exec-append.d.ts +15 -0
- package/dist/lib/runner/exec-append.js +2 -0
- package/dist/lib/runner/executor-client.d.ts +2 -1
- package/dist/lib/runner/executor-state.d.ts +10 -0
- package/dist/lib/runner/executor-state.js +24 -0
- package/dist/lib/runner/github-base.d.ts +24 -0
- package/dist/lib/runner/github-base.js +27 -0
- package/dist/lib/runner/job.d.ts +9 -4
- package/dist/lib/runner/job.js +43 -15
- package/dist/lib/runner/oidc.d.ts +3 -2
- package/dist/lib/runner/oidc.js +7 -4
- package/dist/lib/runner/options.d.ts +6 -0
- package/dist/lib/runner/options.js +19 -1
- package/dist/lib/runner/phases.d.ts +7 -4
- package/dist/lib/runner/phases.js +8 -4
- package/dist/lib/runner/proxy.d.ts +5 -1
- package/dist/lib/runner/proxy.js +7 -1
- package/dist/lib/scan/cost-per-pr.d.ts +93 -0
- package/dist/lib/scan/cost-per-pr.js +101 -0
- package/dist/lib/scan/record.d.ts +27 -1
- package/dist/lib/scan/record.js +114 -16
- package/dist/lib/scan/report-email.d.ts +26 -6
- package/dist/lib/scan/report-email.js +32 -3
- package/dist/lib/scan/scan.d.ts +3 -0
- package/dist/lib/scan/scan.js +13 -1
- package/dist/lib/subscription-login.d.ts +15 -0
- package/dist/lib/subscription-login.js +28 -1
- package/dist/vendor/durable-contract/index.js +240 -18
- package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
- package/dist/vendor/shared/durable-contract/admission.js +533 -0
- package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
- package/dist/vendor/shared/durable-contract/arm.js +133 -0
- package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/canonical.js +98 -0
- package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
- package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
- package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
- package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
- package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
- package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
- package/dist/vendor/shared/durable-contract/codes.js +475 -0
- package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
- package/dist/vendor/shared/durable-contract/constants.js +179 -0
- package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/doors.js +249 -0
- package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
- package/dist/vendor/shared/durable-contract/envelope.js +317 -0
- package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
- package/dist/vendor/shared/durable-contract/events.js +630 -0
- package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
- package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
- package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
- package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
- package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
- package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
- package/dist/vendor/shared/durable-contract/index.js +37 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
- package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
- package/dist/vendor/shared/durable-contract/keys.js +66 -0
- package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
- package/dist/vendor/shared/durable-contract/lock.js +151 -0
- package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
- package/dist/vendor/shared/durable-contract/names.js +78 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
- package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
- package/dist/vendor/shared/durable-contract/paths.js +35 -0
- package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
- package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
- package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
- package/dist/vendor/shared/durable-contract/routes.js +315 -0
- package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
- package/dist/vendor/shared/durable-contract/schema.js +441 -0
- package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
- package/dist/vendor/shared/durable-contract/seal.js +99 -0
- package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
- package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
- package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
- package/dist/vendor/shared/durable-contract/session-id.js +58 -0
- package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
- package/dist/vendor/shared/durable-contract/spec.js +286 -0
- package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
- package/dist/vendor/shared/durable-contract/states.js +218 -0
- package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
- package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
- package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
- package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
- package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
- package/dist/vendor/shared/durable-contract/turn.js +81 -0
- package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
- package/dist/vendor/shared/durable-contract/vectors.js +208 -0
- package/package.json +3 -2
- package/plugins/integrity-manifest.json +35 -0
- package/plugins/mnemom/hooks/hooks.json +15 -1
- package/plugins/mnemom/hooks/register.tsx +60 -2
- package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
- package/dist/commands/agent-implicit-notice.d.ts +0 -31
- package/dist/commands/agent-implicit-notice.js +0 -73
|
@@ -4,10 +4,19 @@
|
|
|
4
4
|
* Read-only and local: the answer is held in memory to build the report and is never uploaded or
|
|
5
5
|
* stored (mnemom-api#4211: Mnemom stores no scan result).
|
|
6
6
|
*
|
|
7
|
-
* The window is
|
|
7
|
+
* The window is the last 30 days ending now, less a one-minute margin at the start so it stays
|
|
8
|
+
* inside the API's retention floor (the contract's maximum is 30 days). It is its own
|
|
8
9
|
* window, not the PR scan period, and the report labels it "last 30 days"; no other scan-window
|
|
9
10
|
* number is ever produced here.
|
|
10
11
|
*
|
|
12
|
+
* An org with more requests in the window than the API will total in one read (it answers 400
|
|
13
|
+
* "window holds more than N requests: narrow it") is read in pieces: the failing window is halved
|
|
14
|
+
* at its midpoint, recursively, and the pieces are summed. The API's window is half-open
|
|
15
|
+
* [from, to), so pieces [from, mid) and [mid, to) neither overlap nor leave a gap. Fan-out is
|
|
16
|
+
* bounded (RECORD_MAX_REQUESTS reads, no piece split below RECORD_MIN_SPLIT_MS per half), and a
|
|
17
|
+
* total is all or nothing: any piece that fails for any other reason makes the whole read
|
|
18
|
+
* "unavailable", never a partial sum.
|
|
19
|
+
*
|
|
11
20
|
* Outcomes are a closed set, and every one has fixed text. The thrown value, the response body
|
|
12
21
|
* and the credential never reach the terminal:
|
|
13
22
|
* ok totals read and shape-checked
|
|
@@ -19,6 +28,21 @@ import { type RecordWindowTotals } from "../api.js";
|
|
|
19
28
|
export declare const RECORD_WINDOW_DAYS = 30;
|
|
20
29
|
export declare const RECORD_WINDOW_LABEL = "last 30 days";
|
|
21
30
|
export declare const RECORD_TIMEOUT_MS = 10000;
|
|
31
|
+
/**
|
|
32
|
+
* The API 400s a `from` earlier than its own now minus 30 days (retention floor, strict on purpose:
|
|
33
|
+
* mnemom-api#4310). Its clock runs later than ours by latency, so a window opening exactly 30 days
|
|
34
|
+
* before our now is always past that floor. Open it this much after instead; `to` stays our now
|
|
35
|
+
* (the API accepts a `to` up to 60s ahead of its clock: WINDOW_SKEW_MS in mnemom-api
|
|
36
|
+
* src/record/totals.ts). Ending at now centres the budget: the window is accepted while our clock is
|
|
37
|
+
* within 60s of the API's in either direction; ending earlier would trade away the floor side.
|
|
38
|
+
*/
|
|
39
|
+
export declare const RECORD_FLOOR_MARGIN_MS = 60000;
|
|
40
|
+
/** Most reads one scan may make (the first full-window read included). */
|
|
41
|
+
export declare const RECORD_MAX_REQUESTS = 64;
|
|
42
|
+
/** Wall-clock budget for all the reads of one scan; past it the result is "unavailable". */
|
|
43
|
+
export declare const RECORD_TOTAL_TIMEOUT_MS = 60000;
|
|
44
|
+
/** A window is split only if both halves stay at least this long. */
|
|
45
|
+
export declare const RECORD_MIN_SPLIT_MS: number;
|
|
22
46
|
export declare const isOrgId: (v: unknown) => v is string;
|
|
23
47
|
export declare const RECORD_AUTH_MSG = "Record totals were refused for this org. Run `mnemom login` (or set MNEMOM_API_KEY) with an owner, admin or auditor of the org passed to --org, then retry.";
|
|
24
48
|
export declare class RecordAuthError extends Error {
|
|
@@ -41,6 +65,8 @@ export interface RecordDeps {
|
|
|
41
65
|
fetchTotals?: (orgId: string, from: string, to: string, signal: AbortSignal) => Promise<unknown>;
|
|
42
66
|
now?: () => number;
|
|
43
67
|
timeoutMs?: number;
|
|
68
|
+
/** Budget for every read of one total together (default RECORD_TOTAL_TIMEOUT_MS). */
|
|
69
|
+
totalTimeoutMs?: number;
|
|
44
70
|
}
|
|
45
71
|
export declare function recordWindow(nowMs: number): RecordWindow;
|
|
46
72
|
/** Shape-check the body: a malformed answer is "unavailable", never rendered. */
|
package/dist/lib/scan/record.js
CHANGED
|
@@ -4,10 +4,19 @@
|
|
|
4
4
|
* Read-only and local: the answer is held in memory to build the report and is never uploaded or
|
|
5
5
|
* stored (mnemom-api#4211: Mnemom stores no scan result).
|
|
6
6
|
*
|
|
7
|
-
* The window is
|
|
7
|
+
* The window is the last 30 days ending now, less a one-minute margin at the start so it stays
|
|
8
|
+
* inside the API's retention floor (the contract's maximum is 30 days). It is its own
|
|
8
9
|
* window, not the PR scan period, and the report labels it "last 30 days"; no other scan-window
|
|
9
10
|
* number is ever produced here.
|
|
10
11
|
*
|
|
12
|
+
* An org with more requests in the window than the API will total in one read (it answers 400
|
|
13
|
+
* "window holds more than N requests: narrow it") is read in pieces: the failing window is halved
|
|
14
|
+
* at its midpoint, recursively, and the pieces are summed. The API's window is half-open
|
|
15
|
+
* [from, to), so pieces [from, mid) and [mid, to) neither overlap nor leave a gap. Fan-out is
|
|
16
|
+
* bounded (RECORD_MAX_REQUESTS reads, no piece split below RECORD_MIN_SPLIT_MS per half), and a
|
|
17
|
+
* total is all or nothing: any piece that fails for any other reason makes the whole read
|
|
18
|
+
* "unavailable", never a partial sum.
|
|
19
|
+
*
|
|
11
20
|
* Outcomes are a closed set, and every one has fixed text. The thrown value, the response body
|
|
12
21
|
* and the credential never reach the terminal:
|
|
13
22
|
* ok totals read and shape-checked
|
|
@@ -19,8 +28,23 @@ import { getRecordWindowTotals, MnemomApiError } from "../api.js";
|
|
|
19
28
|
export const RECORD_WINDOW_DAYS = 30;
|
|
20
29
|
export const RECORD_WINDOW_LABEL = "last 30 days";
|
|
21
30
|
export const RECORD_TIMEOUT_MS = 10_000;
|
|
22
|
-
/**
|
|
23
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The API 400s a `from` earlier than its own now minus 30 days (retention floor, strict on purpose:
|
|
33
|
+
* mnemom-api#4310). Its clock runs later than ours by latency, so a window opening exactly 30 days
|
|
34
|
+
* before our now is always past that floor. Open it this much after instead; `to` stays our now
|
|
35
|
+
* (the API accepts a `to` up to 60s ahead of its clock: WINDOW_SKEW_MS in mnemom-api
|
|
36
|
+
* src/record/totals.ts). Ending at now centres the budget: the window is accepted while our clock is
|
|
37
|
+
* within 60s of the API's in either direction; ending earlier would trade away the floor side.
|
|
38
|
+
*/
|
|
39
|
+
export const RECORD_FLOOR_MARGIN_MS = 60_000;
|
|
40
|
+
/** Most reads one scan may make (the first full-window read included). */
|
|
41
|
+
export const RECORD_MAX_REQUESTS = 64;
|
|
42
|
+
/** Wall-clock budget for all the reads of one scan; past it the result is "unavailable". */
|
|
43
|
+
export const RECORD_TOTAL_TIMEOUT_MS = 60_000;
|
|
44
|
+
/** A window is split only if both halves stay at least this long. */
|
|
45
|
+
export const RECORD_MIN_SPLIT_MS = 60 * 60 * 1000;
|
|
46
|
+
/** The API's bigint ceiling: a larger sum cannot be a real total. */
|
|
47
|
+
const MAX_MMU = 9223372036854775807n;
|
|
24
48
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
25
49
|
const ORG_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
26
50
|
const MMU_RE = /^(0|[1-9][0-9]{0,18})$/;
|
|
@@ -33,9 +57,9 @@ export class RecordAuthError extends Error {
|
|
|
33
57
|
}
|
|
34
58
|
}
|
|
35
59
|
export function recordWindow(nowMs) {
|
|
36
|
-
const to = nowMs
|
|
60
|
+
const to = nowMs;
|
|
37
61
|
return {
|
|
38
|
-
from: new Date(to - RECORD_WINDOW_DAYS * DAY_MS).toISOString(),
|
|
62
|
+
from: new Date(to - RECORD_WINDOW_DAYS * DAY_MS + RECORD_FLOOR_MARGIN_MS).toISOString(),
|
|
39
63
|
to: new Date(to).toISOString(),
|
|
40
64
|
};
|
|
41
65
|
}
|
|
@@ -60,26 +84,100 @@ export function parseWindowTotals(v) {
|
|
|
60
84
|
as_of: o.as_of,
|
|
61
85
|
};
|
|
62
86
|
}
|
|
87
|
+
/** The API's too-many-requests answer: a 400 saying the window holds more than N requests. */
|
|
88
|
+
const isWindowTooBig = (e) => e instanceof MnemomApiError &&
|
|
89
|
+
e.effectiveStatus === 400 &&
|
|
90
|
+
/holds more than \d+ requests/.test(e.message);
|
|
91
|
+
/** Latest of two as_of timestamps (a "latest row" field is combined by max, never added). */
|
|
92
|
+
function laterAsOf(a, b) {
|
|
93
|
+
if (a === null)
|
|
94
|
+
return b;
|
|
95
|
+
if (b === null)
|
|
96
|
+
return a;
|
|
97
|
+
const ta = Date.parse(a);
|
|
98
|
+
const tb = Date.parse(b);
|
|
99
|
+
if (Number.isNaN(ta) || Number.isNaN(tb))
|
|
100
|
+
throw new Error("unparsable as_of");
|
|
101
|
+
return tb > ta ? b : a;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Add two windows' totals. Costs, charges and row counts are additive over disjoint windows (a
|
|
105
|
+
* request sits in the window of its gateway row, and its billing row follows it). `mu_charged_mmu`
|
|
106
|
+
* is null for a window with no billing row, so it is null only when every piece is null, as the
|
|
107
|
+
* API's own multi-lineage sum does. `as_of` is the latest row's time, so it is the max.
|
|
108
|
+
*/
|
|
109
|
+
function addTotals(a, b) {
|
|
110
|
+
const model = BigInt(a.model_cost_mmu) + BigInt(b.model_cost_mmu);
|
|
111
|
+
const mu = a.mu_charged_mmu === null && b.mu_charged_mmu === null
|
|
112
|
+
? null
|
|
113
|
+
: BigInt(a.mu_charged_mmu ?? "0") + BigInt(b.mu_charged_mmu ?? "0");
|
|
114
|
+
const rows = a.row_count + b.row_count;
|
|
115
|
+
if (model > MAX_MMU || (mu !== null && mu > MAX_MMU) || !Number.isSafeInteger(rows)) {
|
|
116
|
+
throw new Error("summed total out of range");
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
model_cost_mmu: model.toString(),
|
|
120
|
+
mu_charged_mmu: mu === null ? null : mu.toString(),
|
|
121
|
+
row_count: rows,
|
|
122
|
+
as_of: laterAsOf(a.as_of, b.as_of),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Any failure other than auth / not-in-cohort makes the whole total unavailable. */
|
|
126
|
+
class SplitFailed extends Error {
|
|
127
|
+
}
|
|
128
|
+
class NotInCohort extends Error {
|
|
129
|
+
}
|
|
63
130
|
/** Never throws except RecordAuthError (401/403, fail closed). */
|
|
64
131
|
export async function fetchRecordTotals(orgId, deps = {}) {
|
|
65
132
|
if (!isOrgId(orgId))
|
|
66
133
|
return { status: "unavailable" };
|
|
67
134
|
const window = recordWindow((deps.now ?? Date.now)());
|
|
68
135
|
const fetchTotals = deps.fetchTotals ?? getRecordWindowTotals;
|
|
69
|
-
const
|
|
136
|
+
const timeoutMs = deps.timeoutMs ?? RECORD_TIMEOUT_MS;
|
|
137
|
+
const deadline = Date.now() + (deps.totalTimeoutMs ?? RECORD_TOTAL_TIMEOUT_MS);
|
|
138
|
+
let requests = 0;
|
|
139
|
+
/** Total of [fromMs, toMs). Throws RecordAuthError, NotInCohort (root 404 only) or SplitFailed. */
|
|
140
|
+
const read = async (fromMs, toMs, root) => {
|
|
141
|
+
const left = deadline - Date.now();
|
|
142
|
+
if (requests >= RECORD_MAX_REQUESTS || left <= 0)
|
|
143
|
+
throw new SplitFailed();
|
|
144
|
+
requests += 1;
|
|
145
|
+
try {
|
|
146
|
+
const body = await fetchTotals(orgId, new Date(fromMs).toISOString(), new Date(toMs).toISOString(), AbortSignal.timeout(Math.min(timeoutMs, left)));
|
|
147
|
+
const totals = parseWindowTotals(body);
|
|
148
|
+
if (!totals)
|
|
149
|
+
throw new SplitFailed();
|
|
150
|
+
return totals;
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
if (e instanceof MnemomApiError) {
|
|
154
|
+
const s = e.effectiveStatus;
|
|
155
|
+
if (s === 401 || s === 403)
|
|
156
|
+
throw new RecordAuthError();
|
|
157
|
+
if (s === 404 && root)
|
|
158
|
+
throw new NotInCohort();
|
|
159
|
+
if (isWindowTooBig(e)) {
|
|
160
|
+
const mid = fromMs + Math.floor((toMs - fromMs) / 2);
|
|
161
|
+
if (mid - fromMs < RECORD_MIN_SPLIT_MS || toMs - mid < RECORD_MIN_SPLIT_MS) {
|
|
162
|
+
throw new SplitFailed();
|
|
163
|
+
}
|
|
164
|
+
const first = await read(fromMs, mid, false);
|
|
165
|
+
return addTotals(first, await read(mid, toMs, false));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// RecordAuthError / NotInCohort / nested failures above are thrown from this handler and never land here.
|
|
169
|
+
throw new SplitFailed();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
70
172
|
try {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
return totals ? { status: "ok", totals, window } : { status: "unavailable" };
|
|
173
|
+
const totals = await read(Date.parse(window.from), Date.parse(window.to), true);
|
|
174
|
+
return { status: "ok", totals, window };
|
|
74
175
|
}
|
|
75
176
|
catch (e) {
|
|
76
|
-
if (e instanceof
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (s === 404)
|
|
81
|
-
return { status: "not_in_cohort" };
|
|
82
|
-
}
|
|
177
|
+
if (e instanceof RecordAuthError)
|
|
178
|
+
throw e;
|
|
179
|
+
if (e instanceof NotInCohort)
|
|
180
|
+
return { status: "not_in_cohort" };
|
|
83
181
|
return { status: "unavailable" };
|
|
84
182
|
}
|
|
85
183
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { type CostPerPr, type CostSpend } from "./cost-per-pr.js";
|
|
1
2
|
import type { ScanResult } from "./scan.js";
|
|
2
3
|
import type { ShareLevel } from "./share.js";
|
|
3
4
|
export declare const REPORT_SCHEMA_VERSION = "org-scan-report/1";
|
|
4
5
|
export declare const ESTIMATE_NAME = "automatable_work_share";
|
|
5
|
-
export declare const METHOD_URL = "https://www.mnemom.ai/method";
|
|
6
|
+
export declare const METHOD_URL = "https://www.mnemom.ai/org-scan-method";
|
|
6
7
|
export interface EmailReport {
|
|
7
8
|
schema_version: typeof REPORT_SCHEMA_VERSION;
|
|
8
9
|
org_id: string;
|
|
@@ -39,6 +40,22 @@ export interface EmailReport {
|
|
|
39
40
|
unit: string;
|
|
40
41
|
method_url: string;
|
|
41
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* Optional, added after org-scan-report/1 shipped; absent whenever it cannot be computed (no spend,
|
|
45
|
+
* no merged PR in the period, an incomplete scan, an empty bucket). See cost-per-pr.ts.
|
|
46
|
+
* cost_per_merged_pr average cost of one merged PR in the period
|
|
47
|
+
* cost_by_pr_size { small | medium | large }: average cost of ONE PR of that size, per-bucket
|
|
48
|
+
* keys absent when the bucket is empty. small < 50 lines changed, medium 50..500, large > 500.
|
|
49
|
+
* A figure is { label: "estimate", spend_source, pr_scope, pr_count } (+ spend_scope: "whole_org" on
|
|
50
|
+
* anthropic_usage) plus { value_cents } (anthropic_usage) or { low_cents, high_cents } (modelled_range). Integer cents.
|
|
51
|
+
* pr_scope is "ai_assisted" for modelled_range (spend and size buckets split across AI-assisted PRs only;
|
|
52
|
+
* absent if none) and "all_merged" otherwise; pr_count counts PRs in that scope.
|
|
53
|
+
* Always attached when present. The API's report validator (mnemom-api src/org-scan/report-schema.ts) is
|
|
54
|
+
* closed, so an API that does not yet list these keys answers 400 invalid_report; sendReportEmail then
|
|
55
|
+
* retries once without them so the report email still goes out.
|
|
56
|
+
*/
|
|
57
|
+
cost_per_merged_pr?: CostPerPr["cost_per_merged_pr"];
|
|
58
|
+
cost_by_pr_size?: CostPerPr["cost_by_pr_size"];
|
|
42
59
|
}
|
|
43
60
|
export interface ReportEmailBody {
|
|
44
61
|
report: EmailReport;
|
|
@@ -56,17 +73,20 @@ export interface ReportEmailBody {
|
|
|
56
73
|
*/
|
|
57
74
|
export declare function buildEmailReport(result: ScanResult, o: {
|
|
58
75
|
orgId: string;
|
|
59
|
-
period: {
|
|
60
|
-
start: string;
|
|
61
|
-
end: string;
|
|
62
|
-
};
|
|
63
76
|
now: () => number;
|
|
64
77
|
sendSpendRange: boolean;
|
|
65
78
|
scanId?: string;
|
|
79
|
+
/** The requested scan period (the one the spend covers), not the examined window. */
|
|
80
|
+
period?: {
|
|
81
|
+
start: string;
|
|
82
|
+
end: string;
|
|
83
|
+
};
|
|
84
|
+
/** The one spend source the cost figures are split from; null/absent leaves them out. */
|
|
85
|
+
costSpend?: CostSpend | null;
|
|
66
86
|
}): EmailReport;
|
|
67
87
|
export declare function buildEmailBody(report: EmailReport, level: ShareLevel, complete: boolean, ctoEmail?: string): ReportEmailBody;
|
|
68
88
|
/** Closed set of failure names; each has fixed text. */
|
|
69
|
-
export type EmailFailure = "presend_check_failed" | "email_not_sent" | "not_signed_in" | "not_a_member" | "report_rejected" | "no_recipients" | "network_error" | "unexpected_response";
|
|
89
|
+
export type EmailFailure = "presend_check_failed" | "email_not_sent" | "not_signed_in" | "not_a_member" | "report_rejected" | "no_recipients" | "runner_not_emailed" | "network_error" | "unexpected_response";
|
|
70
90
|
export declare const failureMessage: (f: EmailFailure) => string;
|
|
71
91
|
export declare const DRY_RUN_MSG = "Report email is in dry-run on this environment; nothing was sent.";
|
|
72
92
|
export declare const UNAVAILABLE_MSG = "Report email is not available for this org yet; nothing was sent.";
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
15
|
import { MnemomApiError, postApi } from "../api.js";
|
|
16
|
+
import { buildCostPerPr } from "./cost-per-pr.js";
|
|
16
17
|
export const REPORT_SCHEMA_VERSION = "org-scan-report/1";
|
|
17
18
|
export const ESTIMATE_NAME = "automatable_work_share";
|
|
18
|
-
export const METHOD_URL = "https://www.mnemom.ai/method";
|
|
19
|
+
export const METHOD_URL = "https://www.mnemom.ai/org-scan-method";
|
|
19
20
|
const cents = (usd) => Math.max(0, Math.round(usd * 100));
|
|
20
21
|
/**
|
|
21
22
|
* Maps the local scan onto the route's closed report shape. Team totals stay empty (they come only from
|
|
@@ -36,11 +37,16 @@ export function buildEmailReport(result, o) {
|
|
|
36
37
|
.filter(([, n]) => typeof n === "number" && n > 0)
|
|
37
38
|
.map(([tool, count]) => ({ tool, count: count }))
|
|
38
39
|
: [];
|
|
40
|
+
// Only a scan that read every merged PR in the period gives an average that means anything.
|
|
41
|
+
const exhaustive = coverage.complete && coverage.prsSkippedLarge === 0 && coverage.prsUnreadable === 0;
|
|
42
|
+
const cost = buildCostPerPr(result.prSizes, o.period ?? { start: coverage.windowStart, end: coverage.windowEnd }, o.costSpend ?? null, exhaustive);
|
|
39
43
|
return {
|
|
40
44
|
schema_version: REPORT_SCHEMA_VERSION,
|
|
41
45
|
org_id: o.orgId,
|
|
42
46
|
scan_id: o.scanId ?? randomUUID(),
|
|
43
|
-
period:
|
|
47
|
+
// The span the scan actually examined (coverage), never the requested period: with no --period
|
|
48
|
+
// that defaults to today..today, which is not what was read.
|
|
49
|
+
period: { start: coverage.windowStart, end: coverage.windowEnd },
|
|
44
50
|
generated_at: new Date(o.now()).toISOString(),
|
|
45
51
|
measured: {
|
|
46
52
|
teams: [],
|
|
@@ -65,6 +71,7 @@ export function buildEmailReport(result, o) {
|
|
|
65
71
|
},
|
|
66
72
|
},
|
|
67
73
|
estimate: { name: ESTIMATE_NAME, value, unit, method_url: METHOD_URL },
|
|
74
|
+
...cost,
|
|
68
75
|
};
|
|
69
76
|
}
|
|
70
77
|
export function buildEmailBody(report, level, complete, ctoEmail) {
|
|
@@ -82,6 +89,7 @@ const FAILURE_TEXT = {
|
|
|
82
89
|
not_a_member: "your account is not a member of the org passed to --org.",
|
|
83
90
|
report_rejected: "Mnemom rejected the report as malformed.",
|
|
84
91
|
no_recipients: "the server accepted the report but reported zero recipients.",
|
|
92
|
+
runner_not_emailed: "the report was not delivered to you (the person who ran the scan); it may have gone only to the CTO. Re-run the scan, and check that your account has a valid email address.",
|
|
85
93
|
network_error: "Mnemom could not be reached.",
|
|
86
94
|
unexpected_response: "Mnemom answered in an unexpected way.",
|
|
87
95
|
};
|
|
@@ -110,6 +118,13 @@ function classifyStatus(status) {
|
|
|
110
118
|
return "report_rejected";
|
|
111
119
|
return "unexpected_response";
|
|
112
120
|
}
|
|
121
|
+
/** The body without the optional cost figures; the same object when it carries none. */
|
|
122
|
+
function withoutCostFigures(body) {
|
|
123
|
+
const { cost_per_merged_pr: _a, cost_by_pr_size: _b, ...rest } = body.report;
|
|
124
|
+
return "cost_per_merged_pr" in body.report || "cost_by_pr_size" in body.report
|
|
125
|
+
? { ...body, report: rest }
|
|
126
|
+
: body;
|
|
127
|
+
}
|
|
113
128
|
/** Never throws. The thrown value, the response body and the credential are never surfaced. */
|
|
114
129
|
export async function sendReportEmail(orgId, body, deps = {}) {
|
|
115
130
|
const post = deps.post ?? defaultPost;
|
|
@@ -120,8 +135,19 @@ export async function sendReportEmail(orgId, body, deps = {}) {
|
|
|
120
135
|
catch (e) {
|
|
121
136
|
if (e instanceof MnemomApiError && e.effectiveStatus === 404)
|
|
122
137
|
return { status: "unavailable" };
|
|
123
|
-
|
|
138
|
+
// An API that predates the cost figures rejects the unknown keys with a 400. The email must still
|
|
139
|
+
// go out, so retry once with the figures left off; any other failure is classified as below.
|
|
140
|
+
if (e instanceof MnemomApiError && e.effectiveStatus === 400) {
|
|
141
|
+
const bare = withoutCostFigures(body);
|
|
142
|
+
if (bare !== body)
|
|
143
|
+
return sendReportEmail(orgId, bare, deps);
|
|
144
|
+
}
|
|
145
|
+
if (e instanceof MnemomApiError) {
|
|
146
|
+
// A 4xx that names the runner (the API refuses a send that would skip the runner) gets its own text.
|
|
147
|
+
if (e.effectiveStatus >= 400 && e.effectiveStatus < 500 && /runner/i.test(e.code ?? ""))
|
|
148
|
+
return { status: "failed", reason: "runner_not_emailed" };
|
|
124
149
|
return { status: "failed", reason: classifyStatus(e.effectiveStatus) };
|
|
150
|
+
}
|
|
125
151
|
return { status: "failed", reason: "network_error" };
|
|
126
152
|
}
|
|
127
153
|
const o = typeof res === "object" && res !== null ? res : null;
|
|
@@ -132,5 +158,8 @@ export async function sendReportEmail(orgId, body, deps = {}) {
|
|
|
132
158
|
return { status: "failed", reason: "unexpected_response" };
|
|
133
159
|
if (recipients === 0)
|
|
134
160
|
return { status: "failed", reason: "no_recipients" };
|
|
161
|
+
// `skipped` lists the roles ("runner", "cto") that got no copy. The runner must always get the report.
|
|
162
|
+
if (Array.isArray(o.skipped) && o.skipped.includes("runner"))
|
|
163
|
+
return { status: "failed", reason: "runner_not_emailed" };
|
|
135
164
|
return o.status === "dry_run" ? { status: "dry_run" } : { status: "sent", recipients };
|
|
136
165
|
}
|
package/dist/lib/scan/scan.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type GitHubCredential } from "./credential.js";
|
|
|
7
7
|
import { type ScanCaps, type StopReason } from "./caps.js";
|
|
8
8
|
import { type FailureReason } from "./failure.js";
|
|
9
9
|
import { type PrSource } from "./pr-source.js";
|
|
10
|
+
import type { PrSize } from "./cost-per-pr.js";
|
|
10
11
|
import { type SpendEstimate } from "./spend/spend-estimate.js";
|
|
11
12
|
export interface ScanInput {
|
|
12
13
|
repos: string[];
|
|
@@ -53,6 +54,8 @@ export interface ScanResult {
|
|
|
53
54
|
};
|
|
54
55
|
byRepo: RepoTotals[];
|
|
55
56
|
spend: SpendEstimate;
|
|
57
|
+
/** Every merged PR read in full (reverts and unreadable/oversize PRs excluded): merge day and size. In memory only. */
|
|
58
|
+
prSizes: PrSize[];
|
|
56
59
|
}
|
|
57
60
|
export type ScanOutcome = {
|
|
58
61
|
status: "done";
|
package/dist/lib/scan/scan.js
CHANGED
|
@@ -109,6 +109,7 @@ export async function runLocalScan(deps, input) {
|
|
|
109
109
|
};
|
|
110
110
|
const totals = { prs: 0, reverts: 0, ciGreen: 0, examined: 0 };
|
|
111
111
|
const spendInputs = [];
|
|
112
|
+
const prSizes = [];
|
|
112
113
|
let skippedLarge = 0;
|
|
113
114
|
let unreadable = 0;
|
|
114
115
|
for (const { repo, item } of chosen) {
|
|
@@ -146,6 +147,11 @@ export async function runLocalScan(deps, input) {
|
|
|
146
147
|
totals.ciGreen += 1;
|
|
147
148
|
bucket(repo).ciGreen += 1;
|
|
148
149
|
}
|
|
150
|
+
prSizes.push({
|
|
151
|
+
mergedDay: item.mergedDay,
|
|
152
|
+
lines: rec.additions + rec.deletions,
|
|
153
|
+
aiAssisted: (rec.tools?.length ?? 0) > 0,
|
|
154
|
+
});
|
|
149
155
|
spendInputs.push({
|
|
150
156
|
additions: rec.additions,
|
|
151
157
|
deletions: rec.deletions,
|
|
@@ -172,7 +178,13 @@ export async function runLocalScan(deps, input) {
|
|
|
172
178
|
};
|
|
173
179
|
return {
|
|
174
180
|
status: "done",
|
|
175
|
-
result: {
|
|
181
|
+
result: {
|
|
182
|
+
coverage,
|
|
183
|
+
totals,
|
|
184
|
+
byRepo: [...byRepo.values()],
|
|
185
|
+
spend: estimateSpend(spendInputs),
|
|
186
|
+
prSizes,
|
|
187
|
+
},
|
|
176
188
|
};
|
|
177
189
|
}
|
|
178
190
|
catch (e) {
|
|
@@ -79,6 +79,21 @@ export declare function resolveSubscriptionMnemomKey(deps: MnemomKeyDeps, purpos
|
|
|
79
79
|
* a non-entitled account to terminal; this is the fail-closed backstop.)
|
|
80
80
|
*/
|
|
81
81
|
export declare function subscriptionShape(shape: LaunchShape, shapeExplicit: boolean, rcAllowed: boolean, say: (line: string) => void): LaunchShape;
|
|
82
|
+
/**
|
|
83
|
+
* The coding agent's env for either auth arm: what `mnemom agent` launches claude with, and what a
|
|
84
|
+
* durable seat launches its claude with (host/seat-launch.ts, dogfood B6). With an Anthropic key:
|
|
85
|
+
* Claude Code logged in via claude.ai (OAuth) PREEMPTS ANTHROPIC_API_KEY, so it is force-unset;
|
|
86
|
+
* the real key rides in ANTHROPIC_CUSTOM_HEADERS (`headers`) and ANTHROPIC_AUTH_TOKEN is a fixed
|
|
87
|
+
* non-secret placeholder. Without one (a subscription launch) NO auth token is set: Claude Code
|
|
88
|
+
* then sends its own claude.ai login. Either way the owner's own Mnemom credential env is
|
|
89
|
+
* stripped (MNE-8245), as is MNEMOM_AGENT_ANTHROPIC_KEY (the key reaches the child only in the
|
|
90
|
+
* headers: C14, B6), and Claude Code's gateway hint headers are on (a value the owner set wins):
|
|
91
|
+
* the seat proxy chains each turn by x-claude-code-prompt-id, and without it a seat's first typed
|
|
92
|
+
* turn chained to the launch prompt (dogfood B8). Set here so the two callers cannot drift apart.
|
|
93
|
+
*/
|
|
94
|
+
export declare function launchChildEnv(base: NodeJS.ProcessEnv, door: string, headers: string, anthropicKey: string | undefined): NodeJS.ProcessEnv;
|
|
95
|
+
/** The fixed, non-secret ANTHROPIC_AUTH_TOKEN of an api-key launch (the key rides in a header). */
|
|
96
|
+
export declare const GATEWAY_AUTH_PLACEHOLDER = "mnemom-gateway";
|
|
82
97
|
/**
|
|
83
98
|
* The child env for a subscription launch: the gateway door, the Mnemom headers, and
|
|
84
99
|
* NOTHING that would make Claude Code send a key instead of its claude.ai login.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Pure apart from the injected deps, so every arm is unit-tested.
|
|
18
18
|
*/
|
|
19
|
-
import { stripOwnerCredentialEnv } from "./agent.js";
|
|
19
|
+
import { applyGatewayHintHeaders, stripOwnerCredentialEnv } from "./agent.js";
|
|
20
20
|
export const AGENT_AUTHS = ["api-key", "subscription"];
|
|
21
21
|
/**
|
|
22
22
|
* The retired `console` value (it read the key Claude Code's Anthropic Console /login
|
|
@@ -132,6 +132,32 @@ export function subscriptionShape(shape, shapeExplicit, rcAllowed, say) {
|
|
|
132
132
|
say("mnemom agent: subscription login runs terminal-only for now — launching in this terminal.");
|
|
133
133
|
return "terminal";
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The coding agent's env for either auth arm: what `mnemom agent` launches claude with, and what a
|
|
137
|
+
* durable seat launches its claude with (host/seat-launch.ts, dogfood B6). With an Anthropic key:
|
|
138
|
+
* Claude Code logged in via claude.ai (OAuth) PREEMPTS ANTHROPIC_API_KEY, so it is force-unset;
|
|
139
|
+
* the real key rides in ANTHROPIC_CUSTOM_HEADERS (`headers`) and ANTHROPIC_AUTH_TOKEN is a fixed
|
|
140
|
+
* non-secret placeholder. Without one (a subscription launch) NO auth token is set: Claude Code
|
|
141
|
+
* then sends its own claude.ai login. Either way the owner's own Mnemom credential env is
|
|
142
|
+
* stripped (MNE-8245), as is MNEMOM_AGENT_ANTHROPIC_KEY (the key reaches the child only in the
|
|
143
|
+
* headers: C14, B6), and Claude Code's gateway hint headers are on (a value the owner set wins):
|
|
144
|
+
* the seat proxy chains each turn by x-claude-code-prompt-id, and without it a seat's first typed
|
|
145
|
+
* turn chained to the launch prompt (dogfood B8). Set here so the two callers cannot drift apart.
|
|
146
|
+
*/
|
|
147
|
+
export function launchChildEnv(base, door, headers, anthropicKey) {
|
|
148
|
+
if (!anthropicKey)
|
|
149
|
+
return applyGatewayHintHeaders(subscriptionChildEnv(base, door, headers));
|
|
150
|
+
const env = { ...base };
|
|
151
|
+
delete env.ANTHROPIC_API_KEY;
|
|
152
|
+
delete env.MNEMOM_AGENT_ANTHROPIC_KEY;
|
|
153
|
+
env.ANTHROPIC_BASE_URL = door;
|
|
154
|
+
env.ANTHROPIC_AUTH_TOKEN = GATEWAY_AUTH_PLACEHOLDER;
|
|
155
|
+
env.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
156
|
+
stripOwnerCredentialEnv(env);
|
|
157
|
+
return applyGatewayHintHeaders(env);
|
|
158
|
+
}
|
|
159
|
+
/** The fixed, non-secret ANTHROPIC_AUTH_TOKEN of an api-key launch (the key rides in a header). */
|
|
160
|
+
export const GATEWAY_AUTH_PLACEHOLDER = "mnemom-gateway";
|
|
135
161
|
/**
|
|
136
162
|
* The child env for a subscription launch: the gateway door, the Mnemom headers, and
|
|
137
163
|
* NOTHING that would make Claude Code send a key instead of its claude.ai login.
|
|
@@ -139,6 +165,7 @@ export function subscriptionShape(shape, shapeExplicit, rcAllowed, say) {
|
|
|
139
165
|
export function subscriptionChildEnv(base, door, headers) {
|
|
140
166
|
const env = { ...base };
|
|
141
167
|
delete env.ANTHROPIC_API_KEY;
|
|
168
|
+
delete env.MNEMOM_AGENT_ANTHROPIC_KEY;
|
|
142
169
|
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
143
170
|
// The key already rides in the headers; the child process has no other use for it.
|
|
144
171
|
// MNE-8245 defense-in-depth: also strips MNEMOM_API_KEY/MNEMOM_TOKEN, in case the
|