create-metamynd-agent 0.14.18 → 0.14.22

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 +131 -34
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -65,7 +65,10 @@ const GUARD_PKG = '@metamynd/agentsafe-guard';
65
65
  // authorize request of its choosing, MAGP §8.2). Required for every scaffolded agent.
66
66
  // 0.20.0: guard.capture() signs as the agent (MAGP-SETTLE-v1) — a hold nobody claimed is settled only by its agent, a
67
67
  // counterparty the owner registered, or (an open testnet owner) anyone (MAGP §8.7.4).
68
- const GUARD_VERSION = '^0.20.0';
68
+ // 0.21.0: guardTool() settles the hold an allowed call was granted (captured when the tool runs, released when it cannot
69
+ // have), guard.void() releases one signed, and a gated tool's .resume(escalationId, args) runs an approved escalation once.
70
+ // Required: the scaffold's npm run resume uses .resume().
71
+ const GUARD_VERSION = '^0.21.0';
69
72
  /** The harness entry point's config load, shared by both harness templates: a fresh clone has no
70
73
  * agent.metamynd.json (it is gitignored), so say what to do instead of a bare ENOENT (BR-004). */
71
74
  function harnessConfigLoad() {
@@ -143,7 +146,9 @@ const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
143
146
  // 0.23.0: acceptCounterpartyChallenge() — a Service signs its acceptance of an owner's registration (MAGP §8.7.6). No
144
147
  // template change; required with GATEWAY_VERSION below so the gateway never resolves a second guard of its own.
145
148
  // 0.24.0: reportOutcome() signs a reportId and an occurrence count (aggregated refusals, a durable spool).
146
- const MCP_GUARD_VERSION = '^0.24.0';
149
+ // 0.25.0: honourApprovals — a Service without requireAuthorization (the value-less gateway) still runs an owner-approved
150
+ // 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';
147
152
  /** A DID as it may appear inside a generated string literal (the gateway's allowedAgents pin): no quote, backslash or space. */
148
153
  const SAFE_DID = /^did:[a-z0-9]+:[A-Za-z0-9._:%-]+$/;
149
154
  const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
@@ -174,7 +179,8 @@ const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
174
179
  // 0.21.0: per-route allowedAgents; depends on agentsafe-mcp-guard ^0.22.0.
175
180
  // 0.22.0: depends on agentsafe-mcp-guard ^0.23.0.
176
181
  // 0.23.0: refusals aggregated with their count, and `reportSpool` (both hosted templates set it); depends on agentsafe-mcp-guard ^0.24.0.
177
- const GATEWAY_VERSION = '^0.23.0';
182
+ // 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';
178
184
 
179
185
  /** Appended to every scaffolded gateway server: give hold settlements still running a bounded moment on shutdown. */
180
186
  const DRAIN_ON_SHUTDOWN = `
@@ -1134,12 +1140,66 @@ function keepConfigAndFail(outDir, config, why) {
1134
1140
  * guard.escalationStatus(id) for the authorization and presents it to run the action (MAGP §8.7.18). Generated code: it
1135
1141
  * reads `g`, the refusal's governance verdict, and `dim`, both in scope at every call site.
1136
1142
  */
1137
- function escalateNote(sandbox) {
1138
- return sandbox
1139
- ? `console.log(dim(' nobody can approve it here: the shared sandbox has no owner you can sign in as.'));
1140
- console.log(dim(' Provision your own agent to approve one in your dashboard.'));`
1141
- : `console.log(dim(' not a failure: it waits in your dashboard, under Escalations' + (g.escalationId ? ' (' + g.escalationId + ')' : '') + '.'));
1143
+ function escalateNote(sandbox, argsExpr = null) {
1144
+ if (sandbox) {
1145
+ return `console.log(dim(' nobody can approve it here: the shared sandbox has no owner you can sign in as.'));
1146
+ console.log(dim(' Provision your own agent to approve one in your dashboard.'));`;
1147
+ }
1148
+ if (!argsExpr) {
1149
+ return `console.log(dim(' not a failure: it waits in your dashboard, under Escalations' + (g.escalationId ? ' (' + g.escalationId + ')' : '') + '.'));
1142
1150
  console.log(dim(' Once you approve it, guard.escalationStatus(id) gives the agent the authorization to run it.'));`;
1151
+ }
1152
+ // Resumable (0.14.21; pre-beta rerun 3, E-1): the call is saved so `npm run resume -- <id>` can run it, once approved.
1153
+ return `console.log(dim(' not a failure: it waits in your dashboard, under Escalations' + (g.escalationId ? ' (' + g.escalationId + ')' : '') + '.'));
1154
+ if (g.escalationId) {
1155
+ saveEscalation(g.escalationId, ${argsExpr});
1156
+ console.log(dim(' Approve it there, then run it - exactly once - with: npm run resume -- ' + g.escalationId));
1157
+ }`;
1158
+ }
1159
+
1160
+ /**
1161
+ * The resume path a resumable scaffold carries (0.14.21; pre-beta rerun 3, E-1 — resuming used to mean writing ~15 lines
1162
+ * by hand). `npm start` saves an escalated call's arguments to ./escalations/<id>.json; `npm run resume -- <id>` hands them
1163
+ * to the gated tool's `.resume()` (agentsafe-guard >= 0.21.0), which waits for the owner's decision and then runs the tool
1164
+ * exactly once under the authorization the approval minted. Generated code; `tool` is the gated tool's variable name.
1165
+ */
1166
+ function resumeBlock(tool) {
1167
+ return `// --- Escalations: \`npm run resume -- <escalationId>\` runs a call your owner APPROVED, exactly once. ---
1168
+ // npm start saves an escalated call's arguments to ./escalations/<id>.json. Approve it in the dashboard (AgentSafe ->
1169
+ // Escalations), then resume it: ${tool}.resume() waits for the decision and runs the tool once, under the authorization
1170
+ // 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';
1172
+ const ESCALATIONS = new URL('./escalations/', import.meta.url);
1173
+ const escalationFile = (id) => {
1174
+ if (!/^[A-Za-z0-9_-]{1,80}$/.test(String(id ?? ''))) throw new Error('not an escalation id: ' + id);
1175
+ return new URL(id + '.json', ESCALATIONS);
1176
+ };
1177
+ function saveEscalation(id, args) {
1178
+ __mkdir(ESCALATIONS, { recursive: true });
1179
+ __write(escalationFile(id), JSON.stringify(args, null, 2));
1180
+ }
1181
+ if (process.argv[2] === '--resume') {
1182
+ 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>');
1186
+ process.exit(1);
1187
+ }
1188
+ console.log(' waiting for your decision on ' + id + ' (approve it in the dashboard: AgentSafe -> Escalations) ...');
1189
+ try {
1190
+ const result = await ${tool}.resume(id, saved);
1191
+ __rm(escalationFile(id), { force: true }); // used: a second resume has nothing to run
1192
+ console.log('\\x1b[32m RAN\\x1b[0m approved, and run exactly once: ' + JSON.stringify(result));
1193
+ } catch (e) {
1194
+ const g = e.governance ?? {};
1195
+ // 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 });
1197
+ console.log('\\x1b[31m NOT RUN\\x1b[0m ' + (g.reasonCode ?? e.message) + (g.status ? ' (' + g.status + ')' : ''));
1198
+ process.exitCode = 1;
1199
+ }
1200
+ process.exit();
1201
+ }
1202
+ `;
1143
1203
  }
1144
1204
 
1145
1205
  function exampleIndexNoGateway(scope, perTxnMax, currency, merchant, sandbox = false) {
@@ -1195,6 +1255,7 @@ const gatedRaiseOwnLimit = guard.guardTool(
1195
1255
  }),
1196
1256
  );
1197
1257
 
1258
+ ${sandbox ? '' : resumeBlock('gatedBookFlight')}
1198
1259
  const dim = (t) => '\\x1b[2m' + t + '\\x1b[0m';
1199
1260
  const bold = (t) => '\\x1b[1m' + t + '\\x1b[0m';
1200
1261
  const rule = (n) => ' ' + '-'.repeat(n);
@@ -1253,7 +1314,7 @@ async function attempt(n, intent, args, tool = gatedBookFlight) {
1253
1314
  if (g.decision === 'escalate') {
1254
1315
  console.log('\\x1b[33m ESCALATED\\x1b[0m held for a human - ' + g.reasonCode);
1255
1316
  console.log(dim(' ' + why));
1256
- ${escalateNote(sandbox)}
1317
+ ${escalateNote(sandbox, sandbox ? null : 'args')}
1257
1318
  } else {
1258
1319
  console.log('\\x1b[31m BLOCKED\\x1b[0m ' + (g.reasonCode ?? 'refused'));
1259
1320
  console.log(dim(' ' + why));
@@ -1400,6 +1461,7 @@ const gatedRaiseOwnLimit = guard.guardTool(
1400
1461
  }),
1401
1462
  );
1402
1463
 
1464
+ ${resumeBlock('gatedBookFlight')}
1403
1465
  const dim = (t) => '\\x1b[2m' + t + '\\x1b[0m';
1404
1466
  const bold = (t) => '\\x1b[1m' + t + '\\x1b[0m';
1405
1467
  const rule = (n) => ' ' + '-'.repeat(n);
@@ -1461,7 +1523,7 @@ async function attempt(n, intent, args, tool = gatedBookFlight) {
1461
1523
  if (g.decision === 'escalate') {
1462
1524
  console.log('\\x1b[33m ESCALATED\\x1b[0m held for a human - ' + g.reasonCode);
1463
1525
  console.log(dim(' ' + why));
1464
- ${escalateNote(false)}
1526
+ ${escalateNote(false, 'args')}
1465
1527
  } else {
1466
1528
  console.log('\\x1b[31m BLOCKED\\x1b[0m ' + (g.reasonCode ?? 'refused'));
1467
1529
  console.log(dim(' ' + why));
@@ -1615,7 +1677,7 @@ const gatedChangeOwnPermissions = guard.guardTool(
1615
1677
  changeOwnPermissions,
1616
1678
  (a) => ({ context: a }),
1617
1679
  );
1618
-
1680
+ ${!sandbox ? '\n' + resumeBlock('gatedAction') : ''}
1619
1681
  const STEPS = ${JSON.stringify(steps, null, 2)};
1620
1682
  const INPUTS = ${JSON.stringify(demo.inputs)};
1621
1683
  const NOT_STAGED = ${JSON.stringify(demo.notDemonstrated)};
@@ -1636,7 +1698,7 @@ async function attempt(n, total, step) {
1636
1698
  got = g.decision ?? 'error';
1637
1699
  if (g.decision === 'escalate') {
1638
1700
  console.log('\\x1b[33m ESCALATED\\x1b[0m held for a human - ' + g.reasonCode);
1639
- ${escalateNote(sandbox)}
1701
+ ${escalateNote(sandbox, !sandbox ? 'step.context' : null)}
1640
1702
  } else {
1641
1703
  console.log('\\x1b[31m BLOCKED\\x1b[0m ' + (g.reasonCode ?? e.message));
1642
1704
  console.log(dim(g.eventId ? ' decided by the MetaMynd gate - your tool never ran.' : ' decided right here from your signed rules (no network call), and reported for audit - your tool never ran.'));
@@ -1759,15 +1821,20 @@ const identity = JSON.parse(readFileSync(new URL('./service.metamynd.json', impo
1759
1821
  // whose owner granted it this same action would run your tool with your credentials (refused AGENT_NOT_ADMITTED instead).
1760
1822
  // Add a DID here only for another agent you mean this gateway to act for. gatewayOwnerPrincipal binds it to YOU, the owner of
1761
1823
  // its credentials: an agent listed here that another principal owns is refused anyway (GATEWAY_OWNER_MISMATCH).
1762
- const guard = createMcpGuard({ serviceDid: identity.serviceDid, serviceKey: identity.serviceKey ?? undefined, issuerApi: MAGP_API, requireAuthorization: false, policyPublicKey: '${policyKey}', allowedAgents: ['${agentDid}'], gatewayOwnerPrincipal: '${ownerPrincipal}' });
1763
- // A guard below 0.20.0 ignores allowedAgents and serves every agent: refuse to start on one (a stale lockfile, say).
1824
+ // honourApprovals: with requireAuthorization off this gateway claims nothing — except an escalated call that presents the
1825
+ // authorization your APPROVAL minted (Dashboard -> Escalations). That one it claims as an approval: the issuer grants it
1826
+ // once, only for a hold a person approved, for this agent and these values, so the approved call runs here exactly once.
1827
+ const guard = createMcpGuard({ serviceDid: identity.serviceDid, serviceKey: identity.serviceKey ?? undefined, issuerApi: MAGP_API, requireAuthorization: false, policyPublicKey: '${policyKey}', allowedAgents: ['${agentDid}'], gatewayOwnerPrincipal: '${ownerPrincipal}', honourApprovals: true });
1828
+ // A guard below 0.22.0 cannot enforce both pins (below 0.20.0 it ignores allowedAgents and serves every agent; 0.22.0 added gatewayOwnerPrincipal): refuse to start on one (a stale lockfile, say).
1764
1829
  if (!Array.isArray(guard.allowedAgents) || !guard.gatewayOwnerPrincipal) throw new Error('this gateway needs @metamynd/agentsafe-mcp-guard >= 0.22.0 to enforce allowedAgents and gatewayOwnerPrincipal - run npm install');
1830
+ // A guard below 0.25.0 ignores honourApprovals, and an approved escalation would stay escalated here: refuse to start on one.
1831
+ if (guard.honoursApprovals !== true) throw new Error('this gateway needs @metamynd/agentsafe-mcp-guard >= 0.25.0 to run approved escalations - run npm install');
1765
1832
 
1766
1833
  const gateway = createHttpGateway({
1767
1834
  guard,
1768
1835
  routes,
1769
1836
  // Every request this gateway runs or refuses is reported, signed with the identity above, to your Activity Log (MAGP
1770
- // 16.4). It claims nothing, so without this the issuer would never see what it executed - or that another agent tried.
1837
+ // 16.4). It claims only an approved escalation, so without this the issuer would never see what it executed - or that another agent tried.
1771
1838
  reportOutcomes: true,
1772
1839
  // A report the issuer could not take (it was down, say) is kept here and re-sent, so nothing it saw is lost. Gitignored.
1773
1840
  reportSpool: './reports.spool.jsonl',
@@ -1895,10 +1962,18 @@ Replace \`performAction()\` in \`server.mjs\` with your implementation and put a
1895
1962
 
1896
1963
  /** The README for a non-financial hosted scaffold. `withGateway` selects the two-process shape. */
1897
1964
  /** The generated README's "Change the rules" body: the dashboard path, or — for the shared sandbox agent — how to get an agent whose rules you can change. */
1898
- function changeRulesSection(sandbox) {
1965
+ function changeRulesSection(sandbox, resumable = false) {
1899
1966
  return sandbox
1900
1967
  ? `${SANDBOX_RULES_FIXED} An \`escalate\` verdict is held for an owner to approve; poll \`guard.escalationStatus(id)\`.`
1901
- : `Edit the agent's SOPs in the dashboard (${SOPS_PATH}). The agent's behaviour changes live —
1968
+ : resumable
1969
+ ? `Edit the agent's SOPs in the dashboard (${SOPS_PATH}). The agent's behaviour changes live —
1970
+ no redeploy.
1971
+
1972
+ An \`escalate\` verdict is held for an owner to approve. \`npm start\` saves an escalated call and prints its id; approve it
1973
+ in the dashboard (AgentSafe → Escalations), then \`npm run resume -- <escalationId>\` runs it exactly once under the
1974
+ authorization the approval minted. In your own agent that is one call: \`await gatedTool.resume(escalationId, sameArgs)\`
1975
+ (\`@metamynd/agentsafe-guard\` ≥ 0.21.0) — pass the SAME arguments; the approval is bound to that request.`
1976
+ : `Edit the agent's SOPs in the dashboard (${SOPS_PATH}). The agent's behaviour changes live —
1902
1977
  no redeploy. An \`escalate\` verdict is held for an owner to approve; poll \`guard.escalationStatus(id)\`.`;
1903
1978
  }
1904
1979
 
@@ -1981,7 +2056,7 @@ ${clonedFreshSection(daemonKey)}
1981
2056
 
1982
2057
  ## Change the rules
1983
2058
 
1984
- ${changeRulesSection(sandbox)}
2059
+ ${changeRulesSection(sandbox, !sandbox)}
1985
2060
 
1986
2061
  ## What this is not
1987
2062
 
@@ -2000,7 +2075,7 @@ Full integration guide: \`docs/integration/INTEGRATE-WITH-METAMYND.md\`.
2000
2075
  `;
2001
2076
  }
2002
2077
 
2003
- function examplePackageJson(slug, neutral = false) {
2078
+ function examplePackageJson(slug, neutral = false, resumable = false) {
2004
2079
  return JSON.stringify(
2005
2080
  {
2006
2081
  name: slug,
@@ -2010,7 +2085,7 @@ function examplePackageJson(slug, neutral = false) {
2010
2085
  // `verify` is scaffolded in because governance that lives only in a dashboard is a
2011
2086
  // thing someone has to remember to look at. As a build step it is a control: a change
2012
2087
  // that widens this agent's authority fails `npm test`.
2013
- scripts: { start: 'node index.mjs', test: neutral ? 'agentsafe-guard verify --context ./verify-context.json' : 'agentsafe-guard verify' },
2088
+ scripts: { start: 'node index.mjs', ...(resumable ? { resume: 'node index.mjs --resume' } : {}), test: neutral ? 'agentsafe-guard verify --context ./verify-context.json' : 'agentsafe-guard verify' },
2014
2089
  dependencies: { [GUARD_PKG]: GUARD_VERSION },
2015
2090
  },
2016
2091
  null,
@@ -2112,14 +2187,14 @@ ${clonedFreshSection(daemonKey)}
2112
2187
 
2113
2188
  ## Change the rules
2114
2189
 
2115
- ${changeRulesSection(sandbox)}
2190
+ ${changeRulesSection(sandbox, !sandbox)}
2116
2191
 
2117
2192
  Full integration guide: \`docs/integration/INTEGRATE-WITH-METAMYND.md\`.
2118
2193
  `;
2119
2194
  }
2120
2195
 
2121
2196
  function gitignore() {
2122
- return `node_modules/\nagent.metamynd.json\n.env\n`;
2197
+ return `node_modules/\nagent.metamynd.json\n.env\nescalations/\n`;
2123
2198
  }
2124
2199
 
2125
2200
  // ---------- the default hosted scaffold's second process: a separate tool gateway ----------
@@ -2194,7 +2269,7 @@ const identity = JSON.parse(readFileSync(new URL('./service.metamynd.json', impo
2194
2269
  // The claim below does not stop that on its own: the hold belongs to the CALLER, and the caller's owner decides which
2195
2270
  // services may claim it — including registering this gateway's DID as one of theirs.
2196
2271
  const guard = createMcpGuard({ serviceDid: identity.serviceDid, serviceKey: identity.serviceKey ?? undefined, issuerApi: MAGP_API, requireAuthorization: true, policyPublicKey: '${policyKey}', allowedAgents: ['${agentDid}'], gatewayOwnerPrincipal: '${ownerPrincipal}' });
2197
- // A guard below 0.20.0 ignores allowedAgents and serves every agent: refuse to start on one (a stale lockfile, say).
2272
+ // A guard below 0.22.0 cannot enforce both pins (below 0.20.0 it ignores allowedAgents and serves every agent; 0.22.0 added gatewayOwnerPrincipal): refuse to start on one (a stale lockfile, say).
2198
2273
  if (!Array.isArray(guard.allowedAgents) || !guard.gatewayOwnerPrincipal) throw new Error('this gateway needs @metamynd/agentsafe-mcp-guard >= 0.22.0 to enforce allowedAgents and gatewayOwnerPrincipal - run npm install');
2199
2274
 
2200
2275
  const gateway = createHttpGateway({
@@ -2388,17 +2463,33 @@ on purpose: an agent must never be able to release or lower-settle a hold it aut
2388
2463
  provisioning, \`npx create-metamynd-agent\` registered that DID as a trusted counterparty for you
2389
2464
  (\`POST /policy/counterparties\`). A **MAINNET** hold is *always* registered-only, regardless of your
2390
2465
  registry (MAGP §8.7.10) — a testnet hold stays open unless you have registered anything at all.
2391
- Manage the registry at \`/dashboard/counterparties\`, or:
2466
+ Manage the registry at \`/dashboard/counterparties\` (it walks you through the proof below), or
2467
+ re-register this gateway yourself — after removing it from the registry, say.
2468
+
2469
+ Registration needs **proof of control** (MAGP §8.7.6): you ask for a challenge naming you, this
2470
+ gateway signs it with its own key from \`service.metamynd.json\`, and you register with that
2471
+ signature. A DID is never registered on its text alone, so nobody can register a gateway they do
2472
+ not hold the key for. Run this from this directory (bash; \`TOKEN\` is your owner access token):
2392
2473
 
2393
2474
  \`\`\`bash
2394
- curl -X POST $MAGP_API/policy/counterparties \\
2395
- -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \\
2396
- -d '{ "did": "'"$(node -e "console.log(JSON.parse(require('fs').readFileSync('./service.metamynd.json')).serviceDid)")"'", "confirmEnforcementChange": true }'
2475
+ MAGP_API=https://metamynd.ai/api/v1 TOKEN=<owner access token> node --input-type=module -e "
2476
+ import { readFileSync } from 'node:fs'; import crypto from 'node:crypto';
2477
+ const { serviceDid, serviceKey } = JSON.parse(readFileSync('./service.metamynd.json', 'utf8'));
2478
+ const api = process.env.MAGP_API, headers = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + process.env.TOKEN };
2479
+ const post = (path, body) => fetch(api + path, { method: 'POST', headers, body: JSON.stringify(body) });
2480
+ const ch = (await (await post('/policy/counterparties/challenge', { did: serviceDid, purpose: 'claim' })).json()).data;
2481
+ const key = crypto.createPrivateKey({ key: Buffer.from(serviceKey, 'hex'), format: 'der', type: 'pkcs8' });
2482
+ const signature = crypto.sign(null, Buffer.from(ch.message, 'utf8'), key).toString('hex');
2483
+ const r = await post('/policy/counterparties', { did: serviceDid, purpose: 'claim', proof: { challengeToken: ch.challengeToken, signature }, confirmEnforcementChange: true });
2484
+ console.log(r.status, await r.text());
2485
+ "
2397
2486
  \`\`\`
2398
2487
 
2399
- \`confirmEnforcementChange: true\` is required only for your account's FIRST-EVER registration —
2400
- it switches EVERY one of your holds (not just this agent's) from open to registered-only, so the
2401
- API refuses a first registration without it.
2488
+ The gateway's own \`guard.acceptCounterpartyChallenge(message)\` (\`@metamynd/agentsafe-mcp-guard\`
2489
+ ≥ 0.23.0) signs the same thing, if you would rather expose it from \`server.mjs\`.
2490
+ \`confirmEnforcementChange: true\` matters only for your account's FIRST-EVER registration — it
2491
+ switches EVERY one of your holds (not just this agent's) from open to registered-only, so the API
2492
+ refuses a first registration without it.
2402
2493
  - **Amount unknown.** \`amount-unknown\` (\`@metamynd/agentsafe-mcp-guard\` ≥ 0.3.0) blocks a
2403
2494
  platform tool by default when its raw bytes or a nested payload hide the amount from a naive
2404
2495
  spend cap — AND this agent's OWN starter SOP (see \`agent.metamynd.json\` /
@@ -2479,7 +2570,8 @@ function scaffoldProject({ outDir, config, slug, scope, perTxnMax, currency = 'U
2479
2570
  : exampleIndexNoGateway(scope, perTxnMax, currency, paymentMerchant, !!sandbox),
2480
2571
  force,
2481
2572
  );
2482
- writeFileSafe(outDir, 'package.json', examplePackageJson(slug, neutral), force);
2573
+ // Resumable = the index carries the resume path (resumeBlock): every agent but the shared sandbox, where nobody can approve.
2574
+ writeFileSafe(outDir, 'package.json', examplePackageJson(slug, neutral, !sandbox), force);
2483
2575
  writeFileSafe(outDir, '.gitignore', gitignore(), force);
2484
2576
  writeFileSafe(
2485
2577
  outDir,
@@ -2792,7 +2884,7 @@ const guard = createMcpGuard({
2792
2884
  return { subject: AGENT_DID, ownerPrincipal: HARNESS_OWNER, ...bundleFromRules(rules) };
2793
2885
  },
2794
2886
  });
2795
- // A guard below 0.20.0 ignores allowedAgents and serves every agent: refuse to start on one (a stale lockfile, say).
2887
+ // A guard below 0.22.0 cannot enforce both pins (below 0.20.0 it ignores allowedAgents and serves every agent; 0.22.0 added gatewayOwnerPrincipal): refuse to start on one (a stale lockfile, say).
2796
2888
  if (!Array.isArray(guard.allowedAgents) || !guard.gatewayOwnerPrincipal) throw new Error('this gateway needs @metamynd/agentsafe-mcp-guard >= 0.22.0 to enforce allowedAgents and gatewayOwnerPrincipal - run npm install');
2797
2889
 
2798
2890
  // One protected route per gated action in index.mjs. This gateway IS the tool, not a proxy in
@@ -4281,8 +4373,13 @@ async function main() {
4281
4373
  console.log(` ${c.green('✓')} gateway DID ${c.b(gatewayDid)} — ${reg.message}`);
4282
4374
  } else {
4283
4375
  console.log(` ${c.yellow('!')} could not register the gateway as a trusted counterparty: ${reg.message}`);
4284
- console.log(c.dim(` it still works on testnet (open by default with no registry entries); a MAINNET hold cannot be`));
4285
- console.log(c.dim(` claimed until it is registered — POST ${base}/policy/counterparties { "did": "${gatewayDid}", "confirmEnforcementChange": true }`));
4376
+ console.log(c.dim(financial
4377
+ ? ` it still works on testnet (open by default with no registry entries); a MAINNET hold cannot be`
4378
+ : ` it still enforces; only its refusal reports cannot reach your Activity Log until it is`));
4379
+ console.log(c.dim(financial
4380
+ ? ` claimed until it is registered — gateway/README.md ("Who may claim this agent's holds") shows how, with the`
4381
+ : ` registered — at /dashboard/counterparties, with the`));
4382
+ console.log(c.dim(` proof-of-control step registration requires, for ${gatewayDid}`));
4286
4383
  }
4287
4384
  }
4288
4385
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-metamynd-agent",
3
- "version": "0.14.18",
3
+ "version": "0.14.22",
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": {