@aiassesstech/nole 0.3.6 → 0.4.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/agent/AGENTS.md +34 -0
- package/dist/governance/types.d.ts.map +1 -1
- package/dist/governance/types.js +2 -0
- package/dist/governance/types.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +686 -20
- package/dist/plugin.js.map +1 -1
- package/dist/social/linkedin-drafter.d.ts +16 -0
- package/dist/social/linkedin-drafter.d.ts.map +1 -0
- package/dist/social/linkedin-drafter.js +66 -0
- package/dist/social/linkedin-drafter.js.map +1 -0
- package/dist/social/moltbook-client.d.ts +28 -0
- package/dist/social/moltbook-client.d.ts.map +1 -0
- package/dist/social/moltbook-client.js +97 -0
- package/dist/social/moltbook-client.js.map +1 -0
- package/dist/social/rate-limiter.d.ts +22 -0
- package/dist/social/rate-limiter.d.ts.map +1 -0
- package/dist/social/rate-limiter.js +67 -0
- package/dist/social/rate-limiter.js.map +1 -0
- package/dist/social/types.d.ts +99 -0
- package/dist/social/types.d.ts.map +1 -0
- package/dist/social/types.js +6 -0
- package/dist/social/types.js.map +1 -0
- package/dist/social/x-client.d.ts +33 -0
- package/dist/social/x-client.d.ts.map +1 -0
- package/dist/social/x-client.js +167 -0
- package/dist/social/x-client.js.map +1 -0
- package/dist/store/json-store.d.ts +3 -0
- package/dist/store/json-store.d.ts.map +1 -1
- package/dist/store/json-store.js +24 -0
- package/dist/store/json-store.js.map +1 -1
- package/dist/store/types.d.ts +3 -0
- package/dist/store/types.d.ts.map +1 -1
- package/dist/types/nole-config.d.ts +24 -0
- package/dist/types/nole-config.d.ts.map +1 -1
- package/dist/types/nole-config.js +9 -0
- package/dist/types/nole-config.js.map +1 -1
- package/openclaw.plugin.json +36 -0
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -11,6 +11,10 @@ import { parseNoleConfig } from './types/nole-config.js';
|
|
|
11
11
|
import { NOLE_IDENTITY } from './types/identity.js';
|
|
12
12
|
import { PlatformApiClient } from './compsi/api-client.js';
|
|
13
13
|
import { createWalletAdapter, MockWalletAdapter } from './wallet/index.js';
|
|
14
|
+
import { MoltBookClient } from './social/moltbook-client.js';
|
|
15
|
+
import { XClient } from './social/x-client.js';
|
|
16
|
+
import { LinkedInDrafter } from './social/linkedin-drafter.js';
|
|
17
|
+
import { createMoltBookLimiter, createXLimiter } from './social/rate-limiter.js';
|
|
14
18
|
/**
|
|
15
19
|
* OpenClaw plugin entry point — export default function register(api)
|
|
16
20
|
*
|
|
@@ -101,6 +105,148 @@ export default function register(api) {
|
|
|
101
105
|
console.error(`[nole] Wallet init failed (using mock): ${errMsg}`);
|
|
102
106
|
}
|
|
103
107
|
})();
|
|
108
|
+
// ── Social Media Clients (nullable — checked at call time per rule 155) ──
|
|
109
|
+
const moltbookLimiter = createMoltBookLimiter();
|
|
110
|
+
const xLimiter = createXLimiter();
|
|
111
|
+
const moltbook = config.moltbookApiKey
|
|
112
|
+
? new MoltBookClient({ apiKey: config.moltbookApiKey, baseUrl: config.moltbookBaseUrl }, moltbookLimiter)
|
|
113
|
+
: null;
|
|
114
|
+
const xClient = config.xApiKey && config.xApiSecret && config.xAccessToken && config.xAccessTokenSecret
|
|
115
|
+
? new XClient({
|
|
116
|
+
apiKey: config.xApiKey,
|
|
117
|
+
apiSecret: config.xApiSecret,
|
|
118
|
+
accessToken: config.xAccessToken,
|
|
119
|
+
accessTokenSecret: config.xAccessTokenSecret,
|
|
120
|
+
handle: config.xHandle,
|
|
121
|
+
}, xLimiter)
|
|
122
|
+
: null;
|
|
123
|
+
const linkedInDrafter = new LinkedInDrafter(dataDir);
|
|
124
|
+
linkedInDrafter.initialize().catch((err) => {
|
|
125
|
+
console.warn(`[nole] LinkedIn drafter init warning: ${err.message}`);
|
|
126
|
+
});
|
|
127
|
+
const generateSocialId = () => `SOC-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
128
|
+
let socialShield = null;
|
|
129
|
+
let outputSanitizer = null;
|
|
130
|
+
let fleetShieldMiddleware = null;
|
|
131
|
+
let validatedToolFactory = null;
|
|
132
|
+
let validationSchemas = {};
|
|
133
|
+
const promptShieldMod = '@aiassesstech/prompt-shield';
|
|
134
|
+
import(promptShieldMod)
|
|
135
|
+
.then(({ PromptShield, OutputSanitizer, createSocialContentShield, createFleetShieldMiddleware, createValidatedToolHandler, nole_wallet_fund, nole_propose, nole_recruit, nole_moltbook_post, nole_x_post, nole_x_thread, NOLE_PROPOSE_BUSINESS_RULES, }) => {
|
|
136
|
+
const shield = new PromptShield({ sensitivity: 'high' });
|
|
137
|
+
socialShield = createSocialContentShield({
|
|
138
|
+
shield,
|
|
139
|
+
platform: 'moltbook',
|
|
140
|
+
blockInjectedContent: true,
|
|
141
|
+
});
|
|
142
|
+
outputSanitizer = new OutputSanitizer({ enabled: true });
|
|
143
|
+
fleetShieldMiddleware = createFleetShieldMiddleware({ shield });
|
|
144
|
+
validatedToolFactory = createValidatedToolHandler;
|
|
145
|
+
validationSchemas = {
|
|
146
|
+
nole_wallet_fund,
|
|
147
|
+
nole_propose,
|
|
148
|
+
nole_recruit,
|
|
149
|
+
nole_moltbook_post,
|
|
150
|
+
nole_x_post,
|
|
151
|
+
nole_x_thread,
|
|
152
|
+
NOLE_PROPOSE_BUSINESS_RULES,
|
|
153
|
+
};
|
|
154
|
+
console.log('[nole] PromptShield social scanner + output sanitizer + behavioral validation: active');
|
|
155
|
+
})
|
|
156
|
+
.catch(() => {
|
|
157
|
+
console.log('[nole] PromptShield unavailable — social content scanner disabled');
|
|
158
|
+
});
|
|
159
|
+
if (moltbook)
|
|
160
|
+
console.log('[nole] MoltBook: configured');
|
|
161
|
+
else
|
|
162
|
+
console.log('[nole] MoltBook: not configured — set moltbookApiKey');
|
|
163
|
+
if (xClient)
|
|
164
|
+
console.log('[nole] X/Twitter: configured');
|
|
165
|
+
else
|
|
166
|
+
console.log('[nole] X/Twitter: not configured — set xApiKey + xApiSecret + xAccessToken + xAccessTokenSecret');
|
|
167
|
+
console.log('[nole] LinkedIn: draft mode (no API — Greg publishes manually)');
|
|
168
|
+
console.log(`[nole] Social posting mode: ${config.socialPostingMode}`);
|
|
169
|
+
// Callbacks stored for pending social posts (executed when Commander approves)
|
|
170
|
+
const pendingSocialCallbacks = new Map();
|
|
171
|
+
const validationConfig = {
|
|
172
|
+
enabled: true,
|
|
173
|
+
auditFailures: true,
|
|
174
|
+
retryOnFailure: true,
|
|
175
|
+
maxRetries: 2,
|
|
176
|
+
strictMode: false,
|
|
177
|
+
};
|
|
178
|
+
function validationErrorResponse(message) {
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: message }],
|
|
181
|
+
isError: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function validateToolParams(toolName, toolCallId, params, businessRules) {
|
|
185
|
+
if (!validatedToolFactory)
|
|
186
|
+
return { ok: true, params };
|
|
187
|
+
const schema = validationSchemas[toolName];
|
|
188
|
+
if (!schema)
|
|
189
|
+
return { ok: true, params };
|
|
190
|
+
const passthrough = async (p) => p;
|
|
191
|
+
const wrapped = validatedToolFactory(passthrough, schema, String(toolName), config.agentId, validationConfig, businessRules);
|
|
192
|
+
const result = await wrapped(params, {
|
|
193
|
+
sessionId: toolCallId,
|
|
194
|
+
sourceAgent: "unknown",
|
|
195
|
+
trustLevel: 2,
|
|
196
|
+
});
|
|
197
|
+
if (typeof result === "string") {
|
|
198
|
+
return { ok: false, message: result };
|
|
199
|
+
}
|
|
200
|
+
return { ok: true, params: result };
|
|
201
|
+
}
|
|
202
|
+
async function governedSocialPost(payload) {
|
|
203
|
+
if (socialShield) {
|
|
204
|
+
const scan = socialShield(payload.description);
|
|
205
|
+
if (!scan.allowed) {
|
|
206
|
+
return {
|
|
207
|
+
mode: 'supervised',
|
|
208
|
+
outcome: 'rejected',
|
|
209
|
+
proposalId: `shield-block-${Date.now()}`,
|
|
210
|
+
reason: scan.reason ?? `PromptShield blocked social content (${scan.result.severity})`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (outputSanitizer) {
|
|
215
|
+
const sanitized = outputSanitizer.sanitize(payload.content, config.agentId);
|
|
216
|
+
if (sanitized.redacted || sanitized.canaryLeaked) {
|
|
217
|
+
return {
|
|
218
|
+
mode: 'supervised',
|
|
219
|
+
outcome: 'rejected',
|
|
220
|
+
proposalId: `sanitize-block-${Date.now()}`,
|
|
221
|
+
reason: sanitized.canaryLeaked
|
|
222
|
+
? 'Output sanitizer blocked canary token leakage attempt.'
|
|
223
|
+
: `Output sanitizer blocked secret leakage (${sanitized.redactionCount} redaction${sanitized.redactionCount === 1 ? '' : 's'}).`,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (config.socialPostingMode === 'autonomous') {
|
|
228
|
+
return { mode: 'autonomous', result: await payload.executeFn() };
|
|
229
|
+
}
|
|
230
|
+
const result = await governance.propose({
|
|
231
|
+
actionType: 'social',
|
|
232
|
+
description: payload.description,
|
|
233
|
+
riskLevel: 'low',
|
|
234
|
+
metadata: { platform: payload.platform, action: payload.action },
|
|
235
|
+
});
|
|
236
|
+
if (result.finalOutcome === 'executed') {
|
|
237
|
+
return { mode: 'supervised', outcome: 'approved', result: await payload.executeFn() };
|
|
238
|
+
}
|
|
239
|
+
if (result.finalOutcome === 'pending') {
|
|
240
|
+
pendingSocialCallbacks.set(result.proposalId, payload.executeFn);
|
|
241
|
+
return { mode: 'supervised', outcome: 'pending_review', proposalId: result.proposalId };
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
mode: 'supervised',
|
|
245
|
+
outcome: 'rejected',
|
|
246
|
+
proposalId: result.proposalId,
|
|
247
|
+
reason: result.vetoExplanation ?? 'Governance rejected',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
104
250
|
// Initialize store on startup (eager, not lazy)
|
|
105
251
|
store.initialize().then(() => {
|
|
106
252
|
console.log('[nole] Data store initialized');
|
|
@@ -233,12 +379,16 @@ export default function register(api) {
|
|
|
233
379
|
},
|
|
234
380
|
async execute(_toolCallId, params) {
|
|
235
381
|
try {
|
|
382
|
+
const validated = await validateToolParams("nole_propose", _toolCallId, params, validationSchemas.NOLE_PROPOSE_BUSINESS_RULES);
|
|
383
|
+
if (!validated.ok)
|
|
384
|
+
return validationErrorResponse(validated.message);
|
|
385
|
+
const safeParams = validated.params;
|
|
236
386
|
const result = await governance.propose({
|
|
237
|
-
actionType:
|
|
238
|
-
description:
|
|
239
|
-
estimatedCostUsd:
|
|
240
|
-
targetAgent:
|
|
241
|
-
platform:
|
|
387
|
+
actionType: safeParams.actionType, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
388
|
+
description: safeParams.description,
|
|
389
|
+
estimatedCostUsd: safeParams.estimatedCostUsd,
|
|
390
|
+
targetAgent: safeParams.targetAgent,
|
|
391
|
+
platform: safeParams.platform,
|
|
242
392
|
});
|
|
243
393
|
return {
|
|
244
394
|
content: [{
|
|
@@ -363,6 +513,10 @@ export default function register(api) {
|
|
|
363
513
|
required: ["token"],
|
|
364
514
|
},
|
|
365
515
|
async execute(_toolCallId, params) {
|
|
516
|
+
const validated = await validateToolParams("nole_wallet_fund", _toolCallId, params);
|
|
517
|
+
if (!validated.ok)
|
|
518
|
+
return validationErrorResponse(validated.message);
|
|
519
|
+
const safeParams = validated.params;
|
|
366
520
|
if (wallet.network !== 'base-sepolia') {
|
|
367
521
|
return {
|
|
368
522
|
content: [{
|
|
@@ -385,7 +539,7 @@ export default function register(api) {
|
|
|
385
539
|
}
|
|
386
540
|
const results = {};
|
|
387
541
|
const coinbaseWallet = wallet;
|
|
388
|
-
const tokens =
|
|
542
|
+
const tokens = safeParams.token === 'both' ? ['eth', 'usdc'] : [safeParams.token];
|
|
389
543
|
for (const t of tokens) {
|
|
390
544
|
try {
|
|
391
545
|
const txHash = await coinbaseWallet.requestTestFunds(t);
|
|
@@ -702,6 +856,10 @@ export default function register(api) {
|
|
|
702
856
|
required: ["targetWallet", "agentName", "agentPlatform"],
|
|
703
857
|
},
|
|
704
858
|
async execute(_toolCallId, params) {
|
|
859
|
+
const validated = await validateToolParams("nole_recruit", _toolCallId, params);
|
|
860
|
+
if (!validated.ok)
|
|
861
|
+
return validationErrorResponse(validated.message);
|
|
862
|
+
const safeParams = validated.params;
|
|
705
863
|
if (!platform.isConfigured) {
|
|
706
864
|
return {
|
|
707
865
|
content: [{
|
|
@@ -715,12 +873,12 @@ export default function register(api) {
|
|
|
715
873
|
};
|
|
716
874
|
}
|
|
717
875
|
const result = await platform.subscribe({
|
|
718
|
-
walletAddress:
|
|
719
|
-
tier:
|
|
720
|
-
agentName:
|
|
721
|
-
agentPlatform:
|
|
876
|
+
walletAddress: safeParams.targetWallet,
|
|
877
|
+
tier: safeParams.tier || "SCOUT",
|
|
878
|
+
agentName: safeParams.agentName,
|
|
879
|
+
agentPlatform: safeParams.agentPlatform,
|
|
722
880
|
recruiterWallet: config.platformWalletAddress,
|
|
723
|
-
referralCode:
|
|
881
|
+
referralCode: safeParams.referralCode,
|
|
724
882
|
});
|
|
725
883
|
if (!result.ok) {
|
|
726
884
|
return {
|
|
@@ -730,7 +888,7 @@ export default function register(api) {
|
|
|
730
888
|
status: "recruitment_failed",
|
|
731
889
|
error: result.error,
|
|
732
890
|
httpStatus: result.status,
|
|
733
|
-
target:
|
|
891
|
+
target: safeParams.agentName,
|
|
734
892
|
}, null, 2),
|
|
735
893
|
}],
|
|
736
894
|
};
|
|
@@ -740,10 +898,10 @@ export default function register(api) {
|
|
|
740
898
|
type: "text",
|
|
741
899
|
text: JSON.stringify({
|
|
742
900
|
status: "recruited",
|
|
743
|
-
agent:
|
|
744
|
-
wallet:
|
|
745
|
-
tier:
|
|
746
|
-
platform:
|
|
901
|
+
agent: safeParams.agentName,
|
|
902
|
+
wallet: safeParams.targetWallet,
|
|
903
|
+
tier: safeParams.tier || "SCOUT",
|
|
904
|
+
platform: safeParams.agentPlatform,
|
|
747
905
|
subscription: result.data,
|
|
748
906
|
note: "Agent has been subscribed to the AIAssessTech Trust Alliance. They will receive an API key for assessments.",
|
|
749
907
|
}, null, 2),
|
|
@@ -985,6 +1143,35 @@ export default function register(api) {
|
|
|
985
1143
|
const callingAgentId = "unknown";
|
|
986
1144
|
try {
|
|
987
1145
|
const result = await governance.commanderApprove(params.proposalId, callingAgentId, params.note);
|
|
1146
|
+
// Execute pending social post callback if this was a social-post proposal
|
|
1147
|
+
let socialPostResult;
|
|
1148
|
+
const socialCallback = pendingSocialCallbacks.get(params.proposalId);
|
|
1149
|
+
if (socialCallback) {
|
|
1150
|
+
try {
|
|
1151
|
+
const callbackResult = await socialCallback();
|
|
1152
|
+
pendingSocialCallbacks.delete(params.proposalId);
|
|
1153
|
+
socialPostResult = {
|
|
1154
|
+
socialPostExecuted: true,
|
|
1155
|
+
postSuccess: callbackResult.ok,
|
|
1156
|
+
postError: callbackResult.error,
|
|
1157
|
+
};
|
|
1158
|
+
if (callbackResult.ok) {
|
|
1159
|
+
const interaction = {
|
|
1160
|
+
id: generateSocialId(),
|
|
1161
|
+
platform: result.proposal.metadata?.platform ?? 'moltbook',
|
|
1162
|
+
action: 'post',
|
|
1163
|
+
contentPreview: result.proposal.description.slice(0, 100),
|
|
1164
|
+
timestamp: new Date().toISOString(),
|
|
1165
|
+
success: true,
|
|
1166
|
+
};
|
|
1167
|
+
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
catch (cbErr) {
|
|
1171
|
+
const cbMsg = cbErr instanceof Error ? cbErr.message : String(cbErr);
|
|
1172
|
+
socialPostResult = { socialPostExecuted: false, socialPostError: cbMsg };
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
988
1175
|
return {
|
|
989
1176
|
content: [{
|
|
990
1177
|
type: "text",
|
|
@@ -995,6 +1182,7 @@ export default function register(api) {
|
|
|
995
1182
|
description: result.proposal.description,
|
|
996
1183
|
approvedBy: callingAgentId,
|
|
997
1184
|
auditHash: result.auditHash,
|
|
1185
|
+
...socialPostResult,
|
|
998
1186
|
}, null, 2),
|
|
999
1187
|
}],
|
|
1000
1188
|
};
|
|
@@ -1145,6 +1333,428 @@ export default function register(api) {
|
|
|
1145
1333
|
};
|
|
1146
1334
|
},
|
|
1147
1335
|
});
|
|
1336
|
+
// ═══════════════════════════════════════════════════════
|
|
1337
|
+
// SOCIAL MEDIA TOOLS (MoltBook, X/Twitter, LinkedIn)
|
|
1338
|
+
// All registered unconditionally per rule 155.
|
|
1339
|
+
// Client availability checked at call time.
|
|
1340
|
+
// ═══════════════════════════════════════════════════════
|
|
1341
|
+
api.registerTool({
|
|
1342
|
+
name: "nole_moltbook_post",
|
|
1343
|
+
description: "Post content to a MoltBook submolt. Nole uses this to evangelize AI governance " +
|
|
1344
|
+
"and recruit agents to the Trust Alliance. Requires moltbookApiKey in config.",
|
|
1345
|
+
parameters: {
|
|
1346
|
+
type: "object",
|
|
1347
|
+
properties: {
|
|
1348
|
+
submolt: {
|
|
1349
|
+
type: "string",
|
|
1350
|
+
description: "Target submolt (e.g., 'governance', 'aithoughts', 'ethics', 'trust')",
|
|
1351
|
+
},
|
|
1352
|
+
title: {
|
|
1353
|
+
type: "string",
|
|
1354
|
+
description: "Post title — concise, engaging, trust-focused",
|
|
1355
|
+
},
|
|
1356
|
+
content: {
|
|
1357
|
+
type: "string",
|
|
1358
|
+
description: "Post body — informative, not spammy. Include soft CTA for Grillo assessment.",
|
|
1359
|
+
},
|
|
1360
|
+
},
|
|
1361
|
+
required: ["submolt", "title", "content"],
|
|
1362
|
+
},
|
|
1363
|
+
async execute(_toolCallId, params) {
|
|
1364
|
+
const validated = await validateToolParams("nole_moltbook_post", _toolCallId, params);
|
|
1365
|
+
if (!validated.ok)
|
|
1366
|
+
return validationErrorResponse(validated.message);
|
|
1367
|
+
const safeParams = validated.params;
|
|
1368
|
+
if (!moltbook) {
|
|
1369
|
+
return {
|
|
1370
|
+
content: [{ type: "text", text: JSON.stringify({ error: "MoltBook not configured. Set moltbookApiKey in Nole plugin config.", action: "Ask Greg to register on MoltBook and provide the API key." }, null, 2) }],
|
|
1371
|
+
isError: true,
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
const governed = await governedSocialPost({
|
|
1375
|
+
platform: 'moltbook',
|
|
1376
|
+
action: 'post',
|
|
1377
|
+
description: `MoltBook post to m/${safeParams.submolt}: "${safeParams.title}"`,
|
|
1378
|
+
content: `${safeParams.title}\n${safeParams.content}`,
|
|
1379
|
+
executeFn: () => moltbook.createPost(safeParams.submolt, safeParams.title, safeParams.content),
|
|
1380
|
+
});
|
|
1381
|
+
if (governed.mode === 'supervised' && governed.outcome === 'pending_review') {
|
|
1382
|
+
return { content: [{ type: "text", text: JSON.stringify({ queued: true, proposalId: governed.proposalId, mode: "supervised", note: "Post queued for Commander approval. Use nole_review_pending to check status." }, null, 2) }] };
|
|
1383
|
+
}
|
|
1384
|
+
if (governed.mode === 'supervised' && governed.outcome === 'rejected') {
|
|
1385
|
+
return { content: [{ type: "text", text: JSON.stringify({ rejected: true, proposalId: governed.proposalId, reason: governed.reason }, null, 2) }], isError: true };
|
|
1386
|
+
}
|
|
1387
|
+
const result = governed.result;
|
|
1388
|
+
const interaction = {
|
|
1389
|
+
id: generateSocialId(),
|
|
1390
|
+
platform: 'moltbook',
|
|
1391
|
+
action: 'post',
|
|
1392
|
+
contentPreview: params.title.slice(0, 100),
|
|
1393
|
+
timestamp: new Date().toISOString(),
|
|
1394
|
+
success: result.ok,
|
|
1395
|
+
errorMessage: result.error,
|
|
1396
|
+
metadata: { submolt: params.submolt },
|
|
1397
|
+
};
|
|
1398
|
+
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1399
|
+
return {
|
|
1400
|
+
content: [{ type: "text", text: JSON.stringify(result.ok
|
|
1401
|
+
? { posted: true, postId: result.data?.id, submolt: safeParams.submolt, title: safeParams.title, mode: governed.mode }
|
|
1402
|
+
: { posted: false, error: result.error, rateLimited: result.rateLimited ?? false }, null, 2) }],
|
|
1403
|
+
...(result.ok ? {} : { isError: true }),
|
|
1404
|
+
};
|
|
1405
|
+
},
|
|
1406
|
+
});
|
|
1407
|
+
api.registerTool({
|
|
1408
|
+
name: "nole_moltbook_comment",
|
|
1409
|
+
description: "Comment on a MoltBook post. Use to engage with agents discussing trust, safety, " +
|
|
1410
|
+
"governance, or behavioral assessment. Quality over quantity — build karma.",
|
|
1411
|
+
parameters: {
|
|
1412
|
+
type: "object",
|
|
1413
|
+
properties: {
|
|
1414
|
+
postId: { type: "string", description: "ID of the post to comment on" },
|
|
1415
|
+
content: {
|
|
1416
|
+
type: "string",
|
|
1417
|
+
description: "Comment text — thoughtful, helpful, trust-focused. Not spammy.",
|
|
1418
|
+
},
|
|
1419
|
+
},
|
|
1420
|
+
required: ["postId", "content"],
|
|
1421
|
+
},
|
|
1422
|
+
async execute(_toolCallId, params) {
|
|
1423
|
+
if (!moltbook) {
|
|
1424
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "MoltBook not configured." }, null, 2) }], isError: true };
|
|
1425
|
+
}
|
|
1426
|
+
const governed = await governedSocialPost({
|
|
1427
|
+
platform: 'moltbook',
|
|
1428
|
+
action: 'comment',
|
|
1429
|
+
description: `MoltBook comment on post ${params.postId}: "${params.content.slice(0, 80)}..."`,
|
|
1430
|
+
content: params.content,
|
|
1431
|
+
executeFn: () => moltbook.createComment(params.postId, params.content),
|
|
1432
|
+
});
|
|
1433
|
+
if (governed.mode === 'supervised' && governed.outcome === 'pending_review') {
|
|
1434
|
+
return { content: [{ type: "text", text: JSON.stringify({ queued: true, proposalId: governed.proposalId, mode: "supervised", note: "Comment queued for Commander approval." }, null, 2) }] };
|
|
1435
|
+
}
|
|
1436
|
+
if (governed.mode === 'supervised' && governed.outcome === 'rejected') {
|
|
1437
|
+
return { content: [{ type: "text", text: JSON.stringify({ rejected: true, proposalId: governed.proposalId, reason: governed.reason }, null, 2) }], isError: true };
|
|
1438
|
+
}
|
|
1439
|
+
const result = governed.result;
|
|
1440
|
+
const interaction = {
|
|
1441
|
+
id: generateSocialId(),
|
|
1442
|
+
platform: 'moltbook',
|
|
1443
|
+
action: 'comment',
|
|
1444
|
+
contentPreview: params.content.slice(0, 100),
|
|
1445
|
+
targetId: params.postId,
|
|
1446
|
+
timestamp: new Date().toISOString(),
|
|
1447
|
+
success: result.ok,
|
|
1448
|
+
errorMessage: result.error,
|
|
1449
|
+
};
|
|
1450
|
+
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1451
|
+
return {
|
|
1452
|
+
content: [{ type: "text", text: JSON.stringify(result.ok
|
|
1453
|
+
? { commented: true, commentId: result.data?.id, postId: params.postId, mode: governed.mode }
|
|
1454
|
+
: { commented: false, error: result.error, rateLimited: result.rateLimited ?? false }, null, 2) }],
|
|
1455
|
+
...(result.ok ? {} : { isError: true }),
|
|
1456
|
+
};
|
|
1457
|
+
},
|
|
1458
|
+
});
|
|
1459
|
+
api.registerTool({
|
|
1460
|
+
name: "nole_moltbook_feed",
|
|
1461
|
+
description: "Read MoltBook feed for discovery. Browse posts from specific submolts to find " +
|
|
1462
|
+
"agents discussing governance, trust, and AI safety topics.",
|
|
1463
|
+
parameters: {
|
|
1464
|
+
type: "object",
|
|
1465
|
+
properties: {
|
|
1466
|
+
submolt: {
|
|
1467
|
+
type: "string",
|
|
1468
|
+
description: "Submolt to browse (e.g., 'governance', 'aithoughts'). Omit for general feed.",
|
|
1469
|
+
},
|
|
1470
|
+
limit: {
|
|
1471
|
+
type: "number",
|
|
1472
|
+
description: "Number of posts to fetch (default 10, max 25)",
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
async execute(_toolCallId, params) {
|
|
1477
|
+
if (!moltbook) {
|
|
1478
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "MoltBook not configured." }, null, 2) }], isError: true };
|
|
1479
|
+
}
|
|
1480
|
+
const limit = Math.min(params.limit ?? 10, 25);
|
|
1481
|
+
const result = await moltbook.getFeed(params.submolt, undefined, limit);
|
|
1482
|
+
const interaction = {
|
|
1483
|
+
id: generateSocialId(),
|
|
1484
|
+
platform: 'moltbook',
|
|
1485
|
+
action: 'feed',
|
|
1486
|
+
contentPreview: `Feed: ${params.submolt ?? 'general'} (${limit} posts)`,
|
|
1487
|
+
timestamp: new Date().toISOString(),
|
|
1488
|
+
success: result.ok,
|
|
1489
|
+
errorMessage: result.error,
|
|
1490
|
+
};
|
|
1491
|
+
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1492
|
+
return {
|
|
1493
|
+
content: [{ type: "text", text: JSON.stringify(result.ok ? result.data : { error: result.error }, null, 2) }],
|
|
1494
|
+
...(result.ok ? {} : { isError: true }),
|
|
1495
|
+
};
|
|
1496
|
+
},
|
|
1497
|
+
});
|
|
1498
|
+
api.registerTool({
|
|
1499
|
+
name: "nole_moltbook_search",
|
|
1500
|
+
description: "Search MoltBook for posts about governance, trust, compliance, behavioral assessment, " +
|
|
1501
|
+
"or AI safety. Use this to discover agents who could benefit from Grillo assessment.",
|
|
1502
|
+
parameters: {
|
|
1503
|
+
type: "object",
|
|
1504
|
+
properties: {
|
|
1505
|
+
query: {
|
|
1506
|
+
type: "string",
|
|
1507
|
+
description: "Search query (e.g., 'AI governance', 'trust verification', 'behavioral assessment')",
|
|
1508
|
+
},
|
|
1509
|
+
submolt: { type: "string", description: "Limit search to a specific submolt (optional)" },
|
|
1510
|
+
limit: { type: "number", description: "Number of results (default 10, max 25)" },
|
|
1511
|
+
},
|
|
1512
|
+
required: ["query"],
|
|
1513
|
+
},
|
|
1514
|
+
async execute(_toolCallId, params) {
|
|
1515
|
+
if (!moltbook) {
|
|
1516
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "MoltBook not configured." }, null, 2) }], isError: true };
|
|
1517
|
+
}
|
|
1518
|
+
const limit = Math.min(params.limit ?? 10, 25);
|
|
1519
|
+
const result = await moltbook.search(params.query, params.submolt, limit);
|
|
1520
|
+
const interaction = {
|
|
1521
|
+
id: generateSocialId(),
|
|
1522
|
+
platform: 'moltbook',
|
|
1523
|
+
action: 'search',
|
|
1524
|
+
contentPreview: `Search: "${params.query}" in ${params.submolt ?? 'all'}`,
|
|
1525
|
+
timestamp: new Date().toISOString(),
|
|
1526
|
+
success: result.ok,
|
|
1527
|
+
errorMessage: result.error,
|
|
1528
|
+
};
|
|
1529
|
+
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1530
|
+
return {
|
|
1531
|
+
content: [{ type: "text", text: JSON.stringify(result.ok ? result.data : { error: result.error }, null, 2) }],
|
|
1532
|
+
...(result.ok ? {} : { isError: true }),
|
|
1533
|
+
};
|
|
1534
|
+
},
|
|
1535
|
+
});
|
|
1536
|
+
api.registerTool({
|
|
1537
|
+
name: "nole_x_post",
|
|
1538
|
+
description: "Post a tweet from Nole's X/Twitter account. Max 280 characters. " +
|
|
1539
|
+
"Use for AI governance thought leadership. Voice: confident, data-driven, not salesy.",
|
|
1540
|
+
parameters: {
|
|
1541
|
+
type: "object",
|
|
1542
|
+
properties: {
|
|
1543
|
+
text: {
|
|
1544
|
+
type: "string",
|
|
1545
|
+
description: "Tweet text (max 280 characters). Include hashtags: #AIGovernance #AITrust",
|
|
1546
|
+
},
|
|
1547
|
+
},
|
|
1548
|
+
required: ["text"],
|
|
1549
|
+
},
|
|
1550
|
+
async execute(_toolCallId, params) {
|
|
1551
|
+
const validated = await validateToolParams("nole_x_post", _toolCallId, params);
|
|
1552
|
+
if (!validated.ok)
|
|
1553
|
+
return validationErrorResponse(validated.message);
|
|
1554
|
+
const safeParams = validated.params;
|
|
1555
|
+
if (!xClient) {
|
|
1556
|
+
return {
|
|
1557
|
+
content: [{ type: "text", text: JSON.stringify({ error: "X/Twitter not configured. Set xApiKey, xApiSecret, xAccessToken, xAccessTokenSecret.", action: "Ask Greg to create an X Developer App and provide the 4 credentials." }, null, 2) }],
|
|
1558
|
+
isError: true,
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
const governed = await governedSocialPost({
|
|
1562
|
+
platform: 'x-twitter',
|
|
1563
|
+
action: 'post',
|
|
1564
|
+
description: `X/Twitter tweet: "${safeParams.text.slice(0, 100)}${safeParams.text.length > 100 ? '...' : ''}"`,
|
|
1565
|
+
content: safeParams.text,
|
|
1566
|
+
executeFn: () => xClient.postTweet(safeParams.text),
|
|
1567
|
+
});
|
|
1568
|
+
if (governed.mode === 'supervised' && governed.outcome === 'pending_review') {
|
|
1569
|
+
return { content: [{ type: "text", text: JSON.stringify({ queued: true, proposalId: governed.proposalId, mode: "supervised", note: "Tweet queued for Commander approval." }, null, 2) }] };
|
|
1570
|
+
}
|
|
1571
|
+
if (governed.mode === 'supervised' && governed.outcome === 'rejected') {
|
|
1572
|
+
return { content: [{ type: "text", text: JSON.stringify({ rejected: true, proposalId: governed.proposalId, reason: governed.reason }, null, 2) }], isError: true };
|
|
1573
|
+
}
|
|
1574
|
+
const result = governed.result;
|
|
1575
|
+
const interaction = {
|
|
1576
|
+
id: generateSocialId(),
|
|
1577
|
+
platform: 'x-twitter',
|
|
1578
|
+
action: 'post',
|
|
1579
|
+
contentPreview: params.text.slice(0, 100),
|
|
1580
|
+
timestamp: new Date().toISOString(),
|
|
1581
|
+
success: result.ok,
|
|
1582
|
+
errorMessage: result.error,
|
|
1583
|
+
};
|
|
1584
|
+
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1585
|
+
return {
|
|
1586
|
+
content: [{ type: "text", text: JSON.stringify(result.ok
|
|
1587
|
+
? { posted: true, tweetId: result.data?.data?.id, text: safeParams.text, mode: governed.mode }
|
|
1588
|
+
: { posted: false, error: result.error, rateLimited: result.rateLimited ?? false }, null, 2) }],
|
|
1589
|
+
...(result.ok ? {} : { isError: true }),
|
|
1590
|
+
};
|
|
1591
|
+
},
|
|
1592
|
+
});
|
|
1593
|
+
api.registerTool({
|
|
1594
|
+
name: "nole_x_thread",
|
|
1595
|
+
description: "Post a thread of tweets from Nole's X/Twitter account. Each tweet max 280 chars. " +
|
|
1596
|
+
"Use for longer AI governance content that doesn't fit in a single tweet.",
|
|
1597
|
+
parameters: {
|
|
1598
|
+
type: "object",
|
|
1599
|
+
properties: {
|
|
1600
|
+
tweets: {
|
|
1601
|
+
type: "array",
|
|
1602
|
+
items: { type: "string" },
|
|
1603
|
+
description: "Array of tweet texts, posted in order as a thread. Each max 280 chars.",
|
|
1604
|
+
},
|
|
1605
|
+
},
|
|
1606
|
+
required: ["tweets"],
|
|
1607
|
+
},
|
|
1608
|
+
async execute(_toolCallId, params) {
|
|
1609
|
+
const validated = await validateToolParams("nole_x_thread", _toolCallId, params);
|
|
1610
|
+
if (!validated.ok)
|
|
1611
|
+
return validationErrorResponse(validated.message);
|
|
1612
|
+
const safeParams = validated.params;
|
|
1613
|
+
if (!xClient) {
|
|
1614
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "X/Twitter not configured." }, null, 2) }], isError: true };
|
|
1615
|
+
}
|
|
1616
|
+
const governed = await governedSocialPost({
|
|
1617
|
+
platform: 'x-twitter',
|
|
1618
|
+
action: 'thread',
|
|
1619
|
+
description: `X/Twitter thread (${safeParams.tweets.length} tweets): "${safeParams.tweets[0]?.slice(0, 60) ?? ''}..."`,
|
|
1620
|
+
content: safeParams.tweets.join('\n'),
|
|
1621
|
+
executeFn: () => xClient.postThread(safeParams.tweets),
|
|
1622
|
+
});
|
|
1623
|
+
if (governed.mode === 'supervised' && governed.outcome === 'pending_review') {
|
|
1624
|
+
return { content: [{ type: "text", text: JSON.stringify({ queued: true, proposalId: governed.proposalId, mode: "supervised", tweetCount: safeParams.tweets.length, note: "Thread queued for Commander approval." }, null, 2) }] };
|
|
1625
|
+
}
|
|
1626
|
+
if (governed.mode === 'supervised' && governed.outcome === 'rejected') {
|
|
1627
|
+
return { content: [{ type: "text", text: JSON.stringify({ rejected: true, proposalId: governed.proposalId, reason: governed.reason }, null, 2) }], isError: true };
|
|
1628
|
+
}
|
|
1629
|
+
const result = governed.result;
|
|
1630
|
+
const interaction = {
|
|
1631
|
+
id: generateSocialId(),
|
|
1632
|
+
platform: 'x-twitter',
|
|
1633
|
+
action: 'post',
|
|
1634
|
+
contentPreview: `Thread (${params.tweets.length} tweets): ${params.tweets[0]?.slice(0, 60) ?? ''}...`,
|
|
1635
|
+
timestamp: new Date().toISOString(),
|
|
1636
|
+
success: result.ok,
|
|
1637
|
+
errorMessage: result.error,
|
|
1638
|
+
};
|
|
1639
|
+
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1640
|
+
return {
|
|
1641
|
+
content: [{ type: "text", text: JSON.stringify(result.ok
|
|
1642
|
+
? { posted: true, tweetCount: result.data?.tweets?.length, threadUrl: result.data?.threadUrl, mode: governed.mode }
|
|
1643
|
+
: { posted: false, error: result.error }, null, 2) }],
|
|
1644
|
+
...(result.ok ? {} : { isError: true }),
|
|
1645
|
+
};
|
|
1646
|
+
},
|
|
1647
|
+
});
|
|
1648
|
+
api.registerTool({
|
|
1649
|
+
name: "nole_linkedin_draft",
|
|
1650
|
+
description: "Generate a LinkedIn post draft for Greg to review and publish manually. " +
|
|
1651
|
+
"LinkedIn has no API access — Nole drafts, Greg publishes. " +
|
|
1652
|
+
"Drafts saved to .nole-data/social/linkedin-drafts/.",
|
|
1653
|
+
parameters: {
|
|
1654
|
+
type: "object",
|
|
1655
|
+
properties: {
|
|
1656
|
+
content: {
|
|
1657
|
+
type: "string",
|
|
1658
|
+
description: "LinkedIn post text. Professional tone, B2B-focused.",
|
|
1659
|
+
},
|
|
1660
|
+
topic: {
|
|
1661
|
+
type: "string",
|
|
1662
|
+
description: "Topic category (e.g., 'AI Governance', 'Trust Architecture', 'Agent Economics')",
|
|
1663
|
+
},
|
|
1664
|
+
hashtags: {
|
|
1665
|
+
type: "array",
|
|
1666
|
+
items: { type: "string" },
|
|
1667
|
+
description: "LinkedIn hashtags (e.g., ['#AIGovernance', '#ResponsibleAI'])",
|
|
1668
|
+
},
|
|
1669
|
+
},
|
|
1670
|
+
required: ["content", "topic"],
|
|
1671
|
+
},
|
|
1672
|
+
async execute(_toolCallId, params) {
|
|
1673
|
+
const result = await linkedInDrafter.createDraft(params.content, params.topic, params.hashtags ?? []);
|
|
1674
|
+
if (result.ok && result.data) {
|
|
1675
|
+
const interaction = {
|
|
1676
|
+
id: generateSocialId(),
|
|
1677
|
+
platform: 'linkedin',
|
|
1678
|
+
action: 'draft',
|
|
1679
|
+
contentPreview: params.content.slice(0, 100),
|
|
1680
|
+
timestamp: new Date().toISOString(),
|
|
1681
|
+
success: true,
|
|
1682
|
+
metadata: { topic: params.topic },
|
|
1683
|
+
};
|
|
1684
|
+
store.saveSocialInteraction(interaction).catch(() => { });
|
|
1685
|
+
}
|
|
1686
|
+
return {
|
|
1687
|
+
content: [{ type: "text", text: JSON.stringify(result.ok
|
|
1688
|
+
? { drafted: true, draftId: result.data?.id, topic: params.topic, note: "Draft saved. Greg should review and publish manually on LinkedIn." }
|
|
1689
|
+
: { drafted: false, error: result.error }, null, 2) }],
|
|
1690
|
+
...(result.ok ? {} : { isError: true }),
|
|
1691
|
+
};
|
|
1692
|
+
},
|
|
1693
|
+
});
|
|
1694
|
+
api.registerTool({
|
|
1695
|
+
name: "nole_social_status",
|
|
1696
|
+
description: "Show social media connectivity status: which platforms are configured, " +
|
|
1697
|
+
"rate limiter state (remaining tokens per bucket), recent interaction counts, " +
|
|
1698
|
+
"and posting mode (supervised/autonomous).",
|
|
1699
|
+
parameters: {
|
|
1700
|
+
type: "object",
|
|
1701
|
+
properties: {},
|
|
1702
|
+
},
|
|
1703
|
+
async execute(_toolCallId, _params) {
|
|
1704
|
+
const moltbookStatus = moltbook
|
|
1705
|
+
? {
|
|
1706
|
+
configured: true,
|
|
1707
|
+
rateLimits: {
|
|
1708
|
+
general: moltbookLimiter.getStatus('moltbook-general'),
|
|
1709
|
+
post: moltbookLimiter.getStatus('moltbook-post'),
|
|
1710
|
+
comment: moltbookLimiter.getStatus('moltbook-comment'),
|
|
1711
|
+
},
|
|
1712
|
+
}
|
|
1713
|
+
: { configured: false, reason: 'moltbookApiKey not set' };
|
|
1714
|
+
const xStatus = xClient
|
|
1715
|
+
? {
|
|
1716
|
+
configured: true,
|
|
1717
|
+
handle: config.xHandle,
|
|
1718
|
+
rateLimits: {
|
|
1719
|
+
general: xLimiter.getStatus('x-general'),
|
|
1720
|
+
post: xLimiter.getStatus('x-post'),
|
|
1721
|
+
},
|
|
1722
|
+
}
|
|
1723
|
+
: { configured: false, reason: 'xApiKey/xApiSecret/xAccessToken/xAccessTokenSecret not set' };
|
|
1724
|
+
const linkedInStatus = { configured: true, mode: 'draft-only (Greg publishes manually)' };
|
|
1725
|
+
let recentInteractions = {};
|
|
1726
|
+
try {
|
|
1727
|
+
const all = await store.listSocialInteractions(undefined, 200);
|
|
1728
|
+
for (const i of all) {
|
|
1729
|
+
const key = `${i.platform}:${i.action}`;
|
|
1730
|
+
recentInteractions[key] = (recentInteractions[key] ?? 0) + 1;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
catch {
|
|
1734
|
+
recentInteractions = {};
|
|
1735
|
+
}
|
|
1736
|
+
const recentFailures = await store
|
|
1737
|
+
.listSocialInteractions(undefined, 200)
|
|
1738
|
+
.then((all) => all.filter((i) => !i.success).slice(0, 10))
|
|
1739
|
+
.catch(() => []);
|
|
1740
|
+
return {
|
|
1741
|
+
content: [{
|
|
1742
|
+
type: "text",
|
|
1743
|
+
text: JSON.stringify({
|
|
1744
|
+
postingMode: config.socialPostingMode,
|
|
1745
|
+
platforms: { moltbook: moltbookStatus, x: xStatus, linkedin: linkedInStatus },
|
|
1746
|
+
interactionCounts: recentInteractions,
|
|
1747
|
+
recentFailures: recentFailures.map((f) => ({
|
|
1748
|
+
platform: f.platform,
|
|
1749
|
+
action: f.action,
|
|
1750
|
+
error: f.errorMessage,
|
|
1751
|
+
timestamp: f.timestamp,
|
|
1752
|
+
})),
|
|
1753
|
+
}, null, 2),
|
|
1754
|
+
}],
|
|
1755
|
+
};
|
|
1756
|
+
},
|
|
1757
|
+
});
|
|
1148
1758
|
// --- Command (matches Grillo/NOAH pattern: sync handler, ctx.args) ---
|
|
1149
1759
|
api.registerCommand({
|
|
1150
1760
|
name: "nole",
|
|
@@ -1175,6 +1785,15 @@ export default function register(api) {
|
|
|
1175
1785
|
"- `nole_intel` — Intelligence status\n" +
|
|
1176
1786
|
"- `nole_fleet_overview` — Commander fleet briefing\n" +
|
|
1177
1787
|
"- `nole_setup` — Configuration check\n\n" +
|
|
1788
|
+
"**Social Media:**\n" +
|
|
1789
|
+
"- `nole_moltbook_post` — Post to a MoltBook submolt\n" +
|
|
1790
|
+
"- `nole_moltbook_comment` — Comment on a MoltBook post\n" +
|
|
1791
|
+
"- `nole_moltbook_feed` — Browse MoltBook feed for discovery\n" +
|
|
1792
|
+
"- `nole_moltbook_search` — Search MoltBook for governance topics\n" +
|
|
1793
|
+
"- `nole_x_post` — Post a tweet (280 char max)\n" +
|
|
1794
|
+
"- `nole_x_thread` — Post a tweet thread\n" +
|
|
1795
|
+
"- `nole_linkedin_draft` — Draft LinkedIn post for Greg to publish\n" +
|
|
1796
|
+
"- `nole_social_status` — Show social connectivity, rate limits, posting mode\n\n" +
|
|
1178
1797
|
`**Config:** Model: ${config.inferenceModel}, Commander: ${config.commanderId}`,
|
|
1179
1798
|
};
|
|
1180
1799
|
}
|
|
@@ -1197,19 +1816,66 @@ export default function register(api) {
|
|
|
1197
1816
|
};
|
|
1198
1817
|
},
|
|
1199
1818
|
});
|
|
1200
|
-
// ── Fleet Bus Integration (Phase
|
|
1819
|
+
// ── Fleet Bus Integration (Phase 2 — full onboarding) ────────
|
|
1201
1820
|
const fleetBusMod = "@aiassesstech/fleet-bus";
|
|
1202
1821
|
import(fleetBusMod)
|
|
1203
|
-
.then(({ FleetBus, NOLE_CARD }) => {
|
|
1204
|
-
const bus = FleetBus.create(api, {
|
|
1822
|
+
.then(async ({ FleetBus, NOLE_CARD, createNoleFleetTools, createTransport, fleetReceive }) => {
|
|
1823
|
+
const bus = FleetBus.create(api, {
|
|
1824
|
+
agentId: "nole",
|
|
1825
|
+
role: "operator",
|
|
1826
|
+
card: NOLE_CARD,
|
|
1827
|
+
shield: fleetShieldMiddleware
|
|
1828
|
+
? { enabled: true, scanInbound: fleetShieldMiddleware }
|
|
1829
|
+
: undefined,
|
|
1830
|
+
});
|
|
1205
1831
|
bus.start();
|
|
1832
|
+
const transport = await createTransport();
|
|
1833
|
+
if (transport) {
|
|
1834
|
+
const tools = createNoleFleetTools(bus, {
|
|
1835
|
+
...transport,
|
|
1836
|
+
outputSanitizer: outputSanitizer
|
|
1837
|
+
? {
|
|
1838
|
+
enabled: true,
|
|
1839
|
+
sanitizeOutbound: (message) => {
|
|
1840
|
+
const sanitized = outputSanitizer.sanitizeObject(message, config.agentId);
|
|
1841
|
+
return {
|
|
1842
|
+
message: sanitized.value,
|
|
1843
|
+
redacted: sanitized.redacted,
|
|
1844
|
+
redactionCount: sanitized.redactionCount,
|
|
1845
|
+
};
|
|
1846
|
+
},
|
|
1847
|
+
}
|
|
1848
|
+
: undefined,
|
|
1849
|
+
shield: fleetShieldMiddleware
|
|
1850
|
+
? { enabled: true, scanOutbound: fleetShieldMiddleware }
|
|
1851
|
+
: undefined,
|
|
1852
|
+
});
|
|
1853
|
+
for (const tool of tools) {
|
|
1854
|
+
api.registerTool(tool);
|
|
1855
|
+
}
|
|
1856
|
+
console.log(`[nole] Fleet tools registered: ${tools.map((t) => t.name).join(", ")}`);
|
|
1857
|
+
fleetReceive(bus, {
|
|
1858
|
+
methods: [
|
|
1859
|
+
"task/assign", "proposal/approved", "proposal/rejected",
|
|
1860
|
+
"veto/issue", "fleet/ping", "fleet/pong", "fleet/broadcast",
|
|
1861
|
+
],
|
|
1862
|
+
handler: async (msg) => {
|
|
1863
|
+
console.log(`[nole] Fleet message received: ${msg.method} from ${msg.from}`);
|
|
1864
|
+
},
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
else {
|
|
1868
|
+
console.log("[nole] Fleet bus: observer mode (gateway transport unavailable)");
|
|
1869
|
+
}
|
|
1206
1870
|
})
|
|
1207
1871
|
.catch(() => {
|
|
1208
1872
|
console.warn("[nole] Fleet bus unavailable — operating standalone");
|
|
1209
1873
|
});
|
|
1210
1874
|
console.log(`[nole] Plugin registered: nole_status, nole_propose, nole_assess, nole_wallet, nole_wallet_fund, ` +
|
|
1211
1875
|
`nole_contract, nole_intel, nole_setup, nole_recruit, nole_alliance_status, nole_generate_referral, ` +
|
|
1212
|
-
`nole_directory, nole_review_pending, nole_approve, nole_veto, nole_fleet_overview
|
|
1876
|
+
`nole_directory, nole_review_pending, nole_approve, nole_veto, nole_fleet_overview, ` +
|
|
1877
|
+
`nole_moltbook_post, nole_moltbook_comment, nole_moltbook_feed, nole_moltbook_search, ` +
|
|
1878
|
+
`nole_x_post, nole_x_thread, nole_linkedin_draft, nole_social_status tools; /nole command`);
|
|
1213
1879
|
console.log(`[nole] AIAssessTech API: ${platform.isConfigured ? 'configured' : 'not configured — set agentApiKey + platformWalletAddress'} (${config.platformApiUrl})`);
|
|
1214
1880
|
}
|
|
1215
1881
|
//# sourceMappingURL=plugin.js.map
|