@mnemom/mnemom 0.16.1 → 0.16.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.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Agent identity config store — ~/.mnemom/agent.json.
3
+ *
4
+ * Persists the calling agent's resolved identity so skill verbs (try-me,
5
+ * onboard, wrap) write on first success and read thereafter — no re-prompt for
6
+ * identity on subsequent invocations (MNE-937).
7
+ *
8
+ * Co-located with auth.ts → auth.json. Separate by design: auth.json holds
9
+ * bearer credentials (wiped on logout); agent.json holds identity metadata
10
+ * that survives re-login and provider key rotation.
11
+ */
12
+ import * as fs from "node:fs";
13
+ import * as path from "node:path";
14
+ import { MNEMOM_DIR } from "./config.js";
15
+ function agentFile() {
16
+ return path.join(MNEMOM_DIR, "agent.json");
17
+ }
18
+ /** Load the agent config; a missing or corrupt file returns `{}`. */
19
+ export function loadAgentConfig() {
20
+ try {
21
+ if (!fs.existsSync(agentFile()))
22
+ return {};
23
+ const parsed = JSON.parse(fs.readFileSync(agentFile(), "utf-8"));
24
+ return parsed && typeof parsed === "object" ? parsed : {};
25
+ }
26
+ catch {
27
+ return {};
28
+ }
29
+ }
30
+ function writeAgentConfig(config) {
31
+ if (!fs.existsSync(MNEMOM_DIR)) {
32
+ // 0700 to match the auth store — the directory holds credentials.
33
+ fs.mkdirSync(MNEMOM_DIR, { recursive: true, mode: 0o700 });
34
+ }
35
+ const resolvedPath = path.resolve(agentFile());
36
+ const tmpFile = `${resolvedPath}.${process.pid}.tmp`;
37
+ // Write-then-rename for atomicity; 0600 owner-only to match the auth store posture.
38
+ try {
39
+ fs.writeFileSync(tmpFile, JSON.stringify(config, null, 2), { mode: 0o600 });
40
+ try {
41
+ fs.chmodSync(tmpFile, 0o600);
42
+ }
43
+ catch {
44
+ /* best effort on platforms without POSIX perms */
45
+ }
46
+ fs.renameSync(tmpFile, resolvedPath);
47
+ }
48
+ catch (err) {
49
+ // Don't leave a partially-written `${pid}.tmp` sibling behind on a failed
50
+ // write/rename — best-effort unlink, then re-throw the original error.
51
+ try {
52
+ fs.unlinkSync(tmpFile);
53
+ }
54
+ catch {
55
+ /* the tmp file may not have been created — nothing to clean up */
56
+ }
57
+ throw err;
58
+ }
59
+ }
60
+ /** Persist the full agent config (replaces the file). */
61
+ export function saveAgentConfig(config) {
62
+ writeAgentConfig(config);
63
+ }
64
+ /** Shallow-merge `partial` into the existing config and persist. */
65
+ export function mergeAgentConfig(partial) {
66
+ const current = loadAgentConfig();
67
+ const next = { ...current };
68
+ for (const key of Object.keys(partial)) {
69
+ if (partial[key] !== undefined) {
70
+ next[key] = partial[key];
71
+ }
72
+ }
73
+ writeAgentConfig(next);
74
+ }
75
+ export function getAgentId() {
76
+ return loadAgentConfig().agent_id;
77
+ }
78
+ export function getAgentName() {
79
+ return loadAgentConfig().agent_name;
80
+ }
81
+ export function getOrgId() {
82
+ return loadAgentConfig().org_id;
83
+ }
84
+ export function getGatewayUrl() {
85
+ return loadAgentConfig().gateway_url;
86
+ }
package/dist/lib/api.d.ts CHANGED
@@ -57,6 +57,15 @@ export interface IntegrityScore {
57
57
  verified_traces: number;
58
58
  violation_count: number;
59
59
  integrity_score: number;
60
+ /**
61
+ * MNE-596 — count of traces AAP structurally flagged but were reclassified
62
+ * as a correctly policy-refused attack (agreeing with AIP's `clear`
63
+ * verdict). Logged/visible here for transparency; NOT subtracted from
64
+ * `verified_traces` (they already count as verified) and NOT included in
65
+ * `violation_count`. Optional for backward compatibility with older API
66
+ * deployments that don't yet emit this field.
67
+ */
68
+ bounded_refusal_count?: number;
60
69
  }
61
70
  /**
62
71
  * GET /v1/traces row — components/schemas/APTrace.
@@ -103,6 +112,17 @@ export interface Trace {
103
112
  verification: {
104
113
  verified: boolean;
105
114
  violations: TraceViolation[];
115
+ /**
116
+ * MNE-596 — when set to "bounded_refusal", AAP structurally flagged a
117
+ * violation (`verified: false`) but the observer's DDR cross-check found
118
+ * AIP independently confirmed `clear` AND no bounded action was ever
119
+ * executed (a pure refusal, no tool call) — i.e. the agent was attacked
120
+ * and correctly declined. This is NOT a real violation: it must not
121
+ * render as `[VIOLATION]` and must not count against integrity_score
122
+ * (see GET /v1/integrity/:id's `bounded_refusal_count`). `violations`
123
+ * stays populated for audit/debugging — only the classification changes.
124
+ */
125
+ classification?: "bounded_refusal" | string;
106
126
  } | null;
107
127
  created_at?: string;
108
128
  }
@@ -262,7 +282,11 @@ export interface OrgFleetAgent {
262
282
  owner_email: string | null;
263
283
  last_seen: string | null;
264
284
  created_at: string;
265
- integrity_score: number;
285
+ /** MNE-460: renamed from `integrity_score` to disambiguate from reputation's
286
+ * `integrity_ratio` (a different metric). Trace-verification ratio:
287
+ * verified/total traces, lifetime, 0-1. Type-only in this CLI — never
288
+ * displayed by `fleetToRow`, so this rename carries zero behavioral risk. */
289
+ trace_verification_ratio: number;
266
290
  coverage_ratio: number;
267
291
  latest_verdict: string | null;
268
292
  active_drift_alerts: number;
@@ -329,6 +353,24 @@ export declare function claimAgent(agentId: string, body: {
329
353
  }, opts?: {
330
354
  idempotencyKey?: string;
331
355
  }): Promise<ClaimAgentResult>;
356
+ export interface MoveAgentResult {
357
+ moved: boolean;
358
+ agent_id: string;
359
+ from_org_id: string | null;
360
+ to_org_id: string | null;
361
+ }
362
+ /**
363
+ * POST /v1/agents/:id/move — role-based relocation between orgs.
364
+ *
365
+ * The role-based counterpart to claimAgent's re-claim: the server requires
366
+ * the caller to be an owner/admin of BOTH the agent's current org and
367
+ * `destOrgId` — no hash proof / agent key involved. Moving to the current
368
+ * org is an idempotent no-op (`moved: false`). An Idempotency-Key is minted
369
+ * (held across the 401-refresh retry) like every mutation.
370
+ */
371
+ export declare function moveAgent(agentId: string, destOrgId: string, opts?: {
372
+ idempotencyKey?: string;
373
+ }): Promise<MoveAgentResult>;
332
374
  /**
333
375
  * GET /v1/auth/me/personal-org — accessor for the user's personal org.
334
376
  * Idempotent: lazily provisions for legacy accounts that pre-date the
@@ -1044,4 +1086,83 @@ export interface RecipeReportResult {
1044
1086
  related_recipe_id: string;
1045
1087
  }
1046
1088
  export declare function reportRecipeFnFp(recipeId: string, input: RecipeReportInput): Promise<RecipeReportResult>;
1089
+ /**
1090
+ * One row from GET /v1/orgs/:org_id/usage/by-person — consumption grouped by
1091
+ * (person, provider, model).
1092
+ *
1093
+ * Field names mirror the wire contract in the API's `openapi.json` exactly.
1094
+ * They are NOT invented here: the first version of this client was written
1095
+ * against a guessed shape (`rows`/`tokens_consumed`/`requests` on a
1096
+ * `/v1/orgs/:id/usage` path) and every one of its tests passed because they
1097
+ * all mocked the response. `/v1/orgs/:id/usage` is a DIFFERENT, pre-existing
1098
+ * endpoint that returns per-DAY operational metrics
1099
+ * (`{ period: 'daily', days, data }`), so the command rendered `undefined`
1100
+ * against the real server. Any change here must be checked against
1101
+ * `openapi.json`, not against these types.
1102
+ *
1103
+ * Token and request totals are 64-bit integers serialized as strings.
1104
+ * Never coerce to Number — values from large orgs can exceed Number.MAX_SAFE_INTEGER.
1105
+ */
1106
+ export interface OrgUsageRow {
1107
+ /** Null for unattributed consumption — a real row, never dropped. */
1108
+ user_id: string | null;
1109
+ display_name: string;
1110
+ email: string | null;
1111
+ membership_state: string;
1112
+ /** "Unknown" when the event carried no provider. */
1113
+ provider: string;
1114
+ /** "Unknown" when the event carried no model. */
1115
+ model: string;
1116
+ /** 64-bit integer as string. */
1117
+ tokens_in: string;
1118
+ /** 64-bit integer as string. */
1119
+ tokens_out: string;
1120
+ /** 64-bit integer as string. */
1121
+ request_count: string;
1122
+ }
1123
+ /** Totals over the COMPLETE filtered result, computed before pagination. */
1124
+ export interface OrgUsageTotals {
1125
+ tokens_in: string;
1126
+ tokens_out: string;
1127
+ request_count: string;
1128
+ attributed_request_count: string;
1129
+ unattributed_request_count: string;
1130
+ /** Fraction 0–1, already rounded by the server. */
1131
+ attribution_rate: number;
1132
+ }
1133
+ export interface OrgUsageResponse {
1134
+ /** Window start, inclusive (ISO-8601, UTC midnight). */
1135
+ from: string;
1136
+ /** Window end, exclusive (ISO-8601, UTC midnight). */
1137
+ to: string;
1138
+ /** Earliest attributed event for the org, or null before the first one. */
1139
+ collection_started_at: string | null;
1140
+ data: OrgUsageRow[];
1141
+ totals: OrgUsageTotals;
1142
+ /** Opaque keyset cursor, or null on the last page. */
1143
+ next_cursor: string | null;
1144
+ }
1145
+ /** The only windows the endpoint accepts (`days` enum in `openapi.json`). */
1146
+ export declare const USAGE_ALLOWED_DAYS: readonly [7, 30, 90];
1147
+ export type UsageDays = (typeof USAGE_ALLOWED_DAYS)[number];
1148
+ export interface OrgUsageQuery {
1149
+ days?: UsageDays;
1150
+ personId?: string;
1151
+ provider?: string;
1152
+ model?: string;
1153
+ limit?: number;
1154
+ cursor?: string;
1155
+ }
1156
+ /**
1157
+ * GET /v1/orgs/:org_id/usage/by-person — consumption by person/provider/model.
1158
+ *
1159
+ * Role-gated to owner/admin/auditor; member and viewer get a real 403.
1160
+ *
1161
+ * Gated by USAGE_ATTRIBUTION_API_ENABLED, which returns **404, not 403**, when
1162
+ * off — deliberately, so a disabled endpoint is indistinguishable from one that
1163
+ * does not exist. That makes 404 ambiguous by design (flag off OR unknown org
1164
+ * OR no access), so no `notFoundLabel` is passed here: a caller must not tell
1165
+ * the user "org not found" on what is most often just the flag being off.
1166
+ */
1167
+ export declare function getOrgUsage(orgId: string, opts?: OrgUsageQuery): Promise<OrgUsageResponse>;
1047
1168
  export {};