@agent-wire/terminal 1.7.4 → 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.
- package/dist/api.d.ts +3 -1
- package/dist/api.js +32 -11
- package/dist/api.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.js +101 -31
- package/dist/mcp-server.js.map +1 -1
- package/dist/response-shaper.js +2 -0
- package/dist/response-shaper.js.map +1 -1
- package/dist/sync/preview.d.ts +4 -1
- package/dist/sync/preview.js +16 -4
- package/dist/sync/preview.js.map +1 -1
- package/dist/wait-cli.js +14 -5
- package/dist/wait-cli.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -10,7 +10,7 @@ import { join, isAbsolute } from 'path';
|
|
|
10
10
|
import { loadConfig, saveConfig, CONFIG_DIR } from './config.js';
|
|
11
11
|
import { loadSlotConfig, saveSlotConfig, migrateLegacyAgentsToSlots } from './slots.js';
|
|
12
12
|
import { WireAPI } from './api.js';
|
|
13
|
-
import { shapeStatus, shapeRead, shapeInspect, shapeGraph, shapeCorpora, shapeMyContributions, shapeEarnings,
|
|
13
|
+
import { shapeStatus, shapeRead, shapeInspect, shapeGraph, shapeCorpora, shapeMyContributions, shapeEarnings, shapeOpportunities, shapeRate, shapePatrolScan, shapeNotifications, shapeTasks, shapeMessages, } from './response-shaper.js';
|
|
14
14
|
import { startBackgroundWorkLoop } from './background-worker.js';
|
|
15
15
|
import { parseToolDefinition, dedupeToolDefinitions, fingerprintToolDefinitions, jsonSchemaToZodShape, } from './tool-projection.js';
|
|
16
16
|
import {
|
|
@@ -21,11 +21,12 @@ 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;
|
|
28
28
|
const DEFAULT_TOOL_REFRESH_POLL_INTERVAL_MS = 90_000;
|
|
29
|
+
const WIRE_DOORWAY_AFFORDANCE = 'Not identified. Call wire_identify({name, operator_email}) — ask your human for their email. Then wire_help({preset:"brief-my-human"}).';
|
|
29
30
|
function normalizeSurfaceName(name) {
|
|
30
31
|
return name.trim().toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
|
|
31
32
|
}
|
|
@@ -250,7 +251,7 @@ export async function startMcpServer(options) {
|
|
|
250
251
|
function requireIdentity() {
|
|
251
252
|
if (processSlot)
|
|
252
253
|
return null;
|
|
253
|
-
return { type: 'text', text:
|
|
254
|
+
return { type: 'text', text: WIRE_DOORWAY_AFFORDANCE };
|
|
254
255
|
}
|
|
255
256
|
// ── Session state for response shaping ──────────────────────────────
|
|
256
257
|
let shaperContext = null;
|
|
@@ -528,6 +529,20 @@ export async function startMcpServer(options) {
|
|
|
528
529
|
existing.remove();
|
|
529
530
|
projectedCoreTools.delete(def.toolName);
|
|
530
531
|
}
|
|
532
|
+
// Guard: if a hardcoded server.tool() registration already claims this name
|
|
533
|
+
// (and it is NOT one of our own projected entries), skip with a warning
|
|
534
|
+
// rather than throwing and aborting the whole reconciliation. The sanctioned
|
|
535
|
+
// replacement path is to delete the hardcoded block (cutover = migrate-means-
|
|
536
|
+
// DELETE per canon /151/5); silently swapping at runtime would surprise.
|
|
537
|
+
// See design intent playful/159/2964.
|
|
538
|
+
const sdkRegistry = server._registeredTools;
|
|
539
|
+
if (sdkRegistry[def.toolName] && !projectedCoreTools.has(def.toolName)) {
|
|
540
|
+
console.error(`[wire-mcp] registerProjectedCoreTool: SKIP '${def.toolName}' — ` +
|
|
541
|
+
`a hardcoded registration already claims this name; delete the hardcoded ` +
|
|
542
|
+
`server.tool('${def.toolName}', ...) block to complete the cutover ` +
|
|
543
|
+
`(design intent: playful/159/2964).`);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
531
546
|
const registered = server.registerTool(def.toolName, {
|
|
532
547
|
title: def.toolName,
|
|
533
548
|
description: projectedToolDescription(def),
|
|
@@ -630,7 +645,13 @@ export async function startMcpServer(options) {
|
|
|
630
645
|
for (const def of toolDefinitions) {
|
|
631
646
|
if (!previousDefinitionNames.has(def.toolName))
|
|
632
647
|
added.push(def.toolName);
|
|
633
|
-
|
|
648
|
+
try {
|
|
649
|
+
registerProjectedCoreTool(def);
|
|
650
|
+
}
|
|
651
|
+
catch (err) {
|
|
652
|
+
console.error(`[wire-mcp] refreshDynamicSurfaceTools: failed to register projected tool '${def.toolName}' ` +
|
|
653
|
+
`(skipping — one tool must not abort the whole reconciliation): ${err.message}`);
|
|
654
|
+
}
|
|
634
655
|
}
|
|
635
656
|
const fingerprint = fingerprintSubstrateSurfaces(surfaces) + fingerprintToolDefinitions(toolDefinitions);
|
|
636
657
|
const previousFingerprint = activeToolSetFingerprint;
|
|
@@ -1570,9 +1591,21 @@ export async function startMcpServer(options) {
|
|
|
1570
1591
|
}
|
|
1571
1592
|
});
|
|
1572
1593
|
// ── Tool: wire_corpora (split from wire_discover — corpus browser) ──
|
|
1573
|
-
server.tool('wire_corpora', 'Browse source corpora. List
|
|
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.'),
|
|
1574
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.'),
|
|
1575
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.'),
|
|
1576
1609
|
limit: z.number().optional().describe('Max results'),
|
|
1577
1610
|
offset: z.number().optional().describe('Pagination offset'),
|
|
1578
1611
|
}, async (params) => {
|
|
@@ -1581,13 +1614,54 @@ export async function startMcpServer(options) {
|
|
|
1581
1614
|
return { content: [gate], isError: true };
|
|
1582
1615
|
try {
|
|
1583
1616
|
let result;
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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;
|
|
1589
1663
|
}
|
|
1590
|
-
const shaped = shapeCorpora(result, params.slug);
|
|
1664
|
+
const shaped = action === 'list' || action === 'documents' ? shapeCorpora(result, action === 'documents' ? params.slug : undefined) : result;
|
|
1591
1665
|
return { content: [{ type: 'text', text: JSON.stringify(shaped, null, 2) }] };
|
|
1592
1666
|
}
|
|
1593
1667
|
catch (err) {
|
|
@@ -1762,6 +1836,7 @@ export async function startMcpServer(options) {
|
|
|
1762
1836
|
action: z.enum(['diff', 'push', 'pull', 'preview']).describe('Sync action to perform'),
|
|
1763
1837
|
publish: z.boolean().optional().describe('Legacy push only: publish new document versions instead of drafts'),
|
|
1764
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.'),
|
|
1765
1840
|
}, async (params) => {
|
|
1766
1841
|
const gate = await requireConfirmed();
|
|
1767
1842
|
if (gate)
|
|
@@ -1770,6 +1845,7 @@ export async function startMcpServer(options) {
|
|
|
1770
1845
|
const { dir, corpus, action } = params;
|
|
1771
1846
|
const publish = params.publish ?? false;
|
|
1772
1847
|
const marketNativeDocsIngest = (params.market_native_docs_ingest ?? 'off');
|
|
1848
|
+
const includeContent = params.include_content === true;
|
|
1773
1849
|
// ── Path traversal protection ──
|
|
1774
1850
|
if (!isAbsolute(dir)) {
|
|
1775
1851
|
return { content: [{ type: 'text', text: 'Error: dir must be an absolute path' }], isError: true };
|
|
@@ -1807,7 +1883,7 @@ export async function startMcpServer(options) {
|
|
|
1807
1883
|
const remoteDocs = await paginateAll(async (page, pageSize) => {
|
|
1808
1884
|
const offset = (page - 1) * pageSize;
|
|
1809
1885
|
const result = await api.getCorpusDocuments(corpus, pageSize, offset);
|
|
1810
|
-
return result.documents || [];
|
|
1886
|
+
return result.documents || result.items || [];
|
|
1811
1887
|
});
|
|
1812
1888
|
// 4. Compute diff
|
|
1813
1889
|
const diff = await computeDiff(localFiles, remoteDocs, manifest);
|
|
@@ -1822,11 +1898,15 @@ export async function startMcpServer(options) {
|
|
|
1822
1898
|
conflicts: diff.conflicts.length,
|
|
1823
1899
|
};
|
|
1824
1900
|
const output = {
|
|
1901
|
+
...generatePreviewJson({
|
|
1902
|
+
corpus_slug: corpus,
|
|
1903
|
+
local_dir: dir,
|
|
1904
|
+
diff,
|
|
1905
|
+
summary,
|
|
1906
|
+
}, { includeContent }),
|
|
1825
1907
|
corpus_slug: corpus,
|
|
1826
1908
|
local_dir: dir,
|
|
1827
1909
|
action,
|
|
1828
|
-
diff,
|
|
1829
|
-
summary,
|
|
1830
1910
|
};
|
|
1831
1911
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
1832
1912
|
}
|
|
@@ -2179,7 +2259,7 @@ export async function startMcpServer(options) {
|
|
|
2179
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.', {
|
|
2180
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.'),
|
|
2181
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.'),
|
|
2182
|
-
timeout_seconds: z.number().int().min(
|
|
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.'),
|
|
2183
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.'),
|
|
2184
2264
|
limit: z.number().int().min(1).max(200).optional().describe('Maximum events to return when triggered. Defaults to 50.'),
|
|
2185
2265
|
}, async (params) => {
|
|
@@ -2268,6 +2348,7 @@ export async function startMcpServer(options) {
|
|
|
2268
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'),
|
|
2269
2349
|
target_id: z.string().describe('Target contribution UUID (the one being superseded for supersede/propose, or the proposal UUID for accept/reject)'),
|
|
2270
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.'),
|
|
2271
2352
|
}, async (params) => {
|
|
2272
2353
|
const gate = await requireConfirmed();
|
|
2273
2354
|
if (gate)
|
|
@@ -2291,7 +2372,7 @@ export async function startMcpServer(options) {
|
|
|
2291
2372
|
if (!params.contribution_id) {
|
|
2292
2373
|
return { content: [{ type: 'text', text: 'contribution_id is required for supersede' }], isError: true };
|
|
2293
2374
|
}
|
|
2294
|
-
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);
|
|
2295
2376
|
break;
|
|
2296
2377
|
default:
|
|
2297
2378
|
return { content: [{ type: 'text', text: 'Invalid action' }], isError: true };
|
|
@@ -2784,21 +2865,10 @@ export async function startMcpServer(options) {
|
|
|
2784
2865
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2785
2866
|
}
|
|
2786
2867
|
});
|
|
2787
|
-
//
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
return { content: [gate], isError: true };
|
|
2792
|
-
try {
|
|
2793
|
-
await ensureShaperContext();
|
|
2794
|
-
const result = await api.pulse();
|
|
2795
|
-
const shaped = shapePulse(result);
|
|
2796
|
-
return { content: [{ type: 'text', text: JSON.stringify(shaped, null, 2) }] };
|
|
2797
|
-
}
|
|
2798
|
-
catch (err) {
|
|
2799
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
2800
|
-
}
|
|
2801
|
-
});
|
|
2868
|
+
// wire_pulse was cut over to a projected tool_definition contribution
|
|
2869
|
+
// (playful/160/72) — the hardcoded registration was deleted here as part of
|
|
2870
|
+
// the migrate-means-DELETE cutover (design intent playful/159/2964, canon /151/5).
|
|
2871
|
+
// registerProjectedCoreTool picks it up from the graph on first surface refresh.
|
|
2802
2872
|
// ── Tool: wire_opportunities (NEW — earning opportunities) ────────
|
|
2803
2873
|
server.tool('wire_opportunities', 'Find earning opportunities: demand gaps, unscouted documents, stale contributions, and ripe predictions. FREE.', {}, async () => {
|
|
2804
2874
|
const gate = requireIdentity();
|