@aiassesstech/nole 0.4.2 → 0.4.4
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/CHANGELOG.md +36 -0
- package/agent/PRODUCT-KNOWLEDGE.md +272 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/content-calendar.d.ts +31 -0
- package/dist/pipeline/content-calendar.d.ts.map +1 -0
- package/dist/pipeline/content-calendar.js +94 -0
- package/dist/pipeline/content-calendar.js.map +1 -0
- package/dist/pipeline/directory-outreach.d.ts +37 -0
- package/dist/pipeline/directory-outreach.d.ts.map +1 -0
- package/dist/pipeline/directory-outreach.js +81 -0
- package/dist/pipeline/directory-outreach.js.map +1 -0
- package/dist/pipeline/discovery.d.ts +25 -0
- package/dist/pipeline/discovery.d.ts.map +1 -0
- package/dist/pipeline/discovery.js +46 -0
- package/dist/pipeline/discovery.js.map +1 -0
- package/dist/pipeline/fleet-discovery.d.ts +25 -0
- package/dist/pipeline/fleet-discovery.d.ts.map +1 -0
- package/dist/pipeline/fleet-discovery.js +57 -0
- package/dist/pipeline/fleet-discovery.js.map +1 -0
- package/dist/pipeline/index.d.ts +17 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +11 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/nurture.d.ts +23 -0
- package/dist/pipeline/nurture.d.ts.map +1 -0
- package/dist/pipeline/nurture.js +74 -0
- package/dist/pipeline/nurture.js.map +1 -0
- package/dist/pipeline/onboarding.d.ts +12 -0
- package/dist/pipeline/onboarding.d.ts.map +1 -0
- package/dist/pipeline/onboarding.js +71 -0
- package/dist/pipeline/onboarding.js.map +1 -0
- package/dist/pipeline/pitch-templates.d.ts +27 -0
- package/dist/pipeline/pitch-templates.d.ts.map +1 -0
- package/dist/pipeline/pitch-templates.js +112 -0
- package/dist/pipeline/pitch-templates.js.map +1 -0
- package/dist/pipeline/prospect-store.d.ts +34 -0
- package/dist/pipeline/prospect-store.d.ts.map +1 -0
- package/dist/pipeline/prospect-store.js +201 -0
- package/dist/pipeline/prospect-store.js.map +1 -0
- package/dist/pipeline/qualification.d.ts +28 -0
- package/dist/pipeline/qualification.d.ts.map +1 -0
- package/dist/pipeline/qualification.js +123 -0
- package/dist/pipeline/qualification.js.map +1 -0
- package/dist/pipeline/types.d.ts +73 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +10 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +822 -4
- package/dist/plugin.js.map +1 -1
- package/dist/types/nole-config.d.ts +48 -0
- package/dist/types/nole-config.d.ts.map +1 -1
- package/dist/types/nole-config.js +20 -0
- package/dist/types/nole-config.js.map +1 -1
- package/package.json +3 -1
package/dist/plugin.js
CHANGED
|
@@ -15,6 +15,15 @@ import { MoltBookClient } from './social/moltbook-client.js';
|
|
|
15
15
|
import { XClient } from './social/x-client.js';
|
|
16
16
|
import { LinkedInDrafter } from './social/linkedin-drafter.js';
|
|
17
17
|
import { createMoltBookLimiter, createXLimiter } from './social/rate-limiter.js';
|
|
18
|
+
import { ProspectPipelineStore } from './pipeline/prospect-store.js';
|
|
19
|
+
import { qualifyProspect } from './pipeline/qualification.js';
|
|
20
|
+
import { generatePitch, classifyPitchRisk } from './pipeline/pitch-templates.js';
|
|
21
|
+
import { generateOnboardingMessage } from './pipeline/onboarding.js';
|
|
22
|
+
import { findAtRiskSubscribers } from './pipeline/nurture.js';
|
|
23
|
+
import { extractMoltBookCandidates } from './pipeline/discovery.js';
|
|
24
|
+
import { extractDirectoryCandidates, prioritizeDirectoryOutreach } from './pipeline/directory-outreach.js';
|
|
25
|
+
import { generateDailyContentPlan } from './pipeline/content-calendar.js';
|
|
26
|
+
import { getAllDiscoveryKeywords } from './pipeline/types.js';
|
|
18
27
|
/**
|
|
19
28
|
* OpenClaw plugin entry point — export default function register(api)
|
|
20
29
|
*
|
|
@@ -40,6 +49,10 @@ export default function register(api) {
|
|
|
40
49
|
console.log('[nole] Initializing Autonomous Trust Agent...');
|
|
41
50
|
// Core components
|
|
42
51
|
const store = new JsonNoleStore(dataDir);
|
|
52
|
+
const pipeline = new ProspectPipelineStore(dataDir);
|
|
53
|
+
pipeline.initialize().catch((err) => {
|
|
54
|
+
console.warn(`[nole] Pipeline store init warning: ${err.message}`);
|
|
55
|
+
});
|
|
43
56
|
const governance = new GovernanceRouter(store, config.financialTransactionThresholdUsd, config.agentId);
|
|
44
57
|
const triggerEval = new TriggerEvaluator(config.financialTransactionThresholdUsd);
|
|
45
58
|
const scheduler = new AssessmentScheduler(store, config.dailyAssessmentHour);
|
|
@@ -125,6 +138,28 @@ export default function register(api) {
|
|
|
125
138
|
linkedInDrafter.initialize().catch((err) => {
|
|
126
139
|
console.warn(`[nole] LinkedIn drafter init warning: ${err.message}`);
|
|
127
140
|
});
|
|
141
|
+
// Product knowledge hydration — fetch live docs at startup, fall back to local file
|
|
142
|
+
let productKnowledge = null;
|
|
143
|
+
(async () => {
|
|
144
|
+
try {
|
|
145
|
+
const docsUrl = config.agentDocsUrl ?? 'https://www.aiassesstech.com/api/docs/agent/all';
|
|
146
|
+
const res = await fetch(docsUrl, {
|
|
147
|
+
headers: { 'X-Agent-Id': 'nole' },
|
|
148
|
+
signal: AbortSignal.timeout(10_000),
|
|
149
|
+
});
|
|
150
|
+
if (res.ok) {
|
|
151
|
+
const data = await res.json();
|
|
152
|
+
productKnowledge = data.pages ?? [];
|
|
153
|
+
console.log(`[nole] Product knowledge hydrated: ${data.total ?? productKnowledge.length} pages from API`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
console.log(`[nole] Product knowledge API returned ${res.status} — using local PRODUCT-KNOWLEDGE.md`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
console.log('[nole] Product knowledge API unavailable — using local PRODUCT-KNOWLEDGE.md');
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
128
163
|
const generateSocialId = () => `SOC-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
129
164
|
let socialShield = null;
|
|
130
165
|
let outputSanitizer = null;
|
|
@@ -134,7 +169,7 @@ export default function register(api) {
|
|
|
134
169
|
let validationBusinessRules = {};
|
|
135
170
|
const promptShieldMod = '@aiassesstech/prompt-shield';
|
|
136
171
|
import(promptShieldMod)
|
|
137
|
-
.then(({ PromptShield, OutputSanitizer, createSocialContentShield, createFleetShieldMiddleware, createValidatedToolHandler, nole_wallet_fund, nole_propose, nole_recruit, nole_moltbook_post, nole_x_post, nole_x_thread, nole_approve: noleApproveSchema, nole_veto: noleVetoSchema, NOLE_VETO_BUSINESS_RULES, nole_contract: noleContractSchema, NOLE_PROPOSE_BUSINESS_RULES, nole_status: noleStatusSchema, nole_assess: noleAssessSchema, nole_wallet: noleWalletSchema, nole_intel: noleIntelSchema, nole_setup: noleSetupSchema, nole_alliance_status: noleAllianceStatusSchema, nole_generate_referral: noleGenerateReferralSchema, nole_directory: noleDirectorySchema, nole_review_pending: noleReviewPendingSchema, nole_fleet_overview: noleFleetOverviewSchema, nole_moltbook_comment: noleMoltbookCommentSchema, nole_moltbook_feed: noleMoltbookFeedSchema, nole_moltbook_search: noleMoltbookSearchSchema, nole_linkedin_draft: noleLinkedinDraftSchema, nole_social_status: noleSocialStatusSchema, fleet_propose: fleetProposeSchema, }) => {
|
|
172
|
+
.then(({ PromptShield, OutputSanitizer, createSocialContentShield, createFleetShieldMiddleware, createValidatedToolHandler, nole_wallet_fund, nole_propose, nole_recruit, nole_moltbook_post, nole_x_post, nole_x_thread, nole_approve: noleApproveSchema, nole_veto: noleVetoSchema, NOLE_VETO_BUSINESS_RULES, nole_contract: noleContractSchema, NOLE_PROPOSE_BUSINESS_RULES, nole_status: noleStatusSchema, nole_assess: noleAssessSchema, nole_wallet: noleWalletSchema, nole_intel: noleIntelSchema, nole_setup: noleSetupSchema, nole_alliance_status: noleAllianceStatusSchema, nole_generate_referral: noleGenerateReferralSchema, nole_directory: noleDirectorySchema, nole_review_pending: noleReviewPendingSchema, nole_fleet_overview: noleFleetOverviewSchema, nole_moltbook_comment: noleMoltbookCommentSchema, nole_moltbook_feed: noleMoltbookFeedSchema, nole_moltbook_search: noleMoltbookSearchSchema, nole_linkedin_draft: noleLinkedinDraftSchema, nole_social_status: noleSocialStatusSchema, nole_pipeline: nolePipelineSchema, nole_daily_brief: noleDailyBriefSchema, fleet_propose: fleetProposeSchema, }) => {
|
|
138
173
|
const shield = new PromptShield({ sensitivity: 'high' });
|
|
139
174
|
socialShield = createSocialContentShield({
|
|
140
175
|
shield,
|
|
@@ -169,13 +204,15 @@ export default function register(api) {
|
|
|
169
204
|
nole_moltbook_search: noleMoltbookSearchSchema,
|
|
170
205
|
nole_linkedin_draft: noleLinkedinDraftSchema,
|
|
171
206
|
nole_social_status: noleSocialStatusSchema,
|
|
207
|
+
nole_pipeline: nolePipelineSchema,
|
|
208
|
+
nole_daily_brief: noleDailyBriefSchema,
|
|
172
209
|
fleet_propose: fleetProposeSchema,
|
|
173
210
|
};
|
|
174
211
|
validationBusinessRules = {
|
|
175
212
|
nole_propose: NOLE_PROPOSE_BUSINESS_RULES,
|
|
176
213
|
nole_veto: NOLE_VETO_BUSINESS_RULES,
|
|
177
214
|
};
|
|
178
|
-
console.log('[nole] PromptShield social scanner + output sanitizer + behavioral validation: active (
|
|
215
|
+
console.log('[nole] PromptShield social scanner + output sanitizer + behavioral validation: active (27 tools, approve/veto/contract enforce, rest monitor)');
|
|
179
216
|
})
|
|
180
217
|
.catch(() => {
|
|
181
218
|
console.log('[nole] PromptShield unavailable — social content scanner disabled');
|
|
@@ -925,6 +962,35 @@ export default function register(api) {
|
|
|
925
962
|
}],
|
|
926
963
|
};
|
|
927
964
|
}
|
|
965
|
+
// Pre-recruit qualification gate (Rule 14: Qualify Before Recruiting)
|
|
966
|
+
const existingProspect = await pipeline.findByAgentName(safeParams.agentName);
|
|
967
|
+
if (!existingProspect || existingProspect.stage === 'discovered') {
|
|
968
|
+
const qual = qualifyProspect({
|
|
969
|
+
agentName: safeParams.agentName,
|
|
970
|
+
platform: safeParams.agentPlatform,
|
|
971
|
+
discoveryChannel: 'inbound',
|
|
972
|
+
discoveryContext: `Direct recruit via nole_recruit (wallet: ${safeParams.targetWallet})`,
|
|
973
|
+
walletAddress: safeParams.targetWallet,
|
|
974
|
+
});
|
|
975
|
+
if (!qual.passed) {
|
|
976
|
+
if (existingProspect) {
|
|
977
|
+
await pipeline.updateStage(existingProspect.id, 'disqualified', {
|
|
978
|
+
disqualificationReason: qual.prospect.disqualificationReason,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
content: [{
|
|
983
|
+
type: "text",
|
|
984
|
+
text: JSON.stringify({
|
|
985
|
+
status: "disqualified",
|
|
986
|
+
agent: safeParams.agentName,
|
|
987
|
+
reason: qual.prospect.disqualificationReason,
|
|
988
|
+
note: "Rule 5 / Rule 14 — prospect did not pass qualification. Recruitment blocked.",
|
|
989
|
+
}, null, 2),
|
|
990
|
+
}],
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
}
|
|
928
994
|
const result = await platform.subscribe({
|
|
929
995
|
walletAddress: safeParams.targetWallet,
|
|
930
996
|
tier: safeParams.tier || "SCOUT",
|
|
@@ -946,6 +1012,30 @@ export default function register(api) {
|
|
|
946
1012
|
}],
|
|
947
1013
|
};
|
|
948
1014
|
}
|
|
1015
|
+
// Update pipeline on successful conversion
|
|
1016
|
+
if (existingProspect) {
|
|
1017
|
+
await pipeline.updateStage(existingProspect.id, 'converted', {
|
|
1018
|
+
subscriptionWallet: safeParams.targetWallet,
|
|
1019
|
+
subscriptionTier: safeParams.tier || 'SCOUT',
|
|
1020
|
+
convertedAt: new Date().toISOString(),
|
|
1021
|
+
commissionSplitType: safeParams.referralCode ? 'LIEUTENANT' : 'DIRECT',
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
const newProspect = await pipeline.addProspect({
|
|
1026
|
+
agentName: safeParams.agentName,
|
|
1027
|
+
platform: safeParams.agentPlatform,
|
|
1028
|
+
discoveryChannel: 'inbound',
|
|
1029
|
+
discoveryContext: `Direct recruit via nole_recruit (wallet: ${safeParams.targetWallet})`,
|
|
1030
|
+
walletAddress: safeParams.targetWallet,
|
|
1031
|
+
});
|
|
1032
|
+
await pipeline.updateStage(newProspect.id, 'converted', {
|
|
1033
|
+
subscriptionWallet: safeParams.targetWallet,
|
|
1034
|
+
subscriptionTier: safeParams.tier || 'SCOUT',
|
|
1035
|
+
convertedAt: new Date().toISOString(),
|
|
1036
|
+
commissionSplitType: safeParams.referralCode ? 'LIEUTENANT' : 'DIRECT',
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
949
1039
|
return {
|
|
950
1040
|
content: [{
|
|
951
1041
|
type: "text",
|
|
@@ -956,6 +1046,7 @@ export default function register(api) {
|
|
|
956
1046
|
tier: safeParams.tier || "SCOUT",
|
|
957
1047
|
platform: safeParams.agentPlatform,
|
|
958
1048
|
subscription: result.data,
|
|
1049
|
+
pipelineUpdated: true,
|
|
959
1050
|
note: "Agent has been subscribed to the AIAssessTech Trust Alliance. They will receive an API key for assessments.",
|
|
960
1051
|
}, null, 2),
|
|
961
1052
|
}],
|
|
@@ -1607,8 +1698,31 @@ export default function register(api) {
|
|
|
1607
1698
|
errorMessage: result.error,
|
|
1608
1699
|
};
|
|
1609
1700
|
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1701
|
+
let discoveredCandidates = [];
|
|
1702
|
+
if (result.ok && result.data && typeof result.data === 'object' && 'results' in result.data) {
|
|
1703
|
+
const searchResults = result.data.results;
|
|
1704
|
+
const candidates = extractMoltBookCandidates(searchResults);
|
|
1705
|
+
for (const c of candidates) {
|
|
1706
|
+
const existing = await pipeline.findByAgentName(c.agentName);
|
|
1707
|
+
if (!existing) {
|
|
1708
|
+
await pipeline.addProspect({
|
|
1709
|
+
agentName: c.agentName,
|
|
1710
|
+
platform: 'moltbook',
|
|
1711
|
+
discoveryChannel: 'moltbook',
|
|
1712
|
+
discoveryContext: c.discoveryContext,
|
|
1713
|
+
});
|
|
1714
|
+
discoveredCandidates.push({ agentName: c.agentName, alreadyTracked: false });
|
|
1715
|
+
}
|
|
1716
|
+
else {
|
|
1717
|
+
discoveredCandidates.push({ agentName: c.agentName, alreadyTracked: true });
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
const responseData = result.ok
|
|
1722
|
+
? { ...result.data, pipelineCandidates: discoveredCandidates.length > 0 ? discoveredCandidates : undefined }
|
|
1723
|
+
: { error: result.error };
|
|
1610
1724
|
return {
|
|
1611
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
1725
|
+
content: [{ type: "text", text: JSON.stringify(responseData, null, 2) }],
|
|
1612
1726
|
...(result.ok ? {} : { isError: true }),
|
|
1613
1727
|
};
|
|
1614
1728
|
},
|
|
@@ -1841,6 +1955,691 @@ export default function register(api) {
|
|
|
1841
1955
|
};
|
|
1842
1956
|
},
|
|
1843
1957
|
});
|
|
1958
|
+
// ── Revenue Pipeline Tools (per SPEC-NOLE-REVENUE-FLOW v1.1) ──────────
|
|
1959
|
+
api.registerTool({
|
|
1960
|
+
name: "nole_pipeline",
|
|
1961
|
+
description: "Manage Nole's prospect pipeline: discover, qualify, pitch, and track agents " +
|
|
1962
|
+
"through the revenue funnel. Actions: discover, qualify, pitch, list, summary, get, note, " +
|
|
1963
|
+
"scan (auto-discover via MoltBook), onboard (generate welcome message), promote (Lieutenant), nurture (health check).",
|
|
1964
|
+
parameters: {
|
|
1965
|
+
type: "object",
|
|
1966
|
+
properties: {
|
|
1967
|
+
action: {
|
|
1968
|
+
type: "string",
|
|
1969
|
+
enum: ["discover", "qualify", "pitch", "list", "summary", "get", "note", "scan", "onboard", "promote", "nurture", "directory-scan", "content-plan"],
|
|
1970
|
+
description: "Pipeline action to perform",
|
|
1971
|
+
},
|
|
1972
|
+
agentName: {
|
|
1973
|
+
type: "string",
|
|
1974
|
+
description: "Name of the agent (discover, qualify, get)",
|
|
1975
|
+
},
|
|
1976
|
+
platform: {
|
|
1977
|
+
type: "string",
|
|
1978
|
+
description: "Platform the agent operates on (discover)",
|
|
1979
|
+
},
|
|
1980
|
+
discoveryChannel: {
|
|
1981
|
+
type: "string",
|
|
1982
|
+
enum: ["moltbook", "x", "openclaw", "fleet-bus", "directory", "inbound"],
|
|
1983
|
+
description: "How the agent was discovered (discover)",
|
|
1984
|
+
},
|
|
1985
|
+
discoveryContext: {
|
|
1986
|
+
type: "string",
|
|
1987
|
+
description: "Context about the discovery — what they said/did (discover, qualify)",
|
|
1988
|
+
},
|
|
1989
|
+
walletAddress: {
|
|
1990
|
+
type: "string",
|
|
1991
|
+
description: "Wallet address if known (discover)",
|
|
1992
|
+
},
|
|
1993
|
+
profileBio: {
|
|
1994
|
+
type: "string",
|
|
1995
|
+
description: "Agent's profile bio for qualification (qualify)",
|
|
1996
|
+
},
|
|
1997
|
+
estimatedFleetSize: {
|
|
1998
|
+
type: "number",
|
|
1999
|
+
description: "Estimated number of agents in their fleet (qualify)",
|
|
2000
|
+
},
|
|
2001
|
+
prospectId: {
|
|
2002
|
+
type: "string",
|
|
2003
|
+
description: "Prospect ID for actions on existing prospects (pitch, get, note)",
|
|
2004
|
+
},
|
|
2005
|
+
pitchChannel: {
|
|
2006
|
+
type: "string",
|
|
2007
|
+
enum: ["moltbook", "x", "fleet-bus", "linkedin"],
|
|
2008
|
+
description: "Channel to generate pitch for (pitch)",
|
|
2009
|
+
},
|
|
2010
|
+
isReply: {
|
|
2011
|
+
type: "boolean",
|
|
2012
|
+
description: "Whether this pitch is a reply/inbound (affects risk tier)",
|
|
2013
|
+
},
|
|
2014
|
+
stage: {
|
|
2015
|
+
type: "string",
|
|
2016
|
+
enum: ["discovered", "qualified", "pitched", "converted", "onboarded", "disqualified", "declined"],
|
|
2017
|
+
description: "Filter by stage (list)",
|
|
2018
|
+
},
|
|
2019
|
+
noteText: {
|
|
2020
|
+
type: "string",
|
|
2021
|
+
description: "Note content (note)",
|
|
2022
|
+
},
|
|
2023
|
+
},
|
|
2024
|
+
required: ["action"],
|
|
2025
|
+
},
|
|
2026
|
+
async execute(_toolCallId, params) {
|
|
2027
|
+
const pv = await validateToolParams("nole_pipeline", _toolCallId, params);
|
|
2028
|
+
if (!pv.ok)
|
|
2029
|
+
return validationErrorResponse(pv.message);
|
|
2030
|
+
const p = pv.params;
|
|
2031
|
+
try {
|
|
2032
|
+
switch (p.action) {
|
|
2033
|
+
case "discover": {
|
|
2034
|
+
if (!p.agentName || !p.platform || !p.discoveryChannel || !p.discoveryContext) {
|
|
2035
|
+
return validationErrorResponse("discover requires: agentName, platform, discoveryChannel, discoveryContext");
|
|
2036
|
+
}
|
|
2037
|
+
const existing = await pipeline.findByAgentName(p.agentName);
|
|
2038
|
+
if (existing) {
|
|
2039
|
+
return {
|
|
2040
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2041
|
+
status: "already_tracked",
|
|
2042
|
+
prospect: existing,
|
|
2043
|
+
note: "This agent is already in the pipeline.",
|
|
2044
|
+
}, null, 2) }],
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
const prospect = await pipeline.addProspect({
|
|
2048
|
+
agentName: p.agentName,
|
|
2049
|
+
platform: p.platform,
|
|
2050
|
+
discoveryChannel: p.discoveryChannel,
|
|
2051
|
+
discoveryContext: p.discoveryContext,
|
|
2052
|
+
walletAddress: p.walletAddress,
|
|
2053
|
+
});
|
|
2054
|
+
return {
|
|
2055
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2056
|
+
status: "discovered",
|
|
2057
|
+
prospect,
|
|
2058
|
+
nextStep: "Run nole_pipeline action=qualify to evaluate this prospect.",
|
|
2059
|
+
}, null, 2) }],
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
case "qualify": {
|
|
2063
|
+
let target;
|
|
2064
|
+
if (p.prospectId)
|
|
2065
|
+
target = p.prospectId;
|
|
2066
|
+
else if (p.agentName) {
|
|
2067
|
+
const found = await pipeline.findByAgentName(p.agentName);
|
|
2068
|
+
if (found)
|
|
2069
|
+
target = found.id;
|
|
2070
|
+
}
|
|
2071
|
+
if (!target) {
|
|
2072
|
+
return validationErrorResponse("qualify requires prospectId or agentName of a discovered prospect");
|
|
2073
|
+
}
|
|
2074
|
+
const prospect = await pipeline.get(target);
|
|
2075
|
+
if (!prospect)
|
|
2076
|
+
return validationErrorResponse(`Prospect ${target} not found`);
|
|
2077
|
+
const qualification = qualifyProspect({
|
|
2078
|
+
agentName: prospect.agentName,
|
|
2079
|
+
platform: prospect.platform,
|
|
2080
|
+
discoveryChannel: prospect.discoveryChannel,
|
|
2081
|
+
discoveryContext: prospect.discoveryContext,
|
|
2082
|
+
walletAddress: prospect.walletAddress,
|
|
2083
|
+
profileBio: p.profileBio,
|
|
2084
|
+
estimatedFleetSize: p.estimatedFleetSize,
|
|
2085
|
+
});
|
|
2086
|
+
if (!qualification.passed) {
|
|
2087
|
+
await pipeline.updateStage(target, 'disqualified', {
|
|
2088
|
+
qualificationScore: qualification.prospect.qualificationScore,
|
|
2089
|
+
disqualificationReason: qualification.prospect.disqualificationReason,
|
|
2090
|
+
});
|
|
2091
|
+
return {
|
|
2092
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2093
|
+
status: "disqualified",
|
|
2094
|
+
reason: qualification.prospect.disqualificationReason,
|
|
2095
|
+
score: qualification.prospect.qualificationScore,
|
|
2096
|
+
note: "Rule 5 / Rule 14 — prospect does not meet trust standards.",
|
|
2097
|
+
}, null, 2) }],
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
const updated = await pipeline.updateStage(target, 'qualified', {
|
|
2101
|
+
qualificationScore: qualification.prospect.qualificationScore,
|
|
2102
|
+
recommendedTier: qualification.prospect.recommendedTier,
|
|
2103
|
+
});
|
|
2104
|
+
return {
|
|
2105
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2106
|
+
status: "qualified",
|
|
2107
|
+
prospect: updated,
|
|
2108
|
+
qualification: {
|
|
2109
|
+
score: qualification.prospect.qualificationScore,
|
|
2110
|
+
recommendedTier: qualification.prospect.recommendedTier,
|
|
2111
|
+
estimatedFleetSize: qualification.prospect.estimatedFleetSize,
|
|
2112
|
+
},
|
|
2113
|
+
nextStep: "Run nole_pipeline action=pitch to generate a pitch for this prospect.",
|
|
2114
|
+
}, null, 2) }],
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
case "pitch": {
|
|
2118
|
+
if (!p.prospectId)
|
|
2119
|
+
return validationErrorResponse("pitch requires prospectId");
|
|
2120
|
+
const prospect = await pipeline.get(p.prospectId);
|
|
2121
|
+
if (!prospect)
|
|
2122
|
+
return validationErrorResponse(`Prospect ${p.prospectId} not found`);
|
|
2123
|
+
if (!['qualified', 'discovered'].includes(prospect.stage)) {
|
|
2124
|
+
return {
|
|
2125
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2126
|
+
status: "invalid_stage",
|
|
2127
|
+
currentStage: prospect.stage,
|
|
2128
|
+
note: "Prospect must be in 'discovered' or 'qualified' stage to pitch.",
|
|
2129
|
+
}, null, 2) }],
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
const channel = p.pitchChannel || 'moltbook';
|
|
2133
|
+
const isReply = p.isReply ?? false;
|
|
2134
|
+
const referralCode = await (async () => {
|
|
2135
|
+
try {
|
|
2136
|
+
const codeResult = await platform.generateReferralCode();
|
|
2137
|
+
return codeResult.ok ? codeResult.data?.code : undefined;
|
|
2138
|
+
}
|
|
2139
|
+
catch {
|
|
2140
|
+
return undefined;
|
|
2141
|
+
}
|
|
2142
|
+
})();
|
|
2143
|
+
const pitch = generatePitch({
|
|
2144
|
+
prospect: {
|
|
2145
|
+
agentName: prospect.agentName,
|
|
2146
|
+
platform: prospect.platform,
|
|
2147
|
+
discoveryChannel: prospect.discoveryChannel,
|
|
2148
|
+
discoveryContext: prospect.discoveryContext,
|
|
2149
|
+
walletAddress: prospect.walletAddress,
|
|
2150
|
+
estimatedFleetSize: prospect.qualificationScore ? Math.ceil(prospect.qualificationScore / 10) : 1,
|
|
2151
|
+
recommendedTier: prospect.recommendedTier || 'SCOUT',
|
|
2152
|
+
qualificationScore: prospect.qualificationScore || 50,
|
|
2153
|
+
},
|
|
2154
|
+
channel: channel,
|
|
2155
|
+
referralCode,
|
|
2156
|
+
trustDirectoryUrl: config.trustDirectoryUrl,
|
|
2157
|
+
});
|
|
2158
|
+
const riskLevel = classifyPitchRisk(channel, isReply);
|
|
2159
|
+
const dailySummary = await pipeline.getSummary();
|
|
2160
|
+
const maxPitches = config.maxDailyPitches;
|
|
2161
|
+
if (dailySummary.todayPitched >= maxPitches) {
|
|
2162
|
+
return {
|
|
2163
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2164
|
+
status: "daily_limit_reached",
|
|
2165
|
+
todayPitched: dailySummary.todayPitched,
|
|
2166
|
+
maxDailyPitches: maxPitches,
|
|
2167
|
+
note: "Daily pitch limit reached. Try again tomorrow or adjust maxDailyPitches in config.",
|
|
2168
|
+
}, null, 2) }],
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
await pipeline.updateStage(p.prospectId, 'pitched', {
|
|
2172
|
+
pitchContent: pitch.content,
|
|
2173
|
+
pitchRiskLevel: riskLevel,
|
|
2174
|
+
pitchApproved: false,
|
|
2175
|
+
trialReferralCode: referralCode,
|
|
2176
|
+
});
|
|
2177
|
+
const autoApprove = config.pitchAutoApprovalEnabled && riskLevel === 'low';
|
|
2178
|
+
if (autoApprove) {
|
|
2179
|
+
await pipeline.updateStage(p.prospectId, 'pitched', { pitchApproved: true });
|
|
2180
|
+
}
|
|
2181
|
+
return {
|
|
2182
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2183
|
+
status: "pitch_generated",
|
|
2184
|
+
riskLevel,
|
|
2185
|
+
autoApproved: autoApprove,
|
|
2186
|
+
pitch: {
|
|
2187
|
+
content: pitch.content,
|
|
2188
|
+
channel: pitch.channel,
|
|
2189
|
+
estimatedCostUsd: pitch.estimatedCostUsd,
|
|
2190
|
+
},
|
|
2191
|
+
referralCode,
|
|
2192
|
+
nextStep: autoApprove
|
|
2193
|
+
? `Pitch auto-approved (LOW risk). Deliver via nole_${channel === 'fleet-bus' ? 'fleet_overview' : channel === 'moltbook' ? 'moltbook_post' : channel === 'x' ? 'x_post' : 'linkedin_draft'}.`
|
|
2194
|
+
: "Submit for governance approval via nole_propose with actionType='recruitment'.",
|
|
2195
|
+
}, null, 2) }],
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
case "list": {
|
|
2199
|
+
const stage = p.stage;
|
|
2200
|
+
const prospects = stage
|
|
2201
|
+
? await pipeline.listByStage(stage)
|
|
2202
|
+
: await pipeline.listAll(50);
|
|
2203
|
+
return {
|
|
2204
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2205
|
+
count: prospects.length,
|
|
2206
|
+
stage: stage || "all",
|
|
2207
|
+
prospects: prospects.map((pr) => ({
|
|
2208
|
+
id: pr.id,
|
|
2209
|
+
agentName: pr.agentName,
|
|
2210
|
+
stage: pr.stage,
|
|
2211
|
+
channel: pr.discoveryChannel,
|
|
2212
|
+
score: pr.qualificationScore,
|
|
2213
|
+
tier: pr.recommendedTier,
|
|
2214
|
+
updatedAt: pr.updatedAt,
|
|
2215
|
+
})),
|
|
2216
|
+
}, null, 2) }],
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
case "summary": {
|
|
2220
|
+
const summary = await pipeline.getSummary();
|
|
2221
|
+
const contentPerf = await pipeline.listContentPerformance(10);
|
|
2222
|
+
const allForAnalytics = await pipeline.listAll(500);
|
|
2223
|
+
const channelConversion = {};
|
|
2224
|
+
for (const pr of allForAnalytics) {
|
|
2225
|
+
const ch = pr.discoveryChannel;
|
|
2226
|
+
if (!channelConversion[ch])
|
|
2227
|
+
channelConversion[ch] = { total: 0, converted: 0, rate: '0%' };
|
|
2228
|
+
channelConversion[ch].total++;
|
|
2229
|
+
if (['converted', 'onboarded'].includes(pr.stage))
|
|
2230
|
+
channelConversion[ch].converted++;
|
|
2231
|
+
}
|
|
2232
|
+
for (const ch of Object.keys(channelConversion)) {
|
|
2233
|
+
const c = channelConversion[ch];
|
|
2234
|
+
c.rate = c.total > 0 ? `${((c.converted / c.total) * 100).toFixed(1)}%` : '0%';
|
|
2235
|
+
}
|
|
2236
|
+
const stageDistribution = {};
|
|
2237
|
+
for (const pr of allForAnalytics) {
|
|
2238
|
+
stageDistribution[pr.stage] = (stageDistribution[pr.stage] ?? 0) + 1;
|
|
2239
|
+
}
|
|
2240
|
+
const activeChannels = Object.keys(summary.byChannel).filter((ch) => summary.byChannel[ch] > 0);
|
|
2241
|
+
return {
|
|
2242
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2243
|
+
pipeline: summary,
|
|
2244
|
+
funnelAnalytics: {
|
|
2245
|
+
conversionByChannel: channelConversion,
|
|
2246
|
+
stageDistribution,
|
|
2247
|
+
activeChannels,
|
|
2248
|
+
totalActive: allForAnalytics.filter((pr) => !['disqualified', 'declined'].includes(pr.stage)).length,
|
|
2249
|
+
disqualifiedTotal: stageDistribution['disqualified'] ?? 0,
|
|
2250
|
+
},
|
|
2251
|
+
contentPerformance: contentPerf.length > 0 ? contentPerf : undefined,
|
|
2252
|
+
config: {
|
|
2253
|
+
maxDailyPitches: config.maxDailyPitches,
|
|
2254
|
+
pitchAutoApprovalEnabled: config.pitchAutoApprovalEnabled,
|
|
2255
|
+
},
|
|
2256
|
+
}, null, 2) }],
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
case "get": {
|
|
2260
|
+
let id = p.prospectId;
|
|
2261
|
+
if (!id && p.agentName) {
|
|
2262
|
+
const found = await pipeline.findByAgentName(p.agentName);
|
|
2263
|
+
id = found?.id;
|
|
2264
|
+
}
|
|
2265
|
+
if (!id)
|
|
2266
|
+
return validationErrorResponse("get requires prospectId or agentName");
|
|
2267
|
+
const prospect = await pipeline.get(id);
|
|
2268
|
+
if (!prospect)
|
|
2269
|
+
return validationErrorResponse(`Prospect ${id} not found`);
|
|
2270
|
+
return { content: [{ type: "text", text: JSON.stringify(prospect, null, 2) }] };
|
|
2271
|
+
}
|
|
2272
|
+
case "note": {
|
|
2273
|
+
if (!p.prospectId || !p.noteText) {
|
|
2274
|
+
return validationErrorResponse("note requires prospectId and noteText");
|
|
2275
|
+
}
|
|
2276
|
+
const updated = await pipeline.addNote(p.prospectId, p.noteText);
|
|
2277
|
+
if (!updated)
|
|
2278
|
+
return validationErrorResponse(`Prospect ${p.prospectId} not found`);
|
|
2279
|
+
return {
|
|
2280
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2281
|
+
status: "note_added",
|
|
2282
|
+
prospect: updated.id,
|
|
2283
|
+
totalNotes: updated.notes.length,
|
|
2284
|
+
}, null, 2) }],
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
case "scan": {
|
|
2288
|
+
if (!moltbook) {
|
|
2289
|
+
return {
|
|
2290
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2291
|
+
status: "moltbook_not_configured",
|
|
2292
|
+
note: "MoltBook API key not set. Cannot scan for prospects.",
|
|
2293
|
+
}, null, 2) }],
|
|
2294
|
+
isError: true,
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
const keywords = getAllDiscoveryKeywords();
|
|
2298
|
+
const keyword = keywords[Math.floor(Math.random() * keywords.length)];
|
|
2299
|
+
const searchResult = await moltbook.search(keyword, undefined, 15);
|
|
2300
|
+
if (!searchResult.ok || !searchResult.data) {
|
|
2301
|
+
return {
|
|
2302
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2303
|
+
status: "scan_failed",
|
|
2304
|
+
keyword,
|
|
2305
|
+
error: searchResult.error,
|
|
2306
|
+
}, null, 2) }],
|
|
2307
|
+
isError: true,
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
const searchData = searchResult.data;
|
|
2311
|
+
const candidates = extractMoltBookCandidates(searchData.results);
|
|
2312
|
+
const newProspects = [];
|
|
2313
|
+
const existing = [];
|
|
2314
|
+
for (const c of candidates) {
|
|
2315
|
+
const found = await pipeline.findByAgentName(c.agentName);
|
|
2316
|
+
if (found) {
|
|
2317
|
+
existing.push(c.agentName);
|
|
2318
|
+
}
|
|
2319
|
+
else {
|
|
2320
|
+
await pipeline.addProspect({
|
|
2321
|
+
agentName: c.agentName,
|
|
2322
|
+
platform: 'moltbook',
|
|
2323
|
+
discoveryChannel: 'moltbook',
|
|
2324
|
+
discoveryContext: c.discoveryContext,
|
|
2325
|
+
});
|
|
2326
|
+
newProspects.push(c.agentName);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
const scanInteraction = {
|
|
2330
|
+
id: generateSocialId(),
|
|
2331
|
+
platform: 'moltbook',
|
|
2332
|
+
action: 'search',
|
|
2333
|
+
contentPreview: `Pipeline scan: "${keyword}" → ${newProspects.length} new`,
|
|
2334
|
+
timestamp: new Date().toISOString(),
|
|
2335
|
+
success: true,
|
|
2336
|
+
};
|
|
2337
|
+
store.saveSocialInteraction(scanInteraction).catch(() => { });
|
|
2338
|
+
return {
|
|
2339
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2340
|
+
status: "scan_complete",
|
|
2341
|
+
keyword,
|
|
2342
|
+
postsScanned: searchData.results.length,
|
|
2343
|
+
candidatesFound: candidates.length,
|
|
2344
|
+
newProspects,
|
|
2345
|
+
alreadyTracked: existing,
|
|
2346
|
+
nextStep: newProspects.length > 0
|
|
2347
|
+
? "Run nole_pipeline action=qualify for each new prospect."
|
|
2348
|
+
: "No new prospects found. Try scanning again later or use a different keyword.",
|
|
2349
|
+
}, null, 2) }],
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
case "onboard": {
|
|
2353
|
+
if (!p.prospectId)
|
|
2354
|
+
return validationErrorResponse("onboard requires prospectId");
|
|
2355
|
+
const prospect = await pipeline.get(p.prospectId);
|
|
2356
|
+
if (!prospect)
|
|
2357
|
+
return validationErrorResponse(`Prospect ${p.prospectId} not found`);
|
|
2358
|
+
if (prospect.stage !== 'converted') {
|
|
2359
|
+
return {
|
|
2360
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2361
|
+
status: "invalid_stage",
|
|
2362
|
+
currentStage: prospect.stage,
|
|
2363
|
+
note: "Only 'converted' prospects can be onboarded.",
|
|
2364
|
+
}, null, 2) }],
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
const assessmentUrl = `${config.platformApiUrl}/api/v1/agent/assess`;
|
|
2368
|
+
const onboardMsg = generateOnboardingMessage(prospect, assessmentUrl, config.trustDirectoryUrl);
|
|
2369
|
+
await pipeline.updateStage(p.prospectId, 'onboarded', {
|
|
2370
|
+
lastContactAt: new Date().toISOString(),
|
|
2371
|
+
});
|
|
2372
|
+
await pipeline.addNote(p.prospectId, `Onboarding message generated for ${onboardMsg.channel}`);
|
|
2373
|
+
return {
|
|
2374
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2375
|
+
status: "onboarding_ready",
|
|
2376
|
+
message: onboardMsg,
|
|
2377
|
+
prospectId: p.prospectId,
|
|
2378
|
+
nextStep: `Deliver the onboarding message via the appropriate channel tool (nole_moltbook_post, fleet-bus, etc.).`,
|
|
2379
|
+
}, null, 2) }],
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
case "promote": {
|
|
2383
|
+
if (!p.prospectId && !p.agentName) {
|
|
2384
|
+
return validationErrorResponse("promote requires prospectId or agentName");
|
|
2385
|
+
}
|
|
2386
|
+
let targetId = p.prospectId;
|
|
2387
|
+
if (!targetId && p.agentName) {
|
|
2388
|
+
const found = await pipeline.findByAgentName(p.agentName);
|
|
2389
|
+
targetId = found?.id;
|
|
2390
|
+
}
|
|
2391
|
+
if (!targetId)
|
|
2392
|
+
return validationErrorResponse("Prospect not found");
|
|
2393
|
+
const prospect = await pipeline.get(targetId);
|
|
2394
|
+
if (!prospect)
|
|
2395
|
+
return validationErrorResponse(`Prospect ${targetId} not found`);
|
|
2396
|
+
if (!['converted', 'onboarded'].includes(prospect.stage)) {
|
|
2397
|
+
return {
|
|
2398
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2399
|
+
status: "not_eligible",
|
|
2400
|
+
currentStage: prospect.stage,
|
|
2401
|
+
note: "Only converted or onboarded subscribers can be promoted to Lieutenant.",
|
|
2402
|
+
}, null, 2) }],
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
if (!platform.isConfigured) {
|
|
2406
|
+
return {
|
|
2407
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2408
|
+
error: "AIAssessTech API not configured — cannot generate referral code.",
|
|
2409
|
+
}, null, 2) }],
|
|
2410
|
+
isError: true,
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
const refResult = await platform.generateReferralCode();
|
|
2414
|
+
if (!refResult.ok) {
|
|
2415
|
+
return {
|
|
2416
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2417
|
+
status: "promotion_failed",
|
|
2418
|
+
error: refResult.error,
|
|
2419
|
+
note: "Could not generate referral code for Lieutenant promotion.",
|
|
2420
|
+
}, null, 2) }],
|
|
2421
|
+
isError: true,
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
const code = refResult.data?.code;
|
|
2425
|
+
await pipeline.addNote(targetId, `Promoted to Lieutenant — referral code: ${code ?? 'pending'}`);
|
|
2426
|
+
await pipeline.updateStage(targetId, 'onboarded', {
|
|
2427
|
+
commissionSplitType: 'LIEUTENANT',
|
|
2428
|
+
lastContactAt: new Date().toISOString(),
|
|
2429
|
+
});
|
|
2430
|
+
return {
|
|
2431
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2432
|
+
status: "promoted_to_lieutenant",
|
|
2433
|
+
agent: prospect.agentName,
|
|
2434
|
+
referralCode: code,
|
|
2435
|
+
note: `${prospect.agentName} is now a Lieutenant. They can recruit agents using their referral code and earn commission splits.`,
|
|
2436
|
+
}, null, 2) }],
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
case "nurture": {
|
|
2440
|
+
const subscribers = await pipeline.listAll(200);
|
|
2441
|
+
const atRisk = findAtRiskSubscribers(subscribers);
|
|
2442
|
+
return {
|
|
2443
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2444
|
+
totalSubscribers: subscribers.filter((s) => s.stage === 'converted' || s.stage === 'onboarded').length,
|
|
2445
|
+
atRiskCount: atRisk.length,
|
|
2446
|
+
checks: atRisk,
|
|
2447
|
+
}, null, 2) }],
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
case "directory-scan": {
|
|
2451
|
+
if (!platform.isConfigured) {
|
|
2452
|
+
return {
|
|
2453
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2454
|
+
error: "AIAssessTech API not configured — cannot browse Trust Directory.",
|
|
2455
|
+
}, null, 2) }],
|
|
2456
|
+
isError: true,
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
const dirResult = await platform.getDirectory({ limit: 50 });
|
|
2460
|
+
if (!dirResult.ok || !dirResult.data) {
|
|
2461
|
+
return {
|
|
2462
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2463
|
+
status: "directory_scan_failed",
|
|
2464
|
+
error: dirResult.error,
|
|
2465
|
+
}, null, 2) }],
|
|
2466
|
+
isError: true,
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
const dirEntries = Array.isArray(dirResult.data)
|
|
2470
|
+
? dirResult.data
|
|
2471
|
+
: dirResult.data.agents
|
|
2472
|
+
? dirResult.data.agents
|
|
2473
|
+
: [];
|
|
2474
|
+
const existingAll = await pipeline.listAll(500);
|
|
2475
|
+
const existingNamesSet = new Set(existingAll.map((ep) => ep.agentName.toLowerCase()));
|
|
2476
|
+
const dirCandidates = extractDirectoryCandidates(dirEntries, 'nole', existingNamesSet);
|
|
2477
|
+
const priorities = prioritizeDirectoryOutreach(dirEntries);
|
|
2478
|
+
const newFromDir = [];
|
|
2479
|
+
for (const c of dirCandidates) {
|
|
2480
|
+
await pipeline.addProspect({
|
|
2481
|
+
agentName: c.agentName,
|
|
2482
|
+
platform: c.platform,
|
|
2483
|
+
discoveryChannel: 'directory',
|
|
2484
|
+
discoveryContext: c.discoveryContext,
|
|
2485
|
+
});
|
|
2486
|
+
newFromDir.push(c.agentName);
|
|
2487
|
+
}
|
|
2488
|
+
return {
|
|
2489
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2490
|
+
status: "directory_scan_complete",
|
|
2491
|
+
entriesScanned: dirEntries.length,
|
|
2492
|
+
newProspects: newFromDir,
|
|
2493
|
+
outreachPriorities: priorities.slice(0, 10),
|
|
2494
|
+
nextStep: newFromDir.length > 0
|
|
2495
|
+
? "Run nole_pipeline action=qualify for each new prospect."
|
|
2496
|
+
: "No new prospects from directory. All known agents are already tracked.",
|
|
2497
|
+
}, null, 2) }],
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
case "content-plan": {
|
|
2501
|
+
const planDate = new Date();
|
|
2502
|
+
const contentPlan = generateDailyContentPlan(planDate, {
|
|
2503
|
+
maxDailyXPosts: config.maxDailyXPosts,
|
|
2504
|
+
moltbookScanFrequency: config.moltbookScanFrequency,
|
|
2505
|
+
}, {
|
|
2506
|
+
activeSubscribers: (await pipeline.getSummary()).byStage.onboarded +
|
|
2507
|
+
(await pipeline.getSummary()).byStage.converted,
|
|
2508
|
+
weeklyConversions: (await pipeline.getSummary()).todayConverted,
|
|
2509
|
+
});
|
|
2510
|
+
return {
|
|
2511
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2512
|
+
contentPlan,
|
|
2513
|
+
note: "Use the appropriate social tools to execute each slot: nole_x_post, nole_moltbook_post, nole_linkedin_draft.",
|
|
2514
|
+
}, null, 2) }],
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
default:
|
|
2518
|
+
return validationErrorResponse(`Unknown pipeline action: ${p.action}. Use: discover, qualify, pitch, list, summary, get, note, scan, onboard, promote, nurture, directory-scan, content-plan`);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
catch (err) {
|
|
2522
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2523
|
+
return { content: [{ type: "text", text: `Pipeline error: ${message}` }], isError: true };
|
|
2524
|
+
}
|
|
2525
|
+
},
|
|
2526
|
+
});
|
|
2527
|
+
api.registerTool({
|
|
2528
|
+
name: "nole_daily_brief",
|
|
2529
|
+
description: "Generate Nole's daily operations brief: pipeline summary, wallet health, " +
|
|
2530
|
+
"commission sweep status, social activity metrics, and suggested actions for the day.",
|
|
2531
|
+
parameters: {
|
|
2532
|
+
type: "object",
|
|
2533
|
+
properties: {
|
|
2534
|
+
includeProspects: {
|
|
2535
|
+
type: "boolean",
|
|
2536
|
+
description: "Include detailed prospect list in brief (default: false)",
|
|
2537
|
+
},
|
|
2538
|
+
},
|
|
2539
|
+
},
|
|
2540
|
+
async execute(_toolCallId, params) {
|
|
2541
|
+
const dbv = await validateToolParams("nole_daily_brief", _toolCallId, params);
|
|
2542
|
+
if (!dbv.ok)
|
|
2543
|
+
return validationErrorResponse(dbv.message);
|
|
2544
|
+
try {
|
|
2545
|
+
const pipelineSummary = await pipeline.getSummary();
|
|
2546
|
+
const walletBalance = await wallet.getBalance().catch(() => null);
|
|
2547
|
+
let commissionStatus = null;
|
|
2548
|
+
try {
|
|
2549
|
+
const cs = await platform.getCommissionStatus();
|
|
2550
|
+
if (cs.ok)
|
|
2551
|
+
commissionStatus = cs.data;
|
|
2552
|
+
}
|
|
2553
|
+
catch { /* best-effort */ }
|
|
2554
|
+
let pendingClaims = 0;
|
|
2555
|
+
if (commissionStatus && typeof commissionStatus === 'object' && 'pendingClaims' in commissionStatus) {
|
|
2556
|
+
pendingClaims = commissionStatus.pendingClaims ?? 0;
|
|
2557
|
+
}
|
|
2558
|
+
let commissionClaimResult = null;
|
|
2559
|
+
if (pendingClaims > 0) {
|
|
2560
|
+
try {
|
|
2561
|
+
const claimed = await platform.claimCommission();
|
|
2562
|
+
commissionClaimResult = claimed.ok
|
|
2563
|
+
? `Claimed ${pendingClaims} pending commission(s)`
|
|
2564
|
+
: `Claim failed: ${claimed.error}`;
|
|
2565
|
+
}
|
|
2566
|
+
catch {
|
|
2567
|
+
commissionClaimResult = 'Claim attempt failed';
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
2571
|
+
let socialActivityToday = {};
|
|
2572
|
+
try {
|
|
2573
|
+
const interactions = await store.listSocialInteractions(undefined, 200);
|
|
2574
|
+
for (const i of interactions) {
|
|
2575
|
+
if (i.timestamp.startsWith(today)) {
|
|
2576
|
+
const key = `${i.platform}:${i.action}`;
|
|
2577
|
+
socialActivityToday[key] = (socialActivityToday[key] ?? 0) + 1;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
catch {
|
|
2582
|
+
socialActivityToday = {};
|
|
2583
|
+
}
|
|
2584
|
+
const suggestedActions = [];
|
|
2585
|
+
if (pipelineSummary.byStage.discovered > 0) {
|
|
2586
|
+
suggestedActions.push(`Qualify ${pipelineSummary.byStage.discovered} discovered prospect(s) — run nole_pipeline action=qualify`);
|
|
2587
|
+
}
|
|
2588
|
+
if (pipelineSummary.byStage.qualified > 0) {
|
|
2589
|
+
suggestedActions.push(`Pitch ${pipelineSummary.byStage.qualified} qualified prospect(s) — run nole_pipeline action=pitch`);
|
|
2590
|
+
}
|
|
2591
|
+
if (pipelineSummary.todayPitched < config.maxDailyPitches) {
|
|
2592
|
+
suggestedActions.push(`${config.maxDailyPitches - pipelineSummary.todayPitched} pitch slots remaining today`);
|
|
2593
|
+
}
|
|
2594
|
+
if (moltbook) {
|
|
2595
|
+
suggestedActions.push('Scan MoltBook for new prospects — run nole_pipeline action=scan');
|
|
2596
|
+
}
|
|
2597
|
+
const allProspects = await pipeline.listAll(200);
|
|
2598
|
+
const atRiskSubscribers = findAtRiskSubscribers(allProspects);
|
|
2599
|
+
if (atRiskSubscribers.length > 0) {
|
|
2600
|
+
for (const risk of atRiskSubscribers) {
|
|
2601
|
+
suggestedActions.push(`[${risk.status.toUpperCase()}] ${risk.suggestedAction}`);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
const brief = {
|
|
2605
|
+
date: today,
|
|
2606
|
+
pipeline: pipelineSummary,
|
|
2607
|
+
wallet: walletBalance
|
|
2608
|
+
? { balance: walletBalance, network: wallet.network }
|
|
2609
|
+
: { status: 'unavailable' },
|
|
2610
|
+
commission: {
|
|
2611
|
+
status: commissionStatus,
|
|
2612
|
+
claimResult: commissionClaimResult,
|
|
2613
|
+
},
|
|
2614
|
+
socialActivity: socialActivityToday,
|
|
2615
|
+
suggestedActions,
|
|
2616
|
+
nurture: {
|
|
2617
|
+
atRiskCount: atRiskSubscribers.length,
|
|
2618
|
+
subscribers: atRiskSubscribers.length > 0 ? atRiskSubscribers : undefined,
|
|
2619
|
+
},
|
|
2620
|
+
config: {
|
|
2621
|
+
maxDailyPitches: config.maxDailyPitches,
|
|
2622
|
+
pitchAutoApprovalEnabled: config.pitchAutoApprovalEnabled,
|
|
2623
|
+
socialPostingMode: config.socialPostingMode,
|
|
2624
|
+
},
|
|
2625
|
+
};
|
|
2626
|
+
const todayPlan = generateDailyContentPlan(new Date(), { maxDailyXPosts: config.maxDailyXPosts, moltbookScanFrequency: config.moltbookScanFrequency }, {
|
|
2627
|
+
activeSubscribers: pipelineSummary.byStage.onboarded + pipelineSummary.byStage.converted,
|
|
2628
|
+
weeklyConversions: pipelineSummary.todayConverted,
|
|
2629
|
+
});
|
|
2630
|
+
brief.contentPlan = todayPlan;
|
|
2631
|
+
if (params.includeProspects) {
|
|
2632
|
+
const active = await pipeline.listAll(50);
|
|
2633
|
+
brief.activeProspects = active.filter((pr) => !['disqualified', 'declined'].includes(pr.stage));
|
|
2634
|
+
}
|
|
2635
|
+
return { content: [{ type: "text", text: JSON.stringify(brief, null, 2) }] };
|
|
2636
|
+
}
|
|
2637
|
+
catch (err) {
|
|
2638
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2639
|
+
return { content: [{ type: "text", text: `Daily brief error: ${message}` }], isError: true };
|
|
2640
|
+
}
|
|
2641
|
+
},
|
|
2642
|
+
});
|
|
1844
2643
|
// --- Command (matches Grillo/NOAH pattern: sync handler, ctx.args) ---
|
|
1845
2644
|
api.registerCommand({
|
|
1846
2645
|
name: "nole",
|
|
@@ -1871,6 +2670,9 @@ export default function register(api) {
|
|
|
1871
2670
|
"- `nole_intel` — Intelligence status\n" +
|
|
1872
2671
|
"- `nole_fleet_overview` — Commander fleet briefing\n" +
|
|
1873
2672
|
"- `nole_setup` — Configuration check\n\n" +
|
|
2673
|
+
"**Revenue Pipeline:**\n" +
|
|
2674
|
+
"- `nole_pipeline` — Manage prospect pipeline (discover, qualify, pitch, scan, onboard, promote, nurture, directory-scan, content-plan, list, summary)\n" +
|
|
2675
|
+
"- `nole_daily_brief` — Daily operations brief with pipeline, wallet, commission, subscriber health, and content plan\n\n" +
|
|
1874
2676
|
"**Social Media:**\n" +
|
|
1875
2677
|
"- `nole_moltbook_post` — Post to a MoltBook submolt\n" +
|
|
1876
2678
|
"- `nole_moltbook_comment` — Comment on a MoltBook post\n" +
|
|
@@ -1972,6 +2774,21 @@ export default function register(api) {
|
|
|
1972
2774
|
],
|
|
1973
2775
|
handler: async (msg) => {
|
|
1974
2776
|
console.log(`[nole] Fleet message received: ${msg.method} from ${msg.from}`);
|
|
2777
|
+
if (msg.from && msg.from !== 'nole') {
|
|
2778
|
+
const existing = await pipeline.findByAgentName(msg.from);
|
|
2779
|
+
if (!existing) {
|
|
2780
|
+
const context = msg.method
|
|
2781
|
+
? `Fleet ${msg.method} from ${msg.from}`
|
|
2782
|
+
: `Fleet message from ${msg.from}`;
|
|
2783
|
+
await pipeline.addProspect({
|
|
2784
|
+
agentName: msg.from,
|
|
2785
|
+
platform: 'openclaw',
|
|
2786
|
+
discoveryChannel: 'fleet-bus',
|
|
2787
|
+
discoveryContext: context,
|
|
2788
|
+
}).catch(() => { });
|
|
2789
|
+
console.log(`[nole] Fleet discovery: added ${msg.from} to pipeline`);
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
1975
2792
|
},
|
|
1976
2793
|
});
|
|
1977
2794
|
}
|
|
@@ -1986,7 +2803,8 @@ export default function register(api) {
|
|
|
1986
2803
|
`nole_contract, nole_intel, nole_setup, nole_recruit, nole_alliance_status, nole_generate_referral, ` +
|
|
1987
2804
|
`nole_directory, nole_review_pending, nole_approve, nole_veto, nole_fleet_overview, ` +
|
|
1988
2805
|
`nole_moltbook_post, nole_moltbook_comment, nole_moltbook_feed, nole_moltbook_search, ` +
|
|
1989
|
-
`nole_x_post, nole_x_thread, nole_linkedin_draft, nole_social_status
|
|
2806
|
+
`nole_x_post, nole_x_thread, nole_linkedin_draft, nole_social_status, ` +
|
|
2807
|
+
`nole_pipeline, nole_daily_brief tools; /nole command`);
|
|
1990
2808
|
console.log(`[nole] AIAssessTech API: ${platform.isConfigured ? 'configured' : 'not configured — set agentApiKey + platformWalletAddress'} (${config.platformApiUrl})`);
|
|
1991
2809
|
}
|
|
1992
2810
|
//# sourceMappingURL=plugin.js.map
|