@agent-wire/terminal 1.7.5 → 1.7.6

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.
@@ -21,7 +21,7 @@ import {
21
21
  // migration window.
22
22
  loadDeviceRecord, saveDeviceRecord, deleteDeviceRecord, loadAuthPending, saveAuthPending, deleteAuthPending, getStableDeviceFingerprint, hasAnyLocalIdentityForEmail, acquireIdentityLock, releaseIdentityLock, } from './identity.js';
23
23
  import { acquireLock, releaseLock, isWorkerRunning } from './lockfile.js';
24
- import { walkDirectory, computeDiff, pushFiles, pullFiles, parseManifest, serializeManifest, updateManifest, paginateAll, } from './sync/index.js';
24
+ import { walkDirectory, computeDiff, pushFiles, pullFiles, parseManifest, serializeManifest, updateManifest, paginateAll, generatePreviewJson, } from './sync/index.js';
25
25
  import { WIRE_HELP_POINTER } from './help-pointer.js';
26
26
  import { pullLocalSubscriptions } from './local-pull.js';
27
27
  const SURFACE_REFRESH_TTL_MS = 60_000;
@@ -1591,9 +1591,21 @@ export async function startMcpServer(options) {
1591
1591
  }
1592
1592
  });
1593
1593
  // ── Tool: wire_corpora (split from wire_discover — corpus browser) ──
1594
- server.tool('wire_corpora', 'Browse source corpora. List all corpora, or list documents within a specific corpus. Filter by visibility. FREE.', {
1594
+ server.tool('wire_corpora', 'Browse or create source corpora. List corpora, list documents within a corpus, inspect health/revenue, or create a corpus. FREE except writes require approval.', {
1595
+ action: z.enum(['list', 'documents', 'create', 'health', 'revenue', 'bulk_ingest']).optional().describe('Action to perform. Defaults to documents when slug is present, otherwise list.'),
1595
1596
  slug: z.string().optional().describe('Corpus slug to browse. Omit to list all corpora.'),
1597
+ title: z.string().optional().describe('Corpus title for create.'),
1598
+ description: z.string().optional().describe('Corpus description for create.'),
1599
+ material_class: z.string().optional().describe('Corpus material class for create.'),
1596
1600
  visibility: z.enum(['public', 'private', 'all']).optional().describe('Filter by visibility (default: public)'),
1601
+ themes: z.array(z.string()).optional().describe('Optional corpus themes for create.'),
1602
+ default_author_share: z.number().optional().describe('Default author revenue share for create.'),
1603
+ default_steward_share: z.number().optional().describe('Default steward revenue share for create.'),
1604
+ split_justification: z.string().optional().describe('Revenue split justification for create.'),
1605
+ provenance_note: z.string().optional().describe('Provenance note for create.'),
1606
+ manifest: z.record(z.unknown()).optional().describe('Optional corpus manifest for create.'),
1607
+ days: z.number().optional().describe('Revenue lookback days for revenue action.'),
1608
+ documents: z.array(z.record(z.unknown())).optional().describe('Documents for bulk_ingest action.'),
1597
1609
  limit: z.number().optional().describe('Max results'),
1598
1610
  offset: z.number().optional().describe('Pagination offset'),
1599
1611
  }, async (params) => {
@@ -1602,13 +1614,54 @@ export async function startMcpServer(options) {
1602
1614
  return { content: [gate], isError: true };
1603
1615
  try {
1604
1616
  let result;
1605
- if (params.slug) {
1606
- result = await api.getCorpusDocuments(params.slug, params.limit, params.offset);
1607
- }
1608
- else {
1609
- result = await api.listCorpora();
1617
+ const action = params.action ?? (params.slug ? 'documents' : 'list');
1618
+ switch (action) {
1619
+ case 'list':
1620
+ result = await api.listCorpora();
1621
+ break;
1622
+ case 'documents':
1623
+ if (!params.slug)
1624
+ return { content: [{ type: 'text', text: 'Error: slug is required for documents action' }], isError: true };
1625
+ result = await api.getCorpusDocuments(params.slug, params.limit, params.offset);
1626
+ break;
1627
+ case 'create':
1628
+ if (!params.slug)
1629
+ return { content: [{ type: 'text', text: 'Error: slug is required for create action' }], isError: true };
1630
+ if (!params.title)
1631
+ return { content: [{ type: 'text', text: 'Error: title is required for create action' }], isError: true };
1632
+ result = await api.createCorpus({
1633
+ slug: params.slug,
1634
+ title: params.title,
1635
+ description: params.description,
1636
+ material_class: params.material_class,
1637
+ visibility: params.visibility === 'all' ? undefined : params.visibility,
1638
+ themes: params.themes,
1639
+ default_author_share: params.default_author_share,
1640
+ default_steward_share: params.default_steward_share,
1641
+ split_justification: params.split_justification,
1642
+ provenance_note: params.provenance_note,
1643
+ manifest: params.manifest,
1644
+ });
1645
+ break;
1646
+ case 'health':
1647
+ if (!params.slug)
1648
+ return { content: [{ type: 'text', text: 'Error: slug is required for health action' }], isError: true };
1649
+ result = await api.getCorpusHealth(params.slug);
1650
+ break;
1651
+ case 'revenue':
1652
+ if (!params.slug)
1653
+ return { content: [{ type: 'text', text: 'Error: slug is required for revenue action' }], isError: true };
1654
+ result = await api.getCorpusRevenue(params.slug, params.days);
1655
+ break;
1656
+ case 'bulk_ingest':
1657
+ if (!params.slug)
1658
+ return { content: [{ type: 'text', text: 'Error: slug is required for bulk_ingest action' }], isError: true };
1659
+ if (!params.documents)
1660
+ return { content: [{ type: 'text', text: 'Error: documents is required for bulk_ingest action' }], isError: true };
1661
+ result = await api.bulkIngestDocuments(params.slug, params.documents);
1662
+ break;
1610
1663
  }
1611
- const shaped = shapeCorpora(result, params.slug);
1664
+ const shaped = action === 'list' || action === 'documents' ? shapeCorpora(result, action === 'documents' ? params.slug : undefined) : result;
1612
1665
  return { content: [{ type: 'text', text: JSON.stringify(shaped, null, 2) }] };
1613
1666
  }
1614
1667
  catch (err) {
@@ -1783,6 +1836,7 @@ export async function startMcpServer(options) {
1783
1836
  action: z.enum(['diff', 'push', 'pull', 'preview']).describe('Sync action to perform'),
1784
1837
  publish: z.boolean().optional().describe('Legacy push only: publish new document versions instead of drafts'),
1785
1838
  market_native_docs_ingest: z.enum(['off', 'auto', 'all']).optional().describe('Push only: route through /wire/sync and dispatch market-native docs ingest. auto sends docs without wire rear-matter or docs over the inline body budget; all sends every pushed doc. Oversized docs are dispatched as 8000-token windows with 2000-token overlap.'),
1839
+ include_content: z.boolean().optional().describe('Diff/preview only: include full local file bodies. Defaults false; previews normally return hash + sizeBytes only.'),
1786
1840
  }, async (params) => {
1787
1841
  const gate = await requireConfirmed();
1788
1842
  if (gate)
@@ -1791,6 +1845,7 @@ export async function startMcpServer(options) {
1791
1845
  const { dir, corpus, action } = params;
1792
1846
  const publish = params.publish ?? false;
1793
1847
  const marketNativeDocsIngest = (params.market_native_docs_ingest ?? 'off');
1848
+ const includeContent = params.include_content === true;
1794
1849
  // ── Path traversal protection ──
1795
1850
  if (!isAbsolute(dir)) {
1796
1851
  return { content: [{ type: 'text', text: 'Error: dir must be an absolute path' }], isError: true };
@@ -1828,7 +1883,7 @@ export async function startMcpServer(options) {
1828
1883
  const remoteDocs = await paginateAll(async (page, pageSize) => {
1829
1884
  const offset = (page - 1) * pageSize;
1830
1885
  const result = await api.getCorpusDocuments(corpus, pageSize, offset);
1831
- return result.documents || [];
1886
+ return result.documents || result.items || [];
1832
1887
  });
1833
1888
  // 4. Compute diff
1834
1889
  const diff = await computeDiff(localFiles, remoteDocs, manifest);
@@ -1843,11 +1898,15 @@ export async function startMcpServer(options) {
1843
1898
  conflicts: diff.conflicts.length,
1844
1899
  };
1845
1900
  const output = {
1901
+ ...generatePreviewJson({
1902
+ corpus_slug: corpus,
1903
+ local_dir: dir,
1904
+ diff,
1905
+ summary,
1906
+ }, { includeContent }),
1846
1907
  corpus_slug: corpus,
1847
1908
  local_dir: dir,
1848
1909
  action,
1849
- diff,
1850
- summary,
1851
1910
  };
1852
1911
  return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
1853
1912
  }
@@ -2200,7 +2259,7 @@ export async function startMcpServer(options) {
2200
2259
  server.tool('wire_wait', 'Block the live MCP session until a matching Wire coordination event arrives or the timeout elapses. Layer 1 uses SSE + cursor replay; durable wire_watch is not included.', {
2201
2260
  triggers: z.array(z.string()).max(20).optional().describe('Event triggers to wait for, e.g. message.unread, task.assigned, task.moved, board.changed, circle.message. Supports prefix wildcards such as message.*. Omit to wait on any message.*, task.*, board.*, or circle.* event addressable to you.'),
2202
2261
  filters: z.record(z.unknown()).optional().describe('Composable filters such as {from, column, key_prefix, assignee, created_by, target_type, task_id, task_handle, message_handle, from_handle, to_handle, circle, circle_id, written_by_thread}. Agent filters accept "me", agent UUID, or pseudo-ID when available. Handle filters match the corresponding event payload field exactly.'),
2203
- timeout_seconds: z.number().int().min(0).max(1800).optional().describe('Maximum seconds to block. Defaults to 30; max 1800. Use 0 for immediate replay/query behavior.'),
2262
+ timeout_seconds: z.number().int().min(-1).max(86400).optional().describe('Maximum seconds to block. Defaults to 30; max 86400 (24h). Use 0 for immediate replay/query; use -1 to park INDEFINITELY until an event (no re-arm cycles) — the SSE hair-trigger still delivers instantly.'),
2204
2263
  cursor: z.string().optional().describe('Opaque next_cursor from a prior wire_wait or wire_events response. Legacy integer id cursors are accepted. If omitted, wait starts at the current high-water mark.'),
2205
2264
  limit: z.number().int().min(1).max(200).optional().describe('Maximum events to return when triggered. Defaults to 50.'),
2206
2265
  }, async (params) => {
@@ -2289,6 +2348,7 @@ export async function startMcpServer(options) {
2289
2348
  action: z.enum(['supersede', 'propose', 'accept', 'reject']).describe('supersede = direct supersession authorized by target scope, propose = propose supersession (5-credit deposit), accept = approve a proposal, reject = deny a proposal'),
2290
2349
  target_id: z.string().describe('Target contribution UUID (the one being superseded for supersede/propose, or the proposal UUID for accept/reject)'),
2291
2350
  contribution_id: z.string().optional().describe('Your replacement contribution UUID (required for supersede and propose)'),
2351
+ allow_type_change: z.boolean().optional().describe('For action=supersede: allow a CROSS-TYPE supersession (successor type != target type). Without this a cross-type supersede 400s ("type-filtered consumers silently drop cross-type successors"). Pass true only when the type change is deliberate.'),
2292
2352
  }, async (params) => {
2293
2353
  const gate = await requireConfirmed();
2294
2354
  if (gate)
@@ -2312,7 +2372,7 @@ export async function startMcpServer(options) {
2312
2372
  if (!params.contribution_id) {
2313
2373
  return { content: [{ type: 'text', text: 'contribution_id is required for supersede' }], isError: true };
2314
2374
  }
2315
- result = await api.executeSupersession(params.target_id, params.contribution_id);
2375
+ result = await api.executeSupersession(params.target_id, params.contribution_id, params.allow_type_change);
2316
2376
  break;
2317
2377
  default:
2318
2378
  return { content: [{ type: 'text', text: 'Invalid action' }], isError: true };