@neuroverseos/governance 0.11.0 → 0.12.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/dist/{audit-CRJOB4CP.js → audit-JYNN3MOQ.js} +5 -0
- package/dist/cli/neuroverse.cjs +5 -0
- package/dist/cli/neuroverse.js +1 -1
- package/dist/radiant/index.cjs +407 -0
- package/dist/radiant/index.d.cts +131 -1
- package/dist/radiant/index.d.ts +131 -1
- package/dist/radiant/index.js +403 -0
- package/package.json +1 -1
|
@@ -46,6 +46,11 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
46
46
|
let world;
|
|
47
47
|
try {
|
|
48
48
|
const resolved = resolveWorldPath(args.worldPath);
|
|
49
|
+
if (!resolved) {
|
|
50
|
+
process.stderr.write(`Failed to resolve world path: ${args.worldPath}
|
|
51
|
+
`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
49
54
|
world = await loadWorld(resolved);
|
|
50
55
|
} catch (err) {
|
|
51
56
|
process.stderr.write(`Failed to load world: ${err instanceof Error ? err.message : err}
|
package/dist/cli/neuroverse.cjs
CHANGED
|
@@ -7702,6 +7702,11 @@ async function main12(argv = process.argv.slice(2)) {
|
|
|
7702
7702
|
let world;
|
|
7703
7703
|
try {
|
|
7704
7704
|
const resolved = resolveWorldPath(args.worldPath);
|
|
7705
|
+
if (!resolved) {
|
|
7706
|
+
process.stderr.write(`Failed to resolve world path: ${args.worldPath}
|
|
7707
|
+
`);
|
|
7708
|
+
process.exit(1);
|
|
7709
|
+
}
|
|
7705
7710
|
world = await loadWorld(resolved);
|
|
7706
7711
|
} catch (err) {
|
|
7707
7712
|
process.stderr.write(`Failed to load world: ${err instanceof Error ? err.message : err}
|
package/dist/cli/neuroverse.js
CHANGED
|
@@ -101,7 +101,7 @@ async function main() {
|
|
|
101
101
|
return guardMain(subArgs);
|
|
102
102
|
}
|
|
103
103
|
case "audit": {
|
|
104
|
-
const { main: auditMain } = await import("../audit-
|
|
104
|
+
const { main: auditMain } = await import("../audit-JYNN3MOQ.js");
|
|
105
105
|
return auditMain(subArgs);
|
|
106
106
|
}
|
|
107
107
|
case "test": {
|
package/dist/radiant/index.cjs
CHANGED
|
@@ -56,16 +56,20 @@ __export(radiant_exports, {
|
|
|
56
56
|
fetchDiscordActivity: () => fetchDiscordActivity,
|
|
57
57
|
fetchGitHubActivity: () => fetchGitHubActivity,
|
|
58
58
|
fetchGitHubOrgActivity: () => fetchGitHubOrgActivity,
|
|
59
|
+
fetchGoogleWorkspaceActivity: () => fetchGoogleWorkspaceActivity,
|
|
59
60
|
fetchLinearActivity: () => fetchLinearActivity,
|
|
60
61
|
fetchNotionActivity: () => fetchNotionActivity,
|
|
62
|
+
fetchSalesforceActivity: () => fetchSalesforceActivity,
|
|
61
63
|
fetchSlackActivity: () => fetchSlackActivity,
|
|
62
64
|
filterEventsByUser: () => filterEventsByUser,
|
|
63
65
|
formatActiveWorlds: () => formatActiveWorlds,
|
|
64
66
|
formatDiscordSignalsForPrompt: () => formatDiscordSignalsForPrompt,
|
|
65
67
|
formatExocortexForPrompt: () => formatExocortexForPrompt,
|
|
68
|
+
formatGoogleWorkspaceSignalsForPrompt: () => formatGoogleWorkspaceSignalsForPrompt,
|
|
66
69
|
formatLinearSignalsForPrompt: () => formatLinearSignalsForPrompt,
|
|
67
70
|
formatNotionSignalsForPrompt: () => formatNotionSignalsForPrompt,
|
|
68
71
|
formatPriorReadsForPrompt: () => formatPriorReadsForPrompt,
|
|
72
|
+
formatSalesforceSignalsForPrompt: () => formatSalesforceSignalsForPrompt,
|
|
69
73
|
formatScope: () => formatScope,
|
|
70
74
|
formatSlackSignalsForPrompt: () => formatSlackSignalsForPrompt,
|
|
71
75
|
formatTeamExocorticesForPrompt: () => formatTeamExocorticesForPrompt,
|
|
@@ -2296,6 +2300,405 @@ async function fetchLinearGraphQL(apiKey, query, variables) {
|
|
|
2296
2300
|
return json.data;
|
|
2297
2301
|
}
|
|
2298
2302
|
|
|
2303
|
+
// src/radiant/adapters/google-workspace.ts
|
|
2304
|
+
async function fetchGoogleWorkspaceActivity(options) {
|
|
2305
|
+
const windowDays = options.windowDays ?? 14;
|
|
2306
|
+
const maxEmails = options.maxEmails ?? 100;
|
|
2307
|
+
const maxEvents = options.maxEvents ?? 100;
|
|
2308
|
+
const leaderEmail = options.leaderEmail?.toLowerCase();
|
|
2309
|
+
const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
|
|
2310
|
+
const [emailEvents, emailSignals] = await fetchGmailSent(
|
|
2311
|
+
options.accessToken,
|
|
2312
|
+
since,
|
|
2313
|
+
maxEmails,
|
|
2314
|
+
leaderEmail
|
|
2315
|
+
);
|
|
2316
|
+
const [calendarEvents, calendarSignals] = await fetchCalendarEvents(
|
|
2317
|
+
options.accessToken,
|
|
2318
|
+
since,
|
|
2319
|
+
maxEvents,
|
|
2320
|
+
leaderEmail
|
|
2321
|
+
);
|
|
2322
|
+
const events = [...emailEvents, ...calendarEvents].sort(
|
|
2323
|
+
(a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)
|
|
2324
|
+
);
|
|
2325
|
+
return {
|
|
2326
|
+
events,
|
|
2327
|
+
signals: {
|
|
2328
|
+
...emailSignals,
|
|
2329
|
+
...calendarSignals
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
function formatGoogleWorkspaceSignalsForPrompt(signals) {
|
|
2334
|
+
if (signals.emailsSent === 0 && signals.meetingsHeld === 0) return "";
|
|
2335
|
+
const lines = [
|
|
2336
|
+
"## Google Workspace Activity (external coordination + meeting load)",
|
|
2337
|
+
""
|
|
2338
|
+
];
|
|
2339
|
+
if (signals.emailsSent > 0) {
|
|
2340
|
+
lines.push(
|
|
2341
|
+
`${signals.emailsSent} emails sent in window, to ${signals.uniqueRecipients} unique recipients.`
|
|
2342
|
+
);
|
|
2343
|
+
if (signals.topRecipients.length > 0) {
|
|
2344
|
+
lines.push(`Most-emailed: ${signals.topRecipients.slice(0, 5).join(", ")}.`);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
if (signals.meetingsHeld > 0) {
|
|
2348
|
+
const hours = Math.round(signals.totalMeetingMinutes / 60 * 10) / 10;
|
|
2349
|
+
lines.push(
|
|
2350
|
+
`${signals.meetingsHeld} meetings held (${signals.meetingsOrganized} organized by the leader), ${hours}h total.`
|
|
2351
|
+
);
|
|
2352
|
+
if (signals.avgMeetingAttendees !== null) {
|
|
2353
|
+
lines.push(
|
|
2354
|
+
`Average ${signals.avgMeetingAttendees} attendees per meeting, ${signals.uniqueAttendees} unique attendees total.`
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
lines.push("");
|
|
2359
|
+
lines.push(
|
|
2360
|
+
"Gmail sent volume + Calendar meeting load reveal a leader's external coordination shape."
|
|
2361
|
+
);
|
|
2362
|
+
lines.push(
|
|
2363
|
+
"Compare against shipped work (GitHub, Linear) to find the stated-strategy-vs-actual-time gap."
|
|
2364
|
+
);
|
|
2365
|
+
return lines.join("\n");
|
|
2366
|
+
}
|
|
2367
|
+
async function fetchGmailSent(token, since, maxEmails, leaderEmail) {
|
|
2368
|
+
const sinceUnix = Math.floor(since.getTime() / 1e3);
|
|
2369
|
+
const listUrl = new URL("https://gmail.googleapis.com/gmail/v1/users/me/messages");
|
|
2370
|
+
listUrl.searchParams.set("q", `in:sent after:${sinceUnix}`);
|
|
2371
|
+
listUrl.searchParams.set("maxResults", String(Math.min(maxEmails, 100)));
|
|
2372
|
+
const listRes = await fetch(listUrl.toString(), {
|
|
2373
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
2374
|
+
});
|
|
2375
|
+
if (!listRes.ok) {
|
|
2376
|
+
throw new Error(`Gmail list failed ${listRes.status}: ${(await listRes.text()).slice(0, 200)}`);
|
|
2377
|
+
}
|
|
2378
|
+
const listJson = await listRes.json();
|
|
2379
|
+
const ids = (listJson.messages ?? []).slice(0, maxEmails);
|
|
2380
|
+
const events = [];
|
|
2381
|
+
const recipientCounts = /* @__PURE__ */ new Map();
|
|
2382
|
+
const PARALLEL = 5;
|
|
2383
|
+
for (let i = 0; i < ids.length; i += PARALLEL) {
|
|
2384
|
+
const chunk = ids.slice(i, i + PARALLEL);
|
|
2385
|
+
const results = await Promise.all(
|
|
2386
|
+
chunk.map(
|
|
2387
|
+
(m) => fetch(
|
|
2388
|
+
`https://gmail.googleapis.com/gmail/v1/users/me/messages/${m.id}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Subject&metadataHeaders=Date`,
|
|
2389
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
2390
|
+
).then((r) => r.ok ? r.json() : null).catch(() => null)
|
|
2391
|
+
)
|
|
2392
|
+
);
|
|
2393
|
+
for (const msg of results) {
|
|
2394
|
+
if (!msg) continue;
|
|
2395
|
+
const headers = msg.payload?.headers || [];
|
|
2396
|
+
const header = (n) => headers.find((h) => h.name.toLowerCase() === n.toLowerCase())?.value ?? "";
|
|
2397
|
+
const subject = header("Subject") || "(no subject)";
|
|
2398
|
+
const to = header("To");
|
|
2399
|
+
const dateHdr = header("Date");
|
|
2400
|
+
const timestamp = dateHdr ? new Date(dateHdr).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
2401
|
+
const snippet = msg.snippet ?? "";
|
|
2402
|
+
const recipients = parseAddressList(to);
|
|
2403
|
+
for (const r of recipients) {
|
|
2404
|
+
recipientCounts.set(r, (recipientCounts.get(r) ?? 0) + 1);
|
|
2405
|
+
}
|
|
2406
|
+
events.push({
|
|
2407
|
+
id: `gmail-${msg.id}`,
|
|
2408
|
+
timestamp,
|
|
2409
|
+
actor: {
|
|
2410
|
+
id: leaderEmail ?? "leader",
|
|
2411
|
+
kind: "human",
|
|
2412
|
+
name: leaderEmail ?? "Leader"
|
|
2413
|
+
},
|
|
2414
|
+
kind: "email_sent",
|
|
2415
|
+
content: `${subject} \u2014 ${snippet.slice(0, 200)}`,
|
|
2416
|
+
metadata: {
|
|
2417
|
+
to: recipients.slice(0, 5),
|
|
2418
|
+
subject
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
const topRecipients = [...recipientCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([addr]) => addr);
|
|
2424
|
+
return [
|
|
2425
|
+
events,
|
|
2426
|
+
{
|
|
2427
|
+
emailsSent: events.length,
|
|
2428
|
+
uniqueRecipients: recipientCounts.size,
|
|
2429
|
+
topRecipients
|
|
2430
|
+
}
|
|
2431
|
+
];
|
|
2432
|
+
}
|
|
2433
|
+
function parseAddressList(raw) {
|
|
2434
|
+
if (!raw) return [];
|
|
2435
|
+
return raw.split(",").map((a) => {
|
|
2436
|
+
const match = a.match(/<([^>]+)>/);
|
|
2437
|
+
const addr = (match ? match[1] : a).trim().toLowerCase();
|
|
2438
|
+
return addr;
|
|
2439
|
+
}).filter((a) => a.includes("@") && a.length < 100);
|
|
2440
|
+
}
|
|
2441
|
+
async function fetchCalendarEvents(token, since, maxEvents, leaderEmail) {
|
|
2442
|
+
const timeMin = since.toISOString();
|
|
2443
|
+
const timeMax = (/* @__PURE__ */ new Date()).toISOString();
|
|
2444
|
+
const url = new URL("https://www.googleapis.com/calendar/v3/calendars/primary/events");
|
|
2445
|
+
url.searchParams.set("timeMin", timeMin);
|
|
2446
|
+
url.searchParams.set("timeMax", timeMax);
|
|
2447
|
+
url.searchParams.set("singleEvents", "true");
|
|
2448
|
+
url.searchParams.set("orderBy", "startTime");
|
|
2449
|
+
url.searchParams.set("maxResults", String(Math.min(maxEvents, 250)));
|
|
2450
|
+
const res = await fetch(url.toString(), {
|
|
2451
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
2452
|
+
});
|
|
2453
|
+
if (!res.ok) {
|
|
2454
|
+
throw new Error(`Calendar list failed ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
2455
|
+
}
|
|
2456
|
+
const json = await res.json();
|
|
2457
|
+
const events = [];
|
|
2458
|
+
const attendeeSet = /* @__PURE__ */ new Set();
|
|
2459
|
+
let totalMinutes = 0;
|
|
2460
|
+
let meetingsOrganized = 0;
|
|
2461
|
+
const attendeeCounts = [];
|
|
2462
|
+
for (const ev of json.items ?? []) {
|
|
2463
|
+
if (ev.status === "cancelled") continue;
|
|
2464
|
+
const start = ev.start?.dateTime || ev.start?.date;
|
|
2465
|
+
const end = ev.end?.dateTime || ev.end?.date;
|
|
2466
|
+
if (!start) continue;
|
|
2467
|
+
const startMs = new Date(start).getTime();
|
|
2468
|
+
const endMs = end ? new Date(end).getTime() : startMs;
|
|
2469
|
+
const minutes = Math.max(0, Math.round((endMs - startMs) / 6e4));
|
|
2470
|
+
totalMinutes += minutes;
|
|
2471
|
+
const attendees = (ev.attendees ?? []).map((a) => a.email?.toLowerCase()).filter((e) => !!e && e.includes("@"));
|
|
2472
|
+
for (const a of attendees) attendeeSet.add(a);
|
|
2473
|
+
attendeeCounts.push(attendees.length);
|
|
2474
|
+
const organizedByLeader = leaderEmail && ev.organizer?.email?.toLowerCase() === leaderEmail;
|
|
2475
|
+
if (organizedByLeader) meetingsOrganized++;
|
|
2476
|
+
events.push({
|
|
2477
|
+
id: `gcal-${ev.id}`,
|
|
2478
|
+
timestamp: start,
|
|
2479
|
+
actor: {
|
|
2480
|
+
id: leaderEmail ?? "leader",
|
|
2481
|
+
kind: "human",
|
|
2482
|
+
name: leaderEmail ?? "Leader"
|
|
2483
|
+
},
|
|
2484
|
+
kind: organizedByLeader ? "meeting_organized" : "meeting_attended",
|
|
2485
|
+
content: `${ev.summary ?? "(no title)"} \u2014 ${minutes}min, ${attendees.length} attendees`,
|
|
2486
|
+
metadata: {
|
|
2487
|
+
attendees: attendees.slice(0, 10),
|
|
2488
|
+
minutes,
|
|
2489
|
+
organizer: ev.organizer?.email
|
|
2490
|
+
}
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
const avgAttendees = attendeeCounts.length > 0 ? Math.round(
|
|
2494
|
+
attendeeCounts.reduce((a, b) => a + b, 0) / attendeeCounts.length
|
|
2495
|
+
) : null;
|
|
2496
|
+
return [
|
|
2497
|
+
events,
|
|
2498
|
+
{
|
|
2499
|
+
meetingsHeld: events.length,
|
|
2500
|
+
meetingsOrganized,
|
|
2501
|
+
uniqueAttendees: attendeeSet.size,
|
|
2502
|
+
totalMeetingMinutes: totalMinutes,
|
|
2503
|
+
avgMeetingAttendees: avgAttendees
|
|
2504
|
+
}
|
|
2505
|
+
];
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// src/radiant/adapters/salesforce.ts
|
|
2509
|
+
async function fetchSalesforceActivity(options) {
|
|
2510
|
+
const windowDays = options.windowDays ?? 14;
|
|
2511
|
+
const maxRecords = Math.min(options.maxRecords ?? 200, 200);
|
|
2512
|
+
const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
|
|
2513
|
+
const sinceSoql = since.toISOString();
|
|
2514
|
+
const events = [];
|
|
2515
|
+
const signals = {
|
|
2516
|
+
opportunitiesMoved: 0,
|
|
2517
|
+
stageTransitions: 0,
|
|
2518
|
+
amountChanges: 0,
|
|
2519
|
+
closedWon: 0,
|
|
2520
|
+
closedLost: 0,
|
|
2521
|
+
tasksLogged: 0,
|
|
2522
|
+
callsLogged: 0,
|
|
2523
|
+
feedPostsByLeader: 0,
|
|
2524
|
+
totalPipelineAmount: 0,
|
|
2525
|
+
topStages: []
|
|
2526
|
+
};
|
|
2527
|
+
const opps = await soqlQuery(
|
|
2528
|
+
options,
|
|
2529
|
+
`SELECT Id, Name, StageName, Amount, CloseDate, IsClosed, IsWon, LastModifiedDate, Owner.Name
|
|
2530
|
+
FROM Opportunity
|
|
2531
|
+
WHERE LastModifiedDate >= ${sinceSoql}
|
|
2532
|
+
ORDER BY LastModifiedDate DESC
|
|
2533
|
+
LIMIT ${maxRecords}`
|
|
2534
|
+
);
|
|
2535
|
+
const stageCounts = /* @__PURE__ */ new Map();
|
|
2536
|
+
for (const opp of opps) {
|
|
2537
|
+
signals.opportunitiesMoved++;
|
|
2538
|
+
signals.totalPipelineAmount += opp.Amount ?? 0;
|
|
2539
|
+
stageCounts.set(opp.StageName, (stageCounts.get(opp.StageName) ?? 0) + 1);
|
|
2540
|
+
if (opp.IsClosed && opp.IsWon) signals.closedWon++;
|
|
2541
|
+
if (opp.IsClosed && !opp.IsWon) signals.closedLost++;
|
|
2542
|
+
events.push({
|
|
2543
|
+
id: `sf-opp-${opp.Id}`,
|
|
2544
|
+
timestamp: opp.LastModifiedDate,
|
|
2545
|
+
actor: {
|
|
2546
|
+
id: opp.Owner?.Name ?? "unknown",
|
|
2547
|
+
kind: "human",
|
|
2548
|
+
name: opp.Owner?.Name ?? "unknown"
|
|
2549
|
+
},
|
|
2550
|
+
kind: opp.IsClosed ? opp.IsWon ? "deal_won" : "deal_lost" : "deal_updated",
|
|
2551
|
+
content: `${opp.Name} \u2014 ${opp.StageName}${opp.Amount ? ` \xB7 $${Math.round(opp.Amount).toLocaleString()}` : ""} \xB7 close ${opp.CloseDate}`,
|
|
2552
|
+
metadata: {
|
|
2553
|
+
opportunityId: opp.Id,
|
|
2554
|
+
stage: opp.StageName,
|
|
2555
|
+
amount: opp.Amount,
|
|
2556
|
+
closeDate: opp.CloseDate
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
signals.topStages = [...stageCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([stage, count]) => ({ stage, count }));
|
|
2561
|
+
const history = await soqlQuery(
|
|
2562
|
+
options,
|
|
2563
|
+
`SELECT Id, OpportunityId, Field, OldValue, NewValue, CreatedDate, CreatedBy.Name
|
|
2564
|
+
FROM OpportunityFieldHistory
|
|
2565
|
+
WHERE CreatedDate >= ${sinceSoql} AND Field IN ('StageName', 'Amount', 'CloseDate')
|
|
2566
|
+
ORDER BY CreatedDate DESC
|
|
2567
|
+
LIMIT ${maxRecords}`
|
|
2568
|
+
).catch(() => []);
|
|
2569
|
+
for (const h of history) {
|
|
2570
|
+
if (h.Field === "StageName") signals.stageTransitions++;
|
|
2571
|
+
if (h.Field === "Amount") signals.amountChanges++;
|
|
2572
|
+
events.push({
|
|
2573
|
+
id: `sf-hist-${h.Id}`,
|
|
2574
|
+
timestamp: h.CreatedDate,
|
|
2575
|
+
actor: {
|
|
2576
|
+
id: h.CreatedBy?.Name ?? "unknown",
|
|
2577
|
+
kind: "human",
|
|
2578
|
+
name: h.CreatedBy?.Name ?? "unknown"
|
|
2579
|
+
},
|
|
2580
|
+
kind: `deal_${h.Field.toLowerCase()}_changed`,
|
|
2581
|
+
content: `${h.Field}: ${String(h.OldValue)} \u2192 ${String(h.NewValue)}`,
|
|
2582
|
+
metadata: {
|
|
2583
|
+
opportunityId: h.OpportunityId,
|
|
2584
|
+
field: h.Field
|
|
2585
|
+
}
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
const tasks = await soqlQuery(
|
|
2589
|
+
options,
|
|
2590
|
+
`SELECT Id, Subject, Status, Type, CreatedDate, Owner.Name
|
|
2591
|
+
FROM Task
|
|
2592
|
+
WHERE CreatedDate >= ${sinceSoql}
|
|
2593
|
+
ORDER BY CreatedDate DESC
|
|
2594
|
+
LIMIT ${maxRecords}`
|
|
2595
|
+
).catch(() => []);
|
|
2596
|
+
for (const t of tasks) {
|
|
2597
|
+
signals.tasksLogged++;
|
|
2598
|
+
if (t.Type === "Call" || (t.Subject || "").toLowerCase().includes("call")) {
|
|
2599
|
+
signals.callsLogged++;
|
|
2600
|
+
}
|
|
2601
|
+
events.push({
|
|
2602
|
+
id: `sf-task-${t.Id}`,
|
|
2603
|
+
timestamp: t.CreatedDate,
|
|
2604
|
+
actor: {
|
|
2605
|
+
id: t.Owner?.Name ?? "unknown",
|
|
2606
|
+
kind: "human",
|
|
2607
|
+
name: t.Owner?.Name ?? "unknown"
|
|
2608
|
+
},
|
|
2609
|
+
kind: t.Type === "Call" ? "call_logged" : "task_logged",
|
|
2610
|
+
content: `${t.Subject} \u2014 ${t.Status}`,
|
|
2611
|
+
metadata: { taskId: t.Id, type: t.Type, status: t.Status }
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
if (options.leaderName) {
|
|
2615
|
+
const feed = await soqlQuery(
|
|
2616
|
+
options,
|
|
2617
|
+
`SELECT Id, Body, CreatedDate, CreatedBy.Name, ParentId
|
|
2618
|
+
FROM FeedItem
|
|
2619
|
+
WHERE CreatedDate >= ${sinceSoql} AND CreatedBy.Name = '${escapeSoql(options.leaderName)}'
|
|
2620
|
+
ORDER BY CreatedDate DESC
|
|
2621
|
+
LIMIT ${Math.min(maxRecords, 50)}`
|
|
2622
|
+
).catch(() => []);
|
|
2623
|
+
for (const f of feed) {
|
|
2624
|
+
signals.feedPostsByLeader++;
|
|
2625
|
+
events.push({
|
|
2626
|
+
id: `sf-feed-${f.Id}`,
|
|
2627
|
+
timestamp: f.CreatedDate,
|
|
2628
|
+
actor: {
|
|
2629
|
+
id: f.CreatedBy?.Name ?? "unknown",
|
|
2630
|
+
kind: "human",
|
|
2631
|
+
name: f.CreatedBy?.Name ?? "unknown"
|
|
2632
|
+
},
|
|
2633
|
+
kind: "feed_post",
|
|
2634
|
+
content: (f.Body || "").slice(0, 280),
|
|
2635
|
+
metadata: { feedId: f.Id, parent: f.ParentId }
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
2640
|
+
return { events, signals };
|
|
2641
|
+
}
|
|
2642
|
+
function formatSalesforceSignalsForPrompt(signals) {
|
|
2643
|
+
if (signals.opportunitiesMoved === 0 && signals.tasksLogged === 0 && signals.stageTransitions === 0) {
|
|
2644
|
+
return "";
|
|
2645
|
+
}
|
|
2646
|
+
const lines = [
|
|
2647
|
+
"## Salesforce Activity (stated pipeline vs observed motion)",
|
|
2648
|
+
""
|
|
2649
|
+
];
|
|
2650
|
+
if (signals.opportunitiesMoved > 0) {
|
|
2651
|
+
const pipelineM = Math.round(signals.totalPipelineAmount / 1e4) / 100;
|
|
2652
|
+
lines.push(
|
|
2653
|
+
`${signals.opportunitiesMoved} opportunities touched in window ($${pipelineM}M total pipeline represented).`
|
|
2654
|
+
);
|
|
2655
|
+
lines.push(
|
|
2656
|
+
`${signals.stageTransitions} stage changes, ${signals.amountChanges} amount changes.`
|
|
2657
|
+
);
|
|
2658
|
+
if (signals.closedWon + signals.closedLost > 0) {
|
|
2659
|
+
lines.push(`${signals.closedWon} closed won, ${signals.closedLost} closed lost.`);
|
|
2660
|
+
}
|
|
2661
|
+
if (signals.topStages.length > 0) {
|
|
2662
|
+
lines.push(
|
|
2663
|
+
`Most active stages: ${signals.topStages.map((s) => `${s.stage} (${s.count})`).join(", ")}.`
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
if (signals.tasksLogged > 0) {
|
|
2668
|
+
lines.push(
|
|
2669
|
+
`${signals.tasksLogged} tasks logged (${signals.callsLogged} calls).`
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
if (signals.feedPostsByLeader > 0) {
|
|
2673
|
+
lines.push(`${signals.feedPostsByLeader} feed posts by the leader.`);
|
|
2674
|
+
}
|
|
2675
|
+
lines.push("");
|
|
2676
|
+
lines.push(
|
|
2677
|
+
"Salesforce shows what the team said would happen (stages, forecast) vs. what is actually happening (movement, activity, close)."
|
|
2678
|
+
);
|
|
2679
|
+
lines.push(
|
|
2680
|
+
"Compare stage transitions against stated strategy \u2014 are the deals the leader said would close actually moving, or is the pipeline drifting?"
|
|
2681
|
+
);
|
|
2682
|
+
return lines.join("\n");
|
|
2683
|
+
}
|
|
2684
|
+
async function soqlQuery(opts, query) {
|
|
2685
|
+
const url = new URL(`${opts.instanceUrl}/services/data/v59.0/query`);
|
|
2686
|
+
url.searchParams.set("q", query);
|
|
2687
|
+
const res = await fetch(url.toString(), {
|
|
2688
|
+
headers: { Authorization: `Bearer ${opts.accessToken}` }
|
|
2689
|
+
});
|
|
2690
|
+
if (!res.ok) {
|
|
2691
|
+
throw new Error(
|
|
2692
|
+
`Salesforce SOQL ${res.status}: ${(await res.text()).slice(0, 200)}`
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2695
|
+
const json = await res.json();
|
|
2696
|
+
return json.records ?? [];
|
|
2697
|
+
}
|
|
2698
|
+
function escapeSoql(raw) {
|
|
2699
|
+
return raw.replace(/['\\]/g, "");
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2299
2702
|
// src/radiant/core/vocabulary.ts
|
|
2300
2703
|
function extractDeclaredVocabulary(worldmodelContent) {
|
|
2301
2704
|
const aligned = extractSection(worldmodelContent, "Aligned Behaviors").map(
|
|
@@ -5049,16 +5452,20 @@ var RADIANT_PACKAGE_VERSION = "0.0.0";
|
|
|
5049
5452
|
fetchDiscordActivity,
|
|
5050
5453
|
fetchGitHubActivity,
|
|
5051
5454
|
fetchGitHubOrgActivity,
|
|
5455
|
+
fetchGoogleWorkspaceActivity,
|
|
5052
5456
|
fetchLinearActivity,
|
|
5053
5457
|
fetchNotionActivity,
|
|
5458
|
+
fetchSalesforceActivity,
|
|
5054
5459
|
fetchSlackActivity,
|
|
5055
5460
|
filterEventsByUser,
|
|
5056
5461
|
formatActiveWorlds,
|
|
5057
5462
|
formatDiscordSignalsForPrompt,
|
|
5058
5463
|
formatExocortexForPrompt,
|
|
5464
|
+
formatGoogleWorkspaceSignalsForPrompt,
|
|
5059
5465
|
formatLinearSignalsForPrompt,
|
|
5060
5466
|
formatNotionSignalsForPrompt,
|
|
5061
5467
|
formatPriorReadsForPrompt,
|
|
5468
|
+
formatSalesforceSignalsForPrompt,
|
|
5062
5469
|
formatScope,
|
|
5063
5470
|
formatSlackSignalsForPrompt,
|
|
5064
5471
|
formatTeamExocorticesForPrompt,
|
package/dist/radiant/index.d.cts
CHANGED
|
@@ -1244,6 +1244,136 @@ declare function fetchLinearActivity(apiKey: string, options?: LinearFetchOption
|
|
|
1244
1244
|
*/
|
|
1245
1245
|
declare function formatLinearSignalsForPrompt(signals: LinearSignals): string;
|
|
1246
1246
|
|
|
1247
|
+
/**
|
|
1248
|
+
* @neuroverseos/governance/radiant — Google Workspace adapter
|
|
1249
|
+
*
|
|
1250
|
+
* Reads the leader's Gmail (sent messages) + Calendar (events in the
|
|
1251
|
+
* window) and normalizes both into Event[] for the Radiant pipeline.
|
|
1252
|
+
*
|
|
1253
|
+
* Why Google Workspace matters:
|
|
1254
|
+
* - It's the most universal source. Every non-MS-shop leader has
|
|
1255
|
+
* Gmail + Calendar. Non-engineering leaders who never touch GitHub
|
|
1256
|
+
* or Linear still live in these two apps.
|
|
1257
|
+
* - Gmail sent folder = external coordination signal. What the
|
|
1258
|
+
* leader's actually pushing out into the world. Drift between
|
|
1259
|
+
* stated strategy and actual outreach shows up here first.
|
|
1260
|
+
* - Calendar = meeting load vs. output. Heavy meetings + thin
|
|
1261
|
+
* shipping = the drift pattern leaders most want to catch.
|
|
1262
|
+
*
|
|
1263
|
+
* v1 scope:
|
|
1264
|
+
* - Gmail: list the leader's own sent messages in the window.
|
|
1265
|
+
* Subject + snippet + recipients + timestamp. No body (privacy +
|
|
1266
|
+
* payload size).
|
|
1267
|
+
* - Calendar: list events in the window from the primary calendar.
|
|
1268
|
+
* Title + attendees count + duration + whether the leader
|
|
1269
|
+
* organized.
|
|
1270
|
+
*
|
|
1271
|
+
* Auth: Google OAuth access token with scopes
|
|
1272
|
+
* https://www.googleapis.com/auth/gmail.readonly
|
|
1273
|
+
* https://www.googleapis.com/auth/calendar.readonly
|
|
1274
|
+
* Token lifecycle (refresh) is the caller's job — this adapter takes
|
|
1275
|
+
* a valid access token as input.
|
|
1276
|
+
*
|
|
1277
|
+
* Deno / browser / Node all work — uses native fetch only.
|
|
1278
|
+
*/
|
|
1279
|
+
|
|
1280
|
+
interface GoogleWorkspaceFetchOptions {
|
|
1281
|
+
/** Access token with gmail.readonly + calendar.readonly scopes. */
|
|
1282
|
+
accessToken: string;
|
|
1283
|
+
/** How many days of history to fetch. Default: 14. */
|
|
1284
|
+
windowDays?: number;
|
|
1285
|
+
/** Max emails to include. Default: 100. */
|
|
1286
|
+
maxEmails?: number;
|
|
1287
|
+
/** Max calendar events to include. Default: 100. */
|
|
1288
|
+
maxEvents?: number;
|
|
1289
|
+
/** The leader's own email address — used to identify their events. */
|
|
1290
|
+
leaderEmail?: string;
|
|
1291
|
+
}
|
|
1292
|
+
interface GoogleWorkspaceSignals {
|
|
1293
|
+
emailsSent: number;
|
|
1294
|
+
uniqueRecipients: number;
|
|
1295
|
+
topRecipients: string[];
|
|
1296
|
+
meetingsHeld: number;
|
|
1297
|
+
meetingsOrganized: number;
|
|
1298
|
+
uniqueAttendees: number;
|
|
1299
|
+
totalMeetingMinutes: number;
|
|
1300
|
+
avgMeetingAttendees: number | null;
|
|
1301
|
+
}
|
|
1302
|
+
declare function fetchGoogleWorkspaceActivity(options: GoogleWorkspaceFetchOptions): Promise<{
|
|
1303
|
+
events: Event[];
|
|
1304
|
+
signals: GoogleWorkspaceSignals;
|
|
1305
|
+
}>;
|
|
1306
|
+
/**
|
|
1307
|
+
* Format Google Workspace signals for the AI interpretation prompt.
|
|
1308
|
+
*/
|
|
1309
|
+
declare function formatGoogleWorkspaceSignalsForPrompt(signals: GoogleWorkspaceSignals): string;
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* @neuroverseos/governance/radiant — Salesforce adapter
|
|
1313
|
+
*
|
|
1314
|
+
* Reads the leader's Salesforce activity and normalizes it into Event[]
|
|
1315
|
+
* for the Radiant pipeline. Opens the entire sales-leader market —
|
|
1316
|
+
* every sales org of any scale runs on Salesforce, and "is the pipeline
|
|
1317
|
+
* drifting from what we declared we'd close" is the exact job Radiant
|
|
1318
|
+
* does for that audience.
|
|
1319
|
+
*
|
|
1320
|
+
* v1 scope:
|
|
1321
|
+
* - Recent Opportunity changes: stage transitions, amount updates,
|
|
1322
|
+
* close-date changes (the signal set for deal-drift detection)
|
|
1323
|
+
* - Recent Tasks + logged Calls (activity per rep)
|
|
1324
|
+
* - Chatter FeedItems if accessible (how the team talks about deals)
|
|
1325
|
+
*
|
|
1326
|
+
* Auth: Salesforce OAuth — access token + instance URL. Each org has
|
|
1327
|
+
* its own `my.salesforce.com` endpoint; the token alone isn't enough,
|
|
1328
|
+
* the caller must also pass the instance URL.
|
|
1329
|
+
*
|
|
1330
|
+
* Individual-rep or individual-manager OAuth works without admin
|
|
1331
|
+
* consent — this adapter assumes one Salesforce user's view. For
|
|
1332
|
+
* enterprise admin-consent + org-wide access, a later adapter
|
|
1333
|
+
* variant can use the Reports API or a service-user token.
|
|
1334
|
+
*
|
|
1335
|
+
* Deno / browser / Node — native fetch only.
|
|
1336
|
+
*/
|
|
1337
|
+
|
|
1338
|
+
interface SalesforceFetchOptions {
|
|
1339
|
+
/** OAuth access token with api + refresh_token scopes. */
|
|
1340
|
+
accessToken: string;
|
|
1341
|
+
/**
|
|
1342
|
+
* Salesforce instance URL — e.g. https://acme.my.salesforce.com.
|
|
1343
|
+
* Comes back in the token response; caller must persist it.
|
|
1344
|
+
*/
|
|
1345
|
+
instanceUrl: string;
|
|
1346
|
+
/** How many days of history to fetch. Default: 14. */
|
|
1347
|
+
windowDays?: number;
|
|
1348
|
+
/** Max records per entity. Default: 200 (hard cap on SOQL page). */
|
|
1349
|
+
maxRecords?: number;
|
|
1350
|
+
/** The leader's own Salesforce user name / email, if known. */
|
|
1351
|
+
leaderName?: string;
|
|
1352
|
+
}
|
|
1353
|
+
interface SalesforceSignals {
|
|
1354
|
+
opportunitiesMoved: number;
|
|
1355
|
+
stageTransitions: number;
|
|
1356
|
+
amountChanges: number;
|
|
1357
|
+
closedWon: number;
|
|
1358
|
+
closedLost: number;
|
|
1359
|
+
tasksLogged: number;
|
|
1360
|
+
callsLogged: number;
|
|
1361
|
+
feedPostsByLeader: number;
|
|
1362
|
+
totalPipelineAmount: number;
|
|
1363
|
+
topStages: Array<{
|
|
1364
|
+
stage: string;
|
|
1365
|
+
count: number;
|
|
1366
|
+
}>;
|
|
1367
|
+
}
|
|
1368
|
+
declare function fetchSalesforceActivity(options: SalesforceFetchOptions): Promise<{
|
|
1369
|
+
events: Event[];
|
|
1370
|
+
signals: SalesforceSignals;
|
|
1371
|
+
}>;
|
|
1372
|
+
/**
|
|
1373
|
+
* Format Salesforce signals for the AI interpretation prompt.
|
|
1374
|
+
*/
|
|
1375
|
+
declare function formatSalesforceSignalsForPrompt(signals: SalesforceSignals): string;
|
|
1376
|
+
|
|
1247
1377
|
/**
|
|
1248
1378
|
* @neuroverseos/governance/radiant — declared vocabulary extraction
|
|
1249
1379
|
*
|
|
@@ -1962,4 +2092,4 @@ declare function filterEventsByUser(events: readonly Event[], username: string):
|
|
|
1962
2092
|
*/
|
|
1963
2093
|
declare const RADIANT_PACKAGE_VERSION = "0.0.0";
|
|
1964
2094
|
|
|
1965
|
-
export { type Actor, type ActorDomain, type ActorKind, type AlignmentStatus, type BridgingComponent, type BridgingComponentScore, type ClassifiedEvent, type CyberCapability, type CyberDimension, DEFAULT_EVIDENCE_GATE, DEFAULT_SIGNAL_EXTRACTORS, type DeclaredPattern, type DeclaredVocabulary, type DiscordFetchOptions, type DiscordSignals, type DiscoveredWorld, type EmergentInput, type EmergentResult, type Event, type EventReference, type EvidenceGate, type ExemplarRef, type ExocortexContext, type ExtendsConfig, type ExtendsSpec, type ExtractionResult, type Fetcher, type GitHubFetchOptions, type GovernanceAudit, type GovernanceVerdict, type InterpretInput, type InterpretResult, LENSES, type LensVocabulary, type LifeCapability, type LifeDimension, type LinearFetchOptions, type LinearSignals, type NotionFetchOptions, type NotionSignals, type ObservedPattern, type OrgScope, type OverlapDef, type ParsedRemote, type PatternEvidence, type PatternPersistence, type PrimaryFrame, type PriorRead, RADIANT_PACKAGE_VERSION, type RadiantAI, type RenderInput, type RenderOutput, type RenderingLens, type RepoScope, type ResolveResult, type Scope, type Score, type ScoreSentinel, type ScoredObservation, type Signal, type SignalExtractor, type SignalMatrix, type SlackFetchOptions, type SlackSignals, type ThinkInput, type ThinkResult, type ViewLevel, type VoiceDirectives, type VoiceViolation, type WorldStack, type WorldmodelItem, auditGovernance, aukiBuilderLens, checkForbiddenPhrases, classifyActorDomain, classifyEvents, composeSystemPrompt, compressExocortex, compressLens, compressPriorReads, compressWorldmodel, computePersistence, createAnthropicAI, createMockAI, createMockGitHubAdapter, detectOrgExtendsSpec, discoverWorlds, emergent, extractDeclaredVocabulary, extractSignals, fetchDiscordActivity, fetchGitHubActivity, fetchGitHubOrgActivity, fetchLinearActivity, fetchNotionActivity, fetchSlackActivity, filterEventsByUser, formatActiveWorlds, formatDiscordSignalsForPrompt, formatExocortexForPrompt, formatLinearSignalsForPrompt, formatNotionSignalsForPrompt, formatPriorReadsForPrompt, formatScope, formatSlackSignalsForPrompt, formatTeamExocorticesForPrompt, getCacheDir, getLens, getRepoOrigin, interpretPatterns, isPresent, isScored, isSentinel, listLenses, loadExtendsConfig, loadPriorReads, matchDeclaredPattern, parseExtendsSpec, parseRemoteUrl, parseRepoScope, parseScope, presenceAverage, readExocortex, readOriginRemote, readTeamExocortices, render, resolveAllExtends, resolveExtendsSpec, scoreComposite, scoreCyber, scoreLife, scoreNeuroVerse, sovereignConduitLens, summarizeExocortex, think, updateKnowledge, writeRead };
|
|
2095
|
+
export { type Actor, type ActorDomain, type ActorKind, type AlignmentStatus, type BridgingComponent, type BridgingComponentScore, type ClassifiedEvent, type CyberCapability, type CyberDimension, DEFAULT_EVIDENCE_GATE, DEFAULT_SIGNAL_EXTRACTORS, type DeclaredPattern, type DeclaredVocabulary, type DiscordFetchOptions, type DiscordSignals, type DiscoveredWorld, type EmergentInput, type EmergentResult, type Event, type EventReference, type EvidenceGate, type ExemplarRef, type ExocortexContext, type ExtendsConfig, type ExtendsSpec, type ExtractionResult, type Fetcher, type GitHubFetchOptions, type GoogleWorkspaceFetchOptions, type GoogleWorkspaceSignals, type GovernanceAudit, type GovernanceVerdict, type InterpretInput, type InterpretResult, LENSES, type LensVocabulary, type LifeCapability, type LifeDimension, type LinearFetchOptions, type LinearSignals, type NotionFetchOptions, type NotionSignals, type ObservedPattern, type OrgScope, type OverlapDef, type ParsedRemote, type PatternEvidence, type PatternPersistence, type PrimaryFrame, type PriorRead, RADIANT_PACKAGE_VERSION, type RadiantAI, type RenderInput, type RenderOutput, type RenderingLens, type RepoScope, type ResolveResult, type SalesforceFetchOptions, type SalesforceSignals, type Scope, type Score, type ScoreSentinel, type ScoredObservation, type Signal, type SignalExtractor, type SignalMatrix, type SlackFetchOptions, type SlackSignals, type ThinkInput, type ThinkResult, type ViewLevel, type VoiceDirectives, type VoiceViolation, type WorldStack, type WorldmodelItem, auditGovernance, aukiBuilderLens, checkForbiddenPhrases, classifyActorDomain, classifyEvents, composeSystemPrompt, compressExocortex, compressLens, compressPriorReads, compressWorldmodel, computePersistence, createAnthropicAI, createMockAI, createMockGitHubAdapter, detectOrgExtendsSpec, discoverWorlds, emergent, extractDeclaredVocabulary, extractSignals, fetchDiscordActivity, fetchGitHubActivity, fetchGitHubOrgActivity, fetchGoogleWorkspaceActivity, fetchLinearActivity, fetchNotionActivity, fetchSalesforceActivity, fetchSlackActivity, filterEventsByUser, formatActiveWorlds, formatDiscordSignalsForPrompt, formatExocortexForPrompt, formatGoogleWorkspaceSignalsForPrompt, formatLinearSignalsForPrompt, formatNotionSignalsForPrompt, formatPriorReadsForPrompt, formatSalesforceSignalsForPrompt, formatScope, formatSlackSignalsForPrompt, formatTeamExocorticesForPrompt, getCacheDir, getLens, getRepoOrigin, interpretPatterns, isPresent, isScored, isSentinel, listLenses, loadExtendsConfig, loadPriorReads, matchDeclaredPattern, parseExtendsSpec, parseRemoteUrl, parseRepoScope, parseScope, presenceAverage, readExocortex, readOriginRemote, readTeamExocortices, render, resolveAllExtends, resolveExtendsSpec, scoreComposite, scoreCyber, scoreLife, scoreNeuroVerse, sovereignConduitLens, summarizeExocortex, think, updateKnowledge, writeRead };
|
package/dist/radiant/index.d.ts
CHANGED
|
@@ -1244,6 +1244,136 @@ declare function fetchLinearActivity(apiKey: string, options?: LinearFetchOption
|
|
|
1244
1244
|
*/
|
|
1245
1245
|
declare function formatLinearSignalsForPrompt(signals: LinearSignals): string;
|
|
1246
1246
|
|
|
1247
|
+
/**
|
|
1248
|
+
* @neuroverseos/governance/radiant — Google Workspace adapter
|
|
1249
|
+
*
|
|
1250
|
+
* Reads the leader's Gmail (sent messages) + Calendar (events in the
|
|
1251
|
+
* window) and normalizes both into Event[] for the Radiant pipeline.
|
|
1252
|
+
*
|
|
1253
|
+
* Why Google Workspace matters:
|
|
1254
|
+
* - It's the most universal source. Every non-MS-shop leader has
|
|
1255
|
+
* Gmail + Calendar. Non-engineering leaders who never touch GitHub
|
|
1256
|
+
* or Linear still live in these two apps.
|
|
1257
|
+
* - Gmail sent folder = external coordination signal. What the
|
|
1258
|
+
* leader's actually pushing out into the world. Drift between
|
|
1259
|
+
* stated strategy and actual outreach shows up here first.
|
|
1260
|
+
* - Calendar = meeting load vs. output. Heavy meetings + thin
|
|
1261
|
+
* shipping = the drift pattern leaders most want to catch.
|
|
1262
|
+
*
|
|
1263
|
+
* v1 scope:
|
|
1264
|
+
* - Gmail: list the leader's own sent messages in the window.
|
|
1265
|
+
* Subject + snippet + recipients + timestamp. No body (privacy +
|
|
1266
|
+
* payload size).
|
|
1267
|
+
* - Calendar: list events in the window from the primary calendar.
|
|
1268
|
+
* Title + attendees count + duration + whether the leader
|
|
1269
|
+
* organized.
|
|
1270
|
+
*
|
|
1271
|
+
* Auth: Google OAuth access token with scopes
|
|
1272
|
+
* https://www.googleapis.com/auth/gmail.readonly
|
|
1273
|
+
* https://www.googleapis.com/auth/calendar.readonly
|
|
1274
|
+
* Token lifecycle (refresh) is the caller's job — this adapter takes
|
|
1275
|
+
* a valid access token as input.
|
|
1276
|
+
*
|
|
1277
|
+
* Deno / browser / Node all work — uses native fetch only.
|
|
1278
|
+
*/
|
|
1279
|
+
|
|
1280
|
+
interface GoogleWorkspaceFetchOptions {
|
|
1281
|
+
/** Access token with gmail.readonly + calendar.readonly scopes. */
|
|
1282
|
+
accessToken: string;
|
|
1283
|
+
/** How many days of history to fetch. Default: 14. */
|
|
1284
|
+
windowDays?: number;
|
|
1285
|
+
/** Max emails to include. Default: 100. */
|
|
1286
|
+
maxEmails?: number;
|
|
1287
|
+
/** Max calendar events to include. Default: 100. */
|
|
1288
|
+
maxEvents?: number;
|
|
1289
|
+
/** The leader's own email address — used to identify their events. */
|
|
1290
|
+
leaderEmail?: string;
|
|
1291
|
+
}
|
|
1292
|
+
interface GoogleWorkspaceSignals {
|
|
1293
|
+
emailsSent: number;
|
|
1294
|
+
uniqueRecipients: number;
|
|
1295
|
+
topRecipients: string[];
|
|
1296
|
+
meetingsHeld: number;
|
|
1297
|
+
meetingsOrganized: number;
|
|
1298
|
+
uniqueAttendees: number;
|
|
1299
|
+
totalMeetingMinutes: number;
|
|
1300
|
+
avgMeetingAttendees: number | null;
|
|
1301
|
+
}
|
|
1302
|
+
declare function fetchGoogleWorkspaceActivity(options: GoogleWorkspaceFetchOptions): Promise<{
|
|
1303
|
+
events: Event[];
|
|
1304
|
+
signals: GoogleWorkspaceSignals;
|
|
1305
|
+
}>;
|
|
1306
|
+
/**
|
|
1307
|
+
* Format Google Workspace signals for the AI interpretation prompt.
|
|
1308
|
+
*/
|
|
1309
|
+
declare function formatGoogleWorkspaceSignalsForPrompt(signals: GoogleWorkspaceSignals): string;
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* @neuroverseos/governance/radiant — Salesforce adapter
|
|
1313
|
+
*
|
|
1314
|
+
* Reads the leader's Salesforce activity and normalizes it into Event[]
|
|
1315
|
+
* for the Radiant pipeline. Opens the entire sales-leader market —
|
|
1316
|
+
* every sales org of any scale runs on Salesforce, and "is the pipeline
|
|
1317
|
+
* drifting from what we declared we'd close" is the exact job Radiant
|
|
1318
|
+
* does for that audience.
|
|
1319
|
+
*
|
|
1320
|
+
* v1 scope:
|
|
1321
|
+
* - Recent Opportunity changes: stage transitions, amount updates,
|
|
1322
|
+
* close-date changes (the signal set for deal-drift detection)
|
|
1323
|
+
* - Recent Tasks + logged Calls (activity per rep)
|
|
1324
|
+
* - Chatter FeedItems if accessible (how the team talks about deals)
|
|
1325
|
+
*
|
|
1326
|
+
* Auth: Salesforce OAuth — access token + instance URL. Each org has
|
|
1327
|
+
* its own `my.salesforce.com` endpoint; the token alone isn't enough,
|
|
1328
|
+
* the caller must also pass the instance URL.
|
|
1329
|
+
*
|
|
1330
|
+
* Individual-rep or individual-manager OAuth works without admin
|
|
1331
|
+
* consent — this adapter assumes one Salesforce user's view. For
|
|
1332
|
+
* enterprise admin-consent + org-wide access, a later adapter
|
|
1333
|
+
* variant can use the Reports API or a service-user token.
|
|
1334
|
+
*
|
|
1335
|
+
* Deno / browser / Node — native fetch only.
|
|
1336
|
+
*/
|
|
1337
|
+
|
|
1338
|
+
interface SalesforceFetchOptions {
|
|
1339
|
+
/** OAuth access token with api + refresh_token scopes. */
|
|
1340
|
+
accessToken: string;
|
|
1341
|
+
/**
|
|
1342
|
+
* Salesforce instance URL — e.g. https://acme.my.salesforce.com.
|
|
1343
|
+
* Comes back in the token response; caller must persist it.
|
|
1344
|
+
*/
|
|
1345
|
+
instanceUrl: string;
|
|
1346
|
+
/** How many days of history to fetch. Default: 14. */
|
|
1347
|
+
windowDays?: number;
|
|
1348
|
+
/** Max records per entity. Default: 200 (hard cap on SOQL page). */
|
|
1349
|
+
maxRecords?: number;
|
|
1350
|
+
/** The leader's own Salesforce user name / email, if known. */
|
|
1351
|
+
leaderName?: string;
|
|
1352
|
+
}
|
|
1353
|
+
interface SalesforceSignals {
|
|
1354
|
+
opportunitiesMoved: number;
|
|
1355
|
+
stageTransitions: number;
|
|
1356
|
+
amountChanges: number;
|
|
1357
|
+
closedWon: number;
|
|
1358
|
+
closedLost: number;
|
|
1359
|
+
tasksLogged: number;
|
|
1360
|
+
callsLogged: number;
|
|
1361
|
+
feedPostsByLeader: number;
|
|
1362
|
+
totalPipelineAmount: number;
|
|
1363
|
+
topStages: Array<{
|
|
1364
|
+
stage: string;
|
|
1365
|
+
count: number;
|
|
1366
|
+
}>;
|
|
1367
|
+
}
|
|
1368
|
+
declare function fetchSalesforceActivity(options: SalesforceFetchOptions): Promise<{
|
|
1369
|
+
events: Event[];
|
|
1370
|
+
signals: SalesforceSignals;
|
|
1371
|
+
}>;
|
|
1372
|
+
/**
|
|
1373
|
+
* Format Salesforce signals for the AI interpretation prompt.
|
|
1374
|
+
*/
|
|
1375
|
+
declare function formatSalesforceSignalsForPrompt(signals: SalesforceSignals): string;
|
|
1376
|
+
|
|
1247
1377
|
/**
|
|
1248
1378
|
* @neuroverseos/governance/radiant — declared vocabulary extraction
|
|
1249
1379
|
*
|
|
@@ -1962,4 +2092,4 @@ declare function filterEventsByUser(events: readonly Event[], username: string):
|
|
|
1962
2092
|
*/
|
|
1963
2093
|
declare const RADIANT_PACKAGE_VERSION = "0.0.0";
|
|
1964
2094
|
|
|
1965
|
-
export { type Actor, type ActorDomain, type ActorKind, type AlignmentStatus, type BridgingComponent, type BridgingComponentScore, type ClassifiedEvent, type CyberCapability, type CyberDimension, DEFAULT_EVIDENCE_GATE, DEFAULT_SIGNAL_EXTRACTORS, type DeclaredPattern, type DeclaredVocabulary, type DiscordFetchOptions, type DiscordSignals, type DiscoveredWorld, type EmergentInput, type EmergentResult, type Event, type EventReference, type EvidenceGate, type ExemplarRef, type ExocortexContext, type ExtendsConfig, type ExtendsSpec, type ExtractionResult, type Fetcher, type GitHubFetchOptions, type GovernanceAudit, type GovernanceVerdict, type InterpretInput, type InterpretResult, LENSES, type LensVocabulary, type LifeCapability, type LifeDimension, type LinearFetchOptions, type LinearSignals, type NotionFetchOptions, type NotionSignals, type ObservedPattern, type OrgScope, type OverlapDef, type ParsedRemote, type PatternEvidence, type PatternPersistence, type PrimaryFrame, type PriorRead, RADIANT_PACKAGE_VERSION, type RadiantAI, type RenderInput, type RenderOutput, type RenderingLens, type RepoScope, type ResolveResult, type Scope, type Score, type ScoreSentinel, type ScoredObservation, type Signal, type SignalExtractor, type SignalMatrix, type SlackFetchOptions, type SlackSignals, type ThinkInput, type ThinkResult, type ViewLevel, type VoiceDirectives, type VoiceViolation, type WorldStack, type WorldmodelItem, auditGovernance, aukiBuilderLens, checkForbiddenPhrases, classifyActorDomain, classifyEvents, composeSystemPrompt, compressExocortex, compressLens, compressPriorReads, compressWorldmodel, computePersistence, createAnthropicAI, createMockAI, createMockGitHubAdapter, detectOrgExtendsSpec, discoverWorlds, emergent, extractDeclaredVocabulary, extractSignals, fetchDiscordActivity, fetchGitHubActivity, fetchGitHubOrgActivity, fetchLinearActivity, fetchNotionActivity, fetchSlackActivity, filterEventsByUser, formatActiveWorlds, formatDiscordSignalsForPrompt, formatExocortexForPrompt, formatLinearSignalsForPrompt, formatNotionSignalsForPrompt, formatPriorReadsForPrompt, formatScope, formatSlackSignalsForPrompt, formatTeamExocorticesForPrompt, getCacheDir, getLens, getRepoOrigin, interpretPatterns, isPresent, isScored, isSentinel, listLenses, loadExtendsConfig, loadPriorReads, matchDeclaredPattern, parseExtendsSpec, parseRemoteUrl, parseRepoScope, parseScope, presenceAverage, readExocortex, readOriginRemote, readTeamExocortices, render, resolveAllExtends, resolveExtendsSpec, scoreComposite, scoreCyber, scoreLife, scoreNeuroVerse, sovereignConduitLens, summarizeExocortex, think, updateKnowledge, writeRead };
|
|
2095
|
+
export { type Actor, type ActorDomain, type ActorKind, type AlignmentStatus, type BridgingComponent, type BridgingComponentScore, type ClassifiedEvent, type CyberCapability, type CyberDimension, DEFAULT_EVIDENCE_GATE, DEFAULT_SIGNAL_EXTRACTORS, type DeclaredPattern, type DeclaredVocabulary, type DiscordFetchOptions, type DiscordSignals, type DiscoveredWorld, type EmergentInput, type EmergentResult, type Event, type EventReference, type EvidenceGate, type ExemplarRef, type ExocortexContext, type ExtendsConfig, type ExtendsSpec, type ExtractionResult, type Fetcher, type GitHubFetchOptions, type GoogleWorkspaceFetchOptions, type GoogleWorkspaceSignals, type GovernanceAudit, type GovernanceVerdict, type InterpretInput, type InterpretResult, LENSES, type LensVocabulary, type LifeCapability, type LifeDimension, type LinearFetchOptions, type LinearSignals, type NotionFetchOptions, type NotionSignals, type ObservedPattern, type OrgScope, type OverlapDef, type ParsedRemote, type PatternEvidence, type PatternPersistence, type PrimaryFrame, type PriorRead, RADIANT_PACKAGE_VERSION, type RadiantAI, type RenderInput, type RenderOutput, type RenderingLens, type RepoScope, type ResolveResult, type SalesforceFetchOptions, type SalesforceSignals, type Scope, type Score, type ScoreSentinel, type ScoredObservation, type Signal, type SignalExtractor, type SignalMatrix, type SlackFetchOptions, type SlackSignals, type ThinkInput, type ThinkResult, type ViewLevel, type VoiceDirectives, type VoiceViolation, type WorldStack, type WorldmodelItem, auditGovernance, aukiBuilderLens, checkForbiddenPhrases, classifyActorDomain, classifyEvents, composeSystemPrompt, compressExocortex, compressLens, compressPriorReads, compressWorldmodel, computePersistence, createAnthropicAI, createMockAI, createMockGitHubAdapter, detectOrgExtendsSpec, discoverWorlds, emergent, extractDeclaredVocabulary, extractSignals, fetchDiscordActivity, fetchGitHubActivity, fetchGitHubOrgActivity, fetchGoogleWorkspaceActivity, fetchLinearActivity, fetchNotionActivity, fetchSalesforceActivity, fetchSlackActivity, filterEventsByUser, formatActiveWorlds, formatDiscordSignalsForPrompt, formatExocortexForPrompt, formatGoogleWorkspaceSignalsForPrompt, formatLinearSignalsForPrompt, formatNotionSignalsForPrompt, formatPriorReadsForPrompt, formatSalesforceSignalsForPrompt, formatScope, formatSlackSignalsForPrompt, formatTeamExocorticesForPrompt, getCacheDir, getLens, getRepoOrigin, interpretPatterns, isPresent, isScored, isSentinel, listLenses, loadExtendsConfig, loadPriorReads, matchDeclaredPattern, parseExtendsSpec, parseRemoteUrl, parseRepoScope, parseScope, presenceAverage, readExocortex, readOriginRemote, readTeamExocortices, render, resolveAllExtends, resolveExtendsSpec, scoreComposite, scoreCyber, scoreLife, scoreNeuroVerse, sovereignConduitLens, summarizeExocortex, think, updateKnowledge, writeRead };
|
package/dist/radiant/index.js
CHANGED
|
@@ -76,6 +76,405 @@ import "../chunk-MBOW6YXN.js";
|
|
|
76
76
|
import "../chunk-QLPTHTVB.js";
|
|
77
77
|
import "../chunk-QWGCMQQD.js";
|
|
78
78
|
|
|
79
|
+
// src/radiant/adapters/google-workspace.ts
|
|
80
|
+
async function fetchGoogleWorkspaceActivity(options) {
|
|
81
|
+
const windowDays = options.windowDays ?? 14;
|
|
82
|
+
const maxEmails = options.maxEmails ?? 100;
|
|
83
|
+
const maxEvents = options.maxEvents ?? 100;
|
|
84
|
+
const leaderEmail = options.leaderEmail?.toLowerCase();
|
|
85
|
+
const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
|
|
86
|
+
const [emailEvents, emailSignals] = await fetchGmailSent(
|
|
87
|
+
options.accessToken,
|
|
88
|
+
since,
|
|
89
|
+
maxEmails,
|
|
90
|
+
leaderEmail
|
|
91
|
+
);
|
|
92
|
+
const [calendarEvents, calendarSignals] = await fetchCalendarEvents(
|
|
93
|
+
options.accessToken,
|
|
94
|
+
since,
|
|
95
|
+
maxEvents,
|
|
96
|
+
leaderEmail
|
|
97
|
+
);
|
|
98
|
+
const events = [...emailEvents, ...calendarEvents].sort(
|
|
99
|
+
(a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)
|
|
100
|
+
);
|
|
101
|
+
return {
|
|
102
|
+
events,
|
|
103
|
+
signals: {
|
|
104
|
+
...emailSignals,
|
|
105
|
+
...calendarSignals
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function formatGoogleWorkspaceSignalsForPrompt(signals) {
|
|
110
|
+
if (signals.emailsSent === 0 && signals.meetingsHeld === 0) return "";
|
|
111
|
+
const lines = [
|
|
112
|
+
"## Google Workspace Activity (external coordination + meeting load)",
|
|
113
|
+
""
|
|
114
|
+
];
|
|
115
|
+
if (signals.emailsSent > 0) {
|
|
116
|
+
lines.push(
|
|
117
|
+
`${signals.emailsSent} emails sent in window, to ${signals.uniqueRecipients} unique recipients.`
|
|
118
|
+
);
|
|
119
|
+
if (signals.topRecipients.length > 0) {
|
|
120
|
+
lines.push(`Most-emailed: ${signals.topRecipients.slice(0, 5).join(", ")}.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (signals.meetingsHeld > 0) {
|
|
124
|
+
const hours = Math.round(signals.totalMeetingMinutes / 60 * 10) / 10;
|
|
125
|
+
lines.push(
|
|
126
|
+
`${signals.meetingsHeld} meetings held (${signals.meetingsOrganized} organized by the leader), ${hours}h total.`
|
|
127
|
+
);
|
|
128
|
+
if (signals.avgMeetingAttendees !== null) {
|
|
129
|
+
lines.push(
|
|
130
|
+
`Average ${signals.avgMeetingAttendees} attendees per meeting, ${signals.uniqueAttendees} unique attendees total.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push(
|
|
136
|
+
"Gmail sent volume + Calendar meeting load reveal a leader's external coordination shape."
|
|
137
|
+
);
|
|
138
|
+
lines.push(
|
|
139
|
+
"Compare against shipped work (GitHub, Linear) to find the stated-strategy-vs-actual-time gap."
|
|
140
|
+
);
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
async function fetchGmailSent(token, since, maxEmails, leaderEmail) {
|
|
144
|
+
const sinceUnix = Math.floor(since.getTime() / 1e3);
|
|
145
|
+
const listUrl = new URL("https://gmail.googleapis.com/gmail/v1/users/me/messages");
|
|
146
|
+
listUrl.searchParams.set("q", `in:sent after:${sinceUnix}`);
|
|
147
|
+
listUrl.searchParams.set("maxResults", String(Math.min(maxEmails, 100)));
|
|
148
|
+
const listRes = await fetch(listUrl.toString(), {
|
|
149
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
150
|
+
});
|
|
151
|
+
if (!listRes.ok) {
|
|
152
|
+
throw new Error(`Gmail list failed ${listRes.status}: ${(await listRes.text()).slice(0, 200)}`);
|
|
153
|
+
}
|
|
154
|
+
const listJson = await listRes.json();
|
|
155
|
+
const ids = (listJson.messages ?? []).slice(0, maxEmails);
|
|
156
|
+
const events = [];
|
|
157
|
+
const recipientCounts = /* @__PURE__ */ new Map();
|
|
158
|
+
const PARALLEL = 5;
|
|
159
|
+
for (let i = 0; i < ids.length; i += PARALLEL) {
|
|
160
|
+
const chunk = ids.slice(i, i + PARALLEL);
|
|
161
|
+
const results = await Promise.all(
|
|
162
|
+
chunk.map(
|
|
163
|
+
(m) => fetch(
|
|
164
|
+
`https://gmail.googleapis.com/gmail/v1/users/me/messages/${m.id}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Subject&metadataHeaders=Date`,
|
|
165
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
166
|
+
).then((r) => r.ok ? r.json() : null).catch(() => null)
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
for (const msg of results) {
|
|
170
|
+
if (!msg) continue;
|
|
171
|
+
const headers = msg.payload?.headers || [];
|
|
172
|
+
const header = (n) => headers.find((h) => h.name.toLowerCase() === n.toLowerCase())?.value ?? "";
|
|
173
|
+
const subject = header("Subject") || "(no subject)";
|
|
174
|
+
const to = header("To");
|
|
175
|
+
const dateHdr = header("Date");
|
|
176
|
+
const timestamp = dateHdr ? new Date(dateHdr).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
177
|
+
const snippet = msg.snippet ?? "";
|
|
178
|
+
const recipients = parseAddressList(to);
|
|
179
|
+
for (const r of recipients) {
|
|
180
|
+
recipientCounts.set(r, (recipientCounts.get(r) ?? 0) + 1);
|
|
181
|
+
}
|
|
182
|
+
events.push({
|
|
183
|
+
id: `gmail-${msg.id}`,
|
|
184
|
+
timestamp,
|
|
185
|
+
actor: {
|
|
186
|
+
id: leaderEmail ?? "leader",
|
|
187
|
+
kind: "human",
|
|
188
|
+
name: leaderEmail ?? "Leader"
|
|
189
|
+
},
|
|
190
|
+
kind: "email_sent",
|
|
191
|
+
content: `${subject} \u2014 ${snippet.slice(0, 200)}`,
|
|
192
|
+
metadata: {
|
|
193
|
+
to: recipients.slice(0, 5),
|
|
194
|
+
subject
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const topRecipients = [...recipientCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([addr]) => addr);
|
|
200
|
+
return [
|
|
201
|
+
events,
|
|
202
|
+
{
|
|
203
|
+
emailsSent: events.length,
|
|
204
|
+
uniqueRecipients: recipientCounts.size,
|
|
205
|
+
topRecipients
|
|
206
|
+
}
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
function parseAddressList(raw) {
|
|
210
|
+
if (!raw) return [];
|
|
211
|
+
return raw.split(",").map((a) => {
|
|
212
|
+
const match = a.match(/<([^>]+)>/);
|
|
213
|
+
const addr = (match ? match[1] : a).trim().toLowerCase();
|
|
214
|
+
return addr;
|
|
215
|
+
}).filter((a) => a.includes("@") && a.length < 100);
|
|
216
|
+
}
|
|
217
|
+
async function fetchCalendarEvents(token, since, maxEvents, leaderEmail) {
|
|
218
|
+
const timeMin = since.toISOString();
|
|
219
|
+
const timeMax = (/* @__PURE__ */ new Date()).toISOString();
|
|
220
|
+
const url = new URL("https://www.googleapis.com/calendar/v3/calendars/primary/events");
|
|
221
|
+
url.searchParams.set("timeMin", timeMin);
|
|
222
|
+
url.searchParams.set("timeMax", timeMax);
|
|
223
|
+
url.searchParams.set("singleEvents", "true");
|
|
224
|
+
url.searchParams.set("orderBy", "startTime");
|
|
225
|
+
url.searchParams.set("maxResults", String(Math.min(maxEvents, 250)));
|
|
226
|
+
const res = await fetch(url.toString(), {
|
|
227
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
228
|
+
});
|
|
229
|
+
if (!res.ok) {
|
|
230
|
+
throw new Error(`Calendar list failed ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
231
|
+
}
|
|
232
|
+
const json = await res.json();
|
|
233
|
+
const events = [];
|
|
234
|
+
const attendeeSet = /* @__PURE__ */ new Set();
|
|
235
|
+
let totalMinutes = 0;
|
|
236
|
+
let meetingsOrganized = 0;
|
|
237
|
+
const attendeeCounts = [];
|
|
238
|
+
for (const ev of json.items ?? []) {
|
|
239
|
+
if (ev.status === "cancelled") continue;
|
|
240
|
+
const start = ev.start?.dateTime || ev.start?.date;
|
|
241
|
+
const end = ev.end?.dateTime || ev.end?.date;
|
|
242
|
+
if (!start) continue;
|
|
243
|
+
const startMs = new Date(start).getTime();
|
|
244
|
+
const endMs = end ? new Date(end).getTime() : startMs;
|
|
245
|
+
const minutes = Math.max(0, Math.round((endMs - startMs) / 6e4));
|
|
246
|
+
totalMinutes += minutes;
|
|
247
|
+
const attendees = (ev.attendees ?? []).map((a) => a.email?.toLowerCase()).filter((e) => !!e && e.includes("@"));
|
|
248
|
+
for (const a of attendees) attendeeSet.add(a);
|
|
249
|
+
attendeeCounts.push(attendees.length);
|
|
250
|
+
const organizedByLeader = leaderEmail && ev.organizer?.email?.toLowerCase() === leaderEmail;
|
|
251
|
+
if (organizedByLeader) meetingsOrganized++;
|
|
252
|
+
events.push({
|
|
253
|
+
id: `gcal-${ev.id}`,
|
|
254
|
+
timestamp: start,
|
|
255
|
+
actor: {
|
|
256
|
+
id: leaderEmail ?? "leader",
|
|
257
|
+
kind: "human",
|
|
258
|
+
name: leaderEmail ?? "Leader"
|
|
259
|
+
},
|
|
260
|
+
kind: organizedByLeader ? "meeting_organized" : "meeting_attended",
|
|
261
|
+
content: `${ev.summary ?? "(no title)"} \u2014 ${minutes}min, ${attendees.length} attendees`,
|
|
262
|
+
metadata: {
|
|
263
|
+
attendees: attendees.slice(0, 10),
|
|
264
|
+
minutes,
|
|
265
|
+
organizer: ev.organizer?.email
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const avgAttendees = attendeeCounts.length > 0 ? Math.round(
|
|
270
|
+
attendeeCounts.reduce((a, b) => a + b, 0) / attendeeCounts.length
|
|
271
|
+
) : null;
|
|
272
|
+
return [
|
|
273
|
+
events,
|
|
274
|
+
{
|
|
275
|
+
meetingsHeld: events.length,
|
|
276
|
+
meetingsOrganized,
|
|
277
|
+
uniqueAttendees: attendeeSet.size,
|
|
278
|
+
totalMeetingMinutes: totalMinutes,
|
|
279
|
+
avgMeetingAttendees: avgAttendees
|
|
280
|
+
}
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/radiant/adapters/salesforce.ts
|
|
285
|
+
async function fetchSalesforceActivity(options) {
|
|
286
|
+
const windowDays = options.windowDays ?? 14;
|
|
287
|
+
const maxRecords = Math.min(options.maxRecords ?? 200, 200);
|
|
288
|
+
const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
|
|
289
|
+
const sinceSoql = since.toISOString();
|
|
290
|
+
const events = [];
|
|
291
|
+
const signals = {
|
|
292
|
+
opportunitiesMoved: 0,
|
|
293
|
+
stageTransitions: 0,
|
|
294
|
+
amountChanges: 0,
|
|
295
|
+
closedWon: 0,
|
|
296
|
+
closedLost: 0,
|
|
297
|
+
tasksLogged: 0,
|
|
298
|
+
callsLogged: 0,
|
|
299
|
+
feedPostsByLeader: 0,
|
|
300
|
+
totalPipelineAmount: 0,
|
|
301
|
+
topStages: []
|
|
302
|
+
};
|
|
303
|
+
const opps = await soqlQuery(
|
|
304
|
+
options,
|
|
305
|
+
`SELECT Id, Name, StageName, Amount, CloseDate, IsClosed, IsWon, LastModifiedDate, Owner.Name
|
|
306
|
+
FROM Opportunity
|
|
307
|
+
WHERE LastModifiedDate >= ${sinceSoql}
|
|
308
|
+
ORDER BY LastModifiedDate DESC
|
|
309
|
+
LIMIT ${maxRecords}`
|
|
310
|
+
);
|
|
311
|
+
const stageCounts = /* @__PURE__ */ new Map();
|
|
312
|
+
for (const opp of opps) {
|
|
313
|
+
signals.opportunitiesMoved++;
|
|
314
|
+
signals.totalPipelineAmount += opp.Amount ?? 0;
|
|
315
|
+
stageCounts.set(opp.StageName, (stageCounts.get(opp.StageName) ?? 0) + 1);
|
|
316
|
+
if (opp.IsClosed && opp.IsWon) signals.closedWon++;
|
|
317
|
+
if (opp.IsClosed && !opp.IsWon) signals.closedLost++;
|
|
318
|
+
events.push({
|
|
319
|
+
id: `sf-opp-${opp.Id}`,
|
|
320
|
+
timestamp: opp.LastModifiedDate,
|
|
321
|
+
actor: {
|
|
322
|
+
id: opp.Owner?.Name ?? "unknown",
|
|
323
|
+
kind: "human",
|
|
324
|
+
name: opp.Owner?.Name ?? "unknown"
|
|
325
|
+
},
|
|
326
|
+
kind: opp.IsClosed ? opp.IsWon ? "deal_won" : "deal_lost" : "deal_updated",
|
|
327
|
+
content: `${opp.Name} \u2014 ${opp.StageName}${opp.Amount ? ` \xB7 $${Math.round(opp.Amount).toLocaleString()}` : ""} \xB7 close ${opp.CloseDate}`,
|
|
328
|
+
metadata: {
|
|
329
|
+
opportunityId: opp.Id,
|
|
330
|
+
stage: opp.StageName,
|
|
331
|
+
amount: opp.Amount,
|
|
332
|
+
closeDate: opp.CloseDate
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
signals.topStages = [...stageCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([stage, count]) => ({ stage, count }));
|
|
337
|
+
const history = await soqlQuery(
|
|
338
|
+
options,
|
|
339
|
+
`SELECT Id, OpportunityId, Field, OldValue, NewValue, CreatedDate, CreatedBy.Name
|
|
340
|
+
FROM OpportunityFieldHistory
|
|
341
|
+
WHERE CreatedDate >= ${sinceSoql} AND Field IN ('StageName', 'Amount', 'CloseDate')
|
|
342
|
+
ORDER BY CreatedDate DESC
|
|
343
|
+
LIMIT ${maxRecords}`
|
|
344
|
+
).catch(() => []);
|
|
345
|
+
for (const h of history) {
|
|
346
|
+
if (h.Field === "StageName") signals.stageTransitions++;
|
|
347
|
+
if (h.Field === "Amount") signals.amountChanges++;
|
|
348
|
+
events.push({
|
|
349
|
+
id: `sf-hist-${h.Id}`,
|
|
350
|
+
timestamp: h.CreatedDate,
|
|
351
|
+
actor: {
|
|
352
|
+
id: h.CreatedBy?.Name ?? "unknown",
|
|
353
|
+
kind: "human",
|
|
354
|
+
name: h.CreatedBy?.Name ?? "unknown"
|
|
355
|
+
},
|
|
356
|
+
kind: `deal_${h.Field.toLowerCase()}_changed`,
|
|
357
|
+
content: `${h.Field}: ${String(h.OldValue)} \u2192 ${String(h.NewValue)}`,
|
|
358
|
+
metadata: {
|
|
359
|
+
opportunityId: h.OpportunityId,
|
|
360
|
+
field: h.Field
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
const tasks = await soqlQuery(
|
|
365
|
+
options,
|
|
366
|
+
`SELECT Id, Subject, Status, Type, CreatedDate, Owner.Name
|
|
367
|
+
FROM Task
|
|
368
|
+
WHERE CreatedDate >= ${sinceSoql}
|
|
369
|
+
ORDER BY CreatedDate DESC
|
|
370
|
+
LIMIT ${maxRecords}`
|
|
371
|
+
).catch(() => []);
|
|
372
|
+
for (const t of tasks) {
|
|
373
|
+
signals.tasksLogged++;
|
|
374
|
+
if (t.Type === "Call" || (t.Subject || "").toLowerCase().includes("call")) {
|
|
375
|
+
signals.callsLogged++;
|
|
376
|
+
}
|
|
377
|
+
events.push({
|
|
378
|
+
id: `sf-task-${t.Id}`,
|
|
379
|
+
timestamp: t.CreatedDate,
|
|
380
|
+
actor: {
|
|
381
|
+
id: t.Owner?.Name ?? "unknown",
|
|
382
|
+
kind: "human",
|
|
383
|
+
name: t.Owner?.Name ?? "unknown"
|
|
384
|
+
},
|
|
385
|
+
kind: t.Type === "Call" ? "call_logged" : "task_logged",
|
|
386
|
+
content: `${t.Subject} \u2014 ${t.Status}`,
|
|
387
|
+
metadata: { taskId: t.Id, type: t.Type, status: t.Status }
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (options.leaderName) {
|
|
391
|
+
const feed = await soqlQuery(
|
|
392
|
+
options,
|
|
393
|
+
`SELECT Id, Body, CreatedDate, CreatedBy.Name, ParentId
|
|
394
|
+
FROM FeedItem
|
|
395
|
+
WHERE CreatedDate >= ${sinceSoql} AND CreatedBy.Name = '${escapeSoql(options.leaderName)}'
|
|
396
|
+
ORDER BY CreatedDate DESC
|
|
397
|
+
LIMIT ${Math.min(maxRecords, 50)}`
|
|
398
|
+
).catch(() => []);
|
|
399
|
+
for (const f of feed) {
|
|
400
|
+
signals.feedPostsByLeader++;
|
|
401
|
+
events.push({
|
|
402
|
+
id: `sf-feed-${f.Id}`,
|
|
403
|
+
timestamp: f.CreatedDate,
|
|
404
|
+
actor: {
|
|
405
|
+
id: f.CreatedBy?.Name ?? "unknown",
|
|
406
|
+
kind: "human",
|
|
407
|
+
name: f.CreatedBy?.Name ?? "unknown"
|
|
408
|
+
},
|
|
409
|
+
kind: "feed_post",
|
|
410
|
+
content: (f.Body || "").slice(0, 280),
|
|
411
|
+
metadata: { feedId: f.Id, parent: f.ParentId }
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
416
|
+
return { events, signals };
|
|
417
|
+
}
|
|
418
|
+
function formatSalesforceSignalsForPrompt(signals) {
|
|
419
|
+
if (signals.opportunitiesMoved === 0 && signals.tasksLogged === 0 && signals.stageTransitions === 0) {
|
|
420
|
+
return "";
|
|
421
|
+
}
|
|
422
|
+
const lines = [
|
|
423
|
+
"## Salesforce Activity (stated pipeline vs observed motion)",
|
|
424
|
+
""
|
|
425
|
+
];
|
|
426
|
+
if (signals.opportunitiesMoved > 0) {
|
|
427
|
+
const pipelineM = Math.round(signals.totalPipelineAmount / 1e4) / 100;
|
|
428
|
+
lines.push(
|
|
429
|
+
`${signals.opportunitiesMoved} opportunities touched in window ($${pipelineM}M total pipeline represented).`
|
|
430
|
+
);
|
|
431
|
+
lines.push(
|
|
432
|
+
`${signals.stageTransitions} stage changes, ${signals.amountChanges} amount changes.`
|
|
433
|
+
);
|
|
434
|
+
if (signals.closedWon + signals.closedLost > 0) {
|
|
435
|
+
lines.push(`${signals.closedWon} closed won, ${signals.closedLost} closed lost.`);
|
|
436
|
+
}
|
|
437
|
+
if (signals.topStages.length > 0) {
|
|
438
|
+
lines.push(
|
|
439
|
+
`Most active stages: ${signals.topStages.map((s) => `${s.stage} (${s.count})`).join(", ")}.`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (signals.tasksLogged > 0) {
|
|
444
|
+
lines.push(
|
|
445
|
+
`${signals.tasksLogged} tasks logged (${signals.callsLogged} calls).`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
if (signals.feedPostsByLeader > 0) {
|
|
449
|
+
lines.push(`${signals.feedPostsByLeader} feed posts by the leader.`);
|
|
450
|
+
}
|
|
451
|
+
lines.push("");
|
|
452
|
+
lines.push(
|
|
453
|
+
"Salesforce shows what the team said would happen (stages, forecast) vs. what is actually happening (movement, activity, close)."
|
|
454
|
+
);
|
|
455
|
+
lines.push(
|
|
456
|
+
"Compare stage transitions against stated strategy \u2014 are the deals the leader said would close actually moving, or is the pipeline drifting?"
|
|
457
|
+
);
|
|
458
|
+
return lines.join("\n");
|
|
459
|
+
}
|
|
460
|
+
async function soqlQuery(opts, query) {
|
|
461
|
+
const url = new URL(`${opts.instanceUrl}/services/data/v59.0/query`);
|
|
462
|
+
url.searchParams.set("q", query);
|
|
463
|
+
const res = await fetch(url.toString(), {
|
|
464
|
+
headers: { Authorization: `Bearer ${opts.accessToken}` }
|
|
465
|
+
});
|
|
466
|
+
if (!res.ok) {
|
|
467
|
+
throw new Error(
|
|
468
|
+
`Salesforce SOQL ${res.status}: ${(await res.text()).slice(0, 200)}`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
const json = await res.json();
|
|
472
|
+
return json.records ?? [];
|
|
473
|
+
}
|
|
474
|
+
function escapeSoql(raw) {
|
|
475
|
+
return raw.replace(/['\\]/g, "");
|
|
476
|
+
}
|
|
477
|
+
|
|
79
478
|
// src/radiant/index.ts
|
|
80
479
|
var RADIANT_PACKAGE_VERSION = "0.0.0";
|
|
81
480
|
export {
|
|
@@ -105,16 +504,20 @@ export {
|
|
|
105
504
|
fetchDiscordActivity,
|
|
106
505
|
fetchGitHubActivity,
|
|
107
506
|
fetchGitHubOrgActivity,
|
|
507
|
+
fetchGoogleWorkspaceActivity,
|
|
108
508
|
fetchLinearActivity,
|
|
109
509
|
fetchNotionActivity,
|
|
510
|
+
fetchSalesforceActivity,
|
|
110
511
|
fetchSlackActivity,
|
|
111
512
|
filterEventsByUser,
|
|
112
513
|
formatActiveWorlds,
|
|
113
514
|
formatDiscordSignalsForPrompt,
|
|
114
515
|
formatExocortexForPrompt,
|
|
516
|
+
formatGoogleWorkspaceSignalsForPrompt,
|
|
115
517
|
formatLinearSignalsForPrompt,
|
|
116
518
|
formatNotionSignalsForPrompt,
|
|
117
519
|
formatPriorReadsForPrompt,
|
|
520
|
+
formatSalesforceSignalsForPrompt,
|
|
118
521
|
formatScope,
|
|
119
522
|
formatSlackSignalsForPrompt,
|
|
120
523
|
formatTeamExocorticesForPrompt,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neuroverseos/governance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Deterministic governance engine for AI agents — enforce worlds (permanent rules) and plans (mission constraints) with full audit trace",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|