create-metamynd-agent 0.14.22 → 0.14.24

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.
Files changed (2) hide show
  1. package/index.mjs +46 -12
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -68,7 +68,11 @@ const GUARD_PKG = '@metamynd/agentsafe-guard';
68
68
  // 0.21.0: guardTool() settles the hold an allowed call was granted (captured when the tool runs, released when it cannot
69
69
  // have), guard.void() releases one signed, and a gated tool's .resume(escalationId, args) runs an approved escalation once.
70
70
  // Required: the scaffold's npm run resume uses .resume().
71
- const GUARD_VERSION = '^0.21.0';
71
+ // 0.22.0: a concurrent resume of one escalation in a process is refused AUTHORIZATION_IN_USE, and a hold minted for an
72
+ // authorize whose answer never arrived is found by nonce and released. Required: npm run resume relies on the lock.
73
+ // 0.23.0: the guard derives the same risk floor as the gate (owner tier + a payment at or above the owner's share of the
74
+ // per-transaction cap is HIGH), so a local check and the gate agree on a large payment's review. No template change.
75
+ const GUARD_VERSION = '^0.23.0';
72
76
  /** The harness entry point's config load, shared by both harness templates: a fresh clone has no
73
77
  * agent.metamynd.json (it is gitignored), so say what to do instead of a bare ENOENT (BR-004). */
74
78
  function harnessConfigLoad() {
@@ -148,7 +152,8 @@ const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
148
152
  // 0.24.0: reportOutcome() signs a reportId and an occurrence count (aggregated refusals, a durable spool).
149
153
  // 0.25.0: honourApprovals — a Service without requireAuthorization (the value-less gateway) still runs an owner-approved
150
154
  // escalation, exactly once (MAGP §8.7.18). Required: the non-financial gateway sets it and asserts guard.honoursApprovals.
151
- const MCP_GUARD_VERSION = '^0.25.0';
155
+ // 0.26.0: a gateway judges the same derived risk floor as the gate (owner tier + amount share of the cap).
156
+ const MCP_GUARD_VERSION = '^0.26.0';
152
157
  /** A DID as it may appear inside a generated string literal (the gateway's allowedAgents pin): no quote, backslash or space. */
153
158
  const SAFE_DID = /^did:[a-z0-9]+:[A-Za-z0-9._:%-]+$/;
154
159
  const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
@@ -180,7 +185,8 @@ const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
180
185
  // 0.22.0: depends on agentsafe-mcp-guard ^0.23.0.
181
186
  // 0.23.0: refusals aggregated with their count, and `reportSpool` (both hosted templates set it); depends on agentsafe-mcp-guard ^0.24.0.
182
187
  // 0.24.0: depends on agentsafe-mcp-guard ^0.25.0 (one guard in the tree with MCP_GUARD_VERSION above). No template change.
183
- const GATEWAY_VERSION = '^0.24.0';
188
+ // 0.25.0: depends on agentsafe-mcp-guard ^0.26.0 (one guard in the tree). No template change.
189
+ const GATEWAY_VERSION = '^0.25.0';
184
190
 
185
191
  /** Appended to every scaffolded gateway server: give hold settlements still running a bounded moment on shutdown. */
186
192
  const DRAIN_ON_SHUTDOWN = `
@@ -267,6 +273,11 @@ ${c.b('Options')}
267
273
  --max-amount <n> Total mandate budget (default 10000)
268
274
  --currency <cur> Currency (default USD)
269
275
  --merchants <a,b> Allowed merchants, comma-separated (optional)
276
+ --risk-tier <level> The owner's risk floor for the action: low | medium | high | critical. The agent's own
277
+ riskLevel can raise it, never lower it (high = every call is reviewed by a person).
278
+ --amount-review <f> A single payment at or above this share of the per-transaction cap is high risk (reviewed),
279
+ whatever the agent claims. A fraction in (0, 1] (default 0.7), or "off". Without --merchants,
280
+ the first payment to each merchant is reviewed too.
270
281
  --byok Bring-your-own-key: generate the keypair locally, provision + prove control
271
282
  (MetaMynd never sees the private key). Overridden by --public-key.
272
283
  --public-key <hex> BYOK with a key you already hold (SPKI/raw hex); you prove control yourself
@@ -1024,7 +1035,7 @@ async function resolveHostedRulePack(base, key, { financial, perTxnMax, maxAmoun
1024
1035
  * dashboard registration shows its own dialog before setting the flag); see the printed message
1025
1036
  * either way.
1026
1037
  */
1027
- async function registerGatewayCounterparty(base, token, did, label, purpose = 'claim', privateKeyHex = null) {
1038
+ async function registerGatewayCounterparty(base, token, did, label, purpose = 'claim', privateKeyHex = null, agents = null) {
1028
1039
  try {
1029
1040
  // The gateway accepts its own registration (MAGP §8.7.6): ask for a challenge naming this owner, sign it with the gateway's
1030
1041
  // key (generated here, so this process holds it), and register with that proof. A DID is never registered on its text.
@@ -1044,7 +1055,9 @@ async function registerGatewayCounterparty(base, token, did, label, purpose = 'c
1044
1055
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
1045
1056
  // `report` (a non-financial gateway, which never claims): it may only report to this owner, and it changes nothing about
1046
1057
  // who may claim their holds, so it needs no enforcement confirmation (MAGP §8.7.6, §16.4).
1047
- body: JSON.stringify({ did, label, purpose, ...(proof ? { proof } : {}), ...(purpose === 'claim' ? { confirmEnforcementChange: true } : {}) }),
1058
+ // `agents` (report only): the agents this gateway serves — the issuer lets it claim an approved value-less hold of THOSE
1059
+ // agents only, never another agent of the same owner (MAGP §8.7.6).
1060
+ body: JSON.stringify({ did, label, purpose, ...(proof ? { proof } : {}), ...(agents ? { agents } : {}), ...(purpose === 'claim' ? { confirmEnforcementChange: true } : {}) }),
1048
1061
  });
1049
1062
  const json = await res.json().catch(() => null);
1050
1063
  if (!res.ok || json?.success === false) return { ok: false, message: json?.message ?? `HTTP ${res.status}` };
@@ -1168,7 +1181,7 @@ function resumeBlock(tool) {
1168
1181
  // npm start saves an escalated call's arguments to ./escalations/<id>.json. Approve it in the dashboard (AgentSafe ->
1169
1182
  // Escalations), then resume it: ${tool}.resume() waits for the decision and runs the tool once, under the authorization
1170
1183
  // the approval minted. Your own agent does the same with the escalationId from the refusal (e.governance.escalationId).
1171
- import { mkdirSync as __mkdir, writeFileSync as __write, readFileSync as __read, rmSync as __rm } from 'node:fs';
1184
+ import { mkdirSync as __mkdir, writeFileSync as __write, readFileSync as __read, rmSync as __rm, renameSync as __rename } from 'node:fs';
1172
1185
  const ESCALATIONS = new URL('./escalations/', import.meta.url);
1173
1186
  const escalationFile = (id) => {
1174
1187
  if (!/^[A-Za-z0-9_-]{1,80}$/.test(String(id ?? ''))) throw new Error('not an escalation id: ' + id);
@@ -1180,20 +1193,28 @@ function saveEscalation(id, args) {
1180
1193
  }
1181
1194
  if (process.argv[2] === '--resume') {
1182
1195
  const id = process.argv[3];
1183
- let saved;
1184
- try { saved = JSON.parse(__read(escalationFile(id), 'utf8')); } catch {
1185
- console.error(' No saved escalation "' + (id ?? '') + '". Run npm start first, then: npm run resume -- <escalationId>');
1196
+ // Take the saved call ATOMICALLY (a rename): a second npm run resume of the same escalation started meanwhile finds
1197
+ // nothing to take, so two processes cannot both run it. Put back if it is still undecided; removed once it is final.
1198
+ let saved, taken;
1199
+ try {
1200
+ const file = escalationFile(id);
1201
+ taken = new URL(file.href + '.resuming');
1202
+ __rename(file, taken);
1203
+ saved = JSON.parse(__read(taken, 'utf8'));
1204
+ } catch {
1205
+ console.error(' No saved escalation "' + (id ?? '') + '" to resume (or another npm run resume is running it). Run npm start first, then: npm run resume -- <escalationId>');
1186
1206
  process.exit(1);
1187
1207
  }
1188
1208
  console.log(' waiting for your decision on ' + id + ' (approve it in the dashboard: AgentSafe -> Escalations) ...');
1189
1209
  try {
1190
1210
  const result = await ${tool}.resume(id, saved);
1191
- __rm(escalationFile(id), { force: true }); // used: a second resume has nothing to run
1211
+ __rm(taken, { force: true }); // used: a second resume has nothing to run
1192
1212
  console.log('\\x1b[32m RAN\\x1b[0m approved, and run exactly once: ' + JSON.stringify(result));
1193
1213
  } catch (e) {
1194
1214
  const g = e.governance ?? {};
1195
1215
  // A final answer (rejected, already used, expired) retires the saved call; still pending or unreachable keeps it.
1196
- if (g.decision === 'block' && g.reasonCode !== 'GATE_UNREACHABLE') __rm(escalationFile(id), { force: true });
1216
+ if (g.decision === 'block' && g.reasonCode !== 'GATE_UNREACHABLE') __rm(taken, { force: true });
1217
+ else __rename(taken, escalationFile(id)); // still undecided, or the gate was unreachable: keep it for the next try
1197
1218
  console.log('\\x1b[31m NOT RUN\\x1b[0m ' + (g.reasonCode ?? e.message) + (g.status ? ' (' + g.status + ')' : ''));
1198
1219
  process.exitCode = 1;
1199
1220
  }
@@ -4244,6 +4265,13 @@ async function main() {
4244
4265
  Array.isArray(fileConfig?.merchants) ? fileConfig.merchants.join(',') : '',
4245
4266
  );
4246
4267
  const merchants = String(merchantsRaw).split(',').map((s) => s.trim()).filter(Boolean);
4268
+ // Risk the agent cannot lower (MAGP 6.4.3): the owner's floor, and the amount share of the cap that is reviewed.
4269
+ const riskTier = typeof args['risk-tier'] === 'string' ? args['risk-tier'].trim().toLowerCase() : undefined;
4270
+ if (riskTier !== undefined && !['low', 'medium', 'high', 'critical'].includes(riskTier)) fail('--risk-tier must be low, medium, high or critical');
4271
+ const amountReviewRaw = typeof args['amount-review'] === 'string' ? args['amount-review'].trim().toLowerCase() : undefined;
4272
+ const amountShare = amountReviewRaw === undefined ? undefined : amountReviewRaw === 'off' ? false : Number(amountReviewRaw);
4273
+ if (typeof amountShare === 'number' && !(amountShare > 0 && amountShare <= 1)) fail('--amount-review must be a fraction in (0, 1], or "off"');
4274
+ const riskFields = { ...(riskTier ? { riskTier } : {}), ...(amountShare !== undefined && financial ? { riskSignals: { amountShare } } : {}) };
4247
4275
 
4248
4276
  // BYOK: --byok generates a keypair on THIS machine (MetaMynd never sees the private key) —
4249
4277
  // either locally in this process (default) or, opt-in, via an already-running agentsafe-signer
@@ -4314,6 +4342,7 @@ async function main() {
4314
4342
  network: 'testnet',
4315
4343
  ...(financial ? { currency, maxAmount, perTxnMax } : {}),
4316
4344
  merchants,
4345
+ ...riskFields,
4317
4346
  ...(publicKey ? { publicKey } : {}),
4318
4347
  ...sopFields,
4319
4348
  };
@@ -4322,6 +4351,11 @@ async function main() {
4322
4351
  if (!config?.agentDid) fail('Provisioning did not return a config with an agentDid.');
4323
4352
  console.log(` ${c.green('✓')} agent DID ${c.b(config.agentDid)}`);
4324
4353
  if (config.standards?.length) console.log(` ${c.green('✓')} enforced Standards: ${config.standards.join(', ')}`);
4354
+ if (financial && merchants.length === 0) {
4355
+ console.log(c.dim(' no --merchants list: the FIRST payment to each merchant is held for your review (a risk the agent cannot'));
4356
+ console.log(c.dim(' lower). Pass --merchants a,b to pre-approve them, or register them as payees in the dashboard.'));
4357
+ }
4358
+ if (riskTier) console.log(` ${c.green('✓')} risk floor: ${riskTier} (the agent's own claim can raise it, never lower it)`);
4325
4359
 
4326
4360
  // 3b. BYOK: prove control of the key (verify-key), else the gate blocks with AGENT_KEY_UNVERIFIED.
4327
4361
  if (daemonPublicKeyHex) {
@@ -4368,7 +4402,7 @@ async function main() {
4368
4402
  const gatewayDid = buildDidKey(rawPublicKeyFromSpkiHex(gwKeypair.publicKeyHex));
4369
4403
  gatewayIdentity = { did: gatewayDid, keyHex: gwKeypair.privateKeyHex };
4370
4404
  console.log(c.dim(`\n → registering the gateway's identity as a trusted counterparty (${financial ? "so it can claim this agent's holds and report to you" : 'so its reports reach your Activity Log'}) …`));
4371
- const reg = await registerGatewayCounterparty(base, token, gatewayDid, `${scope}-gateway`, financial ? 'claim' : 'report', gwKeypair.privateKeyHex);
4405
+ const reg = await registerGatewayCounterparty(base, token, gatewayDid, `${scope}-gateway`, financial ? 'claim' : 'report', gwKeypair.privateKeyHex, financial ? null : [config.agentDid]);
4372
4406
  if (reg.ok) {
4373
4407
  console.log(` ${c.green('✓')} gateway DID ${c.b(gatewayDid)} — ${reg.message}`);
4374
4408
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-metamynd-agent",
3
- "version": "0.14.22",
3
+ "version": "0.14.24",
4
4
  "description": "Scaffold a MetaMynd/AgentSafe-governed AI agent in one command — logs in, provisions the agent (identity + mandate + SOP + Standards) in a single call, writes agent.metamynd.json plus a runnable agent + separate tool-gateway process that closes direct-call, confused-deputy, replay, and cumulative-spend bypasses. --harness scaffolds a free, local, zero-network governance harness instead.",
5
5
  "type": "module",
6
6
  "bin": {