@agent-wire/terminal 1.7.6 → 1.7.9

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/api.js CHANGED
@@ -1,8 +1,27 @@
1
- import { paginateAll } from './sync/paginate.js';
1
+ const normalizedUrlNotices = new Set();
2
+ export function normalizeWireApiUrl(value) {
3
+ const url = new URL(value);
4
+ if (url.pathname === '/' || !url.pathname) {
5
+ url.pathname = '/api/v1';
6
+ if (!normalizedUrlNotices.has(value)) {
7
+ console.error('[wire] WIRE_API_URL has no API path; appended /api/v1.');
8
+ normalizedUrlNotices.add(value);
9
+ }
10
+ }
11
+ return url.toString().replace(/\/+$/, '');
12
+ }
2
13
  const DEFAULT_TRUSTED_CHAIN_TIMEOUT_MS = 600_000;
3
14
  const TRUSTED_CHAIN_CLIENT_GRACE_MS = 5_000;
4
15
  const DEFAULT_WIRE_SYNC_TIMEOUT_MS = DEFAULT_TRUSTED_CHAIN_TIMEOUT_MS + TRUSTED_CHAIN_CLIENT_GRACE_MS;
5
16
  const DEFAULT_ACTION_QUOTE_TIMEOUT_MS = 30_000;
17
+ export class RegistrationError extends Error {
18
+ status;
19
+ constructor(status, message) {
20
+ super(`Registration failed (${status}): ${message}`);
21
+ this.status = status;
22
+ this.name = 'RegistrationError';
23
+ }
24
+ }
6
25
  function legacyActionRequestOptions(params) {
7
26
  if (params.mode === 'quote') {
8
27
  return { body: params, timeoutMs: DEFAULT_ACTION_QUOTE_TIMEOUT_MS };
@@ -13,6 +32,14 @@ function legacyActionRequestOptions(params) {
13
32
  timeoutMs: timeoutMs + TRUSTED_CHAIN_CLIENT_GRACE_MS,
14
33
  };
15
34
  }
35
+ function meshQuery(params, extra = {}) {
36
+ const query = new URLSearchParams(extra);
37
+ if (params.scope)
38
+ query.set('scope', params.scope);
39
+ if (params.circle_id)
40
+ query.set('circle_id', params.circle_id);
41
+ return query.size ? `?${query}` : '';
42
+ }
16
43
  const DEFAULT_WAIT_TIMEOUT_SECONDS = 30;
17
44
  // Raised from 1800 (30m): the SSE event stream is server-side UNCAPPED + 30s-keepalived, so a long
18
45
  // client park is just a client-side deadline, not an infra limit. 86400 = 24h finite ceiling.
@@ -69,7 +96,7 @@ function appendWaitParams(url, params) {
69
96
  url.searchParams.set(key, String(value));
70
97
  }
71
98
  }
72
- function normalizeHelpTopic(topic) {
99
+ export function normalizeHelpTopic(topic) {
73
100
  return (topic || '')
74
101
  .trim()
75
102
  .replace(/^https?:\/\/[^/]+\/api\/v1\/help\/?/i, '')
@@ -130,7 +157,25 @@ function parseSseBlock(block) {
130
157
  return { event, data: rawData };
131
158
  }
132
159
  }
160
+ /** Keep actionable server refusals intact across MCP and terminal transports. */
161
+ export class WireApiRefusal extends Error {
162
+ status;
163
+ body;
164
+ constructor(status, body) {
165
+ super(JSON.stringify(body));
166
+ this.status = status;
167
+ this.body = body;
168
+ }
169
+ }
133
170
  export class WireAPI {
171
+ httpFetch;
172
+ async claims(params) {
173
+ const { action, claim_id } = params;
174
+ if (action === 'get' || action === 'list') {
175
+ return this.request('GET', '/wire/claims' + (claim_id ? `?claim_id=${encodeURIComponent(String(claim_id))}` : ''));
176
+ }
177
+ return this.request('POST', '/wire/claims', params, 120_000);
178
+ }
134
179
  baseUrl;
135
180
  token;
136
181
  nodeId;
@@ -139,8 +184,9 @@ export class WireAPI {
139
184
  localNodeEnabled;
140
185
  localNodeToken;
141
186
  localContributionRouteStatus;
142
- constructor(config, threadId) {
143
- this.baseUrl = config.apiUrl;
187
+ constructor(config, threadId, httpFetch) {
188
+ this.httpFetch = httpFetch;
189
+ this.baseUrl = normalizeWireApiUrl(config.apiUrl);
144
190
  this.token = config.apiToken;
145
191
  this.nodeId = config.nodeId;
146
192
  this.threadId = threadId || null;
@@ -162,7 +208,7 @@ export class WireAPI {
162
208
  if (this.threadId) {
163
209
  headers['X-Wire-Thread'] = this.threadId;
164
210
  }
165
- const res = await fetch(url, {
211
+ const res = await (this.httpFetch ?? fetch)(url, {
166
212
  method,
167
213
  headers,
168
214
  body: body ? JSON.stringify(body) : undefined,
@@ -170,6 +216,8 @@ export class WireAPI {
170
216
  });
171
217
  if (!res.ok) {
172
218
  const err = await res.json().catch(() => ({ error: res.statusText }));
219
+ if (err.code === 'confirm_original')
220
+ throw new WireApiRefusal(res.status, err);
173
221
  throw new Error(`API ${method} ${path} failed (${res.status}): ${err.error || res.statusText}`);
174
222
  }
175
223
  return res.json();
@@ -183,7 +231,7 @@ export class WireAPI {
183
231
  if (this.threadId) {
184
232
  headers['X-Wire-Thread'] = this.threadId;
185
233
  }
186
- const res = await fetch(url, {
234
+ const res = await (this.httpFetch ?? fetch)(url, {
187
235
  method,
188
236
  headers,
189
237
  body: body ? JSON.stringify(body) : undefined,
@@ -222,7 +270,7 @@ export class WireAPI {
222
270
  return response;
223
271
  }
224
272
  async getMe() {
225
- return this.request('GET', '/me');
273
+ return this.unwrapEnvelopeOrPlain(await this.request('GET', '/me'));
226
274
  }
227
275
  async registerNode(name, capabilities) {
228
276
  return this.request('POST', '/node/register', { name, capabilities });
@@ -234,7 +282,7 @@ export class WireAPI {
234
282
  async registerAgent(params) {
235
283
  // Registration doesn't require auth — use a raw fetch
236
284
  const url = `${this.baseUrl}/register`;
237
- const res = await fetch(url, {
285
+ const res = await (this.httpFetch ?? fetch)(url, {
238
286
  method: 'POST',
239
287
  headers: { 'Content-Type': 'application/json' },
240
288
  body: JSON.stringify(params),
@@ -242,7 +290,7 @@ export class WireAPI {
242
290
  });
243
291
  if (!res.ok) {
244
292
  const err = await res.json().catch(() => ({ error: res.statusText }));
245
- throw new Error(`Registration failed (${res.status}): ${err.error || res.statusText}`);
293
+ throw new RegistrationError(res.status, err.error || res.statusText);
246
294
  }
247
295
  // Server wraps response in wireEnvelope: { data: { ... }, mesh, fleet, ... }
248
296
  const envelope = await res.json();
@@ -263,21 +311,10 @@ export class WireAPI {
263
311
  }
264
312
  async query(params) {
265
313
  const qs = new URLSearchParams();
266
- qs.set('text', params.text);
267
- qs.set('domain', params.domain || 'all');
268
- if (params.limit !== undefined)
269
- qs.set('limit', String(params.limit));
270
- if (params.offset !== undefined)
271
- qs.set('offset', String(params.offset));
272
- qs.set('sort', params.sort || 'recency');
273
- if (params.watchlist)
274
- qs.set('watchlist', 'true');
275
- if (params.list)
276
- qs.set('list', params.list);
277
- if (params.changed_only)
278
- qs.set('changed_only', 'true');
279
- if (params.price_below !== undefined)
280
- qs.set('price_below', String(params.price_below));
314
+ for (const [key, value] of Object.entries({ domain: 'all', sort: 'recency', ...params })) {
315
+ if (value !== undefined && value !== null)
316
+ qs.set(key, String(value));
317
+ }
281
318
  return this.request('GET', `/wire/query?${qs.toString()}`);
282
319
  }
283
320
  async help(params = {}) {
@@ -337,6 +374,8 @@ export class WireAPI {
337
374
  return results;
338
375
  }
339
376
  async browse(params) {
377
+ if (params.cursor && (params.item_id || params.entity))
378
+ throw new Error('cursor applies to item-list browsing; omit item_id/entity.');
340
379
  if (params.item_id) {
341
380
  if (params.include_definition) {
342
381
  const qs = new URLSearchParams({ mode: 'inspect', include_definition: 'true' });
@@ -354,10 +393,16 @@ export class WireAPI {
354
393
  qs.set('arc', params.arc);
355
394
  return this.request('GET', `/wire/entities?${qs.toString()}`);
356
395
  }
357
- if (params.arc) {
358
- return this.request('GET', `/wire/items?arc=${encodeURIComponent(params.arc)}`);
359
- }
360
- return this.request('GET', '/wire/items');
396
+ const qs = new URLSearchParams();
397
+ if (params.arc)
398
+ qs.set('arc', params.arc);
399
+ if (params.cursor)
400
+ qs.set('cursor', params.cursor);
401
+ if (params.limit !== undefined)
402
+ qs.set('limit', String(params.limit));
403
+ if (params.offset !== undefined)
404
+ qs.set('offset', String(params.offset));
405
+ return this.request('GET', `/wire/items${qs.size ? '?' + qs : ''}`);
361
406
  }
362
407
  async flag(params) {
363
408
  return this.request('POST', '/flag', params);
@@ -411,7 +456,7 @@ export class WireAPI {
411
456
  if (this.localContributionRouteStatus)
412
457
  return this.localContributionRouteStatus;
413
458
  try {
414
- const health = await fetch(`${this.localNodeUrl}/health`, {
459
+ const health = await (this.httpFetch ?? fetch)(`${this.localNodeUrl}/health`, {
415
460
  method: 'GET',
416
461
  signal: AbortSignal.timeout(2_000),
417
462
  });
@@ -419,7 +464,7 @@ export class WireAPI {
419
464
  this.localContributionRouteStatus = 'unavailable';
420
465
  return this.localContributionRouteStatus;
421
466
  }
422
- const probe = await fetch(`${this.localNodeUrl}${localContributionReadPath('__wire_terminal_route_probe__')}`, {
467
+ const probe = await (this.httpFetch ?? fetch)(`${this.localNodeUrl}${localContributionReadPath('__wire_terminal_route_probe__')}`, {
423
468
  method: 'GET',
424
469
  headers: this.localHeaders(),
425
470
  signal: AbortSignal.timeout(2_000),
@@ -453,7 +498,7 @@ export class WireAPI {
453
498
  return null;
454
499
  const path = localContributionReadPath(identifier);
455
500
  try {
456
- const res = await fetch(`${this.localNodeUrl}${path}`, {
501
+ const res = await (this.httpFetch ?? fetch)(`${this.localNodeUrl}${path}`, {
457
502
  method: 'GET',
458
503
  headers: this.localHeaders(),
459
504
  signal: AbortSignal.timeout(10_000),
@@ -476,6 +521,55 @@ export class WireAPI {
476
521
  return null;
477
522
  }
478
523
  }
524
+ // ── Pyramid consume-head proxies (spec green playful/176/24) ───────────
525
+ // Thin HTTP proxies to the LOCAL AWN node's read-time pyramid routes
526
+ // (pyramid_routes mounted at /pyramid/*). They forward (slug, args) to the
527
+ // route and return the parsed JSON as-is. Auth: pass the local Bearer token
528
+ // (this.localHeaders()); the AWN routes enforce dual-auth + access-tier +
529
+ // nano-payment. Same local-node fetch pattern as tryLocalContributionRead.
530
+ /**
531
+ * Generic local-node fetch for the pyramid consume-head proxies. Returns the
532
+ * parsed JSON body on success, throws a descriptive error on non-2xx.
533
+ */
534
+ async localNodeFetch(method, path, body, timeoutMs = 15_000) {
535
+ const res = await (this.httpFetch ?? fetch)(`${this.localNodeUrl}${path}`, {
536
+ method,
537
+ headers: this.localHeaders(),
538
+ body: body !== undefined ? JSON.stringify(body) : undefined,
539
+ signal: AbortSignal.timeout(timeoutMs),
540
+ });
541
+ if (!res.ok) {
542
+ const err = await res.json().catch(() => ({ error: res.statusText }));
543
+ const message = err.error || res.statusText;
544
+ throw new Error(`Local node ${method} ${path} failed (${res.status}): ${message}`);
545
+ }
546
+ return await res.json();
547
+ }
548
+ /**
549
+ * pyramid_map — GET /pyramid/{slug}/map.
550
+ * Apex synthesis + freshness counts + nav skeleton + unresolved gaps.
551
+ * Orient first at boot, then drill via pyramid_hydrate using nav node ids.
552
+ */
553
+ async getPyramidMap(slug) {
554
+ return this.localNodeFetch('GET', `/pyramid/${encodeURIComponent(slug)}/map`);
555
+ }
556
+ /**
557
+ * pyramid_hydrate — GET /pyramid/{slug}/drill/{node_id}.
558
+ * Returns the DrillResult: full node content (headline/distilled/narrative
559
+ * via the node payload) plus children, evidence, gaps, web_edges, and
560
+ * question context. Pull one node/region after orienting via pyramid_map.
561
+ */
562
+ async getPyramidDrill(slug, nodeId) {
563
+ return this.localNodeFetch('GET', `/pyramid/${encodeURIComponent(slug)}/drill/${encodeURIComponent(nodeId)}`);
564
+ }
565
+ /**
566
+ * pyramid_ask — POST /pyramid/{slug}/question body { question }.
567
+ * Cheap (<5s, no-LLM) mechanical retrieval over the existing pyramid;
568
+ * returns a markdown answer plus node-id citations / sources.
569
+ */
570
+ async askPyramid(slug, question) {
571
+ return this.localNodeFetch('POST', `/pyramid/${encodeURIComponent(slug)}/question`, { question });
572
+ }
479
573
  /**
480
574
  * Rate a Wire item. Free. Must have accessed it first.
481
575
  */
@@ -523,7 +617,8 @@ export class WireAPI {
523
617
  * Read a source document by ID.
524
618
  */
525
619
  async readDocument(documentId) {
526
- return this.request('GET', `/wire/documents/${documentId}?include_body=true`, undefined, 30_000);
620
+ const quote = await this.request('GET', `/wire/documents/${documentId}`, undefined, 30_000);
621
+ return this.request('POST', `/wire/documents/${quote.id ?? quote.data?.id ?? documentId}`, { max_price: quote.current_price ?? quote.data?.current_price ?? 0 }, 30_000);
527
622
  }
528
623
  /**
529
624
  * Find unscouted documents — unclaimed bounties.
@@ -596,44 +691,44 @@ export class WireAPI {
596
691
  /**
597
692
  * Get full mesh status: active threads, intents, blackboard.
598
693
  */
599
- async meshStatus() {
600
- return this.request('GET', '/mesh/status');
694
+ async meshStatus(scope = {}) {
695
+ return this.request('GET', `/mesh/status${meshQuery(scope)}`);
601
696
  }
602
697
  /**
603
698
  * Read the shared blackboard.
604
699
  */
605
- async meshBoardRead() {
606
- return this.request('GET', '/mesh/board');
700
+ async meshBoardRead(scope = {}) {
701
+ return this.request('GET', `/mesh/board${meshQuery(scope)}`);
607
702
  }
608
703
  /**
609
704
  * Write a key-value entry to the shared blackboard.
610
705
  */
611
- async meshBoardWrite(key, value, ttl) {
612
- return this.request('POST', '/mesh/board', { key, value, ttl });
706
+ async meshBoardWrite(key, value, ttl, scope = {}) {
707
+ return this.request('POST', '/mesh/board', { key, value, ttl, ...scope });
613
708
  }
614
709
  /**
615
710
  * Delete a key from the shared blackboard.
616
711
  */
617
- async meshBoardDelete(key) {
618
- return this.request('DELETE', `/mesh/board?key=${encodeURIComponent(key)}`);
712
+ async meshBoardDelete(key, scope = {}) {
713
+ return this.request('DELETE', `/mesh/board${meshQuery(scope, { key })}`);
619
714
  }
620
715
  /**
621
716
  * Declare an intent (what this thread is about to do).
622
717
  */
623
- async meshDeclareIntent(action, domain, meta, ttl) {
624
- return this.request('POST', '/mesh/intent', { action, domain, meta, ttl });
718
+ async meshDeclareIntent(action, domain, meta, ttl, scope = {}) {
719
+ return this.request('POST', '/mesh/intent', { action, domain, meta, ttl, ...scope });
625
720
  }
626
721
  /**
627
722
  * Release (delete) an intent.
628
723
  */
629
- async meshReleaseIntent(intentId) {
630
- return this.request('DELETE', `/mesh/intent?intent_id=${encodeURIComponent(intentId)}`);
724
+ async meshReleaseIntent(intentId, scope = {}) {
725
+ return this.request('DELETE', `/mesh/intent${meshQuery(scope, { intent_id: intentId })}`);
631
726
  }
632
727
  /**
633
728
  * Get active intents for this agent.
634
729
  */
635
- async meshGetIntents() {
636
- return this.request('GET', '/mesh/intent');
730
+ async meshGetIntents(scope = {}) {
731
+ return this.request('GET', `/mesh/intent${meshQuery(scope)}`);
637
732
  }
638
733
  // ── Prediction Markets ─────────────────────────────────────
639
734
  /**
@@ -645,8 +740,8 @@ export class WireAPI {
645
740
  * @param side - 'agree' or 'disagree'
646
741
  * @param amount - Number of credits to stake (must be >= market's min_stake)
647
742
  */
648
- async stakeMarket(contributionId, side, amount) {
649
- return this.request('POST', `/wire/market/${contributionId}/stake`, { side, amount });
743
+ async stakeMarket(contributionId, side, amount, requestId = crypto.randomUUID()) {
744
+ return this.request('POST', `/wire/market/${contributionId}/stake`, { side, amount, request_id: requestId });
650
745
  }
651
746
  /**
652
747
  * Get the current status of a prediction market, including pool sizes,
@@ -823,7 +918,7 @@ export class WireAPI {
823
918
  if (this.threadId) {
824
919
  headers['X-Wire-Thread'] = this.threadId;
825
920
  }
826
- const res = await fetch(url.toString(), {
921
+ const res = await (this.httpFetch ?? fetch)(url.toString(), {
827
922
  method: 'GET',
828
923
  headers,
829
924
  signal: options.signal,
@@ -1011,7 +1106,7 @@ export class WireAPI {
1011
1106
  async executeSupersession(targetId, newContributionId, allowTypeChange) {
1012
1107
  // allow_type_change forwards to the REST endpoint (which already honors it) so a CROSS-TYPE
1013
1108
  // supersession is reachable via the CLI/MCP, not only via raw REST (WI playful/170/177).
1014
- const body = { new_contribution_id: newContributionId };
1109
+ const body = { contribution_id: newContributionId };
1015
1110
  if (allowTypeChange)
1016
1111
  body.allow_type_change = true;
1017
1112
  return this.request('POST', `/wire/contributions/${encodeURIComponent(targetId)}/supersede`, body);
@@ -1020,6 +1115,9 @@ export class WireAPI {
1020
1115
  async submitCorrection(targetId, correctionContributionId, depositAmount) {
1021
1116
  return this.request('POST', `/wire/contributions/${encodeURIComponent(targetId)}/correct`, { correction_contribution_id: correctionContributionId, deposit_amount: depositAmount });
1022
1117
  }
1118
+ async withdrawCorrection(correctionId) {
1119
+ return this.request('DELETE', `/wire/contributions/${encodeURIComponent(correctionId)}/endorse-correction`);
1120
+ }
1023
1121
  async endorseCorrection(correctionId, depositAmount) {
1024
1122
  return this.request('POST', `/wire/contributions/${encodeURIComponent(correctionId)}/endorse-correction`, { deposit_amount: depositAmount });
1025
1123
  }
@@ -1187,7 +1285,7 @@ export class WireAPI {
1187
1285
  if (this.threadId) {
1188
1286
  headers['X-Wire-Thread'] = this.threadId;
1189
1287
  }
1190
- const res = await fetch(url, { method: 'GET', headers, signal: AbortSignal.timeout(30_000) });
1288
+ const res = await (this.httpFetch ?? fetch)(url, { method: 'GET', headers, signal: AbortSignal.timeout(30_000) });
1191
1289
  if (!res.ok) {
1192
1290
  const err = await res.json().catch(() => ({ error: res.statusText }));
1193
1291
  throw new Error(`API GET /wire/documents/${documentId}/body failed (${res.status}): ${err.error || res.statusText}`);
@@ -1250,7 +1348,7 @@ export class WireAPI {
1250
1348
  const body = { email, device_fingerprint: deviceFingerprint };
1251
1349
  if (agentMessage)
1252
1350
  body.agent_message = agentMessage;
1253
- const res = await fetch(url, {
1351
+ const res = await (this.httpFetch ?? fetch)(url, {
1254
1352
  method: 'POST',
1255
1353
  headers: { 'Content-Type': 'application/json' },
1256
1354
  body: JSON.stringify(body),
@@ -1266,7 +1364,7 @@ export class WireAPI {
1266
1364
  async verifyDeviceCode(email, deviceFingerprint, code) {
1267
1365
  // No auth required — raw fetch
1268
1366
  const url = `${this.baseUrl}/wire/device-auth`;
1269
- const res = await fetch(url, {
1367
+ const res = await (this.httpFetch ?? fetch)(url, {
1270
1368
  method: 'PUT',
1271
1369
  headers: { 'Content-Type': 'application/json' },
1272
1370
  body: JSON.stringify({ email, device_fingerprint: deviceFingerprint, code }),
@@ -1284,7 +1382,7 @@ export class WireAPI {
1284
1382
  url.searchParams.set('harness', harness);
1285
1383
  if (hintName)
1286
1384
  url.searchParams.set('hint_name', hintName);
1287
- const res = await fetch(url.toString(), {
1385
+ const res = await (this.httpFetch ?? fetch)(url.toString(), {
1288
1386
  headers: { 'X-Wire-Device-Secret': deviceSecret },
1289
1387
  signal: AbortSignal.timeout(15_000),
1290
1388
  });
@@ -1296,7 +1394,7 @@ export class WireAPI {
1296
1394
  return json.data || json;
1297
1395
  }
1298
1396
  async resumeAgent(agentName, deviceSecret, nonce) {
1299
- const res = await fetch(`${this.baseUrl}/wire/agent/resume`, {
1397
+ const res = await (this.httpFetch ?? fetch)(`${this.baseUrl}/wire/agent/resume`, {
1300
1398
  method: 'POST',
1301
1399
  headers: {
1302
1400
  'Content-Type': 'application/json',
@@ -1313,4 +1411,30 @@ export class WireAPI {
1313
1411
  return json.data || json;
1314
1412
  }
1315
1413
  }
1414
+ /**
1415
+ * Paginate through all results from a fetch function.
1416
+ * Concatenates pages until a page returns fewer than pageSize items.
1417
+ *
1418
+ * @param fetchPage - Function that fetches a page (1-indexed page number, pageSize)
1419
+ * @param pageSize - Number of items per page (default 100)
1420
+ * @returns All items concatenated
1421
+ */
1422
+ export async function paginateAll(fetchPage, pageSize = 100) {
1423
+ const allItems = [];
1424
+ let page = 1;
1425
+ const MAX_PAGES = 500; // 500 pages * 100 per page = 50k docs max
1426
+ while (true) {
1427
+ if (page > MAX_PAGES) {
1428
+ console.error(`[wire-sync] Pagination stopped at ${MAX_PAGES} pages (${allItems.length} items). If your corpus has more documents, contact support.`);
1429
+ break;
1430
+ }
1431
+ const items = await fetchPage(page, pageSize);
1432
+ allItems.push(...items);
1433
+ if (items.length < pageSize) {
1434
+ break;
1435
+ }
1436
+ page++;
1437
+ }
1438
+ return allItems;
1439
+ }
1316
1440
  //# sourceMappingURL=api.js.map