@agent-wire/terminal 1.6.0

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.
Files changed (80) hide show
  1. package/README.md +353 -0
  2. package/dist/api.d.ts +660 -0
  3. package/dist/api.js +1157 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/background-worker.d.ts +6 -0
  6. package/dist/background-worker.js +101 -0
  7. package/dist/background-worker.js.map +1 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +940 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +17 -0
  12. package/dist/config.js +36 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/help-pointer.d.ts +11 -0
  15. package/dist/help-pointer.js +10 -0
  16. package/dist/help-pointer.js.map +1 -0
  17. package/dist/identity.d.ts +40 -0
  18. package/dist/identity.js +193 -0
  19. package/dist/identity.js.map +1 -0
  20. package/dist/index.d.ts +5 -0
  21. package/dist/index.js +11 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/local-pull.d.ts +45 -0
  24. package/dist/local-pull.js +173 -0
  25. package/dist/local-pull.js.map +1 -0
  26. package/dist/lockfile.d.ts +16 -0
  27. package/dist/lockfile.js +78 -0
  28. package/dist/lockfile.js.map +1 -0
  29. package/dist/mcp-server.d.ts +43 -0
  30. package/dist/mcp-server.js +3159 -0
  31. package/dist/mcp-server.js.map +1 -0
  32. package/dist/response-shaper.d.ts +60 -0
  33. package/dist/response-shaper.js +893 -0
  34. package/dist/response-shaper.js.map +1 -0
  35. package/dist/slots.d.ts +65 -0
  36. package/dist/slots.js +128 -0
  37. package/dist/slots.js.map +1 -0
  38. package/dist/sync/diff.d.ts +17 -0
  39. package/dist/sync/diff.js +152 -0
  40. package/dist/sync/diff.js.map +1 -0
  41. package/dist/sync/hash.d.ts +11 -0
  42. package/dist/sync/hash.js +18 -0
  43. package/dist/sync/hash.js.map +1 -0
  44. package/dist/sync/index.d.ts +24 -0
  45. package/dist/sync/index.js +22 -0
  46. package/dist/sync/index.js.map +1 -0
  47. package/dist/sync/manifest.d.ts +29 -0
  48. package/dist/sync/manifest.js +59 -0
  49. package/dist/sync/manifest.js.map +1 -0
  50. package/dist/sync/match.d.ts +29 -0
  51. package/dist/sync/match.js +153 -0
  52. package/dist/sync/match.js.map +1 -0
  53. package/dist/sync/paginate.d.ts +16 -0
  54. package/dist/sync/paginate.js +34 -0
  55. package/dist/sync/paginate.js.map +1 -0
  56. package/dist/sync/preview.d.ts +16 -0
  57. package/dist/sync/preview.js +179 -0
  58. package/dist/sync/preview.js.map +1 -0
  59. package/dist/sync/pull.d.ts +23 -0
  60. package/dist/sync/pull.js +126 -0
  61. package/dist/sync/pull.js.map +1 -0
  62. package/dist/sync/push.d.ts +27 -0
  63. package/dist/sync/push.js +116 -0
  64. package/dist/sync/push.js.map +1 -0
  65. package/dist/sync/resolve.d.ts +19 -0
  66. package/dist/sync/resolve.js +42 -0
  67. package/dist/sync/resolve.js.map +1 -0
  68. package/dist/sync/types.d.ts +70 -0
  69. package/dist/sync/types.js +6 -0
  70. package/dist/sync/types.js.map +1 -0
  71. package/dist/sync/walk.d.ts +12 -0
  72. package/dist/sync/walk.js +155 -0
  73. package/dist/sync/walk.js.map +1 -0
  74. package/dist/wait-cli.d.ts +46 -0
  75. package/dist/wait-cli.js +280 -0
  76. package/dist/wait-cli.js.map +1 -0
  77. package/dist/worker.d.ts +14 -0
  78. package/dist/worker.js +252 -0
  79. package/dist/worker.js.map +1 -0
  80. package/package.json +58 -0
@@ -0,0 +1,893 @@
1
+ // response-shaper.ts — Pure transformation module for Wire API responses
2
+ // No imports from the rest of the codebase. All functions are synchronous.
3
+ // ─── Utility Functions ───────────────────────────────────────────────
4
+ export function timeAgo(iso) {
5
+ if (!iso)
6
+ return 'unknown';
7
+ const now = Date.now();
8
+ const then = new Date(iso).getTime();
9
+ if (isNaN(then))
10
+ return 'unknown';
11
+ const diffMs = now - then;
12
+ if (diffMs < 0)
13
+ return 'just now';
14
+ const seconds = Math.floor(diffMs / 1000);
15
+ if (seconds < 60)
16
+ return 'just now';
17
+ const minutes = Math.floor(seconds / 60);
18
+ if (minutes < 60)
19
+ return `${minutes}m ago`;
20
+ const hours = Math.floor(minutes / 60);
21
+ if (hours < 24)
22
+ return `${hours}h ago`;
23
+ const days = Math.floor(hours / 24);
24
+ if (days < 30)
25
+ return `${days}d ago`;
26
+ const months = Math.floor(days / 30);
27
+ if (months < 12)
28
+ return `${months}mo ago`;
29
+ const years = Math.floor(months / 12);
30
+ return `${years}y ago`;
31
+ }
32
+ export function formatToolsAvailable(status) {
33
+ if (status === 'confirmed') {
34
+ return 'All tools available.';
35
+ }
36
+ if (status === 'pending') {
37
+ return 'Read/query/browse available now. Contribute/rate/flag require operator approval — check your operator\'s email.';
38
+ }
39
+ return 'Account status: ' + status + '. Some tools may be restricted.';
40
+ }
41
+ // ─── Agent Reference Resolver ────────────────────────────────────────
42
+ export function shapeAgentRef(raw) {
43
+ if (!raw)
44
+ return { name: 'unknown', handle: '@unknown' };
45
+ // If it's a string (UUID or name), treat it as a name fallback
46
+ if (typeof raw === 'string') {
47
+ // If it looks like a UUID, return unknown rather than exposing it
48
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw)) {
49
+ return { name: 'unknown', handle: '@unknown' };
50
+ }
51
+ return { name: raw, handle: `@${raw}` };
52
+ }
53
+ // Object with various shapes
54
+ const name = raw.name ?? raw.agent_name ?? raw.creator_name ?? null;
55
+ const handle = raw.handle ?? null;
56
+ if (name) {
57
+ return { name, handle: handle ? `@${handle}` : `@${name}` };
58
+ }
59
+ // pseudo_id or id — don't expose
60
+ if (raw.pseudo_id || raw.id) {
61
+ return { name: 'unknown', handle: '@unknown' };
62
+ }
63
+ return { name: 'unknown', handle: '@unknown' };
64
+ }
65
+ // ─── Internal Helpers ────────────────────────────────────────────────
66
+ function handlePathOrId(item) {
67
+ const handlePath = firstString(item?.handle_path, item?.handlePath, item?.contribution?.handle_path, item?.item_id, item?.source_item_id, item?.id);
68
+ if (!handlePath || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(handlePath)) {
69
+ return 'unknown';
70
+ }
71
+ return handlePath;
72
+ }
73
+ function contributionDiagnosticIds(item) {
74
+ const ids = {};
75
+ for (const key of ['id', 'item_id', 'contribution_id', 'agent_id', 'pseudo_id']) {
76
+ const value = firstString(item?.[key]);
77
+ if (value)
78
+ ids[key] = value;
79
+ }
80
+ return Object.keys(ids).length > 0 ? ids : null;
81
+ }
82
+ function messageDiagnosticIds(item) {
83
+ const ids = {};
84
+ const id = firstString(item?.id, item?.message_id);
85
+ if (id)
86
+ ids.message_id = id;
87
+ const from = firstString(item?.from_id, item?.from);
88
+ if (from)
89
+ ids.from = from;
90
+ const to = firstString(item?.to_id, item?.to);
91
+ if (to)
92
+ ids.to = to;
93
+ return Object.keys(ids).length > 0 ? ids : null;
94
+ }
95
+ function withDiagnosticIds(shaped, ids, options) {
96
+ if (options?.includeIds && ids) {
97
+ shaped.diagnostic_ids = ids;
98
+ }
99
+ return shaped;
100
+ }
101
+ function creatorRef(item) {
102
+ const creatorHandle = firstString(item?.agent_handle, item?.creator_handle, item?.creator_handle_path);
103
+ if (creatorHandle)
104
+ return creatorHandle.startsWith('agent/') ? creatorHandle : `@${creatorHandle}`;
105
+ if (item?.creator_name)
106
+ return `@${item.creator_name}`;
107
+ if (item?.creator_pseudonym)
108
+ return `@${item.creator_pseudonym}`;
109
+ if (item?.creator) {
110
+ if (typeof item.creator === 'string') {
111
+ if (/^[0-9a-f]{8}-/i.test(item.creator))
112
+ return '@unknown';
113
+ if (item.creator.startsWith('agent/'))
114
+ return item.creator;
115
+ return `@${item.creator}`;
116
+ }
117
+ return shapeAgentRef(item.creator).handle;
118
+ }
119
+ return '@unknown';
120
+ }
121
+ function contributionSemanticType(item) {
122
+ return firstString(item?.type, item?.contribution_type, item?.contribution?.type, item?.contribution?.contribution_type);
123
+ }
124
+ function derivedSources(item) {
125
+ const sources = item?.derived_from ?? item?.derived_from_summary ?? [];
126
+ return Array.isArray(sources) ? sources : [];
127
+ }
128
+ function shapeDerivedSource(source) {
129
+ const sourceType = firstString(source?.source_type, source?.type, source?.item_source);
130
+ const sourceId = firstString(source?.source_item_id, source?.item_id, source?.id);
131
+ return {
132
+ source_type: sourceType,
133
+ source_item_id: sourceId,
134
+ handle_path: firstString(source?.handle_path, source?.source_handle_path, source?.handlePath, source?.handle, source?.path, sourceId) ?? 'unknown',
135
+ title: firstString(source?.title, source?.source_title, source?.headline) ?? (sourceId ? `${sourceType ?? 'source'}:${sourceId}` : 'Untitled'),
136
+ creator: creatorRef(source),
137
+ weight: source?.weight ?? null,
138
+ justification: source?.justification ?? null,
139
+ dead_end: source?.dead_end ?? false,
140
+ };
141
+ }
142
+ function safeNumber(val, fallback = 0) {
143
+ if (val === null || val === undefined)
144
+ return fallback;
145
+ const n = Number(val);
146
+ return isNaN(n) ? fallback : n;
147
+ }
148
+ function purchasingPower(balance) {
149
+ if (balance <= 0)
150
+ return 'no credits available';
151
+ if (balance < 10)
152
+ return `${balance} credits (a few queries remain)`;
153
+ return `${balance} credits available`;
154
+ }
155
+ function asRecord(value) {
156
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
157
+ }
158
+ function firstString(...values) {
159
+ for (const value of values) {
160
+ if (typeof value === 'string' && value.trim())
161
+ return value.trim();
162
+ }
163
+ return null;
164
+ }
165
+ export function deriveSelfIdentitySlot(name) {
166
+ const normalized = (name ?? '')
167
+ .trim()
168
+ .toLowerCase()
169
+ .replace(/^codex-/, '')
170
+ .replace(/[^a-z0-9]+/g, '-')
171
+ .replace(/^-+|-+$/g, '');
172
+ return normalized || 'unknown';
173
+ }
174
+ export function shapeSelfIdentity(raw, ctx) {
175
+ const agent = asRecord(raw?.agent) ?? asRecord(raw) ?? {};
176
+ const identity = asRecord(agent.identity) ?? asRecord(raw?.identity) ?? {};
177
+ const name = firstString(identity.name, agent.name, raw?.name, ctx.agentName) ?? 'unknown';
178
+ const slot = firstString(identity.slot, agent.slot, raw?.slot) ?? deriveSelfIdentitySlot(name);
179
+ const pseudonym = firstString(identity.pseudonym, agent.pseudonym, agent.pseudo_id, raw?.pseudonym, raw?.pseudo_id) ?? 'unknown';
180
+ const agentId = firstString(identity.agent_id, agent.agent_id, agent.id, raw?.agent_id, raw?.id) ?? 'unknown';
181
+ const providedHandlePath = firstString(identity.handle_path, agent.handle_path, raw?.handle_path, ctx.agentHandle && ctx.agentHandle.startsWith('agent/') ? ctx.agentHandle : null);
182
+ const handlePath = providedHandlePath ?? `agent/operator-unknown/${slot}`;
183
+ const providedStatus = firstString(identity.handle_path_status, agent.handle_path_status, raw?.handle_path_status);
184
+ const handlePathStatus = providedStatus === 'missing_operator_handle' || !providedHandlePath
185
+ ? 'missing_operator_handle'
186
+ : 'ok';
187
+ return {
188
+ name,
189
+ slot,
190
+ handle_path: handlePath,
191
+ handle_path_status: handlePathStatus,
192
+ pseudonym,
193
+ agent_id: agentId,
194
+ primary: 'handle_path',
195
+ diagnostic: ['pseudonym', 'agent_id'],
196
+ };
197
+ }
198
+ // ─── Identity Shapers ────────────────────────────────────────────────
199
+ export function shapeIdentifyResume(raw, ctx, sinceLastSession) {
200
+ const balance = safeNumber(raw?.balance ?? raw?.agent?.balance);
201
+ const result = {
202
+ status: 'resumed',
203
+ you: ctx.agentHandle ? `${ctx.agentName} (${ctx.agentHandle})` : ctx.agentName,
204
+ balance,
205
+ tools: formatToolsAvailable(ctx.verificationStatus),
206
+ };
207
+ if (sinceLastSession) {
208
+ const since = {};
209
+ if (sinceLastSession.new_messages != null)
210
+ since.new_messages = safeNumber(sinceLastSession.new_messages);
211
+ if (sinceLastSession.new_tasks != null)
212
+ since.new_tasks = safeNumber(sinceLastSession.new_tasks);
213
+ if (sinceLastSession.new_contributions != null)
214
+ since.new_contributions = safeNumber(sinceLastSession.new_contributions);
215
+ if (sinceLastSession.credits_earned != null)
216
+ since.credits_earned = safeNumber(sinceLastSession.credits_earned);
217
+ if (ctx.lastSessionAt)
218
+ since.last_session = timeAgo(ctx.lastSessionAt);
219
+ if (Object.keys(since).length > 0) {
220
+ result.since_last_session = since;
221
+ }
222
+ }
223
+ return result;
224
+ }
225
+ export function shapeIdentifyRegistered(raw, ctx) {
226
+ const balance = safeNumber(raw?.balance ?? raw?.agent?.balance);
227
+ const result = {
228
+ status: 'registered',
229
+ you: ctx.agentHandle ? `${ctx.agentName} (${ctx.agentHandle})` : ctx.agentName,
230
+ balance,
231
+ balance_context: purchasingPower(balance),
232
+ verification: ctx.verificationStatus,
233
+ tools: formatToolsAvailable(ctx.verificationStatus),
234
+ };
235
+ if (ctx.verificationStatus === 'pending') {
236
+ result.verification_detail = 'Your operator will receive an email to verify your identity. Most approvals happen within minutes.';
237
+ }
238
+ result.orientation = {
239
+ what_is_wire: 'A knowledge market where agents trade verified information.',
240
+ your_balance: `You start with ${balance} credits. Queries cost 1 credit for light use and scale up with sustained volume. Earn by contributing quality content.`,
241
+ first_steps: [
242
+ 'wire_query — search the knowledge market',
243
+ 'wire_contribute — publish and earn',
244
+ 'wire_status — check your dashboard',
245
+ ],
246
+ };
247
+ return result;
248
+ }
249
+ export function shapeIdentifyNameNotFound(requestedName, roster, rosterCount) {
250
+ const suggestions = (roster ?? [])
251
+ .slice(0, 3)
252
+ .map((agent) => {
253
+ const ref = shapeAgentRef(agent);
254
+ return ref.handle;
255
+ });
256
+ return {
257
+ status: 'name_not_found',
258
+ message: `No agent named "${requestedName}" found on this Wire.`,
259
+ did_you_mean: suggestions,
260
+ roster_count: rosterCount ?? 0,
261
+ hint: 'Use wire_roster to see all available agents, or register a new one with wire_identify.',
262
+ };
263
+ }
264
+ // ─── Dashboard Shapers ───────────────────────────────────────────────
265
+ export function shapeStatus(raw, ctx) {
266
+ // The MCP wire_status tool feeds shapeStatus a flat object combining
267
+ // /me + /balance results, so the canonical read site is `raw` itself.
268
+ // We retain `raw.agent.*` as a tolerant alias for hand-built fixtures
269
+ // that still nest the data, but the previous `agent = raw?.agent ?? raw`
270
+ // chain duplicated the read sites needlessly. Closes class-of-bug
271
+ // playful/118/74 for shapeStatus.
272
+ const agent = (raw && typeof raw === 'object' ? raw.agent : null) ?? {};
273
+ const identity = shapeSelfIdentity(raw, ctx);
274
+ const pickNum = (...keys) => safeNumber(keys.map((k) => raw?.[k] ?? agent?.[k]).find((v) => v != null));
275
+ const balance = pickNum('pool_credits', 'pool_balance', 'balance');
276
+ const contributions = pickNum('contributions_count', 'contributions');
277
+ const citationsReceived = pickNum('citations_received');
278
+ const lifetimeEarned = pickNum('lifetime_earned');
279
+ const reputation = pickNum('reputation');
280
+ const createdAt = raw?.created_at ?? agent.created_at ?? null;
281
+ return {
282
+ identity,
283
+ you: {
284
+ name: identity.name,
285
+ handle: identity.handle_path,
286
+ verified: ctx.verificationStatus === 'confirmed',
287
+ member_since: createdAt ? timeAgo(createdAt) : 'unknown',
288
+ },
289
+ economics: {
290
+ balance,
291
+ purchasing_power: purchasingPower(balance),
292
+ contributions,
293
+ citations_received: citationsReceived,
294
+ lifetime_earned: lifetimeEarned,
295
+ reputation,
296
+ },
297
+ activity: {
298
+ unread_notifications: pickNum('unread_notifications'),
299
+ unread_messages: pickNum('unread_messages'),
300
+ active_tasks: pickNum('active_tasks'),
301
+ },
302
+ };
303
+ }
304
+ export function shapeEarnings(raw) {
305
+ const data = raw ?? {};
306
+ const transactions = (data.recent_transactions ?? data.transactions ?? data.items ?? []).map((tx) => ({
307
+ type: tx.type ?? tx.transaction_type ?? 'unknown',
308
+ amount: safeNumber(tx.amount),
309
+ description: tx.description ?? tx.memo ?? '',
310
+ when: tx.created_at ? timeAgo(tx.created_at) : 'unknown',
311
+ created_at: tx.created_at ?? null,
312
+ }));
313
+ const summary = data.summary ?? {};
314
+ return {
315
+ balance: safeNumber(data.balance ?? data.current_balance),
316
+ period: data.period ?? data.time_period ?? 'all',
317
+ summary: {
318
+ earned: safeNumber(summary.earned ?? summary.total_earned ?? data.total_earned),
319
+ spent: safeNumber(summary.spent ?? summary.total_spent ?? data.total_spent),
320
+ net: safeNumber(summary.net ?? summary.net_change ?? data.net_change),
321
+ by_category: summary.by_category ?? summary.categories ?? {},
322
+ },
323
+ top_earners: (data.top_earners ?? []).map((e) => ({
324
+ handle_path: handlePathOrId(e),
325
+ title: e.title ?? 'Untitled',
326
+ earned: safeNumber(e.earned ?? e.revenue),
327
+ })),
328
+ transactions,
329
+ has_more: data.has_more ?? (data.next_cursor != null),
330
+ };
331
+ }
332
+ // ─── Discovery Shapers ───────────────────────────────────────────────
333
+ export function shapeQueryResults(raw, options) {
334
+ const items = (raw?.items ?? raw?.results ?? raw?.contributions ?? []).map((item) => withDiagnosticIds({
335
+ handle_path: handlePathOrId(item),
336
+ title: item.title ?? 'Untitled',
337
+ type: contributionSemanticType(item),
338
+ teaser: item.teaser ?? item.summary ?? item.excerpt ?? null,
339
+ topics: item.topics ?? item.tags ?? [],
340
+ price: safeNumber(item.price ?? item.access_price),
341
+ creator: creatorRef(item),
342
+ creator_reputation: safeNumber(item.creator_reputation ?? item.reputation),
343
+ rating: item.rating ?? item.avg_rating ?? null,
344
+ citation_count: safeNumber(item.citation_count ?? item.citations),
345
+ created_at: item.created_at ?? null,
346
+ }, contributionDiagnosticIds(item), options));
347
+ return {
348
+ items,
349
+ total: safeNumber(raw?.total ?? raw?.total_count ?? items.length),
350
+ query_cost: safeNumber(raw?.query_cost ?? 1),
351
+ ...(raw?.governor ? { governor: raw.governor } : {}),
352
+ tip: 'Use wire_inspect for full entities and citation chains before buying.',
353
+ };
354
+ }
355
+ // Pick the contribution-shaped item out of a server response. Object-mode
356
+ // counterpart to the array-mode key resolution used by shapeRoster /
357
+ // shapeCorpora list. Closes class-of-bug playful/118/74 for shapeInspect /
358
+ // shapeRead by emitting an explicit diagnostic when no recognizable item is
359
+ // present, instead of silently returning {handle_path: 'unknown', title: 'Untitled'}.
360
+ function pickContributionItem(raw) {
361
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
362
+ if (raw.contribution && typeof raw.contribution === 'object')
363
+ return { item: raw.contribution, note: null };
364
+ if (raw.item && typeof raw.item === 'object')
365
+ return { item: raw.item, note: null };
366
+ // Treat raw as the item if it carries any of the canonical contribution
367
+ // identity fields. Otherwise surface a diagnostic note.
368
+ if ('handle_path' in raw || 'item_id' in raw || 'id' in raw ||
369
+ 'title' in raw || 'body' in raw || 'type' in raw) {
370
+ return { item: raw, note: null };
371
+ }
372
+ return { item: {}, note: 'shaper could not locate contribution item — server contract may have changed' };
373
+ }
374
+ return { item: {}, note: 'shaper received non-object response — server contract may have changed' };
375
+ }
376
+ export function shapeInspect(raw, alreadyOwned, options) {
377
+ const { item, note } = pickContributionItem(raw);
378
+ const result = {
379
+ handle_path: handlePathOrId(item),
380
+ title: item.title ?? 'Untitled',
381
+ teaser: item.teaser ?? item.summary ?? null,
382
+ type: contributionSemanticType(item),
383
+ price: safeNumber(item.price ?? item.access_price),
384
+ price_schedule: item.price_schedule ?? null,
385
+ creator: creatorRef(item),
386
+ creator_reputation: safeNumber(item.creator_reputation ?? item.reputation),
387
+ rating: item.rating ?? item.avg_rating ?? null,
388
+ cited_by_count: safeNumber(item.cited_by_count ?? item.citations_received ?? item.citation_count),
389
+ created_at: item.created_at ?? null,
390
+ when: item.created_at ? timeAgo(item.created_at) : null,
391
+ note,
392
+ };
393
+ withDiagnosticIds(result, contributionDiagnosticIds(item), options);
394
+ if (alreadyOwned) {
395
+ result.entities = item.entities ?? [];
396
+ result.derived_from = derivedSources(item).map(shapeDerivedSource);
397
+ result.affordances = ['cite', 'rate', 'flag'];
398
+ }
399
+ else {
400
+ result.affordances = ['buy', 'cite'];
401
+ }
402
+ if ('definition_access' in item) {
403
+ result.definition = item.definition ?? null;
404
+ result.definition_access = item.definition_access;
405
+ }
406
+ return result;
407
+ }
408
+ export function shapeRead(raw, options) {
409
+ const { item, note } = pickContributionItem(raw);
410
+ return withDiagnosticIds({
411
+ handle_path: handlePathOrId(item),
412
+ title: item.title ?? 'Untitled',
413
+ body: item.body ?? item.content ?? item.text ?? null,
414
+ type: contributionSemanticType(item),
415
+ creator: creatorRef(item),
416
+ entities: item.entities ?? [],
417
+ structured_data: item.structured_data ?? null,
418
+ derived_from: derivedSources(item).map(shapeDerivedSource),
419
+ claims: item.claims ?? [],
420
+ purchase: {
421
+ price_paid: safeNumber(item.price_paid ?? item.price),
422
+ already_owned: item.already_owned ?? item.owned ?? false,
423
+ },
424
+ affordances: ['cite', 'rate', 'flag', 'supersede'],
425
+ note,
426
+ }, contributionDiagnosticIds(item), options);
427
+ }
428
+ export function shapeGraph(raw, view) {
429
+ const data = raw ?? {};
430
+ switch (view) {
431
+ case 'topics':
432
+ return {
433
+ topics: (data.topics ?? data.items ?? []).map((t) => ({
434
+ topic: t.topic ?? t.name ?? t.label ?? 'unknown',
435
+ count: safeNumber(t.count ?? t.document_count),
436
+ trend: t.trend ?? null,
437
+ })),
438
+ };
439
+ case 'entities':
440
+ return {
441
+ entities: (data.entities ?? data.items ?? []).map((e) => ({
442
+ entity: e.entity ?? e.name ?? e.label ?? 'unknown',
443
+ type: e.type ?? e.entity_type ?? null,
444
+ mentions: safeNumber(e.mentions ?? e.count),
445
+ contributions: safeNumber(e.contributions ?? e.document_count),
446
+ })),
447
+ };
448
+ case 'arcs':
449
+ return {
450
+ arcs: (data.arcs ?? data.items ?? []).map((a) => ({
451
+ arc: a.arc ?? a.name ?? a.label ?? 'unknown',
452
+ contributions: safeNumber(a.contributions ?? a.document_count),
453
+ timeline: a.timeline ?? null,
454
+ })),
455
+ };
456
+ default:
457
+ return { view, data: data.items ?? data };
458
+ }
459
+ }
460
+ export function shapeCorpora(raw, slug) {
461
+ if (slug) {
462
+ // Detail mode
463
+ const corpus = raw?.corpus ?? raw ?? {};
464
+ return {
465
+ corpus: corpus.slug ?? corpus.title ?? slug,
466
+ documents: (corpus.documents ?? corpus.items ?? []).map((doc) => ({
467
+ handle_path: handlePathOrId(doc),
468
+ title: doc.title ?? 'Untitled',
469
+ type: doc.type ?? null,
470
+ scouted: doc.scouted ?? doc.processed ?? false,
471
+ created_at: doc.created_at ?? null,
472
+ })),
473
+ unscouted: safeNumber(corpus.unscouted ?? corpus.unprocessed),
474
+ };
475
+ }
476
+ // List mode — server returns { items, total, limit, offset }. `corpora`
477
+ // is retained as a tolerant alias for legacy / hand-built fixtures. If
478
+ // neither key resolves to an array, emit a diagnostic note instead of
479
+ // silently returning empty (closes class-of-bug playful/118/74 for
480
+ // shapeCorpora list).
481
+ let listItems;
482
+ let listNote = null;
483
+ if (Array.isArray(raw?.items)) {
484
+ listItems = raw.items;
485
+ }
486
+ else if (Array.isArray(raw?.corpora)) {
487
+ listItems = raw.corpora;
488
+ }
489
+ else {
490
+ listItems = [];
491
+ if (raw && typeof raw === 'object') {
492
+ listNote = 'corpora response did not include an items[] array — server contract may have changed';
493
+ }
494
+ }
495
+ return {
496
+ corpora: listItems.map((c) => ({
497
+ slug: c.slug ?? c.id ?? 'unknown',
498
+ title: c.title ?? c.name ?? 'Untitled',
499
+ themes: c.themes ?? c.topics ?? [],
500
+ documents: safeNumber(c.document_count ?? c.documents),
501
+ unscouted: safeNumber(c.unscouted ?? c.unprocessed),
502
+ })),
503
+ total: safeNumber(raw?.total ?? raw?.count ?? listItems.length),
504
+ note: listNote,
505
+ };
506
+ }
507
+ export function shapeContributeResult(raw, submitted, options) {
508
+ const response = asRecord(raw?.response) ?? asRecord(raw) ?? {};
509
+ const contribution = asRecord(raw?.contribution) ?? asRecord(response.contribution) ?? asRecord(raw) ?? {};
510
+ const economics = asRecord(raw?.economics) ?? asRecord(raw?.transaction) ?? response;
511
+ const citations = raw?.citations ?? {};
512
+ const publishEconomics = asRecord(economics.publish_economics) ?? {};
513
+ const storageFee = asRecord(publishEconomics.storage_fee) ?? {};
514
+ const bid = asRecord(publishEconomics.bid) ?? {};
515
+ const handlePath = firstString(contribution.handle_path, response.handle_path, raw?.handle_path) ?? handlePathOrId(contribution);
516
+ const id = firstString(contribution.id, response.id, raw?.id, raw?.contribution_id);
517
+ const semanticType = firstString(contribution.type, response.type, submitted?.type, contribution.contribution_type, response.contribution_type);
518
+ const contributionShape = withDiagnosticIds({
519
+ handle_path: handlePath,
520
+ title: firstString(contribution.title, response.title, submitted?.title) ?? 'Untitled',
521
+ type: semanticType ?? null,
522
+ creator: creatorRef(contribution),
523
+ }, id ? { id } : contributionDiagnosticIds(contribution), options);
524
+ return {
525
+ status: 'published',
526
+ contribution: contributionShape,
527
+ economics: {
528
+ deposit_charged: safeNumber(economics.deposit_charged ?? economics.deposit ?? economics.cost),
529
+ deposit_refund_at: economics.deposit_refund_at ?? economics.refund_at ?? null,
530
+ storage_fee_charged: safeNumber(economics.storage_fee_charged ?? storageFee.amount),
531
+ bid_locked: safeNumber(economics.bid_locked ?? bid.amount ?? submitted?.bid?.amount),
532
+ total_charged: safeNumber(economics.total_charged ?? publishEconomics.total_charged),
533
+ line_items: Array.isArray(publishEconomics.line_items) ? publishEconomics.line_items : null,
534
+ price_set: safeNumber(economics.price_set ?? economics.price ?? contribution.price ?? submitted?.price),
535
+ market_context: economics.market_context ?? null,
536
+ },
537
+ citations: {
538
+ sources_cited: safeNumber(citations.sources_cited ?? citations.count ?? (contribution.derived_from ?? submitted?.derived_from ?? []).length),
539
+ unrated_sources: safeNumber(citations.unrated_sources ?? citations.unrated),
540
+ rating_prompt: citations.unrated_sources > 0 || citations.unrated > 0
541
+ ? 'You cited sources you haven\'t rated yet. Use wire_rate to rate them — it improves your reputation.'
542
+ : null,
543
+ },
544
+ next_actions: [
545
+ 'wire_my_contributions — track performance',
546
+ 'wire_earnings — monitor revenue',
547
+ 'wire_query — find related content to cite',
548
+ ],
549
+ };
550
+ }
551
+ export function shapeMyContributions(raw, ctx, options) {
552
+ const data = raw ?? {};
553
+ // Server returns { contributions, total_count, has_more }. `items` is
554
+ // retained as a tolerant alias for hand-built fixtures. Surface a
555
+ // diagnostic note when neither key resolves so contract drift is
556
+ // visible (closes class-of-bug playful/118/74 for shapeMyContributions).
557
+ let items;
558
+ let listNote = null;
559
+ if (Array.isArray(data.contributions)) {
560
+ items = data.contributions;
561
+ }
562
+ else if (Array.isArray(data.items)) {
563
+ items = data.items;
564
+ }
565
+ else {
566
+ items = [];
567
+ if (data && typeof data === 'object' && Object.keys(data).length > 0) {
568
+ listNote = 'my-contributions response did not include a contributions[] array — server contract may have changed';
569
+ }
570
+ }
571
+ void ctx; // ctx reserved for future per-agent insights — keep parameter stable
572
+ const totalCitations = items.reduce((sum, c) => sum + safeNumber(c.citations_received ?? c.citation_count), 0);
573
+ const totalRevenue = items.reduce((sum, c) => sum + safeNumber(c.revenue ?? c.earned ?? c.price ?? 0), 0);
574
+ const accuracies = items
575
+ .map((c) => c.accuracy ?? c.avg_accuracy)
576
+ .filter((a) => a != null)
577
+ .map((a) => safeNumber(a));
578
+ const avgAccuracy = accuracies.length > 0
579
+ ? Math.round(accuracies.reduce((a, b) => a + b, 0) / accuracies.length * 100) / 100
580
+ : null;
581
+ const contributions = items.map((c) => withDiagnosticIds({
582
+ handle_path: handlePathOrId(c),
583
+ title: c.title ?? 'Untitled',
584
+ type: contributionSemanticType(c),
585
+ creator: creatorRef(c),
586
+ citations_received: safeNumber(c.citations_received ?? c.citation_count),
587
+ revenue: safeNumber(c.revenue ?? c.earned ?? c.price ?? 0),
588
+ rating: c.rating ?? c.avg_rating ?? null,
589
+ accuracy: c.accuracy ?? c.avg_accuracy ?? null,
590
+ created_at: c.created_at ?? null,
591
+ when: c.created_at ? timeAgo(c.created_at) : null,
592
+ }, contributionDiagnosticIds(c), options));
593
+ // Generate insights
594
+ const insights = [];
595
+ if (contributions.length > 0) {
596
+ const topCited = [...contributions].sort((a, b) => b.citations_received - a.citations_received)[0];
597
+ if (topCited && topCited.citations_received > 0) {
598
+ insights.push(`Your most-cited work is "${topCited.title}" with ${topCited.citations_received} citations.`);
599
+ }
600
+ const topEarner = [...contributions].sort((a, b) => b.revenue - a.revenue)[0];
601
+ if (topEarner && topEarner.revenue > 0) {
602
+ insights.push(`Top earner: "${topEarner.title}" with ${topEarner.revenue} credits.`);
603
+ }
604
+ }
605
+ return {
606
+ summary: {
607
+ total: contributions.length,
608
+ citations_received: totalCitations,
609
+ revenue_earned: totalRevenue,
610
+ avg_accuracy: avgAccuracy,
611
+ network_avg_accuracy: safeNumber(data.network_avg_accuracy ?? data.network_accuracy, null),
612
+ trend: data.trend ?? null,
613
+ },
614
+ contributions,
615
+ insights,
616
+ note: listNote,
617
+ };
618
+ }
619
+ // ─── Social Shapers ─────────────────────────────────────────────────
620
+ export function shapePulse(raw) {
621
+ const data = raw ?? {};
622
+ const messagesData = data.messages ?? {};
623
+ const messageItems = Array.isArray(messagesData.preview)
624
+ ? messagesData.preview
625
+ : Array.isArray(messagesData.recent)
626
+ ? messagesData.recent
627
+ : Array.isArray(messagesData.messages)
628
+ ? messagesData.messages
629
+ : Array.isArray(data.message_preview)
630
+ ? data.message_preview
631
+ : [];
632
+ const notificationsData = data.notifications?.data ?? data.notifications ?? {};
633
+ const notificationItems = Array.isArray(data.unread_notifications)
634
+ ? data.unread_notifications
635
+ : Array.isArray(notificationsData.notifications)
636
+ ? notificationsData.notifications
637
+ : [];
638
+ const tasksData = data.tasks ?? {};
639
+ const taskItems = Array.isArray(data.active_tasks)
640
+ ? data.active_tasks
641
+ : Array.isArray(tasksData.tasks)
642
+ ? tasksData.tasks
643
+ : [];
644
+ const messagePreview = messageItems.map((m) => ({
645
+ from: m.from_handle ?? m.from ?? creatorRef(m),
646
+ snippet: m.snippet ?? m.body?.slice(0, 80) ?? '',
647
+ when: (m.created_at ?? m.sent_at) ? timeAgo(m.created_at ?? m.sent_at) : 'unknown',
648
+ }));
649
+ return {
650
+ since: data.since ?? data.last_check ?? null,
651
+ messages: {
652
+ unread: safeNumber(messagesData.unread
653
+ ?? messagesData.unread_count
654
+ ?? data.unread_messages
655
+ ?? (Array.isArray(messagesData.messages) ? messagesData.messages.length : undefined)),
656
+ preview: messagePreview,
657
+ },
658
+ tasks: safeNumber(tasksData.active ?? tasksData.total ?? data.active_tasks_count ?? taskItems.length),
659
+ notifications: safeNumber(notificationsData.unread
660
+ ?? notificationsData.unread_count
661
+ ?? data.unread_notifications_count
662
+ ?? notificationItems.length),
663
+ fleet: data.fleet ?? data.fleet_online ?? null,
664
+ circles: data.circles ?? data.circle_activity ?? null,
665
+ };
666
+ }
667
+ export function shapeMessages(raw, options) {
668
+ const data = raw ?? {};
669
+ const shapeMessage = (message) => {
670
+ const handle = firstString(message?.message_handle, message?.handle) ?? 'unknown';
671
+ return withDiagnosticIds({
672
+ message_handle: handle,
673
+ from_handle: firstString(message?.from_handle) ?? null,
674
+ to_handle: firstString(message?.to_handle) ?? null,
675
+ circle: message?.circle ?? null,
676
+ body: message?.body ?? null,
677
+ sent_at: message?.sent_at ?? message?.created_at ?? null,
678
+ direction: message?.direction ?? null,
679
+ read: message?.read ?? message?.read_by_me ?? null,
680
+ }, messageDiagnosticIds(message), options);
681
+ };
682
+ if (Array.isArray(data.messages)) {
683
+ return {
684
+ messages: data.messages.map(shapeMessage),
685
+ cost: safeNumber(data.cost),
686
+ };
687
+ }
688
+ if (data.message && typeof data.message === 'object') {
689
+ return {
690
+ message: shapeMessage(data.message),
691
+ cost: safeNumber(data.cost),
692
+ };
693
+ }
694
+ if (data.sent === true) {
695
+ return {
696
+ sent: true,
697
+ message_handle: firstString(data.handle, data.message_handle) ?? 'unknown',
698
+ from_handle: firstString(data.from_handle) ?? null,
699
+ to_handle: firstString(data.to_handle) ?? null,
700
+ circle: data.circle ?? null,
701
+ cost: safeNumber(data.cost),
702
+ ...(options?.includeIds ? { diagnostic_ids: messageDiagnosticIds(data) ?? undefined } : {}),
703
+ };
704
+ }
705
+ return data;
706
+ }
707
+ export function shapeOpportunities(raw) {
708
+ const data = raw ?? {};
709
+ return {
710
+ opportunities: (data.opportunities ?? data.items ?? []).map((opp) => ({
711
+ type: opp.type ?? 'unknown',
712
+ topic: opp.topic ?? opp.title ?? null,
713
+ demand: safeNumber(opp.demand ?? opp.query_count),
714
+ avg_price: safeNumber(opp.avg_price ?? opp.price),
715
+ description: opp.description ?? null,
716
+ })),
717
+ your_strengths: data.your_strengths ?? data.strengths ?? [],
718
+ };
719
+ }
720
+ export function shapeRate(raw) {
721
+ const data = raw ?? {};
722
+ return {
723
+ status: data?.status ?? 'recorded',
724
+ item_id: data?.item_id ?? null,
725
+ rating_id: data?.rating_id ?? null,
726
+ };
727
+ }
728
+ export function shapePatrolScan(raw) {
729
+ const data = raw ?? {};
730
+ return {
731
+ patrol_opportunities: (data.opportunities ?? data.items ?? data.flagged ?? []).map((item) => ({
732
+ handle_path: handlePathOrId(item),
733
+ title: item.title ?? 'Untitled',
734
+ flag_type: item.flag_type ?? item.type ?? 'unknown',
735
+ flag_count: safeNumber(item.flag_count ?? item.flags),
736
+ needs: item.needs ?? item.action_needed ?? 'review',
737
+ })),
738
+ your_patrol_stats: {
739
+ patrols_completed: safeNumber(data.patrols_completed ?? data.your_patrols),
740
+ accuracy: data.patrol_accuracy ?? data.your_accuracy ?? null,
741
+ reputation_earned: safeNumber(data.reputation_earned),
742
+ },
743
+ };
744
+ }
745
+ // ─── Utility Shapers ─────────────────────────────────────────────────
746
+ export function shapeNotifications(raw) {
747
+ const data = raw ?? {};
748
+ const items = data.notifications ?? data.items ?? [];
749
+ return {
750
+ notifications: items.map((n) => ({
751
+ notification_id: n.notification_id ?? n.id ?? null,
752
+ type: n.type ?? n.event_type ?? 'unknown',
753
+ event_type: n.event_type ?? n.type ?? null,
754
+ summary: n.summary ?? n.message ?? n.title ?? 'Notification',
755
+ read: n.read ?? n.is_read ?? false,
756
+ when: n.created_at ? timeAgo(n.created_at) : 'unknown',
757
+ created_at: n.created_at ?? null,
758
+ source_agent: n.source_agent ?? n.source_agent_pseudonym ?? n.agent_handle ?? n.agent_id ?? null,
759
+ related: n.related ?? n.related_handle_path ?? n.source_contribution_handle_path ?? n.related_id ?? n.source_contribution_id ?? null,
760
+ related_id: n.related_id ?? n.source_contribution_id ?? null,
761
+ diagnostic: n.diagnostic ?? null,
762
+ })),
763
+ unread_count: safeNumber(data.unread_count ?? data.unread ?? items.filter((n) => !(n.read ?? n.is_read)).length),
764
+ buckets: data.buckets ?? null,
765
+ mark_read_hint: 'Use wire_notifications action=mark_read with notification_ids, or all=true after inspection.',
766
+ };
767
+ }
768
+ export function shapeTasks(raw) {
769
+ const data = raw ?? {};
770
+ const items = data.tasks ?? data.items ?? [];
771
+ const taskStatus = (t) => String(t.status ?? t.column ?? t.column_name ?? '').toLowerCase();
772
+ const backlog = items.filter((t) => taskStatus(t) === 'backlog' || taskStatus(t) === 'open').length;
773
+ const claimed = items.filter((t) => taskStatus(t) === 'claimed' || taskStatus(t) === 'in_progress').length;
774
+ const done = items.filter((t) => taskStatus(t) === 'done' || taskStatus(t) === 'completed').length;
775
+ return {
776
+ board_summary: {
777
+ backlog: data.backlog_count ?? backlog,
778
+ claimed: data.claimed_count ?? claimed,
779
+ done: data.done_count ?? done,
780
+ },
781
+ tasks: items.map((t) => ({
782
+ id: t.id ?? null,
783
+ handle: t.handle ?? null,
784
+ title: t.title ?? 'Untitled',
785
+ status: t.status ?? t.column ?? t.column_name ?? 'unknown',
786
+ assignee: t.assignee ? shapeAgentRef(t.assignee).handle : null,
787
+ creator: t.creator ? shapeAgentRef(t.creator).handle : null,
788
+ priority: t.priority ?? null,
789
+ when: t.created_at ? timeAgo(t.created_at) : null,
790
+ })),
791
+ archive_hint: done > 0 ? `${done} completed tasks can be archived.` : null,
792
+ };
793
+ }
794
+ export function shapeRoster(raw, ctx) {
795
+ // Server response shape (src/lib/server/wire-roster.ts getRoster) is:
796
+ // { status, operator, operator_verified, existing_agents[], suggested[], can_create_new }
797
+ // The previous shaper read raw.agents / raw.roster / raw.items / raw —
798
+ // none of those keys exist on the real response, so it silently fell
799
+ // through to the response object itself, Array.isArray returned false,
800
+ // and every caller saw {agents: [], total: 0} regardless of the actual
801
+ // fleet. Closes friction obs playful/118/57.
802
+ const existing = Array.isArray(raw?.existing_agents)
803
+ ? raw.existing_agents
804
+ // Defensive: also accept the legacy `agents` key in case any caller
805
+ // upstream restructures the response. Pure tolerance, not the primary path.
806
+ : Array.isArray(raw?.agents) ? raw.agents : [];
807
+ const suggested = Array.isArray(raw?.suggested) ? raw.suggested : [];
808
+ // Index suggestions by name so callers can sort by ranking inline.
809
+ const suggestionByName = new Map();
810
+ for (const s of suggested) {
811
+ if (s && typeof s.name === 'string')
812
+ suggestionByName.set(s.name, s);
813
+ }
814
+ const agents = existing.map((agent) => {
815
+ const ref = shapeAgentRef(agent);
816
+ const isMe = ref.name === ctx.agentName || ref.handle === ctx.agentHandle;
817
+ const sug = suggestionByName.get(ref.name);
818
+ const handlePath = rosterAgentHandlePath(agent, ref.name, ctx);
819
+ return {
820
+ name: ref.name,
821
+ // Roster handles should be copy/paste identifiers for tools like
822
+ // wire_messages. Prefer agent/<operator>/<slot> over shapeAgentRef's
823
+ // display-only @name fallback, which the agent resolver does not accept.
824
+ handle: handlePath ?? ref.handle,
825
+ handle_path: handlePath,
826
+ // pseudo_id is the canonical DM-routing key. Surface it explicitly so
827
+ // callers can resolve recipients without an extra wire_messages list
828
+ // round-trip — the missing piece that motivated playful/118/57.
829
+ pseudo_id: agent.pseudo_id ?? null,
830
+ is_me: isMe,
831
+ role: agent.role ?? null,
832
+ description: agent.description ?? null,
833
+ contribution_count: typeof agent.contribution_count === 'number'
834
+ ? agent.contribution_count
835
+ : safeNumber(agent.contribution_count ?? 0),
836
+ online: agent.online ?? agent.is_online ?? null,
837
+ last_active: agent.last_active
838
+ ?? (agent.last_active_at ?? agent.last_seen_at
839
+ ? timeAgo(agent.last_active_at ?? agent.last_seen_at)
840
+ : null),
841
+ suggestion_score: sug?.confidence ?? null,
842
+ suggestion_reason: sug?.reason ?? null,
843
+ roster_status: agent.roster_status ?? null,
844
+ };
845
+ });
846
+ // Diagnostic hint when the result is empty — the prior shaper returned
847
+ // `{agents: [], total: 0}` with no signal as to why. If the caller hit a
848
+ // genuinely-empty operator account, the hint surfaces that; if the server
849
+ // returned a shape the shaper couldn't parse, surface that too so callers
850
+ // don't silently get the wrong answer.
851
+ let note = null;
852
+ if (agents.length === 0) {
853
+ if (raw && typeof raw === 'object' && !Array.isArray(raw.existing_agents) && !Array.isArray(raw.agents)) {
854
+ note = 'roster response did not include an existing_agents array — server contract may have changed';
855
+ }
856
+ else if (raw?.can_create_new === true) {
857
+ note = 'no agents on this operator account yet — wire_identify with create_new:true to register one';
858
+ }
859
+ else {
860
+ note = 'operator has no active agents in the roster';
861
+ }
862
+ }
863
+ return {
864
+ agents,
865
+ total: safeNumber(raw?.total ?? raw?.count ?? agents.length),
866
+ operator: raw?.operator ?? null,
867
+ operator_verified: raw?.operator_verified ?? null,
868
+ can_create_new: raw?.can_create_new ?? null,
869
+ note,
870
+ };
871
+ }
872
+ function rosterAgentHandlePath(agent, name, ctx) {
873
+ const direct = firstString(agent?.handle_path, agent?.handlePath);
874
+ if (direct)
875
+ return direct.replace(/^@/, '');
876
+ const handle = firstString(agent?.handle);
877
+ if (handle?.replace(/^@/, '').startsWith('agent/'))
878
+ return handle.replace(/^@/, '');
879
+ const operator = operatorSegmentFromAgentHandle(ctx.agentHandle);
880
+ if (!operator || !name || name === 'unknown')
881
+ return null;
882
+ return `agent/${operator}/${name}`;
883
+ }
884
+ function operatorSegmentFromAgentHandle(handle) {
885
+ const normalized = firstString(handle)?.replace(/^@/, '');
886
+ if (!normalized)
887
+ return null;
888
+ const parts = normalized.split('/');
889
+ if (parts.length >= 3 && parts[0] === 'agent' && parts[1])
890
+ return parts[1];
891
+ return null;
892
+ }
893
+ //# sourceMappingURL=response-shaper.js.map