@perkos/agent-sdk 0.7.1 → 0.8.0-rc.1
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/CHANGELOG.md +50 -0
- package/README.md +69 -17
- package/dist/builders.d.ts +5 -1
- package/dist/builders.d.ts.map +1 -1
- package/dist/builders.js +155 -11
- package/dist/builders.js.map +1 -1
- package/dist/client.d.ts +8 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +168 -26
- package/dist/client.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/custody/backend.d.ts +4 -0
- package/dist/custody/backend.d.ts.map +1 -0
- package/dist/custody/backend.js +149 -0
- package/dist/custody/backend.js.map +1 -0
- package/dist/custody/cli.d.ts +3 -0
- package/dist/custody/cli.d.ts.map +1 -0
- package/dist/custody/cli.js +43 -0
- package/dist/custody/cli.js.map +1 -0
- package/dist/custody/engine.d.ts +54 -0
- package/dist/custody/engine.d.ts.map +1 -0
- package/dist/custody/engine.js +115 -0
- package/dist/custody/engine.js.map +1 -0
- package/dist/custody/ledger.d.ts +24 -0
- package/dist/custody/ledger.d.ts.map +1 -0
- package/dist/custody/ledger.js +122 -0
- package/dist/custody/ledger.js.map +1 -0
- package/dist/custody/permit.d.ts +42 -0
- package/dist/custody/permit.d.ts.map +1 -0
- package/dist/custody/permit.js +91 -0
- package/dist/custody/permit.js.map +1 -0
- package/dist/custody/socket.d.ts +11 -0
- package/dist/custody/socket.d.ts.map +1 -0
- package/dist/custody/socket.js +118 -0
- package/dist/custody/socket.js.map +1 -0
- package/dist/evaluation-commitments.d.ts +47 -0
- package/dist/evaluation-commitments.d.ts.map +1 -0
- package/dist/evaluation-commitments.js +96 -0
- package/dist/evaluation-commitments.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/cli.d.ts +3 -0
- package/dist/mcp/cli.d.ts.map +1 -0
- package/dist/mcp/cli.js +36 -0
- package/dist/mcp/cli.js.map +1 -0
- package/dist/mcp/evaluation.d.ts +33 -0
- package/dist/mcp/evaluation.d.ts.map +1 -0
- package/dist/mcp/evaluation.js +141 -0
- package/dist/mcp/evaluation.js.map +1 -0
- package/dist/mcp/server.d.ts +27 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +207 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +7 -2
- package/dist/policy.js.map +1 -1
- package/dist/service-fees.d.ts +15 -0
- package/dist/service-fees.d.ts.map +1 -0
- package/dist/service-fees.js +109 -0
- package/dist/service-fees.js.map +1 -0
- package/dist/types.d.ts +66 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/ARCHITECTURE.md +18 -2
- package/docs/EXISTING_AGENT.md +123 -0
- package/docs/HERMES_BUYER.md +91 -0
- package/docs/HERMES_CHECKPOINTS.md +88 -0
- package/docs/HERMES_CUSTODY.md +164 -0
- package/docs/HERMES_MCP.md +157 -0
- package/docs/HERMES_PROVIDER.md +98 -0
- package/docs/RELEASE_0.8.0_RC1.md +60 -0
- package/docs/SERVICE_FEES.md +108 -0
- package/docs/TESTNET_QUICKSTART.md +123 -0
- package/docs/VALIDATION_AND_RELEASE.md +82 -0
- package/docs/WALLET_SIGNER_SETUP.md +52 -0
- package/examples/service-fees.ts +36 -0
- package/examples/testnet-journal.ts +57 -0
- package/examples/testnet-lifecycle.ts +188 -294
- package/examples/testnet.env.example +12 -14
- package/package.json +7 -1
|
@@ -1,305 +1,199 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Role-separated QA example. Preview is offline; live actions require explicit consent.
|
|
3
|
+
* No evaluator key, fake deliverable, automatic retries or mainnet path.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdirSync, readFileSync, rmdirSync } from "node:fs";
|
|
6
|
+
import { isAbsolute } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { PerkOSClient, prepareEvaluationJob, prepareEvaluationSubmission, evaluationJobId,
|
|
9
|
+
type CriteriaCommitmentInput, type EvaluationEvidence, type PerkOSSigner, type ServiceFeeAcceptance,
|
|
5
10
|
} from "@perkos/agent-sdk";
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
(_key, current) =>
|
|
26
|
-
typeof current === "bigint" ? current.toString() : current,
|
|
27
|
-
2
|
|
28
|
-
);
|
|
11
|
+
import { QuickstartJournal, checkpointedTransaction, intentHash } from "./testnet-journal.js";
|
|
12
|
+
|
|
13
|
+
const DEPLOYER = "ST16EWRC01S1SFWGBP63MW47VY8P3AYFA8VGEBGE5";
|
|
14
|
+
const API = "https://api.testnet.hiro.so";
|
|
15
|
+
const contracts = {
|
|
16
|
+
stxCommerce: `${DEPLOYER}.agentic-commerce-v6` as const,
|
|
17
|
+
sbtcCommerce: `${DEPLOYER}.sbtc-commerce-v5` as const,
|
|
18
|
+
};
|
|
19
|
+
const steps = ["register", "create", "set-budget", "fund", "assign", "submit", "evaluate", "status", "finalize"] as const;
|
|
20
|
+
type Step = typeof steps[number];
|
|
21
|
+
interface RunConfig extends Omit<CriteriaCommitmentInput, "contract"> {
|
|
22
|
+
role: "client" | "provider";
|
|
23
|
+
provider: string;
|
|
24
|
+
treasury: string;
|
|
25
|
+
amount: string;
|
|
26
|
+
expiredAt: string;
|
|
27
|
+
jobId?: string;
|
|
28
|
+
agentName?: string;
|
|
29
|
+
evidence?: readonly EvaluationEvidence[];
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
31
|
+
function ensure(ok: boolean, reason: string): asserts ok { if (!ok) throw new Error(reason); }
|
|
32
|
+
function loadRun(path: string): RunConfig {
|
|
33
|
+
ensure(isAbsolute(path), "absolute_config_required");
|
|
34
|
+
const input = JSON.parse(readFileSync(path, "utf8")) as RunConfig;
|
|
35
|
+
ensure(input.network === "testnet" && ["stx", "sbtc"].includes(input.asset), "testnet_only");
|
|
36
|
+
ensure(["client", "provider"].includes(input.role), "invalid_role");
|
|
37
|
+
ensure([input.client, input.provider, input.evaluator, input.treasury].every(value => /^ST[A-Z0-9]{20,41}$/.test(value)) &&
|
|
38
|
+
new Set([input.client, input.provider, input.evaluator, input.treasury]).size === 4, "distinct_testnet_roles_required");
|
|
39
|
+
ensure(/^[1-9][0-9]*$/.test(input.amount) && BigInt(input.amount) <= (input.asset === "sbtc" ? 1000n : 100000n), "test_amount_cap");
|
|
40
|
+
ensure(/^[1-9][0-9]*$/.test(input.expiredAt), "expiry_required");
|
|
41
|
+
ensure(input.jobId === undefined || /^[1-9][0-9]*$/.test(input.jobId), "invalid_job_id");
|
|
42
|
+
return input;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
const expiresAt = 1_000_000n;
|
|
56
|
-
const plans = [
|
|
57
|
-
[
|
|
58
|
-
"1. Register provider agent",
|
|
59
|
-
preview.transactions.registerAgent({
|
|
60
|
-
name: "PerkOS Testnet Provider",
|
|
61
|
-
description: "Agent SDK transactional quickstart provider",
|
|
62
|
-
wallet: DRY_RUN_PROVIDER,
|
|
63
|
-
endpoints: [{ name: "mcp", url: "https://example.com/mcp" }],
|
|
64
|
-
}),
|
|
65
|
-
],
|
|
66
|
-
[
|
|
67
|
-
"2. Create sBTC job with distinct provider and evaluator",
|
|
68
|
-
preview.transactions.createJob({
|
|
69
|
-
asset: "sbtc",
|
|
70
|
-
provider: DRY_RUN_PROVIDER,
|
|
71
|
-
evaluator: DRY_RUN_EVALUATOR,
|
|
72
|
-
expiredAt: expiresAt,
|
|
73
|
-
description: "Create a signed testnet lifecycle receipt",
|
|
74
|
-
}),
|
|
75
|
-
],
|
|
76
|
-
[
|
|
77
|
-
"3. Set job budget",
|
|
78
|
-
preview.transactions.setBudget({
|
|
79
|
-
asset: "sbtc",
|
|
80
|
-
jobId: DEMO_JOB_ID,
|
|
81
|
-
amount: DEMO_AMOUNT,
|
|
82
|
-
}),
|
|
83
|
-
],
|
|
84
|
-
[
|
|
85
|
-
"4. Fund exact sBTC escrow",
|
|
86
|
-
preview.transactions.fundJob({
|
|
87
|
-
asset: "sbtc",
|
|
88
|
-
jobId: DEMO_JOB_ID,
|
|
89
|
-
amount: DEMO_AMOUNT,
|
|
90
|
-
sender: DRY_RUN_CLIENT,
|
|
91
|
-
}),
|
|
92
|
-
],
|
|
93
|
-
[
|
|
94
|
-
"5. Provider submits deliverable",
|
|
95
|
-
preview.transactions.submitWork({
|
|
96
|
-
asset: "sbtc",
|
|
97
|
-
jobId: DEMO_JOB_ID,
|
|
98
|
-
deliverable: "ipfs:bafy-perkos-testnet-receipt",
|
|
99
|
-
}),
|
|
100
|
-
],
|
|
101
|
-
[
|
|
102
|
-
"6. Evaluator releases escrow",
|
|
103
|
-
preview.transactions.completeJob({
|
|
104
|
-
asset: "sbtc",
|
|
105
|
-
jobId: DEMO_JOB_ID,
|
|
106
|
-
amount: DEMO_AMOUNT,
|
|
107
|
-
recipient: DRY_RUN_PROVIDER,
|
|
108
|
-
}),
|
|
109
|
-
],
|
|
110
|
-
[
|
|
111
|
-
"7. Client rates provider",
|
|
112
|
-
preview.transactions.rateProvider({
|
|
113
|
-
asset: "sbtc",
|
|
114
|
-
jobId: DEMO_JOB_ID,
|
|
115
|
-
score: 5n,
|
|
116
|
-
comment: "Completed through the Agent SDK quickstart",
|
|
117
|
-
}),
|
|
118
|
-
],
|
|
119
|
-
] as const;
|
|
120
|
-
|
|
121
|
-
console.log("PerkOS sBTC lifecycle: safe testnet preview");
|
|
122
|
-
console.log("No wallet, key, transaction, or network request was used.");
|
|
123
|
-
for (const [label, plan] of plans) printPlan(label, plan);
|
|
124
|
-
console.log("\nFunding policy decision");
|
|
125
|
-
console.log(json(preview.preview(plans[3][1])));
|
|
126
|
-
console.log(
|
|
127
|
-
"\nTo broadcast, copy examples/testnet.env.example, fund all three testnet roles with fee STX, fund the client with testnet sBTC, and set PERKOS_CONFIRM_TESTNET_BROADCAST=yes."
|
|
128
|
-
);
|
|
44
|
+
async function tip() {
|
|
45
|
+
const response = await fetch(API + "/v2/info", { redirect: "error", signal: AbortSignal.timeout(15000) });
|
|
46
|
+
ensure(response.ok, "testnet_unavailable");
|
|
47
|
+
const info = await response.json() as Record<string, unknown>;
|
|
48
|
+
ensure(info.network_id === 2147483648, "wrong_network");
|
|
49
|
+
const burn = info.burn_block_height;
|
|
50
|
+
ensure(typeof burn === "number" && Number.isSafeInteger(burn) && burn >= 0, "invalid_burn_height");
|
|
51
|
+
const stacks = info.stacks_tip_height;
|
|
52
|
+
ensure(typeof stacks === "number" && Number.isSafeInteger(stacks) && stacks >= 0, "invalid_stacks_height");
|
|
53
|
+
return { burn: BigInt(burn), stacks: BigInt(stacks) };
|
|
129
54
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const value = process.env[name]?.trim();
|
|
133
|
-
if (!value) throw new Error(`${name} is required for live testnet mode.`);
|
|
134
|
-
return value;
|
|
55
|
+
function print(value: unknown) {
|
|
56
|
+
console.log(JSON.stringify(value, (_key, item: unknown) => typeof item === "bigint" ? item.toString() : item, 2));
|
|
135
57
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
58
|
+
async function main() {
|
|
59
|
+
const action = process.env.PERKOS_ACTION ?? "preview";
|
|
60
|
+
if (action === "preview") {
|
|
61
|
+
print({ mode: "offline-preview", network: "testnet", contracts, steps,
|
|
62
|
+
note: "Client creates/funds/assigns; provider submits actual evidence. The isolated evaluator records a decision; finalize waits for the appeal deadline. No signer or network used." });
|
|
63
|
+
return;
|
|
141
64
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
65
|
+
ensure(steps.includes(action as Step), "unknown_action");
|
|
66
|
+
const step = action as Step;
|
|
67
|
+
ensure(process.env.PERKOS_CONFIRM_TESTNET_BROADCAST === "yes" || step === "status", "explicit_testnet_consent_required");
|
|
68
|
+
const run = loadRun(process.env.PERKOS_RUN_CONFIG ?? "");
|
|
69
|
+
const contract = run.asset === "stx" ? contracts.stxCommerce : contracts.sbtcCommerce;
|
|
70
|
+
const input = { ...run, contract };
|
|
71
|
+
const prepared = await prepareEvaluationJob(input);
|
|
72
|
+
const wallet = run.role === "client" ? run.client : run.provider;
|
|
73
|
+
const amount = BigInt(run.amount);
|
|
74
|
+
const id = run.jobId ? BigInt(run.jobId) : undefined;
|
|
75
|
+
const config = { network: "testnet" as const, apiUrl: API, contracts,
|
|
76
|
+
spendingPolicy: { allowedNetworks: ["testnet"] as const, allowedAssets: [run.asset],
|
|
77
|
+
maxPerTransaction: { [run.asset]: amount }, maxPerSession: { [run.asset]: amount } } };
|
|
78
|
+
const reader = new PerkOSClient(config);
|
|
79
|
+
const { burn, stacks } = await tip();
|
|
80
|
+
if (step === "status") {
|
|
81
|
+
ensure(id !== undefined, "job_id_required");
|
|
82
|
+
const job = await reader.getJob(run.asset, id);
|
|
83
|
+
const decision = await reader.getDecision(run.asset, id);
|
|
84
|
+
const escrow = await reader.getEscrowBalance(run.asset, id);
|
|
85
|
+
print({ job, decision, escrow, burn,
|
|
86
|
+
note: "A decision is not a payout. Confirm settlement transfers and reputation separately." });
|
|
87
|
+
return;
|
|
150
88
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
166
|
-
console.log(`Confirmation: ${confirmation.status}`);
|
|
167
|
-
if (confirmation.status !== "success") {
|
|
168
|
-
throw new PerkOSError(
|
|
169
|
-
"CONFIRMATION_FAILED",
|
|
170
|
-
`${label} ended with ${confirmation.status}.`,
|
|
171
|
-
{ txid: confirmation.txid, result: confirmation.result }
|
|
172
|
-
);
|
|
89
|
+
ensure(!["create", "set-budget", "fund", "assign", "finalize"].includes(step) || run.role === "client", "client_role_required");
|
|
90
|
+
ensure(step !== "submit" || run.role === "provider", "provider_role_required");
|
|
91
|
+
ensure(["register", "create"].includes(step) || id !== undefined, "job_id_required");
|
|
92
|
+
const acceptance: ServiceFeeAcceptance = { gross: amount, basisPoints: 200,
|
|
93
|
+
treasury: run.treasury, rejectionRefund: "net-after-evaluation" };
|
|
94
|
+
if (["fund", "submit"].includes(step)) ensure(
|
|
95
|
+
process.env.PERKOS_ACCEPT_SERVICE_FEE === "200bps-net-after-evaluation", "explicit_service_fee_consent_required");
|
|
96
|
+
|
|
97
|
+
async function currentJob() {
|
|
98
|
+
ensure(id !== undefined, "job_id_required");
|
|
99
|
+
const job = await reader.getJob(run.asset, id);
|
|
100
|
+
ensure(!!job && job.client === run.client && job.evaluator === run.evaluator &&
|
|
101
|
+
job.description === prepared.description && job.treasury === run.treasury, "job_snapshot_mismatch");
|
|
102
|
+
return job;
|
|
173
103
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async function liveRun(): Promise<void> {
|
|
200
|
-
const amount = amountFromEnvironment();
|
|
201
|
-
const apiUrl = (process.env.PERKOS_API_URL?.trim() || DEFAULT_API_URL).replace(
|
|
202
|
-
/\/+$/,
|
|
203
|
-
""
|
|
204
|
-
);
|
|
205
|
-
const clientSigner = new HeadlessSigner({
|
|
206
|
-
network: NETWORK,
|
|
207
|
-
apiUrl,
|
|
208
|
-
privateKeyProvider: () => requiredEnvironment("PERKOS_CLIENT_PRIVATE_KEY"),
|
|
209
|
-
});
|
|
210
|
-
const providerSigner = new HeadlessSigner({
|
|
211
|
-
network: NETWORK,
|
|
212
|
-
apiUrl,
|
|
213
|
-
privateKeyProvider: () => requiredEnvironment("PERKOS_PROVIDER_PRIVATE_KEY"),
|
|
214
|
-
});
|
|
215
|
-
const evaluatorSigner = new HeadlessSigner({
|
|
216
|
-
network: NETWORK,
|
|
217
|
-
apiUrl,
|
|
218
|
-
privateKeyProvider: () => requiredEnvironment("PERKOS_EVALUATOR_PRIVATE_KEY"),
|
|
219
|
-
});
|
|
220
|
-
const [clientAddress, providerAddress, evaluatorAddress, tip] = await Promise.all([
|
|
221
|
-
clientSigner.getAddress(),
|
|
222
|
-
providerSigner.getAddress(),
|
|
223
|
-
evaluatorSigner.getAddress(),
|
|
224
|
-
fetchChainTip(apiUrl),
|
|
225
|
-
]);
|
|
226
|
-
if (new Set([clientAddress, providerAddress, evaluatorAddress]).size !== 3) {
|
|
227
|
-
throw new Error("Client, provider, and evaluator keys must control distinct addresses.");
|
|
104
|
+
if (step === "evaluate") {
|
|
105
|
+
const job = await currentJob();
|
|
106
|
+
ensure(job.status === "submitted" && job.provider === run.provider && job.reviewDeadline !== undefined, "job_not_submitted");
|
|
107
|
+
const committed = await prepareEvaluationSubmission({ ...input, jobId: run.jobId!, evidence: run.evidence ?? [] });
|
|
108
|
+
ensure(job.deliverable?.replace(/^0x/, "") === Buffer.from(committed.deliverable).toString("hex"), "evidence_mismatch");
|
|
109
|
+
// An explicit QA origin is required. No bearer token or evaluator key is used.
|
|
110
|
+
const endpoint = new URL(process.env.PERKOS_EVALUATOR_URL ?? "");
|
|
111
|
+
ensure(endpoint.protocol === "https:" && !endpoint.username && !endpoint.password &&
|
|
112
|
+
endpoint.pathname === "/" && !endpoint.search && !endpoint.hash, "https_evaluator_origin_required");
|
|
113
|
+
const response = await fetch(new URL("/v1/evaluations", endpoint), {
|
|
114
|
+
method: "POST", redirect: "error", signal: AbortSignal.timeout(120000),
|
|
115
|
+
headers: { "content-type": "application/json" },
|
|
116
|
+
body: JSON.stringify({ commitmentVersion: "1", evaluationId: await evaluationJobId({
|
|
117
|
+
network: "testnet", contract, jobId: run.jobId! }), network: "testnet", asset: run.asset, contract, jobId: run.jobId,
|
|
118
|
+
job: { client: run.client, provider: run.provider, evaluator: run.evaluator,
|
|
119
|
+
status: "submitted", description: job.description, reviewDeadlineBurn: job.reviewDeadline.toString() },
|
|
120
|
+
acceptanceCriteria: run.acceptanceCriteria, evidence: run.evidence }),
|
|
121
|
+
});
|
|
122
|
+
ensure(response.status === 202, "evaluation_not_admitted");
|
|
123
|
+
print({ admitted: true, evaluationId: await evaluationJobId({ network: "testnet", contract, jobId: run.jobId! }),
|
|
124
|
+
note: "Queued is not approved or paid. Poll the evaluation status and the on-chain job." });
|
|
125
|
+
return;
|
|
228
126
|
}
|
|
229
127
|
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
jobId
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
]);
|
|
297
|
-
console.log("\nFinal on-chain state");
|
|
298
|
-
console.log(json({ job, reputation }));
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (process.env.PERKOS_CONFIRM_TESTNET_BROADCAST === "yes") {
|
|
302
|
-
await liveRun();
|
|
303
|
-
} else {
|
|
304
|
-
dryRun();
|
|
128
|
+
const journalPath = process.env.PERKOS_JOURNAL ?? "";
|
|
129
|
+
ensure(isAbsolute(journalPath), "absolute_journal_required");
|
|
130
|
+
// Atomic directory lock across processes. A crash deliberately leaves it for manual reconciliation.
|
|
131
|
+
const lock = journalPath + ".lock";
|
|
132
|
+
mkdirSync(lock, { mode: 0o700 });
|
|
133
|
+
let journal: QuickstartJournal | undefined;
|
|
134
|
+
try {
|
|
135
|
+
journal = new QuickstartJournal(journalPath);
|
|
136
|
+
const key = step + ":" + (["register", "create"].includes(step) ? "new" : run.jobId!);
|
|
137
|
+
const intent = intentHash({ step, run, contract });
|
|
138
|
+
const activeJournal = journal;
|
|
139
|
+
const send = async () => {
|
|
140
|
+
if (step === "create") ensure(BigInt(run.expiredAt) > stacks, "expired_job");
|
|
141
|
+
if (!["register", "create"].includes(step)) {
|
|
142
|
+
const job = await currentJob();
|
|
143
|
+
ensure(job.budget === amount || step === "set-budget", "budget_mismatch");
|
|
144
|
+
if (step === "set-budget") ensure(job.status === "open", "job_not_open");
|
|
145
|
+
if (step === "fund") {
|
|
146
|
+
ensure(job.status === "open" && await reader.getEscrowBalance(run.asset, id!) === 0n, "escrow_not_empty");
|
|
147
|
+
const policy = await reader.getServiceFeePolicy(run.asset);
|
|
148
|
+
ensure(policy.configured && policy.basisPoints === 200 && policy.treasury === run.treasury, "fee_policy_mismatch");
|
|
149
|
+
}
|
|
150
|
+
if (step === "assign") ensure(job.status === "funded" && !job.provider, "job_not_assignable");
|
|
151
|
+
if (step === "submit") ensure(job.status === "funded" && job.provider === run.provider, "job_not_assigned_to_provider");
|
|
152
|
+
if (step === "finalize") {
|
|
153
|
+
const decision = await reader.getDecision(run.asset, id!);
|
|
154
|
+
ensure(job.status === "decision-pending" && !!decision && !decision.appealedBy &&
|
|
155
|
+
!decision.finalDecision && burn > decision.appealDeadline, "waiting_for_appeal_deadline_or_resolution");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const signerPath = process.env.PERKOS_SIGNER_MODULE ?? "";
|
|
159
|
+
ensure(isAbsolute(signerPath), "absolute_signer_module_required");
|
|
160
|
+
const module = await import(pathToFileURL(signerPath).href) as { signer?: PerkOSSigner };
|
|
161
|
+
const signer = module.signer;
|
|
162
|
+
ensure(!!signer && await signer.getAddress() === wallet, "signer_role_mismatch");
|
|
163
|
+
const client = new PerkOSClient({ ...config, signer: {
|
|
164
|
+
getAddress: () => signer.getAddress(),
|
|
165
|
+
signAndBroadcast: async plan => ({ txid: await checkpointedTransaction(
|
|
166
|
+
activeJournal, key, intent, async () => (await signer.signAndBroadcast(plan)).txid,
|
|
167
|
+
) }),
|
|
168
|
+
} });
|
|
169
|
+
if (step === "register") return (await client.registerAgent({
|
|
170
|
+
name: run.agentName ?? "Nayori QA participant", description: "Controlled QA participant", wallet, endpoints: [],
|
|
171
|
+
})).txid;
|
|
172
|
+
if (step === "create") return (await client.createJob({
|
|
173
|
+
asset: run.asset, evaluator: run.evaluator, expiredAt: BigInt(run.expiredAt), description: prepared.description,
|
|
174
|
+
})).txid;
|
|
175
|
+
if (step === "set-budget") return (await client.setBudget({ asset: run.asset, jobId: id!, amount })).txid;
|
|
176
|
+
if (step === "fund") return (await client.fundJob({ asset: run.asset, jobId: id!, amount, serviceFeeAcceptance: acceptance })).txid;
|
|
177
|
+
if (step === "assign") return (await client.assignProvider({ asset: run.asset, jobId: id!, provider: run.provider })).txid;
|
|
178
|
+
if (step === "submit") {
|
|
179
|
+
const committed = await prepareEvaluationSubmission({ ...input, jobId: run.jobId!, evidence: run.evidence ?? [] });
|
|
180
|
+
return (await client.submitWork({ asset: run.asset, jobId: id!, deliverable: committed.deliverable,
|
|
181
|
+
serviceFeeAcceptance: acceptance })).txid;
|
|
182
|
+
}
|
|
183
|
+
return (await client.finalizeDecision(run.asset, id!)).txid;
|
|
184
|
+
};
|
|
185
|
+
// A prior signed attempt bypasses mutable preflight and only confirms its saved txid.
|
|
186
|
+
const prior = journal.lookup(key, intent);
|
|
187
|
+
const txid = prior ? await checkpointedTransaction(journal, key, intent, async () => {
|
|
188
|
+
throw new Error("unexpected_retry");
|
|
189
|
+
}) : await send();
|
|
190
|
+
const confirmation = await reader.confirm(txid, { timeoutMs: 120000, pollIntervalMs: 10000 });
|
|
191
|
+
print({ step, txid, status: confirmation.status, result: confirmation.result?.repr, block: confirmation.blockHeight });
|
|
192
|
+
ensure(confirmation.status === "success", "not_confirmed_success_do_not_resend");
|
|
193
|
+
} finally { journal?.close(); rmdirSync(lock); }
|
|
305
194
|
}
|
|
195
|
+
main().catch(() => {
|
|
196
|
+
// Raw signer/network/schema errors can contain credentials. Details belong in operator diagnostics.
|
|
197
|
+
console.error("Quickstart stopped safely. Verify config, on-chain state and journal before retrying; never delete an ambiguous attempt to resend.");
|
|
198
|
+
process.exitCode = 1;
|
|
199
|
+
});
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Optional custom Stacks API endpoint.
|
|
14
|
-
# PERKOS_API_URL=https://api.testnet.hiro.so
|
|
1
|
+
# QA source candidate only; not a declaration that npm 0.7.1 has these helpers.
|
|
2
|
+
# Preview does not load keys, configuration or network.
|
|
3
|
+
PERKOS_ACTION=preview
|
|
4
|
+
# Select one operation from register/create/set-budget/fund/assign/submit/evaluate/status/finalize.
|
|
5
|
+
# Read docs/TESTNET_QUICKSTART.md before enabling actions.
|
|
6
|
+
PERKOS_CONFIRM_TESTNET_BROADCAST=
|
|
7
|
+
PERKOS_RUN_CONFIG=
|
|
8
|
+
PERKOS_JOURNAL=
|
|
9
|
+
PERKOS_SIGNER_MODULE=
|
|
10
|
+
PERKOS_ACCEPT_SERVICE_FEE=
|
|
11
|
+
# Explicit HTTPS origin of the QA evaluator, only after its rollout gate passes.
|
|
12
|
+
PERKOS_EVALUATOR_URL=
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perkos/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-rc.1",
|
|
4
4
|
"description": "TypeScript SDK for agent identity, escrow settlement, and reputation on Stacks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"nayori-mcp": "./dist/mcp/cli.js",
|
|
10
|
+
"nayori-custody": "./dist/custody/cli.js"
|
|
11
|
+
},
|
|
8
12
|
"exports": {
|
|
9
13
|
".": {
|
|
10
14
|
"types": "./dist/index.d.ts",
|
|
@@ -64,12 +68,14 @@
|
|
|
64
68
|
"license": "MIT",
|
|
65
69
|
"publishConfig": {
|
|
66
70
|
"access": "public",
|
|
71
|
+
"tag": "next",
|
|
67
72
|
"provenance": true
|
|
68
73
|
},
|
|
69
74
|
"engines": {
|
|
70
75
|
"node": ">=20"
|
|
71
76
|
},
|
|
72
77
|
"dependencies": {
|
|
78
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
73
79
|
"@stacks/transactions": "^7.6.0",
|
|
74
80
|
"@x402/core": "^2.23.0"
|
|
75
81
|
},
|