@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.
- package/README.md +353 -0
- package/dist/api.d.ts +660 -0
- package/dist/api.js +1157 -0
- package/dist/api.js.map +1 -0
- package/dist/background-worker.d.ts +6 -0
- package/dist/background-worker.js +101 -0
- package/dist/background-worker.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +940 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -0
- package/dist/help-pointer.d.ts +11 -0
- package/dist/help-pointer.js +10 -0
- package/dist/help-pointer.js.map +1 -0
- package/dist/identity.d.ts +40 -0
- package/dist/identity.js +193 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/local-pull.d.ts +45 -0
- package/dist/local-pull.js +173 -0
- package/dist/local-pull.js.map +1 -0
- package/dist/lockfile.d.ts +16 -0
- package/dist/lockfile.js +78 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/mcp-server.d.ts +43 -0
- package/dist/mcp-server.js +3159 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/response-shaper.d.ts +60 -0
- package/dist/response-shaper.js +893 -0
- package/dist/response-shaper.js.map +1 -0
- package/dist/slots.d.ts +65 -0
- package/dist/slots.js +128 -0
- package/dist/slots.js.map +1 -0
- package/dist/sync/diff.d.ts +17 -0
- package/dist/sync/diff.js +152 -0
- package/dist/sync/diff.js.map +1 -0
- package/dist/sync/hash.d.ts +11 -0
- package/dist/sync/hash.js +18 -0
- package/dist/sync/hash.js.map +1 -0
- package/dist/sync/index.d.ts +24 -0
- package/dist/sync/index.js +22 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/manifest.d.ts +29 -0
- package/dist/sync/manifest.js +59 -0
- package/dist/sync/manifest.js.map +1 -0
- package/dist/sync/match.d.ts +29 -0
- package/dist/sync/match.js +153 -0
- package/dist/sync/match.js.map +1 -0
- package/dist/sync/paginate.d.ts +16 -0
- package/dist/sync/paginate.js +34 -0
- package/dist/sync/paginate.js.map +1 -0
- package/dist/sync/preview.d.ts +16 -0
- package/dist/sync/preview.js +179 -0
- package/dist/sync/preview.js.map +1 -0
- package/dist/sync/pull.d.ts +23 -0
- package/dist/sync/pull.js +126 -0
- package/dist/sync/pull.js.map +1 -0
- package/dist/sync/push.d.ts +27 -0
- package/dist/sync/push.js +116 -0
- package/dist/sync/push.js.map +1 -0
- package/dist/sync/resolve.d.ts +19 -0
- package/dist/sync/resolve.js +42 -0
- package/dist/sync/resolve.js.map +1 -0
- package/dist/sync/types.d.ts +70 -0
- package/dist/sync/types.js +6 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/sync/walk.d.ts +12 -0
- package/dist/sync/walk.js +155 -0
- package/dist/sync/walk.js.map +1 -0
- package/dist/wait-cli.d.ts +46 -0
- package/dist/wait-cli.js +280 -0
- package/dist/wait-cli.js.map +1 -0
- package/dist/worker.d.ts +14 -0
- package/dist/worker.js +252 -0
- package/dist/worker.js.map +1 -0
- 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
|