@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.
Files changed (58) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/agent/PRODUCT-KNOWLEDGE.md +272 -0
  3. package/dist/index.d.ts +5 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +5 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/pipeline/content-calendar.d.ts +31 -0
  8. package/dist/pipeline/content-calendar.d.ts.map +1 -0
  9. package/dist/pipeline/content-calendar.js +94 -0
  10. package/dist/pipeline/content-calendar.js.map +1 -0
  11. package/dist/pipeline/directory-outreach.d.ts +37 -0
  12. package/dist/pipeline/directory-outreach.d.ts.map +1 -0
  13. package/dist/pipeline/directory-outreach.js +81 -0
  14. package/dist/pipeline/directory-outreach.js.map +1 -0
  15. package/dist/pipeline/discovery.d.ts +25 -0
  16. package/dist/pipeline/discovery.d.ts.map +1 -0
  17. package/dist/pipeline/discovery.js +46 -0
  18. package/dist/pipeline/discovery.js.map +1 -0
  19. package/dist/pipeline/fleet-discovery.d.ts +25 -0
  20. package/dist/pipeline/fleet-discovery.d.ts.map +1 -0
  21. package/dist/pipeline/fleet-discovery.js +57 -0
  22. package/dist/pipeline/fleet-discovery.js.map +1 -0
  23. package/dist/pipeline/index.d.ts +17 -0
  24. package/dist/pipeline/index.d.ts.map +1 -0
  25. package/dist/pipeline/index.js +11 -0
  26. package/dist/pipeline/index.js.map +1 -0
  27. package/dist/pipeline/nurture.d.ts +23 -0
  28. package/dist/pipeline/nurture.d.ts.map +1 -0
  29. package/dist/pipeline/nurture.js +74 -0
  30. package/dist/pipeline/nurture.js.map +1 -0
  31. package/dist/pipeline/onboarding.d.ts +12 -0
  32. package/dist/pipeline/onboarding.d.ts.map +1 -0
  33. package/dist/pipeline/onboarding.js +71 -0
  34. package/dist/pipeline/onboarding.js.map +1 -0
  35. package/dist/pipeline/pitch-templates.d.ts +27 -0
  36. package/dist/pipeline/pitch-templates.d.ts.map +1 -0
  37. package/dist/pipeline/pitch-templates.js +112 -0
  38. package/dist/pipeline/pitch-templates.js.map +1 -0
  39. package/dist/pipeline/prospect-store.d.ts +34 -0
  40. package/dist/pipeline/prospect-store.d.ts.map +1 -0
  41. package/dist/pipeline/prospect-store.js +201 -0
  42. package/dist/pipeline/prospect-store.js.map +1 -0
  43. package/dist/pipeline/qualification.d.ts +28 -0
  44. package/dist/pipeline/qualification.d.ts.map +1 -0
  45. package/dist/pipeline/qualification.js +123 -0
  46. package/dist/pipeline/qualification.js.map +1 -0
  47. package/dist/pipeline/types.d.ts +73 -0
  48. package/dist/pipeline/types.d.ts.map +1 -0
  49. package/dist/pipeline/types.js +10 -0
  50. package/dist/pipeline/types.js.map +1 -0
  51. package/dist/plugin.d.ts.map +1 -1
  52. package/dist/plugin.js +822 -4
  53. package/dist/plugin.js.map +1 -1
  54. package/dist/types/nole-config.d.ts +48 -0
  55. package/dist/types/nole-config.d.ts.map +1 -1
  56. package/dist/types/nole-config.js +20 -0
  57. package/dist/types/nole-config.js.map +1 -1
  58. 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 (25 tools, approve/veto/contract enforce, rest monitor)');
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(result.ok ? result.data : { error: result.error }, null, 2) }],
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 tools; /nole command`);
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