zk-credits 0.2.4 → 0.2.7

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/README.md CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  Loopback sidecar that attaches a hash-pinned BN254 Groth16 proof to each
4
4
  coding-agent LLM request through the experimental x402 `zk-prepaid` scheme.
5
- Built for the invite-only, unpaid, experimental Base Sepolia pilot; credits
6
- are founder-provisioned test credits, and the circuit is not independently
7
- audited.
5
+ Built for the invite-only experimental Base Sepolia pilot. Product calls use
6
+ founder-provisioned test credits and have no product payment step; the circuit
7
+ is not independently audited. External research participants in slots A and C
8
+ receive $25 for a 30-minute session regardless of setup success. That payment
9
+ is not product revenue or willingness-to-pay evidence.
8
10
 
9
11
  ```bash
10
- npm install --global zk-credits@0.2.4
12
+ npm install --global zk-credits@0.2.7
11
13
  ```
12
14
 
13
15
  ## Pinned pilot versions
@@ -17,17 +19,17 @@ not mix versions between the sidecar, the adapter package, and the gateway:
17
19
 
18
20
  | Package | Version |
19
21
  | --- | --- |
20
- | `zk-credits` (this sidecar) | `0.2.4` |
22
+ | `zk-credits` (this sidecar) | `0.2.7` |
21
23
  | `@zk-credits/x402-zk-prepaid` (adapter) | `0.1.0` |
22
24
  | `@zk-credits/shared` | `0.1.0` |
23
25
 
24
- The pinned Base Sepolia trial version is `0.2.4`. It uses the non-streaming
25
- `POST /v1/chat/completions` path below, includes the loopback Responses bridge
26
- for Codex, and acquires the pinned private proving bundle through the local
27
- GitHub CLI session. It also synchronizes V2 Base events and checks the gateway
28
- known root before local setup completes. Neither this version nor the adapter
29
- supports a Stellar or evaluation path. The proving artifacts are available
30
- only from the private immutable release pinned by `circuits/manifest.json`.
26
+ The pinned Base Sepolia trial version is `0.2.7`. It uses the non-streaming
27
+ `POST /v1/chat/completions` path below, includes the narrow loopback Responses
28
+ bridge for supported Codex text and function-tool requests, and acquires the
29
+ pinned private proving bundle through the local GitHub CLI session. It also
30
+ synchronizes V2 Base events and checks the gateway known root before local
31
+ setup completes. The proving artifacts are available only from the private
32
+ immutable release pinned by `circuits/manifest.json`.
31
33
 
32
34
  Verify the shipped artifacts before your first prove. The package's
33
35
  `circuits/manifest.json` fixes the SHA-256 of the frozen
@@ -57,14 +59,19 @@ still receives the class. A rejected request consumes no credit.
57
59
 
58
60
  ## First run
59
61
 
60
- 1. Redeem an invite and download the encrypted V2 credential export. Credits
61
- are founder-provisioned test credits; there is no payment step.
62
- 2. Ask the pilot contact for read access to the private bundle repository
63
- `mangekyou-labs/zk-credits-base-sepolia-v2-bundle`, then authenticate the
64
- GitHub CLI on this machine with `gh auth login`. The sidecar uses that local
65
- GitHub CLI session to read the immutable release identified in
66
- `circuits/manifest.json`; it never receives or stores your GitHub token.
67
- Set the credential, gateway, and Base Sepolia RPC endpoint:
62
+ 1. Redeem the founder-prepared invite and download the encrypted V2 credential
63
+ export. The founder grants access to the private bundle repository
64
+ `mangekyou-labs/zk-credits-base-sepolia-v2-bundle` before the session.
65
+ 2. Give Codex the single setup instruction in the
66
+ [first-use guide](../../docs/onboarding/base-zk-credits-codex-first-use.md).
67
+ Codex checks local GitHub CLI authentication and repository access, installs
68
+ this pinned release, downloads and verifies the bundle, and runs setup.
69
+ The human signs in if needed, enters the recovery password at the hidden
70
+ prompt, gives consent, and chooses their own task. No product payment is
71
+ involved. The sidecar uses the local GitHub CLI session and never receives
72
+ or stores the GitHub token.
73
+ 3. The setup command sets the credential, gateway, and Base Sepolia RPC
74
+ endpoint:
68
75
 
69
76
  ```bash
70
77
  export ZK_CREDITS_CREDENTIAL_PATH="$HOME/Downloads/zk-credits-credential.json"
@@ -99,7 +106,7 @@ still receives the class. A rejected request consumes no credit.
99
106
  leaves, and the local slot stays reusable. The gateway never serves a path
100
107
  for a named leaf or commitment.
101
108
 
102
- 3. Start setup. It verifies the credential, pinned artifact hashes, and
109
+ 4. Start setup. It verifies the credential, pinned artifact hashes, and
103
110
  witness locally, then prompts for the backup password without echo:
104
111
 
105
112
  ```bash
@@ -107,7 +114,9 @@ still receives the class. A rejected request consumes no credit.
107
114
  ```
108
115
 
109
116
  No gateway admin token is required. Then run `zk-credits codex` to open
110
- Codex with the local sidecar.
117
+ Codex with the local sidecar. A/C first-use measurement targets five
118
+ minutes of active human work; unattended download, proving, and provider
119
+ waits are measured separately. More than five minutes is a friction finding.
111
120
 
112
121
  The sidecar binds `127.0.0.1:3210` only. It does not modify `~/.cline` or
113
122
  `~/.codex`.
@@ -198,11 +207,20 @@ zk-credits cline [cline arguments...]
198
207
  zk-credits setup codex [--model <model>]
199
208
  zk-credits codex [codex arguments...]
200
209
  zk-credits status
201
- zk-credits serve [--port <port>]
210
+ zk-credits serve [--port <port>] [--internal-trial-one-proof]
211
+ zk-credits x402-agent
212
+ zk-credits founder-demo-agent
202
213
  zk-credits trial-registered-adapter
203
214
  eval "$(zk-credits env)"
204
215
  ```
205
216
 
217
+ `serve --internal-trial-one-proof` is an opt-in maintainer mode for the
218
+ internal Base Sepolia trial. During that process lifetime, one authenticated,
219
+ valid spend request may reach the prepaid transport and the proof factory may
220
+ attempt one proof. A later valid request receives HTTP 409 with
221
+ `internal_trial_limit_reached`; ordinary `serve` behavior is unchanged without
222
+ the flag.
223
+
206
224
  `trial-registered-adapter` is a maintainer-only internal trial command. It
207
225
  requires the locally recovered credential and pinned proving bundle, prompts
208
226
  for the credential password and gateway admin token without echo when they are
@@ -220,6 +238,17 @@ Codex SDK:
220
238
  import { buildCodexSdkOptions, buildCodexThreadOptions } from 'zk-credits/codex';
221
239
  ```
222
240
 
241
+ `zk-credits/x402` exports `createLocalX402Agent` for an operator who is
242
+ integrating the adapter into their own x402 agent. It loads the same local
243
+ credential, pinned proof artifacts, public witness, proof engine, and slot
244
+ ledger used by the sidecar. The request-aware client binds the actual method,
245
+ URL, and body into each spend; its metrics endpoint exposes authenticated
246
+ aggregate counters on loopback. See
247
+ [`docs/onboarding/base-zk-credits-x402-agent.md`](../../docs/onboarding/base-zk-credits-x402-agent.md)
248
+ for integration and measured-call steps. The included `zk-credits x402-agent`
249
+ command is a small task-running starter for the founder demo and local
250
+ rehearsal; external slot C uses their own agent.
251
+
223
252
  The credential secret is decrypted only in local memory. The sidecar cache
224
253
  contains public Base event leaves and never stores the secret, prompt, response,
225
254
  proof, account, or wallet.
@@ -1,11 +1,11 @@
1
1
  /**
2
- * One-shot local transport for exercising the official @x402/core adapter.
2
+ * One-shot local transport for the founder's task-running x402 demo agent.
3
3
  *
4
4
  * @x402/core creates scheme payloads from PaymentRequired and does not pass
5
5
  * HTTP method/body context to a scheme client. This harness captures that
6
6
  * request context locally, then binds it into the same Base proof factory
7
7
  * before invoking the registered `zk-prepaid` scheme. It is intended only for
8
- * the internal Base Sepolia trial, not as a general HTTP client.
8
+ * the internal Base Sepolia founder demo, not as a general HTTP client.
9
9
  *
10
10
  * SPDX-License-Identifier: AGPL-3.0-or-later
11
11
  */
@@ -28,6 +28,10 @@ export interface BaseRegisteredTrialOptions extends BasePrepaidClientOptions {
28
28
  body: string;
29
29
  headers?: HeadersInit;
30
30
  fetch?: typeof fetch;
31
+ /** The response can be shown locally to the operator; it is never retained by metrics. */
32
+ onResponse?: (response: Response) => void | Promise<void>;
31
33
  }
34
+ /** A bounded, single-task request for the founder's internal x402 demo. */
35
+ export declare function buildBaseRegisteredTrialRequestBody(task?: string): string;
32
36
  /** Runs one POST to the pilot chat route through a directly registered adapter. */
33
37
  export declare function runBaseRegisteredTrialExchange(options: BaseRegisteredTrialOptions): Promise<BaseRegisteredTrialResult>;
@@ -1,17 +1,27 @@
1
1
  /**
2
- * One-shot local transport for exercising the official @x402/core adapter.
2
+ * One-shot local transport for the founder's task-running x402 demo agent.
3
3
  *
4
4
  * @x402/core creates scheme payloads from PaymentRequired and does not pass
5
5
  * HTTP method/body context to a scheme client. This harness captures that
6
6
  * request context locally, then binds it into the same Base proof factory
7
7
  * before invoking the registered `zk-prepaid` scheme. It is intended only for
8
- * the internal Base Sepolia trial, not as a general HTTP client.
8
+ * the internal Base Sepolia founder demo, not as a general HTTP client.
9
9
  *
10
10
  * SPDX-License-Identifier: AGPL-3.0-or-later
11
11
  */
12
12
  import { x402Client } from '@x402/core/client';
13
13
  import { BASE_SEPOLIA_NETWORK, decodeHeader, encodeHeader, lifecycleFailure, lifecycleStage, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PAYMENT_SIGNATURE_HEADER, registerZkPrepaidClient, ZK_PREPAID_SCHEME, } from '@zk-credits/x402-zk-prepaid';
14
14
  import { createBasePaymentFactory } from './base-sidecar.js';
15
+ /** A bounded, single-task request for the founder's internal x402 demo. */
16
+ export function buildBaseRegisteredTrialRequestBody(task = 'Reply with exactly: internal trial complete.') {
17
+ if (typeof task !== 'string' || task.trim().length === 0 || task.length > 4_000) {
18
+ throw new Error('Task must contain between 1 and 4000 characters');
19
+ }
20
+ return JSON.stringify({
21
+ model: 'deepseek/deepseek-v4-flash',
22
+ messages: [{ role: 'user', content: task }],
23
+ });
24
+ }
15
25
  function observe(options, event) {
16
26
  try {
17
27
  options.lifecycle?.(event);
@@ -163,6 +173,12 @@ export async function runBaseRegisteredTrialExchange(options) {
163
173
  }
164
174
  result.responseSucceeded = true;
165
175
  observe(options, lifecycleStage('exchange_succeeded'));
176
+ try {
177
+ await options.onResponse?.(paidResponse.clone());
178
+ }
179
+ catch {
180
+ // Displaying a completed response is local convenience, not settlement.
181
+ }
166
182
  result.committedSlotDelta = ledger.snapshot().committed - startingCommittedSlots;
167
183
  return result;
168
184
  }
@@ -50,6 +50,8 @@ export interface BasePrepaidClientOptions {
50
50
  credential: CreditCredential;
51
51
  witnessProvider: BaseWitnessProvider;
52
52
  prove: BaseProofGenerator;
53
+ /** Limits this local client to one proof attempt for a single internal trial. */
54
+ internalTrialOneProof?: boolean;
53
55
  fetch?: typeof fetch;
54
56
  /**
55
57
  * Durable local slot ledger. A slot is provisional until the proof passes
@@ -16,6 +16,12 @@ const TREE_DEPTH = 20;
16
16
  const FIELD_ORDER = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
17
17
  const REPLAY_TTL_MS = 24 * 60 * 60 * 1000;
18
18
  const MAX_PENDING_PAYMENTS = 64;
19
+ const PAYMENT_PROOF_FIELDS = ['pi_a', 'pi_b', 'pi_c'];
20
+ function proofForPayment(proof) {
21
+ return Object.fromEntries(PAYMENT_PROOF_FIELDS
22
+ .filter((field) => Object.prototype.hasOwnProperty.call(proof, field))
23
+ .map((field) => [field, proof[field]]));
24
+ }
19
25
  function field(value, label) {
20
26
  if (!/^\d+$/u.test(value))
21
27
  throw new Error(`Invalid ${label}`);
@@ -63,6 +69,7 @@ function requestBodyBytes(body) {
63
69
  /** Creates the shared local proof factory used by both client transports. */
64
70
  export function createBasePaymentFactory(options) {
65
71
  const secret = secretFromBase64Url(options.credential.secret);
72
+ let internalTrialProofAttempted = false;
66
73
  return async ({ method, url, body, requirements }) => {
67
74
  if (requirements.extra.deploymentDomain !== options.credential.deploymentDomain) {
68
75
  throw new Error('Credential deployment domain does not match the gateway challenge');
@@ -98,6 +105,11 @@ export function createBasePaymentFactory(options) {
98
105
  nullifier,
99
106
  share,
100
107
  ];
108
+ if (options.internalTrialOneProof) {
109
+ if (internalTrialProofAttempted)
110
+ throw new Error('internal_trial_limit_reached');
111
+ internalTrialProofAttempted = true;
112
+ }
101
113
  const proof = await options.prove({
102
114
  secret: secretToField(secret),
103
115
  tier_id: String(options.credential.tierId),
@@ -118,7 +130,7 @@ export function createBasePaymentFactory(options) {
118
130
  await slotLedger.commit(selectedSlot);
119
131
  const payment = buildPaymentPayload({
120
132
  requirements,
121
- proof: proof.proof,
133
+ proof: proofForPayment(proof.proof),
122
134
  publicSignals: [...proof.publicSignals],
123
135
  nonce,
124
136
  responseKey: responseKeys.publicKey,
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { launchCodexProcess } from './codex-launcher.js';
7
7
  import { launchClineProcess } from './cline-launcher.js';
8
8
  import { isCodexProfileInstalled, resolveCodexHome, writeCodexProfile, } from './codex-profile.js';
9
9
  import { createBasePrepaidClient, createFileWitnessProvider, validateBaseCreditWitness } from './base-sidecar.js';
10
+ import { createLocalX402Agent } from './local-x402-agent.js';
10
11
  import { createBaseEventWitnessProvider } from './base-event-sync.js';
11
12
  import { createPinnedBaseProofGenerator } from './proof-coordinator.js';
12
13
  import { createBaseProofMetrics } from './proof-metrics.js';
@@ -21,10 +22,11 @@ import { applySetupConfig, readSetupConfig, resolveSetupInputs, writeSetupConfig
21
22
  import { ensureSidecarReady } from './sidecar-lifecycle.js';
22
23
  import { readLoopbackToken, writeLoopbackToken } from './sidecar-state.js';
23
24
  import { createSidecarServer } from './sidecar.js';
24
- import { runBaseRegisteredTrialExchange } from './base-registered-trial.js';
25
+ import { buildBaseRegisteredTrialRequestBody, runBaseRegisteredTrialExchange } from './base-registered-trial.js';
25
26
  import { loadCircuitManifest } from './artifact-bundle.js';
26
27
  import { BASE_SEPOLIA_NETWORK } from '@zk-credits/x402-zk-prepaid';
27
28
  import { requireGatewayKnownRoot as checkGatewayKnownRoot } from './gateway-root-check.js';
29
+ import { evaluateGatewayReadiness, isGatewayStatusFresh } from './gateway-trial-status.js';
28
30
  const DEFAULT_GATEWAY_URL = 'https://zk-credits-gateway.onrender.com';
29
31
  const DEFAULT_PORT = 3210;
30
32
  function stateDirectory() {
@@ -164,16 +166,25 @@ function printHelp() {
164
166
  zk-credits setup codex [--model <model>]
165
167
  zk-credits codex [codex arguments...]
166
168
  zk-credits status
167
- zk-credits serve [--port <port>]
168
- zk-credits trial-registered-adapter
169
+ zk-credits serve [--port <port>] [--internal-trial-one-proof]
170
+ zk-credits x402-agent
171
+ zk-credits founder-demo-agent
169
172
  eval "$(zk-credits env)"
170
173
 
171
174
  "zk-credits cline" and the Codex companion configure and launch coding agents
172
175
  against the x402 zk-prepaid loopback sidecar. The gateway spend path is
173
176
  non-streaming POST /v1/chat/completions; Codex Responses requests pass through
174
177
  the sidecar's bounded local compatibility bridge.
175
- "zk-credits trial-registered-adapter" runs one confirmed, billable internal
176
- trial request through the directly registered official x402 client adapter.
178
+ "zk-credits founder-demo-agent" asks for one local task and runs it through
179
+ the request-aware x402 adapter and local proof engine. It requires explicit
180
+ confirmation before one billable internal exchange; the task and response are
181
+ displayed locally and never added to activation evidence.
182
+ "zk-credits x402-agent" is a small task-running starter for the participant's
183
+ own x402 agent loop. It asks for tasks locally and sends each through the same
184
+ request-aware adapter and local proof engine; aggregate metrics stay on the
185
+ loopback-only authenticated endpoint.
186
+ "zk-credits serve --internal-trial-one-proof" limits this sidecar process to
187
+ one authenticated, valid spend request and one local proof attempt.
177
188
  Set ZK_CREDITS_CREDENTIAL_PATH and BASE_RPC_URL before setup. For Base Sepolia
178
189
  V2, setup can download the immutable bundle pinned by the package manifest by
179
190
  using this machine's authenticated GitHub CLI session, then synchronizes the
@@ -193,16 +204,22 @@ async function loadBaseClientRuntime(passwordInput) {
193
204
  if (!artifactDirectory) {
194
205
  throw new Error('Set ZK_CREDITS_ARTIFACT_DIR to the directory holding the installed pinned proving bundle');
195
206
  }
207
+ const baseRpcUrl = process.env.BASE_RPC_URL;
208
+ const baseContractAddress = process.env.BASE_PRIVATE_CREDIT_BOND_ADDRESS;
196
209
  const witnessProvider = witnessPath
197
210
  ? createFileWitnessProvider(JSON.parse(await readFile(witnessPath, 'utf8')))
198
- : process.env.BASE_RPC_URL && process.env.BASE_PRIVATE_CREDIT_BOND_ADDRESS
199
- ? createBaseEventWitnessProvider({
200
- rpcUrl: process.env.BASE_RPC_URL,
201
- contractAddress: process.env.BASE_PRIVATE_CREDIT_BOND_ADDRESS,
202
- deploymentBlock: process.env.BASE_DEPLOYMENT_BLOCK && /^\d+$/u.test(process.env.BASE_DEPLOYMENT_BLOCK) ? BigInt(process.env.BASE_DEPLOYMENT_BLOCK) : undefined,
203
- confirmations: process.env.BASE_CONFIRMATIONS && /^\d+$/u.test(process.env.BASE_CONFIRMATIONS) ? BigInt(process.env.BASE_CONFIRMATIONS) : undefined,
204
- cachePath: join(stateDirectory(), 'base-event-sync.json'),
205
- })
211
+ : baseRpcUrl && baseContractAddress
212
+ ? await witnessProviderForSource({
213
+ kind: 'base-events',
214
+ rpcUrl: baseRpcUrl,
215
+ contractAddress: baseContractAddress,
216
+ ...(process.env.BASE_DEPLOYMENT_BLOCK && /^\d+$/u.test(process.env.BASE_DEPLOYMENT_BLOCK)
217
+ ? { deploymentBlock: process.env.BASE_DEPLOYMENT_BLOCK }
218
+ : {}),
219
+ ...(process.env.BASE_CONFIRMATIONS && /^\d+$/u.test(process.env.BASE_CONFIRMATIONS)
220
+ ? { confirmations: process.env.BASE_CONFIRMATIONS }
221
+ : {}),
222
+ }, join(stateDirectory(), 'base-event-sync.json'))
206
223
  : (() => { throw new Error('Set ZK_CREDITS_WITNESS_PATH or configure BASE_RPC_URL and BASE_PRIVATE_CREDIT_BOND_ADDRESS'); })();
207
224
  const proofMetrics = createBaseProofMetrics();
208
225
  const exchangeMetrics = createZkPrepaidLifecycleMetrics();
@@ -230,7 +247,6 @@ const GATEWAY_COUNTER_NAMES = [
230
247
  'request_rejected',
231
248
  ];
232
249
  const GATEWAY_CLAIM_COUNT_NAMES = ['reserved', 'ready', 'committed', 'cancelled'];
233
- const GATEWAY_READINESS_CHECK_NAMES = new Set(['launchControl', 'database', 'baseRoot', 'baseRpc', 'verifierAssets', 'provider']);
234
250
  function isRecord(value) {
235
251
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
236
252
  }
@@ -265,16 +281,10 @@ async function requireGatewayKnownRoot(root) {
265
281
  }
266
282
  async function gatewayReadiness(origin) {
267
283
  const response = await readGatewayJson(new URL('/ready', origin), { accept: 'application/json' });
268
- const report = isRecord(response.body) ? response.body : undefined;
269
- const checks = Array.isArray(report?.checks) ? report.checks : [];
270
- const failedChecks = checks.flatMap((check) => (isRecord(check) && check.ok === true
271
- ? []
272
- : [isRecord(check) && typeof check.name === 'string' && GATEWAY_READINESS_CHECK_NAMES.has(check.name) ? check.name : 'unknown']));
273
- const hasLaunchCheck = checks.some((check) => isRecord(check) && check.name === 'launchControl' && check.ok === true);
284
+ const gate = evaluateGatewayReadiness(response.status, response.body);
274
285
  return {
275
286
  status: response.status,
276
- passed: response.status === 200 && report?.ready === true && failedChecks.length === 0 && hasLaunchCheck,
277
- failedChecks,
287
+ ...gate,
278
288
  };
279
289
  }
280
290
  async function gatewayTrialSnapshot(origin, token) {
@@ -314,6 +324,7 @@ async function gatewayTrialSnapshot(origin, token) {
314
324
  status: response.status,
315
325
  ...(snapshot ? { snapshot } : {}),
316
326
  gatePassed: response.status === 200
327
+ && isGatewayStatusFresh(body?.generatedAt)
317
328
  && body?.network === BASE_SEPOLIA_NETWORK
318
329
  && launch?.state === 'enabled'
319
330
  && knownRoot
@@ -338,15 +349,107 @@ async function confirmDirectTrial() {
338
349
  input.close();
339
350
  }
340
351
  }
352
+ async function readFounderDemoTask() {
353
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
354
+ throw new Error('the founder demo requires a local interactive terminal');
355
+ }
356
+ const input = createInterface({ input: process.stdin, output: process.stdout });
357
+ try {
358
+ const task = (await input.question('Task for the founder x402 demo agent (up to 4000 characters): ')).trim();
359
+ if (task.length === 0 || task.length > 4_000) {
360
+ throw new Error('Task must contain between 1 and 4000 characters');
361
+ }
362
+ return task;
363
+ }
364
+ finally {
365
+ input.close();
366
+ }
367
+ }
368
+ async function readX402AgentTask() {
369
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
370
+ throw new Error('the x402 starter agent requires a local interactive terminal');
371
+ }
372
+ const input = createInterface({ input: process.stdin, output: process.stdout });
373
+ try {
374
+ const task = (await input.question('Task for your x402 agent (or :quit): ')).trim();
375
+ if (task === ':quit')
376
+ return null;
377
+ if (task.length === 0 || task.length > 4_000) {
378
+ throw new Error('Task must contain between 1 and 4000 characters');
379
+ }
380
+ return task;
381
+ }
382
+ finally {
383
+ input.close();
384
+ }
385
+ }
386
+ async function runX402Agent() {
387
+ const gateway = gatewayOrigin();
388
+ const runtime = await createLocalX402Agent({
389
+ credentialPassword: await readHiddenValue('Credential backup password: '),
390
+ });
391
+ const url = new URL('/v1/chat/completions', gateway).toString();
392
+ console.log(`Local aggregate metrics: ${runtime.metricsUrl}`);
393
+ console.log('Enter one task at a time. Each task can spend one founder-provisioned test credit.');
394
+ try {
395
+ while (true) {
396
+ const task = await readX402AgentTask();
397
+ if (task === null)
398
+ break;
399
+ const response = await runtime.client.fetch(url, {
400
+ method: 'POST',
401
+ headers: { 'content-type': 'application/json' },
402
+ body: buildBaseRegisteredTrialRequestBody(task),
403
+ });
404
+ await displayAgentResponse(response);
405
+ if (!response.ok)
406
+ console.log(`Request ended with HTTP ${response.status}.`);
407
+ }
408
+ }
409
+ finally {
410
+ await runtime.close();
411
+ }
412
+ }
413
+ async function displayAgentResponse(response) {
414
+ let content;
415
+ try {
416
+ const body = await response.json();
417
+ if (isRecord(body) && Array.isArray(body.choices) && isRecord(body.choices[0])) {
418
+ const message = body.choices[0].message;
419
+ if (isRecord(message))
420
+ content = message.content;
421
+ }
422
+ }
423
+ catch {
424
+ // The response is a local display convenience; activation recording uses
425
+ // only the closed exchange and counter summaries below.
426
+ }
427
+ console.log('\nAgent response (shown locally only):');
428
+ if (typeof content === 'string') {
429
+ console.log(content);
430
+ return;
431
+ }
432
+ if (Array.isArray(content)) {
433
+ const textParts = content
434
+ .filter((part) => isRecord(part))
435
+ .filter((part) => part.type === 'text' && typeof part.text === 'string')
436
+ .map((part) => part.text);
437
+ if (textParts.length > 0) {
438
+ console.log(textParts.join('\n'));
439
+ return;
440
+ }
441
+ }
442
+ console.log('[The response completed, but its text content was not available for local display.]');
443
+ }
341
444
  function gatewayCounterDelta(before, after) {
342
445
  return {
343
446
  metrics: Object.fromEntries(GATEWAY_COUNTER_NAMES.map((name) => [name, after.metrics[name] - before.metrics[name]])),
344
447
  claims: Object.fromEntries(GATEWAY_CLAIM_COUNT_NAMES.map((name) => [name, after.claims[name] - before.claims[name]])),
345
448
  };
346
449
  }
347
- async function trialRegisteredAdapter() {
450
+ async function trialRegisteredAdapter(task) {
348
451
  if (!await confirmDirectTrial()) {
349
- console.log(JSON.stringify({ path: 'registered-adapter', status: 'not_run' }));
452
+ console.log(JSON.stringify({ path: 'founder-demo-agent', status: 'not_run' }));
350
453
  return;
351
454
  }
352
455
  try {
@@ -354,13 +457,13 @@ async function trialRegisteredAdapter() {
354
457
  if (runtime.credential.tierId !== 0
355
458
  || runtime.credential.deploymentDomain !== '84532'
356
459
  || runtime.credential.expiry <= Math.floor(Date.now() / 1000)) {
357
- console.log(JSON.stringify({ path: 'registered-adapter', status: 'stopped', failurePhase: 'credential_gate' }));
460
+ console.log(JSON.stringify({ path: 'founder-demo-agent', status: 'stopped', failurePhase: 'credential_gate' }));
358
461
  return;
359
462
  }
360
463
  const startingSlots = runtime.slotLedger.snapshot();
361
464
  if (startingSlots.committed >= startingSlots.capacity) {
362
465
  console.log(JSON.stringify({
363
- path: 'registered-adapter',
466
+ path: 'founder-demo-agent',
364
467
  status: 'stopped',
365
468
  failurePhase: 'local_credit_gate',
366
469
  localCredits: { capacity: startingSlots.capacity, committed: startingSlots.committed, available: 0 },
@@ -370,7 +473,7 @@ async function trialRegisteredAdapter() {
370
473
  const gateway = gatewayOrigin();
371
474
  const adminToken = process.env.BILLING_INTERNAL_TOKEN || await readHiddenValue('Gateway admin token: ');
372
475
  if (!adminToken.trim()) {
373
- console.log(JSON.stringify({ path: 'registered-adapter', status: 'stopped', failurePhase: 'admin_auth' }));
476
+ console.log(JSON.stringify({ path: 'founder-demo-agent', status: 'stopped', failurePhase: 'admin_auth' }));
374
477
  return;
375
478
  }
376
479
  const lifecycle = createNodeSidecarLifecycle({
@@ -387,33 +490,29 @@ async function trialRegisteredAdapter() {
387
490
  ]);
388
491
  const launcherStatus = { codexProfileInstalled, sidecarRunning };
389
492
  if (sidecarRunning) {
390
- console.log(JSON.stringify({ path: 'registered-adapter', status: 'stopped', failurePhase: 'sidecar_running', launcherStatus }));
493
+ console.log(JSON.stringify({ path: 'founder-demo-agent', status: 'stopped', failurePhase: 'sidecar_running', launcherStatus }));
391
494
  return;
392
495
  }
393
496
  const witness = await runtime.witnessProvider.witnessForCredential(runtime.credential);
394
497
  const readiness = await gatewayReadiness(gateway);
395
498
  if (!readiness.passed) {
396
- console.log(JSON.stringify({ path: 'registered-adapter', status: 'stopped', failurePhase: 'readiness', launcherStatus, readiness }));
499
+ console.log(JSON.stringify({ path: 'founder-demo-agent', status: 'stopped', failurePhase: 'readiness', launcherStatus, readiness }));
397
500
  return;
398
501
  }
399
502
  const adminBefore = await gatewayTrialSnapshot(gateway, adminToken);
400
503
  if (!adminBefore.gatePassed || !adminBefore.snapshot) {
401
- console.log(JSON.stringify({ path: 'registered-adapter', status: 'stopped', failurePhase: 'admin_status', launcherStatus, readiness, adminStatus: adminBefore.status }));
504
+ console.log(JSON.stringify({ path: 'founder-demo-agent', status: 'stopped', failurePhase: 'admin_status', launcherStatus, readiness, adminStatus: adminBefore.status }));
402
505
  return;
403
506
  }
404
507
  if (witness.root !== adminBefore.snapshot.currentRoot) {
405
- console.log(JSON.stringify({ path: 'registered-adapter', status: 'stopped', failurePhase: 'root_mismatch', launcherStatus, readiness, adminStatus: adminBefore.status }));
508
+ console.log(JSON.stringify({ path: 'founder-demo-agent', status: 'stopped', failurePhase: 'root_mismatch', launcherStatus, readiness, adminStatus: adminBefore.status }));
406
509
  return;
407
510
  }
408
511
  const baselineSlots = runtime.slotLedger.snapshot();
409
512
  const baselineProof = runtime.proofMetrics.snapshot();
410
513
  const baselineLifecycle = runtime.exchangeMetrics.snapshot();
411
514
  const requestUrl = new URL('/v1/chat/completions', gateway).toString();
412
- const requestBody = JSON.stringify({
413
- model: 'deepseek/deepseek-v4-flash',
414
- messages: [{ role: 'user', content: 'Reply with exactly: internal trial complete.' }],
415
- stream: false,
416
- });
515
+ const requestBody = buildBaseRegisteredTrialRequestBody(task);
417
516
  const exchange = await runBaseRegisteredTrialExchange({
418
517
  url: requestUrl,
419
518
  body: requestBody,
@@ -423,6 +522,7 @@ async function trialRegisteredAdapter() {
423
522
  prove: runtime.prove,
424
523
  slotLedger: runtime.slotLedger,
425
524
  lifecycle: runtime.exchangeMetrics.observe,
525
+ onResponse: displayAgentResponse,
426
526
  });
427
527
  const adminAfter = await gatewayTrialSnapshot(gateway, adminToken);
428
528
  const proofAfter = runtime.proofMetrics.snapshot();
@@ -451,7 +551,7 @@ async function trialRegisteredAdapter() {
451
551
  ?? (adminAfter.status !== 200 || !adminAfter.snapshot ? 'postflight_admin_status' : undefined)
452
552
  ?? (!gatewayCountersMatch ? 'gateway_counter_delta' : undefined);
453
553
  console.log(JSON.stringify({
454
- path: 'registered-adapter',
554
+ path: 'founder-demo-agent',
455
555
  readiness: { status: readiness.status, passed: readiness.passed, failedChecks: readiness.failedChecks },
456
556
  launcherStatus,
457
557
  adminStatus: { before: adminBefore.status, after: adminAfter.status },
@@ -479,11 +579,12 @@ async function trialRegisteredAdapter() {
479
579
  }));
480
580
  }
481
581
  catch {
482
- console.log(JSON.stringify({ path: 'registered-adapter', status: 'stopped', failurePhase: 'local_setup' }));
582
+ console.log(JSON.stringify({ path: 'founder-demo-agent', status: 'stopped', failurePhase: 'local_setup' }));
483
583
  }
484
584
  }
485
585
  async function serve(args, passwordInput) {
486
586
  const port = readPort(args);
587
+ const internalTrialOneProof = args.includes('--internal-trial-one-proof');
487
588
  const statePaths = sidecarStatePaths(stateDirectory());
488
589
  const localToken = createLoopbackToken();
489
590
  const { credential, witnessProvider, proofMetrics, exchangeMetrics, slotLedger, prove } = await loadBaseClientRuntime(passwordInput);
@@ -492,6 +593,7 @@ async function serve(args, passwordInput) {
492
593
  witnessProvider,
493
594
  prove,
494
595
  slotLedger,
596
+ internalTrialOneProof,
495
597
  lifecycle: exchangeMetrics.observe,
496
598
  });
497
599
  const gatewayBaseUrl = process.env.ZK_CREDITS_GATEWAY_URL || DEFAULT_GATEWAY_URL;
@@ -499,6 +601,7 @@ async function serve(args, passwordInput) {
499
601
  localToken,
500
602
  gatewayBaseUrl,
501
603
  prepaidClient: prepaid.client,
604
+ internalTrialOneProof,
502
605
  metrics: () => ({ ...proofMetrics.snapshot(), exchange: exchangeMetrics.snapshot() }),
503
606
  });
504
607
  let address;
@@ -538,8 +641,17 @@ async function main() {
538
641
  await serve(args.slice(1).filter((argument) => argument !== '--credential-password-stdin'), passwordFromStdin);
539
642
  return;
540
643
  }
541
- if (args[0] === 'trial-registered-adapter') {
542
- await trialRegisteredAdapter();
644
+ if (args[0] === 'founder-demo-agent') {
645
+ if (args.length !== 1)
646
+ throw new Error('founder-demo-agent does not accept arguments; it reads the task locally');
647
+ const task = await readFounderDemoTask();
648
+ await trialRegisteredAdapter(task);
649
+ return;
650
+ }
651
+ if (args[0] === 'x402-agent') {
652
+ if (args.length !== 1)
653
+ throw new Error('x402-agent does not accept arguments; it reads tasks locally');
654
+ await runX402Agent();
543
655
  return;
544
656
  }
545
657
  const statePaths = sidecarStatePaths(sidecarHome);
@@ -23,6 +23,7 @@ export function renderCodexProfile(options) {
23
23
  const model = options.model?.trim() || DEFAULT_CODEX_MODEL;
24
24
  return [
25
25
  `model = ${tomlString(model)}`,
26
+ 'model_reasoning_effort = "none"',
26
27
  'model_provider = "zk_credits"',
27
28
  '',
28
29
  '[model_providers.zk_credits]',
@@ -69,7 +70,7 @@ export function codexModelsResponse() {
69
70
  apply_patch_tool_type: 'freeform',
70
71
  web_search_tool_type: 'text',
71
72
  truncation_policy: { mode: 'tokens', limit: 10_000 },
72
- supports_parallel_tool_calls: true,
73
+ supports_parallel_tool_calls: false,
73
74
  supports_image_detail_original: false,
74
75
  context_window: 128_000,
75
76
  max_context_window: 128_000,
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Freshness and readiness checks used by the separately registered adapter
3
+ * trial. Keep this gate fail-closed when a gateway omits a named check.
4
+ *
5
+ * SPDX-License-Identifier: AGPL-3.0-or-later
6
+ */
7
+ export declare const GATEWAY_READINESS_CHECK_NAMES: readonly ["launchControl", "database", "baseRoot", "baseRpc", "verifierAssets", "provider"];
8
+ export declare function isGatewayStatusFresh(value: unknown, now?: number): boolean;
9
+ export declare function evaluateGatewayReadiness(status: number | null, body: unknown, now?: number): {
10
+ passed: boolean;
11
+ failedChecks: string[];
12
+ timestampFresh: boolean;
13
+ };