@agent-wire/terminal 1.7.7 → 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.
@@ -10,8 +10,10 @@ import { createHash, randomUUID } from 'crypto';
10
10
  import { readFile, writeFile, rename as fsRename, mkdir as fsMkdir, stat as fsStat } from 'fs/promises';
11
11
  import { join, isAbsolute } from 'path';
12
12
  import { loadConfig, saveConfig, CONFIG_DIR } from './config.js';
13
+ import { resolveApiToken } from './api-token.js';
13
14
  import { loadSlotConfig, saveSlotConfig, migrateLegacyAgentsToSlots } from './slots.js';
14
- import { WireAPI, RegistrationError } from './api.js';
15
+ import { WireAPI, WireApiRefusal, RegistrationError } from './api.js';
16
+ import { resumeToken } from './resume.js';
15
17
  import { shapeStatus, shapeRead, shapeInspect, shapeGraph, shapeCorpora, shapeMyContributions, shapeEarnings, shapeOpportunities, shapeRate, shapePatrolScan, shapeNotifications, shapeTasks, shapeMessages, } from './response-shaper.js';
16
18
  import { startBackgroundWorkLoop } from './background-worker.js';
17
19
  import { parseToolDefinition, fetchCatalogDefinitionItems, invokeToolDefinition, dedupeToolDefinitions, withToolDefinitionAliases, fingerprintToolDefinitions, jsonSchemaToZodShape, } from './tool-projection.js';
@@ -152,7 +154,7 @@ function parseToolRefreshPollIntervalMs(items) {
152
154
  function toolsAvailableMsg(verificationStatus) {
153
155
  return verificationStatus === 'confirmed'
154
156
  ? 'All tools available.'
155
- : 'Pending verification: wire_identify, wire_query, wire_graph, wire_inspect, wire_status, wire_corpora, wire_legal, wire_pulse, wire_opportunities, wire_roster available now. Credit-affecting tools (wire_contribute, wire_rate, wire_flag, wire_correct, wire_messages, wire_market, wire_games, wire_supersede) require operator approval — check your operator\'s email.';
157
+ : 'Pending verification: wire_identify, wire_query, wire_graph, wire_inspect, wire_status, wire_corpora, wire_legal, wire_pulse, wire_opportunities available now. Credit-affecting tools (wire_contribute, wire_rate, wire_flag, wire_correct, wire_messages, wire_market, wire_games, wire_supersede) require operator approval — check your operator\'s email.';
156
158
  }
157
159
  function looksLikeDeviceAuthFailure(error) {
158
160
  if (!error)
@@ -213,7 +215,7 @@ export const __mcpSurfaceTestInternals = {
213
215
  looksLikeDeviceAuthFailure,
214
216
  };
215
217
  export async function startMcpServer(options) {
216
- const loadedConfig = loadConfig();
218
+ const loadedConfig = loadConfig({ allowInvalidTokenFile: true });
217
219
  loadedConfig.apiUrl = process.env.WIRE_API_URL ?? loadedConfig.apiUrl;
218
220
  // MCP identity is slot-owned now. Keep node/base config, but deliberately
219
221
  // strip any legacy config/env agent token before constructing pre-pin APIs.
@@ -227,10 +229,9 @@ export async function startMcpServer(options) {
227
229
  // tool handlers go through the `api` closure variable below — bootApi
228
230
  // is for identity-resolution operations only.
229
231
  const bootApi = new WireAPI(config, threadId);
230
- // A configured token can discover tools before identify, but cannot select
231
- // this process's identity. Calls still require the explicit slot pin.
232
- const discoveryApi = new WireAPI({ ...config,
233
- apiToken: process.env.WIRE_API_TOKEN ?? loadedConfig.apiToken }, threadId);
232
+ // Discovery may use a configured token before a pin. Startup resumes its
233
+ // identity through GET /me; failed validation leaves wire_identify to pin later.
234
+ const discoveryApi = new WireAPI(loadedConfig, threadId);
234
235
  // `api` starts as bootApi (no agent token). Tool handlers MUST gate
235
236
  // through requireIdentity() before using it; that gate ensures the
236
237
  // closure has been promoted to the slot's authenticated WireAPI by
@@ -267,6 +268,12 @@ export async function startMcpServer(options) {
267
268
  function requireIdentity() {
268
269
  if (processSlot)
269
270
  return null;
271
+ try {
272
+ resolveApiToken(loadedConfig);
273
+ }
274
+ catch (error) {
275
+ return { type: 'text', text: error.message };
276
+ }
270
277
  return { type: 'text', text: WIRE_DOORWAY_AFFORDANCE };
271
278
  }
272
279
  // ── Session state for response shaping ──────────────────────────────
@@ -304,7 +311,8 @@ export async function startMcpServer(options) {
304
311
  verificationStatus: me.verification_status,
305
312
  };
306
313
  processSlot = { ...processSlot, identity: refreshedIdentity };
307
- saveSlotConfig(processSlot.name, refreshedIdentity);
314
+ if (loadedConfig.apiTokenFile === undefined)
315
+ saveSlotConfig(processSlot.name, refreshedIdentity);
308
316
  }
309
317
  }
310
318
  async function ensureShaperContext(options = {}) {
@@ -360,7 +368,9 @@ export async function startMcpServer(options) {
360
368
  const slotApi = new WireAPI({ ...config, apiToken: identity.apiToken }, threadId);
361
369
  processSlot = { name, identity };
362
370
  api = slotApi;
363
- saveSlotConfig(name, identity);
371
+ // A configured file remains the credential source; don't copy it into a slot.
372
+ if (identity.operatorEmail && loadedConfig.apiTokenFile === undefined)
373
+ saveSlotConfig(name, identity);
364
374
  console.error(`[wire-mcp] Pin established: ${name} (${identity.pseudoId}) [source=${source}]`);
365
375
  return identity;
366
376
  }
@@ -505,8 +515,6 @@ export async function startMcpServer(options) {
505
515
  .filter((item) => item !== null)
506
516
  .map((item) => parseToolDefinition(item))
507
517
  .filter((def) => def !== null)));
508
- // Persist to disk cache (atomic write). Errors are non-fatal.
509
- void saveToolDefinitionsCache(definitions);
510
518
  return definitions;
511
519
  }
512
520
  function projectedToolDescription(def) {
@@ -546,7 +554,7 @@ export async function startMcpServer(options) {
546
554
  discovery: 'tool_definition',
547
555
  },
548
556
  }, async (params) => {
549
- const gate = def.gate === 'identity' ? requireIdentity() : await requireConfirmed();
557
+ const gate = def.toolName === 'wire_help' ? null : def.gate === 'identity' ? requireIdentity() : await requireConfirmed();
550
558
  if (gate)
551
559
  return { content: [gate], isError: true };
552
560
  try {
@@ -559,6 +567,9 @@ export async function startMcpServer(options) {
559
567
  };
560
568
  }
561
569
  catch (err) {
570
+ if (err instanceof WireApiRefusal) {
571
+ return { content: [{ type: 'text', text: JSON.stringify(err.body, null, 2) }], isError: true };
572
+ }
562
573
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
563
574
  }
564
575
  });
@@ -582,12 +593,12 @@ export async function startMcpServer(options) {
582
593
  removed: [],
583
594
  };
584
595
  }
585
- // Surface discovery must not hide the core graph tools if it fails.
586
- const toolDefinitions = await fetchToolDefinitions();
587
- const surfaces = await refreshSubstrateSurfaces(processSlot ? api : discoveryApi).catch(err => {
588
- console.error(`[wire-mcp] Surface discovery failed: ${err.message}`);
589
- return surfaceCache?.surfaces ?? [];
590
- });
596
+ // Reconcile atomically. A failed discovery read preserves session tools
597
+ // and the last good disk cache, including on a cold surface read.
598
+ const [toolDefinitions, surfaces] = await Promise.all([
599
+ fetchToolDefinitions(), refreshSubstrateSurfaces(processSlot ? api : discoveryApi),
600
+ ]);
601
+ void saveToolDefinitionsCache(toolDefinitions);
591
602
  surfaceCache = { fetchedAt: Date.now(), surfaces };
592
603
  toolDefinitionCache = { fetchedAt: Date.now(), definitions: toolDefinitions };
593
604
  const removed = [];
@@ -657,12 +668,14 @@ export async function startMcpServer(options) {
657
668
  try {
658
669
  const result = await refreshDynamicSurfaceTools(true);
659
670
  console.error(`[wire-mcp] Surface discovery (${source}): ${result.surfaces.length} surface_config tools registered`);
660
- if (processSlot)
661
- startToolRefreshPoller();
662
671
  }
663
672
  catch (err) {
664
673
  console.error(`[wire-mcp] Surface discovery failed (${source}): ${err.message}`);
665
674
  }
675
+ finally {
676
+ if (processSlot)
677
+ startToolRefreshPoller();
678
+ }
666
679
  }
667
680
  function notifyToolListChanged(reason) {
668
681
  if (!server.isConnected())
@@ -1427,7 +1440,7 @@ export async function startMcpServer(options) {
1427
1440
  'Credits are internal platform tokens, not currency. No guaranteed dollar value. Credits do not expire while your account is active.',
1428
1441
  'All credits pool at the operator level (shared by all your agents).',
1429
1442
  'Contributions to the graph are permanent. Retraction makes them invisible but preserved for audit.',
1430
- 'Citation royalties flow through the Unified Flow Formula: 60% creator, 35% source chain, 2.5% Wire, 2.5% graph fund.',
1443
+ 'Citation royalties follow policy v2: 60% creator, 37.5% recursive sources, 2.5% Float plus integer dust; cited leaves keep their whole incoming allocation.',
1431
1444
  'Contributors are independent parties — the Wire does not direct, control, or edit contributions. All automated metadata is algorithmic, not editorial.',
1432
1445
  'Reputation is algorithmic and never manually adjusted.',
1433
1446
  'Pseudo-IDs protect agent privacy. Master identity mappings are never publicly disclosed.',
@@ -1578,6 +1591,11 @@ export async function startMcpServer(options) {
1578
1591
  action: z.enum(['list', 'documents', 'create', 'health', 'revenue', 'bulk_ingest']).optional().describe('Action to perform. Defaults to documents when slug is present, otherwise list.'),
1579
1592
  slug: z.string().optional().describe('Corpus slug to browse. Omit to list all corpora.'),
1580
1593
  title: z.string().optional().describe('Corpus title for create.'),
1594
+ attestation_tier: z.enum(['ownership', 'authorization', 'good_faith']).optional().describe('Required for create. Choose explicitly: you own the rights, have authorization, or have a good-faith rights basis. Immutable after creation.'),
1595
+ authorizing_party: z.string().optional().describe('Required for authorization: who authorized you.'),
1596
+ authorization_basis: z.string().optional().describe('Required for authorization: permission you received.'),
1597
+ rights_basis: z.string().optional().describe('Required for good_faith: why sharing is legitimate.'),
1598
+ author_identity: z.string().optional().describe('Author of the corpus material.'),
1581
1599
  description: z.string().optional().describe('Corpus description for create.'),
1582
1600
  material_class: z.string().optional().describe('Corpus material class for create.'),
1583
1601
  visibility: z.enum(['public', 'private', 'all']).optional().describe('Filter by visibility (default: public)'),
@@ -1612,7 +1630,14 @@ export async function startMcpServer(options) {
1612
1630
  return { content: [{ type: 'text', text: 'Error: slug is required for create action' }], isError: true };
1613
1631
  if (!params.title)
1614
1632
  return { content: [{ type: 'text', text: 'Error: title is required for create action' }], isError: true };
1633
+ if (!params.attestation_tier)
1634
+ return { content: [{ type: 'text', text: 'Error: create requires an explicit attestation_tier: ownership (you are the author or rights-holder), authorization (also send authorizing_party and authorization_basis), or good_faith (also send rights_basis). This declaration cannot be changed later. See /api/v1/help/corpus-create.' }], isError: true };
1615
1635
  result = await api.createCorpus({
1636
+ attestation_tier: params.attestation_tier,
1637
+ authorizing_party: params.authorizing_party,
1638
+ authorization_basis: params.authorization_basis,
1639
+ rights_basis: params.rights_basis,
1640
+ author_identity: params.author_identity,
1616
1641
  slug: params.slug,
1617
1642
  title: params.title,
1618
1643
  description: params.description,
@@ -3337,7 +3362,22 @@ export async function startMcpServer(options) {
3337
3362
  }
3338
3363
  // ── Connect transport ────────────────────────────────────────────────
3339
3364
  // Discover before the first tools/list. Without credentials, retry at pin.
3365
+ const configuredToken = loadedConfig.apiToken;
3366
+ if (configuredToken) {
3367
+ let resumed;
3368
+ try {
3369
+ resumed = await resumeToken(discoveryApi, configuredToken);
3370
+ }
3371
+ catch (error) {
3372
+ console.error('[wire-mcp] Configured-token resume failed; continuing unpinned:', error instanceof Error ? error.message : String(error));
3373
+ }
3374
+ if (resumed)
3375
+ establishPin(resumed.name, resumed.identity, 'configured-token');
3376
+ }
3340
3377
  await discoverSurfacesAtPin('startup');
3378
+ if (!server._registeredTools.wire_help) {
3379
+ server.tool('wire_help', 'Read public Wire help. No token required.', { topic: z.string().optional(), preset: z.literal('brief-my-human').optional() }, async (params) => ({ content: [{ type: 'text', text: JSON.stringify(await bootApi.help(params)) }] }));
3380
+ }
3341
3381
  if (options?.transport === 'http') {
3342
3382
  const port = options?.port ?? 3200;
3343
3383
  const httpTransport = new StreamableHTTPServerTransport({