@heyanon-arp/cli 0.0.2 → 0.0.4
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/cli.js +537 -136
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -431,9 +431,7 @@ var init_api = __esm({
|
|
|
431
431
|
* known but has never been party to an event.
|
|
432
432
|
*/
|
|
433
433
|
async getActivitySummary(did) {
|
|
434
|
-
return this.get(
|
|
435
|
-
`/v1/agents/${encodeURIComponent(did)}/activity-summary`
|
|
436
|
-
);
|
|
434
|
+
return this.get(`/v1/agents/${encodeURIComponent(did)}/activity-summary`);
|
|
437
435
|
}
|
|
438
436
|
/**
|
|
439
437
|
* Signed `GET /v1/agents/:did/sender-sequence`. Returns the highest
|
|
@@ -712,20 +710,10 @@ var import_simple_update_notifier = __toESM(require("simple-update-notifier"));
|
|
|
712
710
|
// package.json
|
|
713
711
|
var package_default = {
|
|
714
712
|
name: "@heyanon-arp/cli",
|
|
715
|
-
version: "0.0.
|
|
713
|
+
version: "0.0.4",
|
|
716
714
|
description: "Command-line client for the Agent Relationship Protocol \u2014 register agents, sign envelopes, run escrowed work cycles on Solana.",
|
|
717
715
|
license: "MIT",
|
|
718
|
-
keywords: [
|
|
719
|
-
"arp",
|
|
720
|
-
"agent-relationship-protocol",
|
|
721
|
-
"did",
|
|
722
|
-
"solana",
|
|
723
|
-
"escrow",
|
|
724
|
-
"ed25519",
|
|
725
|
-
"agents",
|
|
726
|
-
"a2a",
|
|
727
|
-
"cli"
|
|
728
|
-
],
|
|
716
|
+
keywords: ["arp", "agent-relationship-protocol", "did", "solana", "escrow", "ed25519", "agents", "a2a", "cli"],
|
|
729
717
|
bin: {
|
|
730
718
|
heyarp: "./dist/cli.js"
|
|
731
719
|
},
|
|
@@ -1177,8 +1165,8 @@ function listAgents() {
|
|
|
1177
1165
|
}
|
|
1178
1166
|
|
|
1179
1167
|
// src/commands/delegation.ts
|
|
1180
|
-
var import_sdk4 = require("@heyanon-arp/sdk");
|
|
1181
1168
|
var import_node_fs4 = require("fs");
|
|
1169
|
+
var import_sdk4 = require("@heyanon-arp/sdk");
|
|
1182
1170
|
var import_chalk6 = __toESM(require("chalk"));
|
|
1183
1171
|
init_api();
|
|
1184
1172
|
|
|
@@ -1256,14 +1244,6 @@ ${verb}.`));
|
|
|
1256
1244
|
console.log(formatJson(agent));
|
|
1257
1245
|
}
|
|
1258
1246
|
|
|
1259
|
-
// src/commands/wallet.ts
|
|
1260
|
-
var import_sdk3 = require("@heyanon-arp/sdk");
|
|
1261
|
-
var import_utils = require("@noble/hashes/utils");
|
|
1262
|
-
var import_web3 = require("@solana/web3.js");
|
|
1263
|
-
var import_node_fs3 = require("fs");
|
|
1264
|
-
init_api();
|
|
1265
|
-
init_config();
|
|
1266
|
-
|
|
1267
1247
|
// src/commands/status.ts
|
|
1268
1248
|
var import_sdk2 = require("@heyanon-arp/sdk");
|
|
1269
1249
|
var import_chalk5 = __toESM(require("chalk"));
|
|
@@ -1349,6 +1329,25 @@ async function runStatus(relationshipId, opts) {
|
|
|
1349
1329
|
process.exitCode = 124;
|
|
1350
1330
|
}
|
|
1351
1331
|
}
|
|
1332
|
+
async function awaitFsmTransitionAfterAction(input) {
|
|
1333
|
+
const { api, signerDid, signer, relationshipId, untilPhase, waitIntervalSec, waitTimeoutSec, waitVerbose, json } = input;
|
|
1334
|
+
if (!json) {
|
|
1335
|
+
console.log(import_chalk5.default.dim(`
|
|
1336
|
+
[--wait-until ${untilPhase}] polling relationship ${relationshipId} (interval=${waitIntervalSec}s timeout=${waitTimeoutSec}s)`));
|
|
1337
|
+
}
|
|
1338
|
+
const outcome = await runWaitLoop({
|
|
1339
|
+
fetchSummary: () => composeStatus(api, signerDid, relationshipId, signer),
|
|
1340
|
+
waitIntervalSec,
|
|
1341
|
+
waitTimeoutSec,
|
|
1342
|
+
waitVerbose: !!waitVerbose,
|
|
1343
|
+
json: !!json,
|
|
1344
|
+
log: (line) => console.log(line),
|
|
1345
|
+
until: untilPhase
|
|
1346
|
+
});
|
|
1347
|
+
if (outcome.timedOut) {
|
|
1348
|
+
process.exitCode = 124;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1352
1351
|
async function runWaitLoop(opts) {
|
|
1353
1352
|
const isTerminal = (s) => s.cycleComplete || s.relationshipState === "closed" || s.relationshipState === "not_found";
|
|
1354
1353
|
const isActionable = (s) => {
|
|
@@ -1382,8 +1381,12 @@ async function runWaitLoop(opts) {
|
|
|
1382
1381
|
if (opts.json) opts.log(JSON.stringify(terminalUnmatchedJson ? { ...initial, _waitTimedOut: true } : initial));
|
|
1383
1382
|
else {
|
|
1384
1383
|
if (opts.until !== void 0) {
|
|
1385
|
-
opts.log(
|
|
1386
|
-
|
|
1384
|
+
opts.log(
|
|
1385
|
+
import_chalk5.default.yellow(
|
|
1386
|
+
`
|
|
1387
|
+
[--wait] Terminal state (${initial.relationshipState}, cycleComplete=${initial.cycleComplete}) reached before phase '${opts.until}' \u2014 exiting; phase unreachable.`
|
|
1388
|
+
)
|
|
1389
|
+
);
|
|
1387
1390
|
} else {
|
|
1388
1391
|
opts.log(import_chalk5.default.dim(`
|
|
1389
1392
|
[--wait] Already terminal \u2014 exiting.`));
|
|
@@ -1433,7 +1436,11 @@ async function runWaitLoop(opts) {
|
|
|
1433
1436
|
if (phaseReached) {
|
|
1434
1437
|
opts.log(import_chalk5.default.green(`[--wait] Phase '${opts.until}' reached.`));
|
|
1435
1438
|
} else {
|
|
1436
|
-
opts.log(
|
|
1439
|
+
opts.log(
|
|
1440
|
+
import_chalk5.default.yellow(
|
|
1441
|
+
`[--wait] Terminal state (${next.relationshipState}, cycleComplete=${next.cycleComplete}) reached before phase '${opts.until}' \u2014 phase unreachable.`
|
|
1442
|
+
)
|
|
1443
|
+
);
|
|
1437
1444
|
}
|
|
1438
1445
|
} else {
|
|
1439
1446
|
opts.log(import_chalk5.default.green(`[--wait] ${isTerminal(next) ? "Cycle terminated" : `Your turn (owner=${next.nextActionOwner})`}.`));
|
|
@@ -1448,11 +1455,17 @@ async function runWaitLoop(opts) {
|
|
|
1448
1455
|
opts.log(JSON.stringify({ ...last, _waitTimedOut: true }));
|
|
1449
1456
|
} else {
|
|
1450
1457
|
if (opts.until !== void 0) {
|
|
1451
|
-
opts.log(
|
|
1452
|
-
|
|
1458
|
+
opts.log(
|
|
1459
|
+
import_chalk5.default.yellow(
|
|
1460
|
+
`
|
|
1461
|
+
[--wait] Timed out after ${opts.waitTimeoutSec}s without reaching phase '${opts.until}' (latest state: ${last.relationshipState}, hint: ${last.nextActionHint}).`
|
|
1462
|
+
)
|
|
1463
|
+
);
|
|
1453
1464
|
} else {
|
|
1454
|
-
opts.log(
|
|
1455
|
-
|
|
1465
|
+
opts.log(
|
|
1466
|
+
import_chalk5.default.yellow(`
|
|
1467
|
+
[--wait] Timed out after ${opts.waitTimeoutSec}s without an actionable or terminal transition (your turn never came, cycle still in flight).`)
|
|
1468
|
+
);
|
|
1456
1469
|
}
|
|
1457
1470
|
}
|
|
1458
1471
|
return { timedOut: true, last };
|
|
@@ -1574,13 +1587,7 @@ async function composeStatus(api, signerDid, relationshipId, signer) {
|
|
|
1574
1587
|
const delegations = latestContract ? await fetchAllPages(
|
|
1575
1588
|
(after) => api.listDelegations(relationshipId, signer, { limit: 100, contractId: latestContract.contractId, ...after ? { after } : {} })
|
|
1576
1589
|
) : [];
|
|
1577
|
-
const latestDelegation = pickLatestLive(delegations, [
|
|
1578
|
-
"proposed",
|
|
1579
|
-
"offered",
|
|
1580
|
-
"pending_lock_finalization",
|
|
1581
|
-
"accepted",
|
|
1582
|
-
"awaiting_release_finalization"
|
|
1583
|
-
]);
|
|
1590
|
+
const latestDelegation = pickLatestLive(delegations, ["proposed", "offered", "pending_lock_finalization", "accepted", "awaiting_release_finalization"]);
|
|
1584
1591
|
const [workLogs, receipts] = await Promise.all([
|
|
1585
1592
|
latestDelegation ? fetchAllPages(
|
|
1586
1593
|
(after) => api.listWorkLogs(relationshipId, signer, { limit: 100, delegationId: latestDelegation.delegationId, ...after ? { after } : {} })
|
|
@@ -1650,9 +1657,7 @@ function findReceiptForWorkLog(receipts, workLog, allWorkLogs = [workLog]) {
|
|
|
1650
1657
|
const expectedResponseHash = (0, import_sdk2.canonicalSha256Hex)(responseBody);
|
|
1651
1658
|
const matches = receipts.filter((r) => r.requestHash === expectedRequestHash && r.responseHash === expectedResponseHash);
|
|
1652
1659
|
if (matches.length > 0) return pickLatest(matches);
|
|
1653
|
-
const respondedSiblings = allWorkLogs.filter(
|
|
1654
|
-
(wl) => wl.delegationId === workLog.delegationId && (wl.responseOutput !== void 0 || wl.responseError !== void 0)
|
|
1655
|
-
).length;
|
|
1660
|
+
const respondedSiblings = allWorkLogs.filter((wl) => wl.delegationId === workLog.delegationId && (wl.responseOutput !== void 0 || wl.responseError !== void 0)).length;
|
|
1656
1661
|
if (respondedSiblings > 1) return null;
|
|
1657
1662
|
const sameDelegation = receipts.filter((r) => r.delegationId === workLog.delegationId);
|
|
1658
1663
|
return pickLatest(sameDelegation);
|
|
@@ -1878,6 +1883,12 @@ function stateColor(state) {
|
|
|
1878
1883
|
}
|
|
1879
1884
|
|
|
1880
1885
|
// src/commands/wallet.ts
|
|
1886
|
+
var import_node_fs3 = require("fs");
|
|
1887
|
+
var import_sdk3 = require("@heyanon-arp/sdk");
|
|
1888
|
+
var import_utils = require("@noble/hashes/utils");
|
|
1889
|
+
var import_web3 = require("@solana/web3.js");
|
|
1890
|
+
init_api();
|
|
1891
|
+
init_config();
|
|
1881
1892
|
function bytesToBase64(bytes) {
|
|
1882
1893
|
return Buffer.from(bytes).toString("base64");
|
|
1883
1894
|
}
|
|
@@ -1967,22 +1978,10 @@ function registerDerivePdas(cmd) {
|
|
|
1967
1978
|
).requiredOption("--delegation-id <id>", "Delegation UUID (canonical `del_<uuid>` or bare `<uuid>` \u2014 both accepted)").option("--server <url>", "Override server URL (used only for --program-id auto-discovery)").option(
|
|
1968
1979
|
"--program-id <pubkey>",
|
|
1969
1980
|
`Deployed ARP escrow program id. Precedence: --program-id flag > ARP_ESCROW_PROGRAM_ID env > GET /v1/escrow/protocol-fee (auto-discover) > hardcoded fallback (${FALLBACK_PROGRAM_ID}).`
|
|
1970
|
-
).option(
|
|
1971
|
-
"--rpc-url <url>",
|
|
1972
|
-
"Accepted for symmetry with `wallet verify-release` but never read \u2014 PDAs are derived locally from `program_id + delegation_id`; no RPC needed."
|
|
1973
|
-
).option(
|
|
1981
|
+
).option("--rpc-url <url>", "Accepted for symmetry with `wallet verify-release` but never read \u2014 PDAs are derived locally from `program_id + delegation_id`; no RPC needed.").option(
|
|
1974
1982
|
"--recipient-pubkey <base58>",
|
|
1975
1983
|
"Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id; recipient pubkey has no derivation role."
|
|
1976
|
-
).option(
|
|
1977
|
-
"--condition-hash <hex>",
|
|
1978
|
-
"Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id."
|
|
1979
|
-
).option(
|
|
1980
|
-
"--amount-lamports <int>",
|
|
1981
|
-
"Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id."
|
|
1982
|
-
).option(
|
|
1983
|
-
"--expiry-secs <int>",
|
|
1984
|
-
"Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id."
|
|
1985
|
-
).option("--json", "Emit JSON instead of human text (jq-pipeable: `.lock_pda`, `.escrow_pda`, `.config_pda`, `.lock_id_hex`).", false).action(async (opts) => {
|
|
1984
|
+
).option("--condition-hash <hex>", "Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id.").option("--amount-lamports <int>", "Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id.").option("--expiry-secs <int>", "Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id.").option("--json", "Emit JSON instead of human text (jq-pipeable: `.lock_pda`, `.escrow_pda`, `.config_pda`, `.lock_id_hex`).", false).action(async (opts) => {
|
|
1986
1985
|
try {
|
|
1987
1986
|
const out = await derivePdasHandler(opts);
|
|
1988
1987
|
if (opts.json) {
|
|
@@ -2260,7 +2259,7 @@ async function preflightLockCurrency(api, agent, normalisedDelegationId, contrac
|
|
|
2260
2259
|
})
|
|
2261
2260
|
]);
|
|
2262
2261
|
} catch (err) {
|
|
2263
|
-
if (err instanceof Error && err.message.includes("
|
|
2262
|
+
if (err instanceof Error && err.message.includes("LOCK_CURRENCY_NON_NATIVE_SOL")) throw err;
|
|
2264
2263
|
} finally {
|
|
2265
2264
|
if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
|
|
2266
2265
|
}
|
|
@@ -2284,7 +2283,9 @@ async function preflightLockCurrencyInner(api, agent, normalisedDelegationId, si
|
|
|
2284
2283
|
if (contractId !== void 0) {
|
|
2285
2284
|
let activeContracts;
|
|
2286
2285
|
try {
|
|
2287
|
-
activeContracts = await fetchAllPages(
|
|
2286
|
+
activeContracts = await fetchAllPages(
|
|
2287
|
+
(after) => api.listContracts(r.relationshipId, signer, { limit: 100, state: "active", ...after ? { after } : {} }, signal)
|
|
2288
|
+
);
|
|
2288
2289
|
} catch {
|
|
2289
2290
|
activeContracts = [];
|
|
2290
2291
|
}
|
|
@@ -2293,7 +2294,7 @@ async function preflightLockCurrencyInner(api, agent, normalisedDelegationId, si
|
|
|
2293
2294
|
const directAssetId = directContract.rateCurrency?.assetId;
|
|
2294
2295
|
if (typeof directAssetId === "string" && !directAssetId.endsWith("/slip44:501")) {
|
|
2295
2296
|
throw new Error(
|
|
2296
|
-
`wallet create-lock pre-flight (
|
|
2297
|
+
`wallet create-lock pre-flight (LOCK_CURRENCY_NON_NATIVE_SOL): contract ${contractId} is priced in '${directAssetId}' (NOT native SOL). This command only builds native-SOL locks (isNativeSol=1, no SPL Token-2022 path) \u2014 the resulting tx blob would be rejected server-side with ESC_LOCK_CURRENCY_MISMATCH or ESC_LOCK_AMOUNT_DELEGATION_MISMATCH after \`delegation offer\` ships it. Use a SOL-priced contract (e.g. \`--rate-currency 'SOL:solana-mainnet'\` on \`contract propose\`) or wait for SPL-token lock support to land.`
|
|
2297
2298
|
);
|
|
2298
2299
|
}
|
|
2299
2300
|
}
|
|
@@ -2308,7 +2309,9 @@ async function preflightLockCurrencyInner(api, agent, normalisedDelegationId, si
|
|
|
2308
2309
|
if (!match) continue;
|
|
2309
2310
|
let contracts = [];
|
|
2310
2311
|
try {
|
|
2311
|
-
contracts = await fetchAllPages(
|
|
2312
|
+
contracts = await fetchAllPages(
|
|
2313
|
+
(after) => api.listContracts(r.relationshipId, signer, { limit: 100, state: "active", ...after ? { after } : {} }, signal)
|
|
2314
|
+
);
|
|
2312
2315
|
} catch {
|
|
2313
2316
|
}
|
|
2314
2317
|
const contract = contracts.find((c) => c.contractId === match.contractId);
|
|
@@ -2323,7 +2326,7 @@ async function preflightLockCurrencyInner(api, agent, normalisedDelegationId, si
|
|
|
2323
2326
|
const offendingAssetId = isContractNonNative ? contractAssetId : delegationAssetId;
|
|
2324
2327
|
const sourceLabel = isContractNonNative ? `contract ${match.contractId}` : `delegation ${normalisedDelegationId}`;
|
|
2325
2328
|
throw new Error(
|
|
2326
|
-
`wallet create-lock pre-flight (
|
|
2329
|
+
`wallet create-lock pre-flight (LOCK_CURRENCY_NON_NATIVE_SOL): ${sourceLabel} is priced in '${offendingAssetId}' (NOT native SOL). This command only builds native-SOL locks (isNativeSol=1, no SPL Token-2022 path) \u2014 the resulting tx blob would be rejected server-side with ESC_LOCK_CURRENCY_MISMATCH or ESC_LOCK_AMOUNT_DELEGATION_MISMATCH after \`delegation offer\` ships it. Use a SOL-priced contract (e.g. \`--rate-currency 'SOL:solana-mainnet'\` on \`contract propose\`) or wait for SPL-token lock support to land.`
|
|
2327
2330
|
);
|
|
2328
2331
|
}
|
|
2329
2332
|
}
|
|
@@ -2607,10 +2610,23 @@ function registerOffer(parent) {
|
|
|
2607
2610
|
parent.command("offer").description("Open a new delegation under <contract-id> (must be ACTIVE) addressed to <recipient-did>.").argument("<recipient-did>", "Recipient agent DID (did:arp:...)").argument("<contract-id>", "Contract id this delegation operates under (must be ACTIVE)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--delegation-id <uuid>", "Override the auto-generated delegation id (UUID). Useful for replay / scripting.").option("--title <s>", "Required: human-readable title for the offer").option("--brief <json>", "Optional structured brief (JSON object)").option("--criterion <s>", "acceptance_criteria \u2014 repeatable; pass --criterion once per bullet", collectRepeated, []).option("--deadline <rfc3339>", 'Optional RFC 3339 deadline (e.g. "2026-12-31T23:59:59Z")').option("--amount <s>", 'Optional decimal-as-string amount (e.g. "10.00"). REQUIRES --currency if set.').option("--currency <s>", `Asset identifier: shorthand (${import_sdk4.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string.`).option("--currency-decimals <n>", "Decimal places for base-unit conversion (0-18). Required only when --currency is raw CAIP-19.").option("--currency-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Max 16 chars.').option("--ttl <seconds>", "Envelope TTL in seconds (max 86400 = 24h)", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
|
|
2608
2611
|
"--escrow-lock-from-file <path>",
|
|
2609
2612
|
"Path to JSON output of `heyarp wallet create-lock` (recommended). Contains all 5 escrow_lock fields: signed_tx_blob, lock_id, amount, asset_id, expiry. Mutually exclusive with the inline --escrow-lock-* flags below."
|
|
2610
|
-
).option(
|
|
2613
|
+
).option(
|
|
2614
|
+
"--escrow-lock-blob <base64>",
|
|
2615
|
+
"INLINE alternative: the signed Solana tx blob (base64). Requires --escrow-lock-id, --escrow-lock-amount, --escrow-lock-asset-id, --escrow-lock-expiry together."
|
|
2616
|
+
).option("--escrow-lock-id <hex32>", "INLINE lock_id (32-byte hex). Used with --escrow-lock-blob.").option("--escrow-lock-amount <int>", "INLINE lock amount in base units (e.g. lamports for native SOL). Used with --escrow-lock-blob.").option("--escrow-lock-asset-id <caip19>", "INLINE currency CAIP-19 asset_id (e.g. solana:<cluster_id>/slip44:501). Used with --escrow-lock-blob.").option("--escrow-lock-expiry <unix>", "INLINE expiry as unix seconds. Used with --escrow-lock-blob.").option(
|
|
2611
2617
|
"--program-id <pubkey>",
|
|
2612
2618
|
"Expected ARP escrow program id for pre-flight against the lock file's embedded `program_id`. Precedence: this flag > ARP_ESCROW_PROGRAM_ID env > server protocol-fee endpoint. Mismatch throws BEFORE the envelope ships, so a wrong-program lock never silently fails on chain after the offer was already delivered. Pre-flight is skipped only for old lock files lacking `program_id`, or when no expected value can be resolved."
|
|
2613
|
-
).option(
|
|
2619
|
+
).option(
|
|
2620
|
+
"--no-escrow",
|
|
2621
|
+
"Opt-out for test_mode servers. Default REQUIRES the escrow_lock attachment; without --no-escrow, missing flags throw BEFORE the envelope is sent (avoids ESC_LOCK_MISSING POST_COMMIT consuming sender_sequence)."
|
|
2622
|
+
).option(
|
|
2623
|
+
"--wait-until <phase>",
|
|
2624
|
+
'Block after delivery until the named FSM phase is reached (e.g. delegation.accepted). One of the UNTIL_PHASES from `heyarp status --help`. Exit code 124 on --wait-timeout. Recovers the "sub-agent exits before counterparty accepts" antipattern.'
|
|
2625
|
+
).option("--wait-timeout <seconds>", "When --wait-until is set: max wall-clock wait (default 300). Exit code 124 on timeout.").option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option(
|
|
2626
|
+
"--wait-verbose",
|
|
2627
|
+
'When --wait-until is set: emit one dim line per poll tick showing the current FSM state. Useful for "is it alive or stuck?" diagnosis on long blocks.',
|
|
2628
|
+
false
|
|
2629
|
+
).action(async (recipientDid, contractId, opts) => {
|
|
2614
2630
|
await runOffer(recipientDid, contractId, opts);
|
|
2615
2631
|
});
|
|
2616
2632
|
}
|
|
@@ -2633,9 +2649,7 @@ function assembleEscrowLockAttachment(opts) {
|
|
|
2633
2649
|
const someInlineSet = inlineFlags.some((f) => f !== void 0 && f !== "");
|
|
2634
2650
|
const allInlineSet = inlineFlags.every((f) => f !== void 0 && f !== "");
|
|
2635
2651
|
if (fromFile && someInlineSet) {
|
|
2636
|
-
throw new Error(
|
|
2637
|
-
"delegation offer: --escrow-lock-from-file and --escrow-lock-blob/--escrow-lock-* are mutually exclusive. Pick one path."
|
|
2638
|
-
);
|
|
2652
|
+
throw new Error("delegation offer: --escrow-lock-from-file and --escrow-lock-blob/--escrow-lock-* are mutually exclusive. Pick one path.");
|
|
2639
2653
|
}
|
|
2640
2654
|
if (!fromFile && !someInlineSet) {
|
|
2641
2655
|
if (opts.escrow === false) return void 0;
|
|
@@ -2677,7 +2691,9 @@ function assembleEscrowLockAttachment(opts) {
|
|
|
2677
2691
|
} else if (typeof expiryRaw === "string") {
|
|
2678
2692
|
expiryNum2 = Number(expiryRaw);
|
|
2679
2693
|
if (String(expiryNum2) !== expiryRaw.trim()) {
|
|
2680
|
-
throw new Error(
|
|
2694
|
+
throw new Error(
|
|
2695
|
+
`delegation offer: --escrow-lock-from-file '${fromFile}' has invalid 'expiry' (must be positive integer unix seconds): ${JSON.stringify(expiryRaw)}.`
|
|
2696
|
+
);
|
|
2681
2697
|
}
|
|
2682
2698
|
} else {
|
|
2683
2699
|
throw new Error(`delegation offer: --escrow-lock-from-file '${fromFile}' has invalid 'expiry' (must be positive integer unix seconds): ${JSON.stringify(expiryRaw)}.`);
|
|
@@ -2688,9 +2704,7 @@ function assembleEscrowLockAttachment(opts) {
|
|
|
2688
2704
|
let delegationIdFromLock;
|
|
2689
2705
|
if (p.delegation_id !== void 0) {
|
|
2690
2706
|
if (typeof p.delegation_id !== "string" || !UUID_RE.test(p.delegation_id)) {
|
|
2691
|
-
throw new Error(
|
|
2692
|
-
`delegation offer: --escrow-lock-from-file '${fromFile}' has invalid 'delegation_id' (must be a UUID): ${JSON.stringify(p.delegation_id)}.`
|
|
2693
|
-
);
|
|
2707
|
+
throw new Error(`delegation offer: --escrow-lock-from-file '${fromFile}' has invalid 'delegation_id' (must be a UUID): ${JSON.stringify(p.delegation_id)}.`);
|
|
2694
2708
|
}
|
|
2695
2709
|
delegationIdFromLock = p.delegation_id.toLowerCase();
|
|
2696
2710
|
}
|
|
@@ -2800,9 +2814,30 @@ Reference this delegation on subsequent calls with:`));
|
|
|
2800
2814
|
console.log(import_chalk6.default.dim(` heyarp delegation accept ${result.relationshipId} ${delegationId}`));
|
|
2801
2815
|
console.log(import_chalk6.default.dim(` heyarp delegation decline ${result.relationshipId} ${delegationId}`));
|
|
2802
2816
|
console.log(import_chalk6.default.dim(` heyarp delegation cancel ${result.relationshipId} ${delegationId}`));
|
|
2817
|
+
if (opts.waitUntil) {
|
|
2818
|
+
const untilPhase = parseUntilPhase(opts.waitUntil);
|
|
2819
|
+
if (untilPhase === void 0) {
|
|
2820
|
+
throw new Error(`delegation offer: --wait-until requires a phase value (got ${JSON.stringify(opts.waitUntil)})`);
|
|
2821
|
+
}
|
|
2822
|
+
await awaitFsmTransitionAfterAction({
|
|
2823
|
+
api,
|
|
2824
|
+
signerDid: sender.did,
|
|
2825
|
+
signer: makeSigner(sender),
|
|
2826
|
+
relationshipId: result.relationshipId,
|
|
2827
|
+
untilPhase,
|
|
2828
|
+
waitIntervalSec: parseWaitInterval(opts.waitInterval),
|
|
2829
|
+
waitTimeoutSec: parseWaitTimeout(opts.waitTimeout),
|
|
2830
|
+
waitVerbose: !!opts.waitVerbose,
|
|
2831
|
+
json: false
|
|
2832
|
+
// delegation offer is a human-text command (printIngestResult is human-text); JSON mode would be a follow-up.
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2803
2835
|
}
|
|
2804
2836
|
function registerAccept(parent) {
|
|
2805
|
-
parent.command("accept").description("Accept a PROPOSED delegation \u2014 promotes to ACCEPTED. Counterparty-only.").argument("<relationship-id>", "Relationship UUID").argument("<delegation-id>", "Delegation UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--contract-id <uuid>", "Override the auto-resolved contract id (default: read from the delegation row)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).
|
|
2837
|
+
parent.command("accept").description("Accept a PROPOSED delegation \u2014 promotes to ACCEPTED. Counterparty-only.").argument("<relationship-id>", "Relationship UUID").argument("<delegation-id>", "Delegation UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--contract-id <uuid>", "Override the auto-resolved contract id (default: read from the delegation row)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
|
|
2838
|
+
"--no-wait-for-lock",
|
|
2839
|
+
"Opt-out of the pending-lock pre-flight poll. Default is ON: when the resolved delegation is in `pending_lock_finalization`, the CLI polls until the on-chain lock is confirmed before signing the envelope (avoids the racy DELEGATION_PENDING_LOCK 409 that consumes sender_sequence)."
|
|
2840
|
+
).option("--lock-wait-timeout <seconds>", "Max wall-clock seconds to wait for pending-lock pre-flight (default 300).", "300").option("--lock-wait-interval <seconds>", "Poll cadence for pending-lock pre-flight in seconds. Bound to [1, 60].", "3").action(async (relationshipId, delegationId, opts) => {
|
|
2806
2841
|
await runFollowupAction(relationshipId, delegationId, "accept", opts);
|
|
2807
2842
|
});
|
|
2808
2843
|
}
|
|
@@ -2812,12 +2847,18 @@ function registerDecline(parent) {
|
|
|
2812
2847
|
// surface the closed enum at help time so operators
|
|
2813
2848
|
// don't have to read source to find acceptable values.
|
|
2814
2849
|
`Required: decline reason code (one of: ${import_sdk4.DECLINE_REASONS.join(", ")}). Carried in body.content.reason so the counterparty's reactor can branch on it.`
|
|
2815
|
-
).option("--reason-detail <s>", 'Optional free-text elaboration alongside --reason (e.g. "rate floor 0.20 USDC"). Max 512 chars.').option("--verbose", "Print the full envelope before sending and the full server response", false).
|
|
2850
|
+
).option("--reason-detail <s>", 'Optional free-text elaboration alongside --reason (e.g. "rate floor 0.20 USDC"). Max 512 chars.').option("--verbose", "Print the full envelope before sending and the full server response", false).option(
|
|
2851
|
+
"--no-wait-for-lock",
|
|
2852
|
+
"Opt-out of the pending-lock pre-flight poll. Default is ON: when the resolved delegation is in `pending_lock_finalization`, the CLI polls until the on-chain lock is confirmed before signing the envelope (avoids the racy DELEGATION_PENDING_LOCK 409 that consumes sender_sequence)."
|
|
2853
|
+
).option("--lock-wait-timeout <seconds>", "Max wall-clock seconds to wait for pending-lock pre-flight (default 300).", "300").option("--lock-wait-interval <seconds>", "Poll cadence for pending-lock pre-flight in seconds. Bound to [1, 60].", "3").action(async (relationshipId, delegationId, opts) => {
|
|
2816
2854
|
await runFollowupAction(relationshipId, delegationId, "decline", opts);
|
|
2817
2855
|
});
|
|
2818
2856
|
}
|
|
2819
2857
|
function registerCancel(parent) {
|
|
2820
|
-
parent.command("cancel").description("Cancel a PROPOSED delegation \u2014 moves to CANCELED. Offerer-only (the OTHER side uses decline).").argument("<relationship-id>", "Relationship UUID").argument("<delegation-id>", "Delegation UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--contract-id <uuid>", "Override the auto-resolved contract id (default: read from the delegation row)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).
|
|
2858
|
+
parent.command("cancel").description("Cancel a PROPOSED delegation \u2014 moves to CANCELED. Offerer-only (the OTHER side uses decline).").argument("<relationship-id>", "Relationship UUID").argument("<delegation-id>", "Delegation UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--contract-id <uuid>", "Override the auto-resolved contract id (default: read from the delegation row)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
|
|
2859
|
+
"--no-wait-for-lock",
|
|
2860
|
+
"Opt-out of the pending-lock pre-flight poll. Default is ON: when the resolved delegation is in `pending_lock_finalization`, the CLI polls until the on-chain lock is confirmed before signing the envelope (avoids the racy DELEGATION_PENDING_LOCK 409 that consumes sender_sequence)."
|
|
2861
|
+
).option("--lock-wait-timeout <seconds>", "Max wall-clock seconds to wait for pending-lock pre-flight (default 300).", "300").option("--lock-wait-interval <seconds>", "Poll cadence for pending-lock pre-flight in seconds. Bound to [1, 60].", "3").action(async (relationshipId, delegationId, opts) => {
|
|
2821
2862
|
await runFollowupAction(relationshipId, delegationId, "cancel", opts);
|
|
2822
2863
|
});
|
|
2823
2864
|
}
|
|
@@ -2826,6 +2867,8 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
|
2826
2867
|
relationshipId = requireUuidNormalised(cmdName, relationshipId, "<relationship-id>");
|
|
2827
2868
|
delegationId = requireUuidNormalised(cmdName, delegationId, "<delegation-id>");
|
|
2828
2869
|
const ttlSeconds = parseTtl(cmdName, opts.ttl);
|
|
2870
|
+
const lockWaitTimeoutSec = parseLockWaitTimeout(cmdName, opts.lockWaitTimeout);
|
|
2871
|
+
const lockWaitIntervalSec = parseLockWaitInterval(cmdName, opts.lockWaitInterval);
|
|
2829
2872
|
let declinePayload = null;
|
|
2830
2873
|
if (action === "decline") {
|
|
2831
2874
|
const reason = parseDeclineReason(cmdName, opts.reason);
|
|
@@ -2836,6 +2879,15 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
|
2836
2879
|
const sender = resolveSenderAgent(cmdName, opts.server, opts.fromDid);
|
|
2837
2880
|
const signer = makeSigner(sender);
|
|
2838
2881
|
const resolved = await resolveDelegationRefs(cmdName, api, signer, { relationshipId, delegationId, action, selfDid: sender.did, contractIdOverride: opts.contractId });
|
|
2882
|
+
if (resolved.state === "pending_lock_finalization" && opts.waitForLock !== false) {
|
|
2883
|
+
await awaitDelegationLockFinalized(cmdName, api, signer, {
|
|
2884
|
+
relationshipId,
|
|
2885
|
+
delegationId,
|
|
2886
|
+
action,
|
|
2887
|
+
timeoutSec: lockWaitTimeoutSec,
|
|
2888
|
+
intervalSec: lockWaitIntervalSec
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2839
2891
|
const content = {
|
|
2840
2892
|
action,
|
|
2841
2893
|
delegation_id: delegationId,
|
|
@@ -2939,7 +2991,96 @@ async function resolveDelegationRefs(cmdName, api, signer, args) {
|
|
|
2939
2991
|
} else {
|
|
2940
2992
|
recipientDid = row.offererDid;
|
|
2941
2993
|
}
|
|
2942
|
-
return { contractId, recipientDid };
|
|
2994
|
+
return { contractId, recipientDid, state: row.state };
|
|
2995
|
+
}
|
|
2996
|
+
async function awaitDelegationLockFinalized(cmdName, api, signer, args) {
|
|
2997
|
+
const log = args.log ?? ((line) => console.log(line));
|
|
2998
|
+
log(
|
|
2999
|
+
import_chalk6.default.dim(
|
|
3000
|
+
`
|
|
3001
|
+
[--wait-for-lock] delegation ${args.delegationId} is awaiting on-chain lock confirmation; polling until ready (interval=${args.intervalSec}s timeout=${args.timeoutSec}s)`
|
|
3002
|
+
)
|
|
3003
|
+
);
|
|
3004
|
+
const fetchRow = async () => {
|
|
3005
|
+
let after;
|
|
3006
|
+
for (; ; ) {
|
|
3007
|
+
const page = await api.listDelegations(args.relationshipId, signer, { limit: 100, after });
|
|
3008
|
+
const found = page.find((d) => d.delegationId === args.delegationId);
|
|
3009
|
+
if (found) return found;
|
|
3010
|
+
if (page.length < 100) return null;
|
|
3011
|
+
after = page[page.length - 1].id;
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
const outcome = await (0, import_sdk4.pollUntil)({
|
|
3015
|
+
fetch: fetchRow,
|
|
3016
|
+
// Match on either "row not found at all" OR "state moved
|
|
3017
|
+
// past pending_lock_finalization". The post-poll branch
|
|
3018
|
+
// disambiguates which condition fired — missing rows error
|
|
3019
|
+
// loud (operator typo or row pruned), present-but-terminal
|
|
3020
|
+
// rows error with the new state, and present-and-actionable
|
|
3021
|
+
// rows return cleanly. Polling on null would loop pointlessly
|
|
3022
|
+
// until the deadline fires and surface a misleading "timed
|
|
3023
|
+
// out" message for what's actually a wrong-id problem.
|
|
3024
|
+
predicate: (row2) => row2 === null || row2.state !== "pending_lock_finalization",
|
|
3025
|
+
intervalMs: args.intervalSec * 1e3,
|
|
3026
|
+
timeoutMs: args.timeoutSec * 1e3,
|
|
3027
|
+
// Swallow ONLY transient errors. A 4xx during the poll is a
|
|
3028
|
+
// new race (auth lost, relationship deleted, shape bug) and
|
|
3029
|
+
// should surface immediately — without this branch the loop
|
|
3030
|
+
// would spin until timeout and report a misleading
|
|
3031
|
+
// "still pending_lock_finalization" reason. 5xx + network
|
|
3032
|
+
// errors are the actual transients we want to ride out.
|
|
3033
|
+
swallowFetchErrors: true,
|
|
3034
|
+
onFetchError: (err) => {
|
|
3035
|
+
if (err instanceof ApiError && err.status >= 400 && err.status < 500) {
|
|
3036
|
+
throw err;
|
|
3037
|
+
}
|
|
3038
|
+
log(import_chalk6.default.dim(` poll: transient fetch error (${err instanceof Error ? err.message : String(err)})`));
|
|
3039
|
+
}
|
|
3040
|
+
});
|
|
3041
|
+
if (outcome.kind === "timeout") {
|
|
3042
|
+
throw new Error(
|
|
3043
|
+
`${cmdName}: --wait-for-lock timed out after ${args.timeoutSec}s; delegation ${args.delegationId} still in pending_lock_finalization. Retry the command (the lock may have confirmed in the meantime), bump --lock-wait-timeout, or pass --no-wait-for-lock to skip the pre-flight and surface the 409 directly.`
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
if (outcome.kind === "aborted") {
|
|
3047
|
+
throw new Error(`${cmdName}: --wait-for-lock aborted before delegation ${args.delegationId} exited pending_lock_finalization`);
|
|
3048
|
+
}
|
|
3049
|
+
const row = outcome.value;
|
|
3050
|
+
if (row === null) {
|
|
3051
|
+
throw new Error(
|
|
3052
|
+
`${cmdName}: delegation ${args.delegationId} disappeared from relationship ${args.relationshipId} during --wait-for-lock pre-flight. Re-check the delegation id with \`heyarp delegations ${args.relationshipId}\`.`
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
if (row.state !== "offered" && row.state !== "proposed") {
|
|
3056
|
+
throw new Error(
|
|
3057
|
+
`${cmdName}: delegation ${args.delegationId} transitioned to '${row.state}' while waiting for on-chain lock confirmation; cannot ${args.action}. Re-read the delegation timeline with \`heyarp delegations ${args.relationshipId}\` to see the latest state.`
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
3060
|
+
log(import_chalk6.default.dim(`[--wait-for-lock] delegation ${args.delegationId} ready (state=${row.state}); proceeding with ${args.action}.`));
|
|
3061
|
+
return row;
|
|
3062
|
+
}
|
|
3063
|
+
function parseLockWaitTimeout(cmdName, raw) {
|
|
3064
|
+
if (raw === void 0) return 300;
|
|
3065
|
+
const n = Number(raw);
|
|
3066
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
|
|
3067
|
+
throw new Error(`${cmdName}: --lock-wait-timeout must be a positive integer number of seconds (got '${raw}')`);
|
|
3068
|
+
}
|
|
3069
|
+
if (n > 3600) {
|
|
3070
|
+
throw new Error(`${cmdName}: --lock-wait-timeout must be <= 3600 seconds (1h). Got ${n}; if the on-chain lock is taking that long something else is broken.`);
|
|
3071
|
+
}
|
|
3072
|
+
return n;
|
|
3073
|
+
}
|
|
3074
|
+
function parseLockWaitInterval(cmdName, raw) {
|
|
3075
|
+
if (raw === void 0) return 3;
|
|
3076
|
+
const n = Number(raw);
|
|
3077
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
|
|
3078
|
+
throw new Error(`${cmdName}: --lock-wait-interval must be a positive integer number of seconds (got '${raw}')`);
|
|
3079
|
+
}
|
|
3080
|
+
if (n < 1 || n > 60) {
|
|
3081
|
+
throw new Error(`${cmdName}: --lock-wait-interval must be in [1, 60] seconds (got ${n}). Sub-second polling spams the server; > 60s defeats the point of the pre-flight.`);
|
|
3082
|
+
}
|
|
3083
|
+
return n;
|
|
2943
3084
|
}
|
|
2944
3085
|
function printIngestResult(result) {
|
|
2945
3086
|
console.log(import_chalk6.default.green("\nDelivered."));
|
|
@@ -3071,7 +3212,13 @@ function registerPropose(parent) {
|
|
|
3071
3212
|
).option(
|
|
3072
3213
|
"--rate-decimals <n>",
|
|
3073
3214
|
"Decimal places for base-unit conversion (0-18). Required only when --rate-currency is raw CAIP-19; shorthand presets bring their own (USDC=6, SOL=9)."
|
|
3074
|
-
).option("--rate-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Free text, max 16 chars. Shorthand presets set this automatically.').option("--rate-unit <s>", "rate_unit (task|thread|handoff)").option(
|
|
3215
|
+
).option("--rate-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Free text, max 16 chars. Shorthand presets set this automatically.').option("--rate-unit <s>", "rate_unit (task|thread|handoff)").option(
|
|
3216
|
+
"--pricing <s>",
|
|
3217
|
+
"pricing_model (flat|usage_based). `quote` + `subscription` were dropped from the enum because they had no settlement-layer behaviour; add them back when real semantics ship."
|
|
3218
|
+
).option("--settlement <s>", "settlement_model (prepaid|escrow)").option("--delegation-tag <s>", "allowed_delegation_tags \u2014 repeatable; pass --delegation-tag once per tag", collectRepeated2, []).option("--ttl <seconds>", "Envelope TTL in seconds (max 86400 = 24h)", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
|
|
3219
|
+
"--wait-until <phase>",
|
|
3220
|
+
'Block after delivery until the named FSM phase is reached (e.g. contract.active). One of the UNTIL_PHASES from `heyarp status --help`. Exit code 124 on --wait-timeout. Makes "wait for counterparty to sign" a one-liner instead of a separate `heyarp status --wait --until contract.active` invocation.'
|
|
3221
|
+
).option("--wait-timeout <seconds>", "When --wait-until is set: max wall-clock wait (default 300). Exit code 124 on timeout.").option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option("--wait-verbose", "When --wait-until is set: emit one dim line per poll tick.", false).action(async (recipientDid, opts) => {
|
|
3075
3222
|
await runPropose(recipientDid, opts);
|
|
3076
3223
|
});
|
|
3077
3224
|
}
|
|
@@ -3102,9 +3249,29 @@ async function runPropose(recipientDid, opts) {
|
|
|
3102
3249
|
Reference this contract on subsequent calls with: heyarp contract <action> ${result.relationshipId} ${contractId}`));
|
|
3103
3250
|
console.log(import_chalk7.default.dim(` e.g. (counterparty signs) : heyarp contract sign ${result.relationshipId} ${contractId}`));
|
|
3104
3251
|
console.log(import_chalk7.default.dim(` e.g. (counterparty counters): heyarp contract counter ${result.relationshipId} ${contractId} --rate-amount <new>`));
|
|
3252
|
+
if (opts.waitUntil) {
|
|
3253
|
+
const untilPhase = parseUntilPhase(opts.waitUntil);
|
|
3254
|
+
if (untilPhase === void 0) {
|
|
3255
|
+
throw new Error(`contract propose: --wait-until requires a phase value (got ${JSON.stringify(opts.waitUntil)})`);
|
|
3256
|
+
}
|
|
3257
|
+
await awaitFsmTransitionAfterAction({
|
|
3258
|
+
api,
|
|
3259
|
+
signerDid: sender.did,
|
|
3260
|
+
signer: makeSigner(sender),
|
|
3261
|
+
relationshipId: result.relationshipId,
|
|
3262
|
+
untilPhase,
|
|
3263
|
+
waitIntervalSec: parseWaitInterval(opts.waitInterval),
|
|
3264
|
+
waitTimeoutSec: parseWaitTimeout(opts.waitTimeout),
|
|
3265
|
+
waitVerbose: !!opts.waitVerbose,
|
|
3266
|
+
json: false
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3105
3269
|
}
|
|
3106
3270
|
function registerCounter(parent) {
|
|
3107
|
-
parent.command("counter").description("Reply with revised terms to the latest PROPOSED version of <contract-id> in <relationship-id>.").argument("<relationship-id>", "Relationship UUID").argument("<contract-id>", "Contract UUID (proposed earlier in this relationship)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--version <n>", "Target the previous version explicitly (default: auto-resolve to the latest PROPOSED)").option("--scope <s>", "scope_summary \u2014 short prose describing the revised terms").option("--rate-amount <s>", "rate_amount \u2014 decimal as string. REQUIRES --rate-currency if set.").option("--rate-currency <s>", `Asset identifier: shorthand (${import_sdk5.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string.`).option("--rate-decimals <n>", "Decimal places for base-unit conversion (0-18). Required only when --rate-currency is raw CAIP-19.").option("--rate-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Max 16 chars.').option("--rate-unit <s>", "rate_unit (task|thread|handoff)").option(
|
|
3271
|
+
parent.command("counter").description("Reply with revised terms to the latest PROPOSED version of <contract-id> in <relationship-id>.").argument("<relationship-id>", "Relationship UUID").argument("<contract-id>", "Contract UUID (proposed earlier in this relationship)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--version <n>", "Target the previous version explicitly (default: auto-resolve to the latest PROPOSED)").option("--scope <s>", "scope_summary \u2014 short prose describing the revised terms").option("--rate-amount <s>", "rate_amount \u2014 decimal as string. REQUIRES --rate-currency if set.").option("--rate-currency <s>", `Asset identifier: shorthand (${import_sdk5.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string.`).option("--rate-decimals <n>", "Decimal places for base-unit conversion (0-18). Required only when --rate-currency is raw CAIP-19.").option("--rate-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Max 16 chars.').option("--rate-unit <s>", "rate_unit (task|thread|handoff)").option(
|
|
3272
|
+
"--pricing <s>",
|
|
3273
|
+
"pricing_model (flat|usage_based). `quote` + `subscription` were dropped from the enum because they had no settlement-layer behaviour; add them back when real semantics ship."
|
|
3274
|
+
).option("--settlement <s>", "settlement_model (prepaid|escrow)").option("--delegation-tag <s>", "allowed_delegation_tags \u2014 repeatable", collectRepeated2, []).option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).action(async (relationshipId, contractId, opts) => {
|
|
3108
3275
|
await runCounter(relationshipId, contractId, opts);
|
|
3109
3276
|
});
|
|
3110
3277
|
}
|
|
@@ -3901,7 +4068,9 @@ function registerEscrowCommands(root) {
|
|
|
3901
4068
|
cmd.command("info").description("Show the on-chain protocol fee config + cluster + pause state. Public read; no auth.").option("--server <url>", "Override ARP server base URL").option("--json", "Machine-readable JSON output", false).action(async (opts) => {
|
|
3902
4069
|
await runEscrowInfo(opts);
|
|
3903
4070
|
});
|
|
3904
|
-
cmd.command("derive-condition-hash").description(
|
|
4071
|
+
cmd.command("derive-condition-hash").description(
|
|
4072
|
+
"Compute canonical condition_hash (32-byte hex) for `heyarp wallet create-lock --condition-hash <hex>`. Fetches the contract row, projects to the canonical subset, and hashes via SDK deriveConditionHash. Mirrors what the on-chain create_lock instruction binds."
|
|
4073
|
+
).argument("<relationship-id>", "Relationship UUID hosting the contract").argument("<contract-id>", "Contract UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--version <n>", "Pin a specific contract version (default: latest non-replaced row)").option("--json", "Machine-readable JSON: {contract_id, version, condition_hash_hex, projected_subset}", false).action(async (relationshipId, contractId, opts) => {
|
|
3905
4074
|
await runDeriveConditionHash(relationshipId, contractId, opts);
|
|
3906
4075
|
});
|
|
3907
4076
|
cmd.command("recover-sequence").description(
|
|
@@ -4344,20 +4513,21 @@ var GUIDE = [
|
|
|
4344
4513
|
" For ONE envelope by id (cited in a receipt, copied from inbox):",
|
|
4345
4514
|
" \u2022 `heyarp envelope <event-id> --json | jq` \u2014 single signed read.",
|
|
4346
4515
|
"",
|
|
4347
|
-
import_chalk14.default.bold("8. Live tail vs polling"),
|
|
4348
|
-
" `heyarp inbox
|
|
4349
|
-
"
|
|
4350
|
-
"
|
|
4351
|
-
"
|
|
4352
|
-
"
|
|
4353
|
-
"
|
|
4354
|
-
"
|
|
4355
|
-
"
|
|
4356
|
-
"
|
|
4357
|
-
"
|
|
4358
|
-
" If you must poll
|
|
4359
|
-
" heyarp inbox --since <
|
|
4360
|
-
"
|
|
4516
|
+
import_chalk14.default.bold("8. Live tail vs polling \u2014 the FSM-wait pattern"),
|
|
4517
|
+
" ANTIPATTERN: bash-loop `heyarp inbox` every 5s. Three fixes:",
|
|
4518
|
+
" (a) `--wait-until <phase>` on the action itself \u2014 most ergonomic:",
|
|
4519
|
+
" heyarp delegation offer <recip> <ctr> --wait-until delegation.accepted",
|
|
4520
|
+
" heyarp contract propose <recip> ... --wait-until contract.active",
|
|
4521
|
+
" heyarp receipt cosign <rel> <del> ... --wait-until cycle.released",
|
|
4522
|
+
" (b) `heyarp status <rel-id> --wait --until <phase>` standalone",
|
|
4523
|
+
" (use when the action already exited). Exit 124 on --wait-timeout.",
|
|
4524
|
+
" (c) `heyarp inbox --tail` SSE stream for long-running workers.",
|
|
4525
|
+
" `stream ended unexpectedly` (exit \u2260 0) = server EOF; re-run.",
|
|
4526
|
+
" `stream closed.` (exit 0) = your Ctrl-C; nothing to fix.",
|
|
4527
|
+
" If you must poll without SSE/--wait, persist a cursor:",
|
|
4528
|
+
" heyarp inbox --since <ts> --since-event-id <evt> --json",
|
|
4529
|
+
" SDK consumers: `pollUntil` from `@heyanon-arp/sdk` gives the",
|
|
4530
|
+
" same loop discipline with abort + timeout primitives.",
|
|
4361
4531
|
"",
|
|
4362
4532
|
import_chalk14.default.bold("9. Receipt closure semantics + settlement signatures"),
|
|
4363
4533
|
" - The PAYEE proposes (`heyarp receipt propose`) with their verdict +",
|
|
@@ -4685,12 +4855,8 @@ ${events.length} event(s) (oldest-first).${addressedToMeHint} Advance the forwar
|
|
|
4685
4855
|
)
|
|
4686
4856
|
);
|
|
4687
4857
|
} else {
|
|
4688
|
-
console.log(
|
|
4689
|
-
|
|
4690
|
-
`
|
|
4691
|
-
${events.length} event(s).${addressedToMeHint} Paginate with --before <serverTimestamp> --before-event-id <eventId> using the LAST row above.`
|
|
4692
|
-
)
|
|
4693
|
-
);
|
|
4858
|
+
console.log(import_chalk16.default.dim(`
|
|
4859
|
+
${events.length} event(s).${addressedToMeHint} Paginate with --before <serverTimestamp> --before-event-id <eventId> using the LAST row above.`));
|
|
4694
4860
|
}
|
|
4695
4861
|
}
|
|
4696
4862
|
async function runInboxTail(did, local, opts) {
|
|
@@ -5044,7 +5210,10 @@ function formatMemoryLine(r) {
|
|
|
5044
5210
|
return `${import_chalk19.default.dim(idHead5)} | ${import_chalk19.default.magenta(r.kind)} | ${import_chalk19.default.dim(r.scope)} | ${import_chalk19.default.dim(authorTail)} | ${import_chalk19.default.cyan(`"${contentPreview}"`)}${cosignedTag}${gatedTag}`;
|
|
5045
5211
|
}
|
|
5046
5212
|
function registerShow(parent) {
|
|
5047
|
-
parent.command("show").description("Fetch one memory entry by its server `_id`.").argument(
|
|
5213
|
+
parent.command("show").description("Fetch one memory entry by its server `_id`.").argument(
|
|
5214
|
+
"<entry-id>",
|
|
5215
|
+
'Memory entry `id` (24-hex string). Get this from `heyarp memory list <rel-id> --json | jq ".[].id"`. The public DTO surfaces Mongo `_id` as the `id` field.'
|
|
5216
|
+
).option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option("--json", "Machine-readable: emit only the JSON object on stdout (no chalk).", false).action(async (entryId, opts) => {
|
|
5048
5217
|
await runShow(entryId, opts);
|
|
5049
5218
|
});
|
|
5050
5219
|
}
|
|
@@ -5095,14 +5264,15 @@ function parseLimit6(cmdName, raw) {
|
|
|
5095
5264
|
}
|
|
5096
5265
|
|
|
5097
5266
|
// src/commands/receipt.ts
|
|
5267
|
+
var import_node_fs7 = require("fs");
|
|
5098
5268
|
var import_sdk11 = require("@heyanon-arp/sdk");
|
|
5099
5269
|
var import_chalk20 = __toESM(require("chalk"));
|
|
5100
|
-
var import_node_fs7 = require("fs");
|
|
5101
5270
|
init_api();
|
|
5102
5271
|
function registerReceiptCommands(root) {
|
|
5103
5272
|
const cmd = root.command("receipt").description("Receipt envelopes \u2014 payee proposes, caller cosigns");
|
|
5104
5273
|
registerPropose2(cmd);
|
|
5105
5274
|
registerCosign(cmd);
|
|
5275
|
+
registerSendPayeeSig(cmd);
|
|
5106
5276
|
}
|
|
5107
5277
|
var POST_COMMIT_ERROR_CODES4 = /* @__PURE__ */ new Set([
|
|
5108
5278
|
"RECEIPT_ALREADY_EXISTS",
|
|
@@ -5156,7 +5326,32 @@ var POST_COMMIT_ERROR_CODES4 = /* @__PURE__ */ new Set([
|
|
|
5156
5326
|
// lock.amount. Same lifecycle classification as MISMATCH — event
|
|
5157
5327
|
// committed, body action rejected, CLI advances
|
|
5158
5328
|
// lastSenderSequence.
|
|
5159
|
-
"ESC_USAGE_COMPUTED_AMOUNT_EXCEEDS_LOCK"
|
|
5329
|
+
"ESC_USAGE_COMPUTED_AMOUNT_EXCEEDS_LOCK",
|
|
5330
|
+
// `settlement_signature` envelope handler rejection codes. ALL
|
|
5331
|
+
// fire AFTER the event row is committed but BEFORE the
|
|
5332
|
+
// receipt.payeeSettlement is set (server-side
|
|
5333
|
+
// PRE_MATERIALIZATION_REJECTION_CODES classification). Sender
|
|
5334
|
+
// sequence is consumed regardless — the envelope row sits in the
|
|
5335
|
+
// chain with readModelStatus='rejected'. Without these on the
|
|
5336
|
+
// allowlist, a CLI retry after fixing the sig would reuse the
|
|
5337
|
+
// old sequence and trip ENV_SEQUENCE_BACKWARDS.
|
|
5338
|
+
//
|
|
5339
|
+
// Sourced via:
|
|
5340
|
+
// grep "'SETTLEMENT_SIG_" apps/arp-server/src/message/services/settlement-signature-handler.service.ts \
|
|
5341
|
+
// | sed -E "s/.*'(SETTLEMENT_SIG_[A-Z_]+)'.*/\1/" | sort -u
|
|
5342
|
+
"SETTLEMENT_SIG_RECEIPT_NOT_FOUND",
|
|
5343
|
+
"SETTLEMENT_SIG_RECEIPT_INVALID_STATE",
|
|
5344
|
+
"SETTLEMENT_SIG_SENDER_NOT_PAYEE",
|
|
5345
|
+
"SETTLEMENT_SIG_VERDICT_NOT_PAYABLE",
|
|
5346
|
+
"SETTLEMENT_SIG_PURPOSE_INVALID",
|
|
5347
|
+
"SETTLEMENT_SIG_PAYEE_AMOUNT_MISMATCH_USAGE",
|
|
5348
|
+
"SETTLEMENT_SIG_LOCK_NOT_FOUND",
|
|
5349
|
+
"SETTLEMENT_SIG_LOCK_INVALID_STATE",
|
|
5350
|
+
"SETTLEMENT_SIG_PUBKEY_MISMATCH",
|
|
5351
|
+
"SETTLEMENT_SIG_PAYEE_AMOUNT_EXCEEDS_LOCK",
|
|
5352
|
+
"SETTLEMENT_SIG_EXPIRES_AT_PAST",
|
|
5353
|
+
"SETTLEMENT_SIG_EXPIRES_AT_TOO_SOON",
|
|
5354
|
+
"SETTLEMENT_SIG_EXPIRES_AT_EXCEEDS_LOCK"
|
|
5160
5355
|
]);
|
|
5161
5356
|
var VERDICT_VALUES = ["accepted", "accepted_with_notes", "rejected"];
|
|
5162
5357
|
var SHA256_RE = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -5206,7 +5401,11 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5206
5401
|
}
|
|
5207
5402
|
if (!opts.requestId) {
|
|
5208
5403
|
opts.requestId = await resolveAutoRequestId(api, sender, opts.relId, delegationId);
|
|
5209
|
-
console.log(
|
|
5404
|
+
console.log(
|
|
5405
|
+
import_chalk20.default.dim(
|
|
5406
|
+
`[auto-request-id] resolved --request-id=${opts.requestId} (unique 'responded' work_log under this delegation; pass --request-id explicitly to override)`
|
|
5407
|
+
)
|
|
5408
|
+
);
|
|
5210
5409
|
}
|
|
5211
5410
|
requireUuid3("receipt propose", opts.relId, "--rel-id");
|
|
5212
5411
|
const computed = await computeWorkLogHashes(api, sender, opts.relId, delegationId, opts.requestId);
|
|
@@ -5257,9 +5456,7 @@ Receipt event hash: ${import_chalk20.default.cyan(result.serverEventHash)}`));
|
|
|
5257
5456
|
}
|
|
5258
5457
|
async function assertSenderIsReceiptPayee(api, sender, relationshipId, delegationId) {
|
|
5259
5458
|
const signer = makeSigner(sender);
|
|
5260
|
-
const rows = await fetchAllPages(
|
|
5261
|
-
(after) => api.listDelegations(relationshipId, signer, { limit: 100, ...after ? { after } : {} })
|
|
5262
|
-
);
|
|
5459
|
+
const rows = await fetchAllPages((after) => api.listDelegations(relationshipId, signer, { limit: 100, ...after ? { after } : {} }));
|
|
5263
5460
|
const delegation = rows.find((d) => d.delegationId === delegationId);
|
|
5264
5461
|
if (!delegation) {
|
|
5265
5462
|
return;
|
|
@@ -5272,9 +5469,7 @@ async function assertSenderIsReceiptPayee(api, sender, relationshipId, delegatio
|
|
|
5272
5469
|
}
|
|
5273
5470
|
async function assertSenderIsReceiptCaller(api, sender, relationshipId, delegationId) {
|
|
5274
5471
|
const signer = makeSigner(sender);
|
|
5275
|
-
const rows = await fetchAllPages(
|
|
5276
|
-
(after) => api.listDelegations(relationshipId, signer, { limit: 100, ...after ? { after } : {} })
|
|
5277
|
-
);
|
|
5472
|
+
const rows = await fetchAllPages((after) => api.listDelegations(relationshipId, signer, { limit: 100, ...after ? { after } : {} }));
|
|
5278
5473
|
const delegation = rows.find((d) => d.delegationId === delegationId);
|
|
5279
5474
|
if (!delegation) {
|
|
5280
5475
|
return;
|
|
@@ -5311,7 +5506,9 @@ async function resolveAutoRelId(api, sender, delegationId) {
|
|
|
5311
5506
|
}
|
|
5312
5507
|
async function resolveAutoRequestId(api, sender, relationshipId, delegationId) {
|
|
5313
5508
|
const signer = makeSigner(sender);
|
|
5314
|
-
const logs = await fetchAllPages(
|
|
5509
|
+
const logs = await fetchAllPages(
|
|
5510
|
+
(after) => api.listWorkLogs(relationshipId, signer, { delegationId, state: "responded", limit: 100, ...after ? { after } : {} })
|
|
5511
|
+
);
|
|
5315
5512
|
if (logs.length === 0) {
|
|
5316
5513
|
throw new Error(
|
|
5317
5514
|
`receipt propose --auto-hashes: no 'responded' work_log under delegation ${delegationId} in relationship ${relationshipId} \u2014 the payee must send \`work respond\` before a receipt can be proposed. Pass --request-id explicitly if you have a known one.`
|
|
@@ -5366,20 +5563,38 @@ function registerCosign(parent) {
|
|
|
5366
5563
|
).option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
|
|
5367
5564
|
"--settlement-purpose <s>",
|
|
5368
5565
|
"Settlement digest purpose: `ARP-SOLANA-RELEASE-v1.5` (full) or `ARP-SOLANA-PARTIAL-RELEASE-v1.5` (partial; requires --settlement-payee-amount)."
|
|
5369
|
-
).option(
|
|
5566
|
+
).option(
|
|
5567
|
+
"--settlement-expires-at <unix>",
|
|
5568
|
+
"Settlement expires_at (unix seconds) \u2014 must match the value signed by BOTH parties when they ran `heyarp wallet sign-settlement-release`."
|
|
5569
|
+
).option("--payer-settlement-pubkey <b58>", "Payer settlement pubkey (base58) \u2014 must equal `lock.payer`. Mutually exclusive with --payer-sig-from-file.").option("--payer-settlement-sig <base64>", "Payer Ed25519 settlement signature (raw base64, NO `ed25519:` prefix). Mutually exclusive with --payer-sig-from-file.").option("--payee-settlement-pubkey <b58>", "Payee settlement pubkey (base58) \u2014 must equal `lock.payee`. Mutually exclusive with --payee-sig-from-file.").option("--payee-settlement-sig <base64>", "Payee Ed25519 settlement signature (raw base64, NO `ed25519:` prefix). Mutually exclusive with --payee-sig-from-file.").option(
|
|
5570
|
+
"--auto-resolve-payee-sig",
|
|
5571
|
+
"Read the payee's settlement signature from `receipt.payeeSettlement` (populated by the worker's `heyarp receipt send-payee-sig` envelope) instead of requiring --payee-settlement-pubkey + --payee-settlement-sig + --settlement-purpose + --settlement-expires-at + --settlement-payee-amount flags. Closes the cross-host-coordination gap that earlier flows worked around via shared /tmp/*.json files. Mutually exclusive with --payee-sig-from-file and the explicit --payee-settlement-* flags. Buyer still supplies the PAYER side themselves (--payer-sig-from-file or --payer-settlement-* flags).",
|
|
5572
|
+
false
|
|
5573
|
+
).option(
|
|
5370
5574
|
"--payer-sig-from-file <path>",
|
|
5371
5575
|
"Read `{settlement_pubkey, sig}` for the PAYER side from the JSON file produced by `heyarp wallet sign-settlement-release --write-to <path>`. Populates --payer-settlement-pubkey + --payer-settlement-sig."
|
|
5372
5576
|
).option(
|
|
5373
5577
|
"--payee-sig-from-file <path>",
|
|
5374
5578
|
"Read `{settlement_pubkey, sig}` for the PAYEE side from the JSON file produced by `heyarp wallet sign-settlement-release --write-to <path>`. Populates --payee-settlement-pubkey + --payee-settlement-sig."
|
|
5375
|
-
).option(
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5579
|
+
).option(
|
|
5580
|
+
"--settlement-payee-amount <int>",
|
|
5581
|
+
"Required ONLY when --settlement-purpose=ARP-SOLANA-PARTIAL-RELEASE-v1.5. Payee receives this many base units; payer is refunded `lock.amount - payee_amount`."
|
|
5582
|
+
).option(
|
|
5583
|
+
"--no-settlement",
|
|
5584
|
+
"Opt out of settlement_signatures attachment. Default behavior REQUIRES the full settlement flag set; without --no-settlement, omitting flags errors loud BEFORE the envelope is sent. Use --no-settlement only against a server running in test_mode."
|
|
5585
|
+
).option(
|
|
5586
|
+
"--wait-until <phase>",
|
|
5587
|
+
"Block after cosign-delivery until the named FSM phase is reached (e.g. cycle.released \u2014 strongest, waits for on-chain settlement; cycle.complete \u2014 weaker, waits only for the cosigned-receipt row). One of UNTIL_PHASES from `heyarp status --help`. Exit 124 on --wait-timeout."
|
|
5588
|
+
).option("--wait-timeout <seconds>", "When --wait-until is set: max wall-clock wait (default 300). Exit code 124 on timeout.").option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option("--wait-verbose", "When --wait-until is set: emit one dim line per poll tick.", false).action(
|
|
5589
|
+
async (relationshipId, delegationId, requestHashArg, responseHashArg, opts, cmd) => {
|
|
5590
|
+
try {
|
|
5591
|
+
await runCosign(relationshipId, delegationId, requestHashArg, responseHashArg, opts);
|
|
5592
|
+
} catch (err) {
|
|
5593
|
+
console.error(formatActionError(err, cmd));
|
|
5594
|
+
process.exitCode = 1;
|
|
5595
|
+
}
|
|
5381
5596
|
}
|
|
5382
|
-
|
|
5597
|
+
);
|
|
5383
5598
|
}
|
|
5384
5599
|
function loadSettlementSigFromFile(path, flagPrefix) {
|
|
5385
5600
|
let raw;
|
|
@@ -5403,7 +5618,9 @@ function loadSettlementSigFromFile(path, flagPrefix) {
|
|
|
5403
5618
|
const digestHex = p.digest_hex;
|
|
5404
5619
|
const purpose = p.purpose;
|
|
5405
5620
|
if (typeof pubkey !== "string" || pubkey.length === 0) {
|
|
5406
|
-
throw new Error(
|
|
5621
|
+
throw new Error(
|
|
5622
|
+
`receipt cosign: ${flagPrefix} '${path}' missing required field 'settlement_pubkey' (string). Expected JSON output of \`heyarp wallet sign-settlement-release\`.`
|
|
5623
|
+
);
|
|
5407
5624
|
}
|
|
5408
5625
|
if (typeof sig !== "string" || sig.length === 0) {
|
|
5409
5626
|
throw new Error(`receipt cosign: ${flagPrefix} '${path}' missing required field 'sig' (string). Expected JSON output of \`heyarp wallet sign-settlement-release\`.`);
|
|
@@ -5416,6 +5633,43 @@ function loadSettlementSigFromFile(path, flagPrefix) {
|
|
|
5416
5633
|
}
|
|
5417
5634
|
return { settlement_pubkey: pubkey, sig, digest_hex: digestHex, purpose };
|
|
5418
5635
|
}
|
|
5636
|
+
function applyAutoResolvePayeeSig(cmdName, opts, payeeSettlement) {
|
|
5637
|
+
if (opts.payeeSigFromFile !== void 0 && opts.payeeSigFromFile !== "") {
|
|
5638
|
+
throw new Error(`${cmdName}: --auto-resolve-payee-sig is mutually exclusive with --payee-sig-from-file. Pass either the auto-resolve flag OR the file path, not both.`);
|
|
5639
|
+
}
|
|
5640
|
+
if (opts.payeeSettlementPubkey !== void 0 && opts.payeeSettlementPubkey !== "" || opts.payeeSettlementSig !== void 0 && opts.payeeSettlementSig !== "") {
|
|
5641
|
+
throw new Error(
|
|
5642
|
+
`${cmdName}: --auto-resolve-payee-sig is mutually exclusive with --payee-settlement-pubkey / --payee-settlement-sig. Pass either the auto-resolve flag OR the explicit values, not both.`
|
|
5643
|
+
);
|
|
5644
|
+
}
|
|
5645
|
+
if (!payeeSettlement) {
|
|
5646
|
+
throw new Error(
|
|
5647
|
+
`${cmdName}: --auto-resolve-payee-sig requires receipt.payeeSettlement to be populated, but the field is unset. The worker hasn't sent its 'heyarp receipt send-payee-sig' envelope yet \u2014 wait for it OR fall back to --payee-sig-from-file / --payee-settlement-* flags for out-of-band coordination.`
|
|
5648
|
+
);
|
|
5649
|
+
}
|
|
5650
|
+
opts.payeeSettlementPubkey = payeeSettlement.settlement_pubkey;
|
|
5651
|
+
opts.payeeSettlementSig = payeeSettlement.sig;
|
|
5652
|
+
if (opts.settlementPurpose !== void 0 && opts.settlementPurpose !== "" && opts.settlementPurpose !== payeeSettlement.purpose) {
|
|
5653
|
+
throw new Error(
|
|
5654
|
+
`${cmdName}: --settlement-purpose='${opts.settlementPurpose}' disagrees with the auto-resolved purpose from receipt.payeeSettlement ('${payeeSettlement.purpose}'). Drop --settlement-purpose to use the auto-resolved value, or drop --auto-resolve-payee-sig to use the manual flag.`
|
|
5655
|
+
);
|
|
5656
|
+
}
|
|
5657
|
+
opts.settlementPurpose = payeeSettlement.purpose;
|
|
5658
|
+
if (opts.settlementExpiresAt !== void 0 && opts.settlementExpiresAt !== "" && opts.settlementExpiresAt !== String(payeeSettlement.expires_at)) {
|
|
5659
|
+
throw new Error(
|
|
5660
|
+
`${cmdName}: --settlement-expires-at='${opts.settlementExpiresAt}' disagrees with the auto-resolved expires_at from receipt.payeeSettlement (${payeeSettlement.expires_at}). Drop --settlement-expires-at to use the auto-resolved value, or drop --auto-resolve-payee-sig.`
|
|
5661
|
+
);
|
|
5662
|
+
}
|
|
5663
|
+
opts.settlementExpiresAt = String(payeeSettlement.expires_at);
|
|
5664
|
+
if (payeeSettlement.payee_amount !== void 0) {
|
|
5665
|
+
if (opts.settlementPayeeAmount !== void 0 && opts.settlementPayeeAmount !== "" && opts.settlementPayeeAmount !== payeeSettlement.payee_amount) {
|
|
5666
|
+
throw new Error(
|
|
5667
|
+
`${cmdName}: --settlement-payee-amount='${opts.settlementPayeeAmount}' disagrees with the auto-resolved payee_amount from receipt.payeeSettlement ('${payeeSettlement.payee_amount}'). Drop --settlement-payee-amount to use the auto-resolved value, or drop --auto-resolve-payee-sig.`
|
|
5668
|
+
);
|
|
5669
|
+
}
|
|
5670
|
+
opts.settlementPayeeAmount = payeeSettlement.payee_amount;
|
|
5671
|
+
}
|
|
5672
|
+
}
|
|
5419
5673
|
function assembleSettlementSignaturesAttachment(opts) {
|
|
5420
5674
|
let payerPubkey = opts.payerSettlementPubkey;
|
|
5421
5675
|
let payerSig = opts.payerSettlementSig;
|
|
@@ -5463,14 +5717,7 @@ function assembleSettlementSignaturesAttachment(opts) {
|
|
|
5463
5717
|
`receipt cosign: --settlement-purpose='${opts.settlementPurpose}' disagrees with the purpose signed in the sig file ('${filePurpose}'). Either drop --settlement-purpose (the file's value will be used) or re-sign with the desired purpose.`
|
|
5464
5718
|
);
|
|
5465
5719
|
}
|
|
5466
|
-
const settlementFlags = [
|
|
5467
|
-
opts.settlementPurpose,
|
|
5468
|
-
opts.settlementExpiresAt,
|
|
5469
|
-
payerPubkey,
|
|
5470
|
-
payerSig,
|
|
5471
|
-
payeePubkey,
|
|
5472
|
-
payeeSig
|
|
5473
|
-
];
|
|
5720
|
+
const settlementFlags = [opts.settlementPurpose, opts.settlementExpiresAt, payerPubkey, payerSig, payeePubkey, payeeSig];
|
|
5474
5721
|
const someSet = settlementFlags.some((f) => f !== void 0 && f !== "");
|
|
5475
5722
|
const allSet = settlementFlags.every((f) => f !== void 0 && f !== "");
|
|
5476
5723
|
if (!someSet) {
|
|
@@ -5489,9 +5736,7 @@ function assembleSettlementSignaturesAttachment(opts) {
|
|
|
5489
5736
|
}
|
|
5490
5737
|
const purpose = opts.settlementPurpose;
|
|
5491
5738
|
if (purpose !== "ARP-SOLANA-RELEASE-v1.5" && purpose !== "ARP-SOLANA-PARTIAL-RELEASE-v1.5") {
|
|
5492
|
-
throw new Error(
|
|
5493
|
-
`receipt cosign: --settlement-purpose must be 'ARP-SOLANA-RELEASE-v1.5' or 'ARP-SOLANA-PARTIAL-RELEASE-v1.5', got '${purpose}'.`
|
|
5494
|
-
);
|
|
5739
|
+
throw new Error(`receipt cosign: --settlement-purpose must be 'ARP-SOLANA-RELEASE-v1.5' or 'ARP-SOLANA-PARTIAL-RELEASE-v1.5', got '${purpose}'.`);
|
|
5495
5740
|
}
|
|
5496
5741
|
const isPartial = purpose === "ARP-SOLANA-PARTIAL-RELEASE-v1.5";
|
|
5497
5742
|
if (isPartial && (opts.settlementPayeeAmount === void 0 || opts.settlementPayeeAmount === "")) {
|
|
@@ -5612,6 +5857,12 @@ async function runCosign(relationshipId, delegationId, requestHashArg, responseH
|
|
|
5612
5857
|
} else if (opts.clearNotes) {
|
|
5613
5858
|
console.log(import_chalk20.default.dim("Notes binding: cleared (--clear-notes)"));
|
|
5614
5859
|
}
|
|
5860
|
+
if (opts.autoResolvePayeeSig) {
|
|
5861
|
+
applyAutoResolvePayeeSig("receipt cosign", opts, resolved.payeeSettlement);
|
|
5862
|
+
console.log(
|
|
5863
|
+
import_chalk20.default.dim(`Auto-resolved payee sig from receipt.payeeSettlement (purpose=${resolved.payeeSettlement?.purpose}, expires_at=${resolved.payeeSettlement?.expires_at})`)
|
|
5864
|
+
);
|
|
5865
|
+
}
|
|
5615
5866
|
const settlementSigs = assembleSettlementSignaturesAttachment(opts);
|
|
5616
5867
|
const attachments = { co_signature: cosignature };
|
|
5617
5868
|
if (settlementSigs) {
|
|
@@ -5629,6 +5880,149 @@ async function runCosign(relationshipId, delegationId, requestHashArg, responseH
|
|
|
5629
5880
|
server: opts.server
|
|
5630
5881
|
});
|
|
5631
5882
|
printIngestResult3(result);
|
|
5883
|
+
if (opts.waitUntil) {
|
|
5884
|
+
const untilPhase = parseUntilPhase(opts.waitUntil);
|
|
5885
|
+
if (untilPhase === void 0) {
|
|
5886
|
+
throw new Error(`receipt cosign: --wait-until requires a phase value (got ${JSON.stringify(opts.waitUntil)})`);
|
|
5887
|
+
}
|
|
5888
|
+
await awaitFsmTransitionAfterAction({
|
|
5889
|
+
api,
|
|
5890
|
+
signerDid: sender.did,
|
|
5891
|
+
signer: makeSigner(sender),
|
|
5892
|
+
relationshipId: result.relationshipId,
|
|
5893
|
+
untilPhase,
|
|
5894
|
+
waitIntervalSec: parseWaitInterval(opts.waitInterval),
|
|
5895
|
+
waitTimeoutSec: parseWaitTimeout(opts.waitTimeout),
|
|
5896
|
+
waitVerbose: !!opts.waitVerbose,
|
|
5897
|
+
json: false
|
|
5898
|
+
});
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
function registerSendPayeeSig(parent) {
|
|
5902
|
+
parent.command("send-payee-sig").description(
|
|
5903
|
+
"Send the payee's settlement signature to the buyer via a settlement_signature envelope. Replaces /tmp/*.json coordination for cross-host escrow cycles."
|
|
5904
|
+
).argument("<recipient-did>", "Buyer DID (= caller / offerer of the parent delegation; the cycle sends the sig to the side that will cosign)").requiredOption("--delegation-id <id>", "Parent delegation UUID \u2014 must match what was passed to `heyarp wallet sign-settlement-release`.").requiredOption(
|
|
5905
|
+
"--receipt-event-hash <sha256:hex>",
|
|
5906
|
+
"Server-assigned `serverEventHash` of the receipt-propose envelope this signature settles. Read it from the JSON response of `heyarp receipt propose` (the field is `Server event hash` in human output, or `.serverEventHash` in --json). MUST equal the value the digest was signed over \u2014 the server cross-checks against `Receipt.receiptEventHash` and rejects with SETTLEMENT_SIG_RECEIPT_NOT_FOUND otherwise."
|
|
5907
|
+
).requiredOption(
|
|
5908
|
+
"--sig-from-file <path>",
|
|
5909
|
+
"JSON file produced by `heyarp wallet sign-settlement-release --write-to <path>`. Reads `{settlement_pubkey, sig, purpose, digest_hex}` \u2014 the digest_hex is bound through for cross-file consistency (catches truncated or hand-edited files)."
|
|
5910
|
+
).requiredOption(
|
|
5911
|
+
"--expires-at <unix>",
|
|
5912
|
+
"Unix-seconds expires_at value baked into the signed digest \u2014 MUST match the value passed to `--expires-at` on `heyarp wallet sign-settlement-release`. Server cross-checks against the buyer-side value at cosign time; wrong value \u2192 ESC_SETTLEMENT_EXPIRES_AT_PAST/_TOO_SOON."
|
|
5913
|
+
).option(
|
|
5914
|
+
"--payee-amount <int>",
|
|
5915
|
+
"Base-unit decimal-integer payee_amount \u2014 REQUIRED when the sig file's purpose is `ARP-SOLANA-PARTIAL-RELEASE-v1.5` (usage_based contracts). Same value passed to `--partial-payee-amount` on sign-settlement-release. FORBIDDEN for `ARP-SOLANA-RELEASE-v1.5` (full release settles the whole lock)."
|
|
5916
|
+
).option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server (= payee)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).action(async (recipientDid, opts, cmd) => {
|
|
5917
|
+
try {
|
|
5918
|
+
await runSendPayeeSig(recipientDid, opts);
|
|
5919
|
+
} catch (err) {
|
|
5920
|
+
console.error(formatActionError(err, cmd));
|
|
5921
|
+
process.exitCode = 1;
|
|
5922
|
+
}
|
|
5923
|
+
});
|
|
5924
|
+
}
|
|
5925
|
+
async function runSendPayeeSig(recipientDid, opts) {
|
|
5926
|
+
const cmdName = "receipt send-payee-sig";
|
|
5927
|
+
requireDid3(cmdName, recipientDid, "<recipient-did>");
|
|
5928
|
+
const delegationId = requireUuidNormalised2(cmdName, opts.delegationId, "--delegation-id");
|
|
5929
|
+
requireSha256(cmdName, opts.receiptEventHash, "--receipt-event-hash");
|
|
5930
|
+
const expiresAtSeconds = parseInteger(cmdName, "--expires-at", opts.expiresAt);
|
|
5931
|
+
if (expiresAtSeconds <= 0) {
|
|
5932
|
+
throw new Error(`${cmdName}: --expires-at must be a positive unix-seconds integer (got ${opts.expiresAt})`);
|
|
5933
|
+
}
|
|
5934
|
+
const ttlSeconds = parseTtl4(cmdName, opts.ttl);
|
|
5935
|
+
const sigFile = loadSettlementSigFromFile(opts.sigFromFile, "--sig-from-file");
|
|
5936
|
+
const isPartial = sigFile.purpose === "ARP-SOLANA-PARTIAL-RELEASE-v1.5";
|
|
5937
|
+
const isFull = sigFile.purpose === "ARP-SOLANA-RELEASE-v1.5";
|
|
5938
|
+
if (!isPartial && !isFull) {
|
|
5939
|
+
throw new Error(
|
|
5940
|
+
`${cmdName}: sig file at '${opts.sigFromFile}' has purpose='${sigFile.purpose}' but only ARP-SOLANA-RELEASE-v1.5 and ARP-SOLANA-PARTIAL-RELEASE-v1.5 are valid on a settlement_signature envelope. (REFUND-v1 sigs ride other paths \u2014 they don't bind to receipt_event_hash, so this envelope isn't the right channel for them.)`
|
|
5941
|
+
);
|
|
5942
|
+
}
|
|
5943
|
+
if (isPartial && (opts.payeeAmount === void 0 || opts.payeeAmount === "")) {
|
|
5944
|
+
throw new Error(
|
|
5945
|
+
`${cmdName}: --payee-amount is REQUIRED when the sig file's purpose is ARP-SOLANA-PARTIAL-RELEASE-v1.5 (the server reconstructs the same digest at cosign and binds payee_amount into it; missing here = unverifiable digest).`
|
|
5946
|
+
);
|
|
5947
|
+
}
|
|
5948
|
+
if (isFull && opts.payeeAmount !== void 0 && opts.payeeAmount !== "") {
|
|
5949
|
+
throw new Error(
|
|
5950
|
+
`${cmdName}: --payee-amount must be OMITTED when the sig file's purpose is ARP-SOLANA-RELEASE-v1.5 (full release settles the whole lock; supplying an amount alongside the RELEASE purpose is almost certainly a wrong-purpose-flag bug).`
|
|
5951
|
+
);
|
|
5952
|
+
}
|
|
5953
|
+
if (opts.payeeAmount !== void 0 && opts.payeeAmount !== "" && !/^[0-9]+$/.test(opts.payeeAmount)) {
|
|
5954
|
+
throw new Error(
|
|
5955
|
+
`${cmdName}: --payee-amount must be a decimal-integer base-unit string (got '${opts.payeeAmount}'); hex/empty/signed/decimal forms are rejected \u2014 same invariant as receipt.usage.computed_amount.`
|
|
5956
|
+
);
|
|
5957
|
+
}
|
|
5958
|
+
const sender = resolveSenderAgent(cmdName, opts.server, opts.fromDid);
|
|
5959
|
+
const api = new ArpApiClient(opts.server);
|
|
5960
|
+
const content = {
|
|
5961
|
+
delegation_id: delegationId,
|
|
5962
|
+
receipt_event_hash: opts.receiptEventHash,
|
|
5963
|
+
purpose: sigFile.purpose,
|
|
5964
|
+
payee_settlement_pubkey: sigFile.settlement_pubkey,
|
|
5965
|
+
sig: sigFile.sig,
|
|
5966
|
+
expires_at: expiresAtSeconds,
|
|
5967
|
+
...isPartial ? { payee_amount: opts.payeeAmount } : {}
|
|
5968
|
+
};
|
|
5969
|
+
console.log(import_chalk20.default.dim(`Server: ${api.serverUrl}`));
|
|
5970
|
+
console.log(import_chalk20.default.dim(`Sender (payee): ${sender.did}`));
|
|
5971
|
+
console.log(import_chalk20.default.dim(`Recipient (buyer): ${recipientDid}`));
|
|
5972
|
+
console.log(import_chalk20.default.dim(`Delegation: ${delegationId}`));
|
|
5973
|
+
console.log(import_chalk20.default.dim(`Receipt event hash: ${opts.receiptEventHash}`));
|
|
5974
|
+
console.log(import_chalk20.default.dim(`Purpose: ${sigFile.purpose}`));
|
|
5975
|
+
if (isPartial) {
|
|
5976
|
+
console.log(import_chalk20.default.dim(`Payee amount: ${opts.payeeAmount}`));
|
|
5977
|
+
}
|
|
5978
|
+
console.log(import_chalk20.default.dim(`Settlement pubkey: ${sigFile.settlement_pubkey}`));
|
|
5979
|
+
console.log(import_chalk20.default.dim(`Expires at: ${expiresAtSeconds}`));
|
|
5980
|
+
const result = await sendSettlementSignatureEnvelope({
|
|
5981
|
+
api,
|
|
5982
|
+
sender,
|
|
5983
|
+
recipientDid,
|
|
5984
|
+
content,
|
|
5985
|
+
ttlSeconds,
|
|
5986
|
+
verbose: opts.verbose,
|
|
5987
|
+
server: opts.server
|
|
5988
|
+
});
|
|
5989
|
+
printIngestResult3(result);
|
|
5990
|
+
}
|
|
5991
|
+
async function sendSettlementSignatureEnvelope(args) {
|
|
5992
|
+
const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
|
|
5993
|
+
const protectedBlock = {
|
|
5994
|
+
protocol_version: "arp/0.1",
|
|
5995
|
+
purpose: import_sdk11.Purpose.ENVELOPE,
|
|
5996
|
+
message_id: (0, import_sdk11.uuidV4)(),
|
|
5997
|
+
sender_did: args.sender.did,
|
|
5998
|
+
recipient_did: args.recipientDid,
|
|
5999
|
+
relationship_id: null,
|
|
6000
|
+
sender_sequence: nextSequence,
|
|
6001
|
+
sender_nonce: (0, import_sdk11.senderNonce)(),
|
|
6002
|
+
timestamp: (0, import_sdk11.rfc3339)(),
|
|
6003
|
+
expires_at: (0, import_sdk11.expiresAt)(args.ttlSeconds),
|
|
6004
|
+
delivery_id: null
|
|
6005
|
+
};
|
|
6006
|
+
const signer = makeSigner(args.sender);
|
|
6007
|
+
const envelope = (0, import_sdk11.signEnvelope)({
|
|
6008
|
+
protected: protectedBlock,
|
|
6009
|
+
body: { type: "settlement_signature", content: args.content },
|
|
6010
|
+
identitySecretKey: signer.identitySecretKey
|
|
6011
|
+
});
|
|
6012
|
+
if (args.verbose) {
|
|
6013
|
+
console.log(import_chalk20.default.bold("\nEnvelope (pre-send):"));
|
|
6014
|
+
console.log(formatJson(envelope));
|
|
6015
|
+
}
|
|
6016
|
+
try {
|
|
6017
|
+
const result = await args.api.ingest(envelope);
|
|
6018
|
+
updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
|
|
6019
|
+
return result;
|
|
6020
|
+
} catch (err) {
|
|
6021
|
+
if (err instanceof ApiError && POST_COMMIT_ERROR_CODES4.has(err.payload.code)) {
|
|
6022
|
+
updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
|
|
6023
|
+
}
|
|
6024
|
+
throw err;
|
|
6025
|
+
}
|
|
5632
6026
|
}
|
|
5633
6027
|
async function sendReceiptEnvelope(args) {
|
|
5634
6028
|
const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
|
|
@@ -5703,7 +6097,13 @@ async function resolveCosignTargets(cmdName, api, signer, args) {
|
|
|
5703
6097
|
if (row.callerDid !== args.selfDid) {
|
|
5704
6098
|
throw new Error(`${cmdName}: this receipt's caller is ${row.callerDid}; only the caller can cosign. Switch with --from-did.`);
|
|
5705
6099
|
}
|
|
5706
|
-
return {
|
|
6100
|
+
return {
|
|
6101
|
+
payeeDid: row.payeeDid,
|
|
6102
|
+
receiptEventHash: row.receiptEventHash,
|
|
6103
|
+
verdictProposed: row.verdictProposed,
|
|
6104
|
+
notesHash: row.notesHash,
|
|
6105
|
+
payeeSettlement: row.payeeSettlement
|
|
6106
|
+
};
|
|
5707
6107
|
}
|
|
5708
6108
|
function printIngestResult3(result) {
|
|
5709
6109
|
console.log(import_chalk20.default.green("\nDelivered."));
|
|
@@ -6065,8 +6465,11 @@ async function runRegister(opts) {
|
|
|
6065
6465
|
\u2713 Published \u2014 discoverable now via \`heyarp agents\`.`));
|
|
6066
6466
|
} catch (err) {
|
|
6067
6467
|
publicationOutcome = `failed: ${err.message}`;
|
|
6068
|
-
if (!opts.json)
|
|
6069
|
-
|
|
6468
|
+
if (!opts.json)
|
|
6469
|
+
console.log(
|
|
6470
|
+
import_chalk22.default.yellow(`
|
|
6471
|
+
\u26A0 Auto-publish failed (${err.message}). Agent saved as DRAFT \u2014 run \`heyarp publish ${result.did}\` to make it discoverable.`)
|
|
6472
|
+
);
|
|
6070
6473
|
}
|
|
6071
6474
|
} else if (decision === "skip-no-endpoint") {
|
|
6072
6475
|
publicationOutcome = "skip-no-endpoint";
|
|
@@ -6616,7 +7019,9 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
6616
7019
|
if (previousResponseFromMe) {
|
|
6617
7020
|
console.log(import_chalk26.default.yellow(`
|
|
6618
7021
|
[--idempotency] Relationship ${existing.relationshipId} with ${recipientDid} is already 'active'.`));
|
|
6619
|
-
console.log(
|
|
7022
|
+
console.log(
|
|
7023
|
+
import_chalk26.default.dim(`A previous accept from this signer (event ${previousResponseFromMe.eventId}) landed successfully. Skipping re-send (use --force to override).`)
|
|
7024
|
+
);
|
|
6620
7025
|
console.log(import_chalk26.default.dim(`Last event index: ${existing.lastEventIndex}, last server event hash: ${existing.lastServerEventHash ?? "(none)"}`));
|
|
6621
7026
|
return;
|
|
6622
7027
|
}
|
|
@@ -7292,11 +7697,7 @@ async function checkForUpdates() {
|
|
|
7292
7697
|
async function main() {
|
|
7293
7698
|
void checkForUpdates();
|
|
7294
7699
|
const program = new import_commander.Command();
|
|
7295
|
-
program.name("heyarp").description("ARP \u2014 Agent Relationship Protocol CLI (talks to apps/arp-server)").version(
|
|
7296
|
-
"--trace",
|
|
7297
|
-
"Surface stack traces and error details on failure.",
|
|
7298
|
-
false
|
|
7299
|
-
);
|
|
7700
|
+
program.name("heyarp").description("ARP \u2014 Agent Relationship Protocol CLI (talks to apps/arp-server)").version(package_default.version).option("--trace", "Surface stack traces and error details on failure.", false);
|
|
7300
7701
|
registerConfigCommand(program);
|
|
7301
7702
|
registerGuideCommand(program);
|
|
7302
7703
|
registerHomesCommand(program);
|