@mnemom/mnemom 0.17.36 → 0.17.38-next.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/dist/commands/agent-run.d.ts +11 -0
- package/dist/commands/agent-run.js +37 -0
- package/dist/commands/agent-share-render.d.ts +5 -5
- package/dist/commands/agent-share-render.js +9 -7
- package/dist/commands/agent-share-test-issuer.d.ts +1 -1
- package/dist/commands/agent-share-test-issuer.js +1 -1
- package/dist/commands/agent.d.ts +8 -1
- package/dist/commands/agent.js +14 -3
- package/dist/commands/scan.d.ts +15 -2
- package/dist/commands/scan.js +48 -3
- package/dist/host/commands.js +4 -1
- package/dist/host/owner.d.ts +2 -0
- package/dist/host/verbs.d.ts +16 -1
- package/dist/host/verbs.js +45 -3
- package/dist/index.js +1 -1
- package/dist/lib/commit-report.d.ts +7 -13
- package/dist/lib/commit-report.js +12 -5
- package/dist/lib/conduct-receipt.d.ts +8 -3
- package/dist/lib/conduct-receipt.js +29 -7
- package/dist/lib/experimental.d.ts +1 -1
- package/dist/lib/experimental.js +1 -12
- package/dist/lib/runner/executor-client.d.ts +5 -0
- package/dist/lib/runner/executor-client.js +8 -0
- package/dist/lib/runner/job.d.ts +37 -0
- package/dist/lib/runner/job.js +74 -1
- package/dist/lib/runner/phases.d.ts +7 -2
- package/dist/lib/runner/phases.js +7 -2
- package/dist/lib/runner/proxy.d.ts +26 -2
- package/dist/lib/runner/proxy.js +33 -1
- package/dist/lib/runner/record-commit.d.ts +16 -0
- package/dist/lib/runner/record-commit.js +7 -0
- package/dist/lib/scan/spend/anthropic-usage.d.ts +20 -1
- package/dist/lib/scan/spend/anthropic-usage.js +32 -7
- package/dist/lib/scan/spend/anthropic-wif.d.ts +42 -0
- package/dist/lib/scan/spend/anthropic-wif.js +153 -0
- package/dist/vendor/durable-contract/index.js +6 -3
- package/dist/vendor/shared/durable-contract/cli-surface.js +2 -2
- package/dist/vendor/shared/durable-contract/codes.js +4 -1
- package/package.json +2 -5
- package/node_modules/@mnemom/aip-verifier/LICENSE +0 -190
- package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.cts +0 -478
- package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.ts +0 -478
- package/node_modules/@mnemom/aip-verifier/dist/index.cjs +0 -971
- package/node_modules/@mnemom/aip-verifier/dist/index.cjs.map +0 -1
- package/node_modules/@mnemom/aip-verifier/dist/index.d.cts +0 -330
- package/node_modules/@mnemom/aip-verifier/dist/index.d.ts +0 -330
- package/node_modules/@mnemom/aip-verifier/dist/index.js +0 -933
- package/node_modules/@mnemom/aip-verifier/dist/index.js.map +0 -1
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs +0 -879
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs.map +0 -1
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.cts +0 -96
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.ts +0 -96
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.js +0 -875
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.js.map +0 -1
- package/node_modules/@mnemom/aip-verifier/package.json +0 -86
- package/node_modules/@noble/ed25519/LICENSE +0 -21
- package/node_modules/@noble/ed25519/README.md +0 -371
- package/node_modules/@noble/ed25519/index.d.ts +0 -391
- package/node_modules/@noble/ed25519/index.js +0 -933
- package/node_modules/@noble/ed25519/index.ts +0 -1134
- package/node_modules/@noble/ed25519/package.json +0 -54
- package/node_modules/@noble/hashes/LICENSE +0 -21
- package/node_modules/@noble/hashes/README.md +0 -478
- package/node_modules/@noble/hashes/_blake.d.ts +0 -20
- package/node_modules/@noble/hashes/_blake.js +0 -59
- package/node_modules/@noble/hashes/_md.d.ts +0 -95
- package/node_modules/@noble/hashes/_md.js +0 -209
- package/node_modules/@noble/hashes/_u64.d.ts +0 -36
- package/node_modules/@noble/hashes/_u64.js +0 -77
- package/node_modules/@noble/hashes/argon2.d.ts +0 -148
- package/node_modules/@noble/hashes/argon2.js +0 -577
- package/node_modules/@noble/hashes/blake1.d.ts +0 -162
- package/node_modules/@noble/hashes/blake1.js +0 -630
- package/node_modules/@noble/hashes/blake2.d.ts +0 -203
- package/node_modules/@noble/hashes/blake2.js +0 -573
- package/node_modules/@noble/hashes/blake3.d.ts +0 -68
- package/node_modules/@noble/hashes/blake3.js +0 -310
- package/node_modules/@noble/hashes/eskdf.d.ts +0 -91
- package/node_modules/@noble/hashes/eskdf.js +0 -225
- package/node_modules/@noble/hashes/hkdf.d.ts +0 -70
- package/node_modules/@noble/hashes/hkdf.js +0 -152
- package/node_modules/@noble/hashes/hmac.d.ts +0 -50
- package/node_modules/@noble/hashes/hmac.js +0 -93
- package/node_modules/@noble/hashes/index.d.ts +0 -1
- package/node_modules/@noble/hashes/index.js +0 -34
- package/node_modules/@noble/hashes/legacy.d.ts +0 -107
- package/node_modules/@noble/hashes/legacy.js +0 -348
- package/node_modules/@noble/hashes/package.json +0 -91
- package/node_modules/@noble/hashes/pbkdf2.d.ts +0 -66
- package/node_modules/@noble/hashes/pbkdf2.js +0 -176
- package/node_modules/@noble/hashes/scrypt.d.ts +0 -98
- package/node_modules/@noble/hashes/scrypt.js +0 -304
- package/node_modules/@noble/hashes/sha2.d.ts +0 -176
- package/node_modules/@noble/hashes/sha2.js +0 -446
- package/node_modules/@noble/hashes/sha3-addons.d.ts +0 -533
- package/node_modules/@noble/hashes/sha3-addons.js +0 -973
- package/node_modules/@noble/hashes/sha3.d.ts +0 -208
- package/node_modules/@noble/hashes/sha3.js +0 -475
- package/node_modules/@noble/hashes/src/_blake.ts +0 -67
- package/node_modules/@noble/hashes/src/_md.ts +0 -227
- package/node_modules/@noble/hashes/src/_u64.ts +0 -111
- package/node_modules/@noble/hashes/src/argon2.ts +0 -693
- package/node_modules/@noble/hashes/src/blake1.ts +0 -677
- package/node_modules/@noble/hashes/src/blake2.ts +0 -648
- package/node_modules/@noble/hashes/src/blake3.ts +0 -344
- package/node_modules/@noble/hashes/src/eskdf.ts +0 -265
- package/node_modules/@noble/hashes/src/hkdf.ts +0 -161
- package/node_modules/@noble/hashes/src/hmac.ts +0 -130
- package/node_modules/@noble/hashes/src/index.ts +0 -33
- package/node_modules/@noble/hashes/src/legacy.ts +0 -361
- package/node_modules/@noble/hashes/src/pbkdf2.ts +0 -219
- package/node_modules/@noble/hashes/src/scrypt.ts +0 -375
- package/node_modules/@noble/hashes/src/sha2.ts +0 -486
- package/node_modules/@noble/hashes/src/sha3-addons.ts +0 -1157
- package/node_modules/@noble/hashes/src/sha3.ts +0 -525
- package/node_modules/@noble/hashes/src/utils.ts +0 -978
- package/node_modules/@noble/hashes/src/webcrypto.ts +0 -283
- package/node_modules/@noble/hashes/utils.d.ts +0 -560
- package/node_modules/@noble/hashes/utils.js +0 -687
- package/node_modules/@noble/hashes/webcrypto.d.ts +0 -121
- package/node_modules/@noble/hashes/webcrypto.js +0 -228
|
@@ -20,6 +20,7 @@ import { type GithubBases } from "../lib/runner/github-base.js";
|
|
|
20
20
|
import type { JobDeps, Workspace } from "../lib/runner/job.js";
|
|
21
21
|
import { type PhaseInvocation } from "../lib/runner/phases.js";
|
|
22
22
|
import { type RunFlags } from "../lib/runner/options.js";
|
|
23
|
+
import { type RecordCommitBody } from "../lib/commit-report.js";
|
|
23
24
|
export interface RunDeps {
|
|
24
25
|
env: NodeJS.ProcessEnv;
|
|
25
26
|
fetch: FetchFn;
|
|
@@ -55,6 +56,16 @@ export declare function prepareWorkspace(git: Git, cloneUrl: string, sessionId:
|
|
|
55
56
|
export declare function cleanupWorkspace(ws: Workspace): Promise<void>;
|
|
56
57
|
/** Commits on the job branch that no origin ref has: the clone's base is origin's default branch. */
|
|
57
58
|
export declare function commitsAhead(git: Git, ws: Workspace): Promise<number>;
|
|
59
|
+
/**
|
|
60
|
+
* The working copy's HEAD; null only when the clone has no commit at all. Any other failure throws,
|
|
61
|
+
* so the job never mistakes an unreadable HEAD for "since the base" and re-reports earlier phases.
|
|
62
|
+
*/
|
|
63
|
+
export declare function headSha(git: Git, ws: Workspace): Promise<string | null>;
|
|
64
|
+
/**
|
|
65
|
+
* RecordCommit/v1 bodies for the commits since `since` (null: since the base), oldest first. The
|
|
66
|
+
* repo is the job's: on a local lane origin is the fake GitHub, not github.com.
|
|
67
|
+
*/
|
|
68
|
+
export declare function commitBodies(git: Git, ws: Workspace, since: string | null, lineageKey: string, repo: string): Promise<RecordCommitBody[]>;
|
|
58
69
|
/** Push the job branch and open its PR against the repo's default branch; resolves its URL. */
|
|
59
70
|
export declare function openPr(git: Git, fetch: FetchFn, token: string | null, repo: string, ws: Workspace, title: string, body: string, apiBase?: string): Promise<string>;
|
|
60
71
|
/** merge_on_green: GitHub's auto-merge on the PR (squash). */
|
|
@@ -31,6 +31,7 @@ import { exitCodeFor, outcomeLine, refusedOutcome, USAGE_EXIT_CODE, } from "../l
|
|
|
31
31
|
import { phaseEnv } from "../lib/runner/phases.js";
|
|
32
32
|
import { resolveRunPlan } from "../lib/runner/options.js";
|
|
33
33
|
import { startModelProxy } from "../lib/runner/proxy.js";
|
|
34
|
+
import { commitBody, realGit } from "../lib/commit-report.js";
|
|
34
35
|
import { resolveGithubToken } from "./agent-fuel.js";
|
|
35
36
|
import { executorStatePath } from "../vendor/durable-contract/index.js";
|
|
36
37
|
/** The refusal of a runner with no ANTHROPIC_API_KEY (34.30.1); a config error, exit 2. */
|
|
@@ -165,6 +166,40 @@ export async function commitsAhead(git, ws) {
|
|
|
165
166
|
throw new Error(`git rev-list answered ${n}`);
|
|
166
167
|
return n;
|
|
167
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* The working copy's HEAD; null only when the clone has no commit at all. Any other failure throws,
|
|
171
|
+
* so the job never mistakes an unreadable HEAD for "since the base" and re-reports earlier phases.
|
|
172
|
+
*/
|
|
173
|
+
export async function headSha(git, ws) {
|
|
174
|
+
let sha;
|
|
175
|
+
try {
|
|
176
|
+
sha = await git(ws.dir, ["rev-parse", "--verify", "--quiet", "HEAD"]);
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
if ((await git(ws.dir, ["rev-list", "-n", "1", "--all"])) === "")
|
|
180
|
+
return null;
|
|
181
|
+
throw e;
|
|
182
|
+
}
|
|
183
|
+
if (!/^[0-9a-f]{40}$/.test(sha))
|
|
184
|
+
throw new Error(`git rev-parse answered ${sha.slice(0, 40)}`);
|
|
185
|
+
return sha;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* RecordCommit/v1 bodies for the commits since `since` (null: since the base), oldest first. The
|
|
189
|
+
* repo is the job's: on a local lane origin is the fake GitHub, not github.com.
|
|
190
|
+
*/
|
|
191
|
+
export async function commitBodies(git, ws, since, lineageKey, repo) {
|
|
192
|
+
const range = since === null ? ["HEAD", "--not", "--remotes=origin"] : [`${since}..HEAD`];
|
|
193
|
+
const out = await git(ws.dir, ["rev-list", "--reverse", ...range]);
|
|
194
|
+
const local = realGit(ws.dir);
|
|
195
|
+
const bodies = [];
|
|
196
|
+
for (const sha of out.split("\n").filter(Boolean)) {
|
|
197
|
+
const built = commitBody(local, sha, lineageKey, repo);
|
|
198
|
+
if (built.ok)
|
|
199
|
+
bodies.push(built.body);
|
|
200
|
+
}
|
|
201
|
+
return bodies;
|
|
202
|
+
}
|
|
168
203
|
/** Push the job branch and open its PR against the repo's default branch; resolves its URL. */
|
|
169
204
|
export async function openPr(git, fetch, token, repo, ws, title, body, apiBase = GITHUB_BASES.api) {
|
|
170
205
|
await git(ws.dir, ["push", "--quiet", "origin", `HEAD:refs/heads/${ws.branch}`]);
|
|
@@ -239,6 +274,8 @@ export function liveJobDeps(api, opts) {
|
|
|
239
274
|
runPhase: (inv, ws, modelUrl, signal) => runPhaseProcess(inv, ws, modelUrl, signal, opts.env),
|
|
240
275
|
openPr: (repo, ws, title, body) => openPr(git, opts.fetch, token, repo, ws, title, body, gh.api),
|
|
241
276
|
commitsAhead: (ws) => commitsAhead(git, ws),
|
|
277
|
+
headSha: (ws) => headSha(git, ws),
|
|
278
|
+
commitBodies: (ws, since, lineageKey, repo) => commitBodies(git, ws, since, lineageKey, repo),
|
|
242
279
|
enableAutoMerge: (prUrl) => enableAutoMerge(opts.fetch, token, prUrl, gh),
|
|
243
280
|
cleanup: cleanupWorkspace,
|
|
244
281
|
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bridge from `mnemom agent share` to the aip verifier (`@mnemom/
|
|
2
|
+
* Bridge from `mnemom agent share` to the aip verifier (`@mnemom/verify`, aip#192/#178):
|
|
3
3
|
* the renderer (`renderLines`) and claim-lint (`lintRendering`) are the ONLY source of what a
|
|
4
|
-
* card may say. No word lists live here. The package is a runtime dependency pinned to
|
|
5
|
-
*
|
|
4
|
+
* card may say. No word lists live here. The package is a runtime dependency pinned to an exact
|
|
5
|
+
* npm registry version and is loaded lazily, so the default CLI path never touches it; if it is
|
|
6
6
|
* absent at runtime the share command fails closed with a clear message.
|
|
7
7
|
*/
|
|
8
8
|
import type { LineRenderer, SharedCardLine } from "./agent-share.js";
|
|
9
|
-
import type * as AipMain from "@mnemom/
|
|
10
|
-
import type * as AipRender from "@mnemom/
|
|
9
|
+
import type * as AipMain from "@mnemom/verify";
|
|
10
|
+
import type * as AipRender from "@mnemom/verify/render-lines";
|
|
11
11
|
export interface VerifierModules {
|
|
12
12
|
lintRendering: typeof AipMain.lintRendering;
|
|
13
13
|
renderLines: typeof AipRender.renderLines;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bridge from `mnemom agent share` to the aip verifier (`@mnemom/
|
|
2
|
+
* Bridge from `mnemom agent share` to the aip verifier (`@mnemom/verify`, aip#192/#178):
|
|
3
3
|
* the renderer (`renderLines`) and claim-lint (`lintRendering`) are the ONLY source of what a
|
|
4
|
-
* card may say. No word lists live here. The package is a runtime dependency pinned to
|
|
5
|
-
*
|
|
4
|
+
* card may say. No word lists live here. The package is a runtime dependency pinned to an exact
|
|
5
|
+
* npm registry version and is loaded lazily, so the default CLI path never touches it; if it is
|
|
6
6
|
* absent at runtime the share command fails closed with a clear message.
|
|
7
7
|
*/
|
|
8
8
|
const defaultLoader = async () => {
|
|
9
9
|
const [main, render] = await Promise.all([
|
|
10
|
-
import("@mnemom/
|
|
11
|
-
import("@mnemom/
|
|
10
|
+
import("@mnemom/verify"),
|
|
11
|
+
import("@mnemom/verify/render-lines"),
|
|
12
12
|
]);
|
|
13
13
|
return { lintRendering: main.lintRendering, renderLines: render.renderLines };
|
|
14
14
|
};
|
|
@@ -20,9 +20,11 @@ async function loadVerifier(load) {
|
|
|
20
20
|
catch (error) {
|
|
21
21
|
const e = error;
|
|
22
22
|
if (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND") {
|
|
23
|
-
throw new Error("the aip verifier (@mnemom/
|
|
23
|
+
throw new Error("the aip verifier (@mnemom/verify) is not installed: it is a dependency of @mnemom/mnemom, so reinstall with `npm install -g @mnemom/mnemom`", { cause: error });
|
|
24
24
|
}
|
|
25
|
-
throw new Error(`the aip verifier (@mnemom/
|
|
25
|
+
throw new Error(`the aip verifier (@mnemom/verify) failed to load: ${e.message ?? error}`, {
|
|
26
|
+
cause: error,
|
|
27
|
+
});
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* TEST_SHARECARD_PUBLIC_KEY_HEX / TEST_SHARECARD_JWKS below, which a verifier must be given
|
|
24
24
|
* explicitly as the trusted key; the production JWKS never contains it.
|
|
25
25
|
*/
|
|
26
|
-
import type * as AipVerifier from "@mnemom/
|
|
26
|
+
import type * as AipVerifier from "@mnemom/verify";
|
|
27
27
|
import type { AgentShareDeps, CardIssuer } from "./agent-share.js";
|
|
28
28
|
/** aip test key id; ids of every test key start with this prefix. */
|
|
29
29
|
export declare const TEST_KEY_ID = "test-sharecard-key-1";
|
|
@@ -80,7 +80,7 @@ const PKCS8_ED25519_PREFIX = Buffer.from("302e020100300506032b657004220420", "he
|
|
|
80
80
|
export function createTestCardIssuer(opts = {}) {
|
|
81
81
|
const now = opts.now ?? (() => new Date());
|
|
82
82
|
const cardId = opts.cardId ?? (() => `card-test-${randomBytes(6).toString("hex")}`);
|
|
83
|
-
const load = opts.loadVerifier ?? (() => import("@mnemom/
|
|
83
|
+
const load = opts.loadVerifier ?? (() => import("@mnemom/verify"));
|
|
84
84
|
return {
|
|
85
85
|
async issue(lines) {
|
|
86
86
|
const { shareCardSigningInput } = await load();
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -401,11 +401,18 @@ export declare function fedStatuslineWanted(version: string): boolean;
|
|
|
401
401
|
*/
|
|
402
402
|
export declare function exemptLoopbackFromProxy(env: NodeJS.ProcessEnv): void;
|
|
403
403
|
/**
|
|
404
|
-
* Print the conduct receipt (
|
|
404
|
+
* Print the conduct receipt (on by default; MNEMOM_AGENT_RECEIPT=0 opts out) — the exit call site here, and the one a
|
|
405
405
|
* durable park calls. Never throws: a failure is one warning line, and the caller's exit code
|
|
406
406
|
* is untouched.
|
|
407
407
|
*/
|
|
408
408
|
export declare function sayConductReceipt(launch: ReceiptLaunch, trigger?: "exit" | "park", receipt?: typeof conductReceiptFor): Promise<void>;
|
|
409
|
+
/**
|
|
410
|
+
* Whether `mnemom agent` reads and prints the conduct receipt at exit (MNE-9013: on by default,
|
|
411
|
+
* graduated from experimental `record`; MNEMOM_AGENT_RECEIPT=0 opts out). Claude only: the
|
|
412
|
+
* receipt reads the lineage key the base-URL proxy captures, and that proxy fronts
|
|
413
|
+
* ANTHROPIC_BASE_URL alone, so another CLI would only gain a proxy that can capture nothing.
|
|
414
|
+
*/
|
|
415
|
+
export declare function receiptWanted(env: NodeJS.ProcessEnv): boolean;
|
|
409
416
|
/** Whether a terminal implicit launch runs the turn-origin proxy (MNEMOM_AGENT_TURN_HEADER=0 opts out). */
|
|
410
417
|
export declare function turnProxyWanted(env: NodeJS.ProcessEnv): boolean;
|
|
411
418
|
/** The flag that lets base-url-proxy.mjs use an http door on a loopback literal (C17-R16 T3). */
|
package/dist/commands/agent.js
CHANGED
|
@@ -847,7 +847,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
847
847
|
if (shape === "terminal") {
|
|
848
848
|
// Record keys the lineage under the served agent's org, not the account org (lib/record-org).
|
|
849
849
|
const recordOrgId = recordOrgWanted({
|
|
850
|
-
receipt:
|
|
850
|
+
receipt: cli.name === "claude" && receiptWanted(process.env),
|
|
851
851
|
commitReport: cli.name === "claude" && commitReportEnabled(process.env[COMMIT_REPORT_ENV]),
|
|
852
852
|
fedStatusline: cli.name === "claude" && fedStatuslineWanted(CLI_VERSION),
|
|
853
853
|
}) && account.orgId
|
|
@@ -895,7 +895,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
895
895
|
},
|
|
896
896
|
}
|
|
897
897
|
: {}),
|
|
898
|
-
|
|
898
|
+
// On by default (MNE-9013); prints only when the org's record returns tally data.
|
|
899
|
+
receipt: cli.name === "claude" && Boolean(account.orgId) && receiptWanted(process.env)
|
|
899
900
|
? { orgId: recordOrgId, agentId: servedAgentId }
|
|
900
901
|
: undefined,
|
|
901
902
|
orgId: recordOrgId,
|
|
@@ -1671,7 +1672,7 @@ export function exemptLoopbackFromProxy(env) {
|
|
|
1671
1672
|
env.no_proxy = [lower, "127.0.0.1,localhost"].filter(Boolean).join(",");
|
|
1672
1673
|
}
|
|
1673
1674
|
/**
|
|
1674
|
-
* Print the conduct receipt (
|
|
1675
|
+
* Print the conduct receipt (on by default; MNEMOM_AGENT_RECEIPT=0 opts out) — the exit call site here, and the one a
|
|
1675
1676
|
* durable park calls. Never throws: a failure is one warning line, and the caller's exit code
|
|
1676
1677
|
* is untouched.
|
|
1677
1678
|
*/
|
|
@@ -1684,6 +1685,16 @@ export async function sayConductReceipt(launch, trigger = "exit", receipt = cond
|
|
|
1684
1685
|
say(fmt.warn(`mnemom agent: no conduct receipt (${err instanceof Error ? err.message : String(err)})`));
|
|
1685
1686
|
}
|
|
1686
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Whether `mnemom agent` reads and prints the conduct receipt at exit (MNE-9013: on by default,
|
|
1690
|
+
* graduated from experimental `record`; MNEMOM_AGENT_RECEIPT=0 opts out). Claude only: the
|
|
1691
|
+
* receipt reads the lineage key the base-URL proxy captures, and that proxy fronts
|
|
1692
|
+
* ANTHROPIC_BASE_URL alone, so another CLI would only gain a proxy that can capture nothing.
|
|
1693
|
+
*/
|
|
1694
|
+
export function receiptWanted(env) {
|
|
1695
|
+
const raw = (env.MNEMOM_AGENT_RECEIPT ?? "").trim().toLowerCase();
|
|
1696
|
+
return !["0", "off", "false", "no"].includes(raw);
|
|
1697
|
+
}
|
|
1687
1698
|
/** Whether a terminal implicit launch runs the turn-origin proxy (MNEMOM_AGENT_TURN_HEADER=0 opts out). */
|
|
1688
1699
|
export function turnProxyWanted(env) {
|
|
1689
1700
|
const raw = (env.MNEMOM_AGENT_TURN_HEADER ?? "").trim().toLowerCase();
|
package/dist/commands/scan.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import { type GitHubCredential } from "../lib/scan/credential.js";
|
|
8
8
|
import { type ScanDeps, type ScanResult } from "../lib/scan/scan.js";
|
|
9
9
|
import { selectSpend } from "../lib/scan/spend/spend-tiers.js";
|
|
10
|
-
import { type AnthropicUsageDeps } from "../lib/scan/spend/anthropic-usage.js";
|
|
10
|
+
import { type AnthropicUsageDeps, type MeasuredUsage } from "../lib/scan/spend/anthropic-usage.js";
|
|
11
|
+
import { type WifDeps } from "../lib/scan/spend/anthropic-wif.js";
|
|
11
12
|
import { type RecordDeps, type RecordOutcome } from "../lib/scan/record.js";
|
|
12
13
|
import { type CostSpend } from "../lib/scan/cost-per-pr.js";
|
|
13
14
|
import { type ReportEmailDeps } from "../lib/scan/report-email.js";
|
|
@@ -33,6 +34,8 @@ export interface ScanCommandOptions {
|
|
|
33
34
|
anthropicOver200kShare?: number | null;
|
|
34
35
|
/** Test seam: the fetch behind the two Anthropic reads. */
|
|
35
36
|
anthropicDeps?: AnthropicUsageDeps;
|
|
37
|
+
/** Test seam: the WIF assertion mint and Anthropic token exchange (MNE-9706). */
|
|
38
|
+
wifDeps?: WifDeps;
|
|
36
39
|
/**
|
|
37
40
|
* The org the report is emailed for; inferred from the signed-in user's single non-personal org when
|
|
38
41
|
* omitted (see resolveScanOrg). The Record totals preview (MNEMOM_ORG_SCAN_MODE=true) reads ONLY an
|
|
@@ -93,10 +96,20 @@ export declare const TIER1_FALLBACK_NOTE = "Tier 1 skipped: falling back to the
|
|
|
93
96
|
* fixed sentence: the key, the thrown value and the response never reach the terminal. The caller then falls
|
|
94
97
|
* back to tier 2, then tier 3.
|
|
95
98
|
*/
|
|
99
|
+
export declare const WIF_OK_NOTE = "Read Anthropic usage through workload identity federation.";
|
|
100
|
+
/**
|
|
101
|
+
* Tier 1 through WIF (MNE-9706): Mnemom issues an identity token for the org, this machine exchanges it at
|
|
102
|
+
* Anthropic and reads only the two usage reports with the result. "not_configured" when the WIF ids are
|
|
103
|
+
* not set; "failed" (after printing a fixed sentence) for every failure, so the caller falls back to the key.
|
|
104
|
+
*/
|
|
105
|
+
export declare function measuredUsageViaWif(o: ScanCommandOptions, orgId: string, period: {
|
|
106
|
+
start: string;
|
|
107
|
+
end: string;
|
|
108
|
+
}): Promise<MeasuredUsage | "not_configured" | "failed">;
|
|
96
109
|
export declare function resolveMeasuredUsage(o: ScanCommandOptions, period: {
|
|
97
110
|
start: string;
|
|
98
111
|
end: string;
|
|
99
|
-
}): Promise<{
|
|
112
|
+
}, orgId?: string): Promise<{
|
|
100
113
|
usd: number;
|
|
101
114
|
over200kShare: number | null;
|
|
102
115
|
} | null>;
|
package/dist/commands/scan.js
CHANGED
|
@@ -9,7 +9,8 @@ import { failureText } from "../lib/scan/failure.js";
|
|
|
9
9
|
import { runLocalScan } from "../lib/scan/scan.js";
|
|
10
10
|
import { askInput, askSelect } from "../lib/prompt.js";
|
|
11
11
|
import { isSpendTiersEnabled, NOT_ENABLED_MSG, parseMonthlySpendUsd, selectSpend, SPEND_INVALID_MSG, SPEND_NO_TERMINAL_MSG, } from "../lib/scan/spend/spend-tiers.js";
|
|
12
|
-
import { ADMIN_KEY_ENV, AnthropicUsageError, fetchMeasuredUsage, isAdminKeyShape, usageFailureText, } from "../lib/scan/spend/anthropic-usage.js";
|
|
12
|
+
import { ADMIN_KEY_ENV, AnthropicUsageError, fetchMeasuredUsage, isAdminKeyShape, usageFailureText, bearerAuth, fetchMeasuredUsageWithAuth, } from "../lib/scan/spend/anthropic-usage.js";
|
|
13
|
+
import { obtainWifAccessToken, readWifConfig, WIF_FALLBACK_NOTE, WifError, wifFailureText, } from "../lib/scan/spend/anthropic-wif.js";
|
|
13
14
|
import { getAuthInfo } from "../lib/auth.js";
|
|
14
15
|
import { listMyOrgs } from "../lib/api.js";
|
|
15
16
|
import { getActiveOrg } from "../lib/cli-config.js";
|
|
@@ -96,7 +97,37 @@ export const TIER1_FALLBACK_NOTE = "Tier 1 skipped: falling back to the next tie
|
|
|
96
97
|
* fixed sentence: the key, the thrown value and the response never reach the terminal. The caller then falls
|
|
97
98
|
* back to tier 2, then tier 3.
|
|
98
99
|
*/
|
|
99
|
-
export
|
|
100
|
+
export const WIF_OK_NOTE = "Read Anthropic usage through workload identity federation.";
|
|
101
|
+
/**
|
|
102
|
+
* Tier 1 through WIF (MNE-9706): Mnemom issues an identity token for the org, this machine exchanges it at
|
|
103
|
+
* Anthropic and reads only the two usage reports with the result. "not_configured" when the WIF ids are
|
|
104
|
+
* not set; "failed" (after printing a fixed sentence) for every failure, so the caller falls back to the key.
|
|
105
|
+
*/
|
|
106
|
+
export async function measuredUsageViaWif(o, orgId, period) {
|
|
107
|
+
let cfg;
|
|
108
|
+
try {
|
|
109
|
+
cfg = readWifConfig(o.env ?? process.env);
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
console.log(wifFailureText(e instanceof WifError ? e.reason : "config_invalid"));
|
|
113
|
+
return "failed";
|
|
114
|
+
}
|
|
115
|
+
if (cfg === null)
|
|
116
|
+
return "not_configured";
|
|
117
|
+
try {
|
|
118
|
+
const token = await obtainWifAccessToken(orgId, cfg, o.wifDeps);
|
|
119
|
+
const usage = await fetchMeasuredUsageWithAuth(bearerAuth(token.accessToken), period, o.anthropicDeps);
|
|
120
|
+
console.log(WIF_OK_NOTE);
|
|
121
|
+
return usage;
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
console.log(e instanceof WifError
|
|
125
|
+
? wifFailureText(e.reason)
|
|
126
|
+
: usageFailureText(e instanceof AnthropicUsageError ? e.reason : "upstream"));
|
|
127
|
+
return "failed";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export async function resolveMeasuredUsage(o, period, orgId) {
|
|
100
131
|
if (o.anthropicAdminKey && !isSpendTiersEnabled(o.env))
|
|
101
132
|
throw new Error(NOT_ENABLED_MSG);
|
|
102
133
|
if (!isSpendTiersEnabled(o.env) || o.noAnthropicUsage)
|
|
@@ -105,6 +136,20 @@ export async function resolveMeasuredUsage(o, period) {
|
|
|
105
136
|
if (o.monthlySpend !== undefined && !o.anthropicAdminKey)
|
|
106
137
|
return null;
|
|
107
138
|
let key = (o.env ?? process.env)[ADMIN_KEY_ENV];
|
|
139
|
+
// MNE-9706: WIF first when it is set up, unless the user explicitly asked to paste a key.
|
|
140
|
+
let wifFailed = false;
|
|
141
|
+
if (!o.anthropicAdminKey && orgId !== undefined) {
|
|
142
|
+
const viaWif = await measuredUsageViaWif(o, orgId, period);
|
|
143
|
+
if (viaWif !== "not_configured" && viaWif !== "failed")
|
|
144
|
+
return viaWif;
|
|
145
|
+
wifFailed = viaWif === "failed";
|
|
146
|
+
}
|
|
147
|
+
if (wifFailed) {
|
|
148
|
+
const hasKey = key !== undefined && key.trim() !== "";
|
|
149
|
+
console.log(hasKey ? WIF_FALLBACK_NOTE : TIER1_FALLBACK_NOTE);
|
|
150
|
+
if (!hasKey)
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
108
153
|
if (o.anthropicAdminKey) {
|
|
109
154
|
if (!(o.isTty ?? process.stdin.isTTY))
|
|
110
155
|
throw new Error(NO_ADMIN_KEY_TERMINAL_MSG);
|
|
@@ -218,7 +263,7 @@ export async function scanCommand(o) {
|
|
|
218
263
|
const now = o.now ?? Date.now;
|
|
219
264
|
const end = new Date(now()).toISOString().slice(0, 10);
|
|
220
265
|
const period = o.period ?? { start: end, end };
|
|
221
|
-
const measured = await resolveMeasuredUsage(o, period);
|
|
266
|
+
const measured = await resolveMeasuredUsage(o, period, emailOrg);
|
|
222
267
|
const credential = pickedCredential ?? (await getCredential());
|
|
223
268
|
// Before the local scan: a refused credential fails closed without spending the GitHub reads.
|
|
224
269
|
const record = await resolveRecordOutcome(o);
|
package/dist/host/commands.js
CHANGED
|
@@ -8,6 +8,7 @@ import * as os from "node:os";
|
|
|
8
8
|
import { CLI_VERSION } from "../version.js";
|
|
9
9
|
import { getApiUrl } from "../lib/config.js";
|
|
10
10
|
import { getAccessToken } from "../lib/auth.js";
|
|
11
|
+
import { getActiveOrg } from "../lib/cli-config.js";
|
|
11
12
|
import { resolveBootId, defaultBootIdDeps } from "./bootid.js";
|
|
12
13
|
import { realLaunchctl } from "./launchd.js";
|
|
13
14
|
import { ownerApi, parseExecutorList } from "./owner.js";
|
|
@@ -37,6 +38,7 @@ function verbIo(json) {
|
|
|
37
38
|
bootId: () => resolveBootId({ ...defaultBootIdDeps(), env }).bootId,
|
|
38
39
|
programArguments: () => [...cliExec(), "agent", "host", "run"],
|
|
39
40
|
claudeBin: () => resolveClaudeBin(env),
|
|
41
|
+
activeOrg: () => getActiveOrg()?.org_id ?? null,
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
async function guarded(json, fn) {
|
|
@@ -66,7 +68,8 @@ export function registerHostCommands(agent, hidden) {
|
|
|
66
68
|
host
|
|
67
69
|
.command("enroll")
|
|
68
70
|
.description("Enroll this machine as an executor (device step); `install` does this when needed")
|
|
69
|
-
.
|
|
71
|
+
.option("--org <org_id>", "Join this organization (you must be a member); defaults to the active org, else your personal org")
|
|
72
|
+
.action(async (o) => guarded(false, (io) => hostEnroll(io, { org: o.org })));
|
|
70
73
|
host
|
|
71
74
|
.command("renew")
|
|
72
75
|
.description("Renew this machine's executor grant (device step)")
|
package/dist/host/owner.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface OwnerApi {
|
|
|
11
11
|
runtime: string;
|
|
12
12
|
contract_version: string;
|
|
13
13
|
machine_label?: string;
|
|
14
|
+
/** Ruling 34.03.4: the org this host joins; the api checks membership. */
|
|
15
|
+
org_id?: string;
|
|
14
16
|
}): Promise<OwnerHttp>;
|
|
15
17
|
enrollPoll(pollSecret: string, approvalId: string | null): Promise<OwnerHttp>;
|
|
16
18
|
list(): Promise<OwnerHttp>;
|
package/dist/host/verbs.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface VerbIo {
|
|
|
20
20
|
readonly pollSleep?: (ms: number) => Promise<void>;
|
|
21
21
|
/** Takes the enrollment lock; acquireHostLock (macOS flock) when absent. */
|
|
22
22
|
readonly acquireLock?: (lockFile: string) => LockResult;
|
|
23
|
+
/** The CLI's active org id (`mnemom org use`), or null with none set; never read when absent. */
|
|
24
|
+
readonly activeOrg?: () => string | null;
|
|
23
25
|
}
|
|
24
26
|
/** Fresh-invocation gate for every host verb (C12-R25): exit 2 with the gated text, nothing else. */
|
|
25
27
|
export declare function freshGate(io: VerbIo): number | null;
|
|
@@ -40,13 +42,26 @@ export declare function executorLiveness(io: VerbIo, executorId: string): Promis
|
|
|
40
42
|
export declare function printRiskLine(io: VerbIo): void;
|
|
41
43
|
/** "<org name> (<org_id>)", or the bare id when the api sent no name. */
|
|
42
44
|
export declare function joinTarget(orgId: string, orgName: string | null): string;
|
|
45
|
+
/**
|
|
46
|
+
* The org a start names (ruling 34.03.4): `--org`, else the active org, else none, so the api
|
|
47
|
+
* keeps its fallback (the token's org, else the personal org).
|
|
48
|
+
*/
|
|
49
|
+
export declare function enrollOrg(io: VerbIo, opts: {
|
|
50
|
+
org?: string;
|
|
51
|
+
}): {
|
|
52
|
+
orgId: string;
|
|
53
|
+
from: "flag" | "active";
|
|
54
|
+
} | null;
|
|
43
55
|
/** The device step: start, show the code, poll, write the state file. Returns an exit code. */
|
|
44
56
|
export declare function enrollDevice(io: VerbIo, opts?: {
|
|
45
57
|
label?: string;
|
|
58
|
+
org?: string;
|
|
46
59
|
}): Promise<number>;
|
|
47
60
|
/** Write the plist and (re)load the job. */
|
|
48
61
|
export declare function writeAndLoad(io: VerbIo): Promise<number>;
|
|
49
62
|
export declare function hostInstall(io: VerbIo): Promise<number>;
|
|
50
|
-
export declare function hostEnroll(io: VerbIo
|
|
63
|
+
export declare function hostEnroll(io: VerbIo, opts?: {
|
|
64
|
+
org?: string;
|
|
65
|
+
}): Promise<number>;
|
|
51
66
|
export declare function hostRenew(io: VerbIo): Promise<number>;
|
|
52
67
|
export declare function hostUninstall(io: VerbIo): Promise<number>;
|
package/dist/host/verbs.js
CHANGED
|
@@ -23,6 +23,7 @@ import { renderPlist } from "./plist.js";
|
|
|
23
23
|
import { loadJob, unloadJob } from "./launchd.js";
|
|
24
24
|
import { classifyPoll, parseEnrollStart, parseExecutorList, EXECUTOR_CONTRACT, } from "./owner.js";
|
|
25
25
|
import { readRuntime } from "./runtime.js";
|
|
26
|
+
import { CODE } from "./s1-names.js";
|
|
26
27
|
import { RENEW_FIX, GRANT_EXPIRED_LINE } from "./grant.js";
|
|
27
28
|
export const INSTALL_FIX = "`mnemom agent host install`";
|
|
28
29
|
export const UNINSTALL_SESSION_LINE = (name) => `${name}: they stop restarting until ${INSTALL_FIX}`;
|
|
@@ -63,6 +64,22 @@ export function printRiskLine(io) {
|
|
|
63
64
|
export function joinTarget(orgId, orgName) {
|
|
64
65
|
return orgName ? `${orgName} (${orgId})` : orgId;
|
|
65
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The org a start names (ruling 34.03.4): `--org`, else the active org, else none, so the api
|
|
69
|
+
* keeps its fallback (the token's org, else the personal org).
|
|
70
|
+
*/
|
|
71
|
+
export function enrollOrg(io, opts) {
|
|
72
|
+
if (opts.org)
|
|
73
|
+
return { orgId: opts.org, from: "flag" };
|
|
74
|
+
const active = io.activeOrg?.() ?? null;
|
|
75
|
+
return active ? { orgId: active, from: "active" } : null;
|
|
76
|
+
}
|
|
77
|
+
/** The api's refusal code from an error body, or null. */
|
|
78
|
+
function errorCode(body) {
|
|
79
|
+
const e = body && typeof body === "object" ? body.error : undefined;
|
|
80
|
+
const code = e && typeof e === "object" ? e.code : undefined;
|
|
81
|
+
return typeof code === "string" ? code : null;
|
|
82
|
+
}
|
|
66
83
|
/** The device step: start, show the code, poll, write the state file. Returns an exit code. */
|
|
67
84
|
export async function enrollDevice(io, opts = {}) {
|
|
68
85
|
const { paths } = ctx(io);
|
|
@@ -71,16 +88,41 @@ export async function enrollDevice(io, opts = {}) {
|
|
|
71
88
|
io.err(`mnemom agent host: an enrollment is already in progress here (${lock.reason}): wait for it, then ${INSTALL_FIX}`);
|
|
72
89
|
return 1;
|
|
73
90
|
}
|
|
91
|
+
let org = enrollOrg(io, opts);
|
|
74
92
|
try {
|
|
75
|
-
const
|
|
93
|
+
const startWith = (o) => io.owner.enrollStart({
|
|
76
94
|
kind: "host",
|
|
77
95
|
runtime: "node",
|
|
78
96
|
contract_version: EXECUTOR_CONTRACT,
|
|
79
97
|
...(opts.label ? { machine_label: opts.label } : {}),
|
|
98
|
+
...(o ? { org_id: o.orgId } : {}),
|
|
80
99
|
});
|
|
100
|
+
let st = await startWith(org);
|
|
101
|
+
// An api from before 34.03.4 refuses org_id on a host start with its plain 400 invalid_scope
|
|
102
|
+
// (34.03.2 (1); mnemom-api enroll.ts "org_id is only accepted with scope org"; recorded beside
|
|
103
|
+
// exec_enroll in S1 codes.ts). An org taken from the active org then falls back to the start this
|
|
104
|
+
// CLI made before; an explicit --org cannot.
|
|
105
|
+
if (org && st.status === 400 && errorCode(st.body) === "invalid_scope") {
|
|
106
|
+
if (org.from === "flag") {
|
|
107
|
+
io.err(`mnemom agent host: this Mnemom server cannot enroll a machine into ${org.orgId} yet.`);
|
|
108
|
+
io.err("fix: `mnemom agent host enroll` without --org (joins your personal org)");
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
io.err(`mnemom agent host: this Mnemom server cannot enroll a machine into your active org ${org.orgId} yet; it joins your personal org.`);
|
|
112
|
+
org = null;
|
|
113
|
+
st = await startWith(null);
|
|
114
|
+
}
|
|
81
115
|
const start = st.status === 202 || st.status === 200 || st.status === 201
|
|
82
116
|
? parseEnrollStart(st.body)
|
|
83
117
|
: null;
|
|
118
|
+
if (!start && org && errorCode(st.body) === CODE.durable_approval_not_eligible) {
|
|
119
|
+
// 34.03.4: only a member of the org may enroll a machine into it.
|
|
120
|
+
io.err(`mnemom agent host: you are not a member of ${org.orgId}, so this machine cannot join it.`);
|
|
121
|
+
io.err(org.from === "flag"
|
|
122
|
+
? "fix: `mnemom org list` shows the organizations you belong to"
|
|
123
|
+
: "fix: `mnemom org use --clear`, then `mnemom agent host enroll` (joins your personal org)");
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
84
126
|
if (!start) {
|
|
85
127
|
const code = st.body && typeof st.body === "object"
|
|
86
128
|
? JSON.stringify(st.body).slice(0, 300)
|
|
@@ -211,8 +253,8 @@ export async function hostInstall(io) {
|
|
|
211
253
|
io.out(reload ? "Host daemon reloaded on this machine's enrollment." : "Host daemon installed.");
|
|
212
254
|
return 0;
|
|
213
255
|
}
|
|
214
|
-
export async function hostEnroll(io) {
|
|
215
|
-
return enrollDevice(io);
|
|
256
|
+
export async function hostEnroll(io, opts = {}) {
|
|
257
|
+
return enrollDevice(io, opts);
|
|
216
258
|
}
|
|
217
259
|
export async function hostRenew(io) {
|
|
218
260
|
const { paths } = ctx(io);
|
package/dist/index.js
CHANGED
|
@@ -2390,7 +2390,7 @@ program
|
|
|
2390
2390
|
.option("--share <level>", "summary | full | none (default: full when asked; none when not interactive)")
|
|
2391
2391
|
.option("--cto-email <email>", "CTO who receives the report (required unless you are the CTO; asked when omitted)")
|
|
2392
2392
|
.option("--monthly-spend <usd>", "Your own approximate monthly AI spend (self-reported; never sent or saved; needs MNEMOM_ORG_SCAN_MODE=true)")
|
|
2393
|
-
.option("--anthropic-admin-key", "Ask (masked) for your Anthropic admin key and read measured usage with it, locally; or set ANTHROPIC_ADMIN_KEY. Never stored, logged or sent anywhere but api.anthropic.com (needs MNEMOM_ORG_SCAN_MODE=true)")
|
|
2393
|
+
.option("--anthropic-admin-key", "Ask (masked) for your Anthropic admin key and read measured usage with it, locally; or set ANTHROPIC_ADMIN_KEY. Never stored, logged or sent anywhere but api.anthropic.com. Without this flag, workload identity federation is tried first when ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID and ANTHROPIC_SERVICE_ACCOUNT_ID are set (needs MNEMOM_ORG_SCAN_MODE=true)")
|
|
2394
2394
|
.option("--no-anthropic-usage", "Decline Anthropic usage and be asked for a monthly figure (needs MNEMOM_ORG_SCAN_MODE=true)")
|
|
2395
2395
|
.option("--org <org_id>", "Your org id; the report is emailed to you for this org (default: your only organization, else the scan stops and asks). Also selects the Record totals preview (needs MNEMOM_ORG_SCAN_MODE=true)")
|
|
2396
2396
|
.action(async (options) => {
|
|
@@ -51,17 +51,8 @@ export declare function attributionSettings(conversationId: string, user: string
|
|
|
51
51
|
};
|
|
52
52
|
/** `owner/name` from a GitHub remote URL (https, ssh or scp form), or null. */
|
|
53
53
|
export declare function parseRepoSlug(url: string): string | null;
|
|
54
|
-
|
|
55
|
-
export
|
|
56
|
-
/** RecordCommit/v1: strings only, every field required; the route rejects an unknown field. */
|
|
57
|
-
export interface RecordCommitBody {
|
|
58
|
-
schema: typeof COMMIT_SCHEMA;
|
|
59
|
-
lineage_key: string;
|
|
60
|
-
repo: string;
|
|
61
|
-
branch: string;
|
|
62
|
-
sha: string;
|
|
63
|
-
patch_id: string;
|
|
64
|
-
}
|
|
54
|
+
import { COMMIT_SCHEMA, type RecordCommitBody } from "./runner/record-commit.js";
|
|
55
|
+
export { COMMIT_SCHEMA, type RecordCommitBody };
|
|
65
56
|
/** Runs git in the session's repository; null on any failure. */
|
|
66
57
|
export type Git = (args: string[], input?: string) => string | null;
|
|
67
58
|
export declare function realGit(cwd: string): Git;
|
|
@@ -71,8 +62,11 @@ export declare function realGit(cwd: string): Git;
|
|
|
71
62
|
* teammate's fetched commit is never reported as this session's.
|
|
72
63
|
*/
|
|
73
64
|
export declare function sessionCommits(git: Git, sinceSec: number): string[];
|
|
74
|
-
/**
|
|
75
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The body for one commit, or the fields that could not be read. `repo` overrides the slug read
|
|
67
|
+
* from `origin`: the runner knows its job's repo, and on a local lane `origin` is not github.com.
|
|
68
|
+
*/
|
|
69
|
+
export declare function commitBody(git: Git, sha: string, lineageKey: string, repoOverride?: string): {
|
|
76
70
|
ok: true;
|
|
77
71
|
body: RecordCommitBody;
|
|
78
72
|
} | {
|
|
@@ -65,8 +65,8 @@ export function parseRepoSlug(url) {
|
|
|
65
65
|
const slug = `${m[1]}/${m[2]}`;
|
|
66
66
|
return REPO_SLUG.test(slug) ? slug : null;
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
export
|
|
68
|
+
import { COMMIT_SCHEMA } from "./runner/record-commit.js";
|
|
69
|
+
export { COMMIT_SCHEMA };
|
|
70
70
|
export function realGit(cwd) {
|
|
71
71
|
return (args, input) => {
|
|
72
72
|
const r = spawnSync("git", args, { cwd, encoding: "utf8", input, timeout: 10_000 });
|
|
@@ -91,8 +91,11 @@ export function sessionCommits(git, sinceSec) {
|
|
|
91
91
|
.filter(([sha, ce]) => sha !== undefined && HEX40.test(sha) && ce === email)
|
|
92
92
|
.map(([sha]) => sha);
|
|
93
93
|
}
|
|
94
|
-
/**
|
|
95
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The body for one commit, or the fields that could not be read. `repo` overrides the slug read
|
|
96
|
+
* from `origin`: the runner knows its job's repo, and on a local lane `origin` is not github.com.
|
|
97
|
+
*/
|
|
98
|
+
export function commitBody(git, sha, lineageKey, repoOverride) {
|
|
96
99
|
const diff = git(["diff-tree", "-p", "--root", sha]);
|
|
97
100
|
const patchId = diff
|
|
98
101
|
? (/^\s*([0-9a-f]{40})\b/.exec(git(["patch-id", "--stable"], diff) ?? "")?.[1] ?? "")
|
|
@@ -103,7 +106,11 @@ export function commitBody(git, sha, lineageKey) {
|
|
|
103
106
|
.map((s) => s.trim())
|
|
104
107
|
.filter(Boolean);
|
|
105
108
|
const branch = holders.includes(current) ? current : (holders[0] ?? "");
|
|
106
|
-
const repo =
|
|
109
|
+
const repo = repoOverride !== undefined
|
|
110
|
+
? REPO_SLUG.test(repoOverride)
|
|
111
|
+
? repoOverride
|
|
112
|
+
: ""
|
|
113
|
+
: (parseRepoSlug(git(["remote", "get-url", "origin"]) ?? "") ?? "");
|
|
107
114
|
const missing = [];
|
|
108
115
|
if (!HEX40.test(sha))
|
|
109
116
|
missing.push("sha");
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The conduct receipt (
|
|
3
|
-
*
|
|
2
|
+
* The conduct receipt (Doors W1.2 / W1.3): the engineer's private view of one session, printed
|
|
3
|
+
* when `mnemom agent` exits and when a durable session parks. On by default since 0.17.38
|
|
4
|
+
* (MNE-9013; MNEMOM_AGENT_RECEIPT=0 opts out). It prints ONLY when the server returns tally data:
|
|
5
|
+
* an org outside the record cohort (404), a caller without the role (403), no captured lineage,
|
|
6
|
+
* or a read that never answers inside the deadline all print nothing.
|
|
4
7
|
*
|
|
5
8
|
* Conduct receipt
|
|
6
9
|
* model cost $0.42
|
|
@@ -13,7 +16,7 @@
|
|
|
13
16
|
*
|
|
14
17
|
* NEVER BLOCKS EXIT. The whole read, auth refresh included, races a hard deadline
|
|
15
18
|
* (RECEIPT_DEADLINE_MS): with the API or the record builder down, exit takes at most that much
|
|
16
|
-
* longer, and the receipt
|
|
19
|
+
* longer, and the receipt prints nothing. The caller keeps its exit code; nothing here
|
|
17
20
|
* throws.
|
|
18
21
|
*
|
|
19
22
|
* The lineage key is the gateway's (x-mnemom-lineage-key, set on every response), captured by the
|
|
@@ -35,6 +38,8 @@ export type TotalsRead = {
|
|
|
35
38
|
muChargedMmu: bigint | null;
|
|
36
39
|
} | {
|
|
37
40
|
kind: "unavailable";
|
|
41
|
+
} | {
|
|
42
|
+
kind: "absent";
|
|
38
43
|
};
|
|
39
44
|
export interface ReceiptDeps {
|
|
40
45
|
/** One totals read for the lineage; may throw or hang (both are "unavailable"). */
|