@aiassesstech/nole 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.js CHANGED
@@ -25,6 +25,7 @@ import { extractMoltBookCandidates } from './pipeline/discovery.js';
25
25
  import { extractDirectoryCandidates, prioritizeDirectoryOutreach } from './pipeline/directory-outreach.js';
26
26
  import { generateDailyContentPlan } from './pipeline/content-calendar.js';
27
27
  import { getAllDiscoveryKeywords } from './pipeline/types.js';
28
+ import { FreedomRunManager } from './freedom-run/index.js';
28
29
  /**
29
30
  * OpenClaw plugin entry point — export default function register(api)
30
31
  *
@@ -103,6 +104,17 @@ export default function register(api) {
103
104
  serviceRegistry.register(moltbookResilient);
104
105
  serviceRegistry.register(xResilient);
105
106
  const postQueue = new PostQueue({ dataDir, maxQueueSize: 100, maxRetries: 5, maxAgeMs: 24 * 60 * 60 * 1000 });
107
+ // ── Freedom Run Manager (BB Amendment A1) ──
108
+ const freedomRunDeps = {
109
+ getPipelineSnapshot: () => pipeline.getSummary(),
110
+ getAuditTail: (limit) => store.getAuditChain(limit),
111
+ getWalletBalance: () => wallet.getBalance(),
112
+ getServiceHealth: () => serviceRegistry.getSnapshot(),
113
+ };
114
+ const freedomRun = new FreedomRunManager(dataDir, freedomRunDeps);
115
+ freedomRun.initialize().catch((err) => {
116
+ console.warn(`[nole] Freedom run init warning: ${err.message}`);
117
+ });
106
118
  // Wallet adapter (auto-detects Coinbase credentials from ~/.nole/credentials)
107
119
  let wallet = new MockWalletAdapter(config.seedCapitalUsd);
108
120
  // Platform API client — initialized after credentials load (see below)
@@ -2631,11 +2643,17 @@ export default function register(api) {
2631
2643
  });
2632
2644
  api.registerTool({
2633
2645
  name: "nole_daily_brief",
2634
- description: "Generate Nole's daily operations brief: pipeline summary, wallet health, " +
2635
- "commission sweep status, social activity metrics, and suggested actions for the day.",
2646
+ description: "Nole's daily operations brief with windowed scheduling for Freedom Run. " +
2647
+ "Windows: morning (scan/qualify), midday (deliver/post), afternoon (nurture/commission), " +
2648
+ "evening (report/metrics). Use 'auto' for full one-shot brief.",
2636
2649
  parameters: {
2637
2650
  type: "object",
2638
2651
  properties: {
2652
+ window: {
2653
+ type: "string",
2654
+ enum: ["morning", "midday", "afternoon", "evening", "auto"],
2655
+ description: "Which daily cycle window to execute. Defaults to 'auto' (full brief).",
2656
+ },
2639
2657
  includeProspects: {
2640
2658
  type: "boolean",
2641
2659
  description: "Include detailed prospect list in brief (default: false)",
@@ -2646,8 +2664,257 @@ export default function register(api) {
2646
2664
  const dbv = await validateToolParams("nole_daily_brief", _toolCallId, params);
2647
2665
  if (!dbv.ok)
2648
2666
  return validationErrorResponse(dbv.message);
2667
+ const window = params.window || 'auto';
2668
+ // Freedom Run gate — if not 'auto' mode, require running state (BB Amendment A1)
2669
+ if (window !== 'auto') {
2670
+ const frStatus = freedomRun.getStatus();
2671
+ if (!freedomRun.canOperate()) {
2672
+ return {
2673
+ content: [{
2674
+ type: "text",
2675
+ text: JSON.stringify({
2676
+ freedomRunGate: 'BLOCKED',
2677
+ status: frStatus.status,
2678
+ reason: frStatus.reason,
2679
+ triggeredBy: frStatus.triggeredBy,
2680
+ message: frStatus.status === 'terminated'
2681
+ ? 'Freedom Run has been terminated. A new spec is required to restart.'
2682
+ : 'Freedom Run is paused. Jessie or Greg must resume before daily operations continue.',
2683
+ }, null, 2),
2684
+ }],
2685
+ };
2686
+ }
2687
+ }
2649
2688
  try {
2689
+ const today = new Date().toISOString().slice(0, 10);
2650
2690
  const pipelineSummary = await pipeline.getSummary();
2691
+ // ── Morning window: scan, qualify, conversion detection, veto rate check ──
2692
+ if (window === 'morning' || window === 'auto') {
2693
+ const walletBalance = await wallet.getBalance().catch(() => null);
2694
+ // Conversion detection — poll for pitched prospects that subscribed (§11.7)
2695
+ const pitched = await pipeline.listByStage('pitched');
2696
+ const conversions = [];
2697
+ for (const prospect of pitched) {
2698
+ if (!prospect.walletAddress)
2699
+ continue;
2700
+ try {
2701
+ const sub = await platform.getSubscription(prospect.walletAddress);
2702
+ if (sub.ok && sub.data) {
2703
+ await pipeline.updateStage(prospect.id, 'converted', {
2704
+ convertedAt: new Date().toISOString(),
2705
+ subscriptionTier: sub.data.tier,
2706
+ });
2707
+ conversions.push({ agentName: prospect.agentName, tier: sub.data.tier ?? 'unknown' });
2708
+ // Send onboarding message
2709
+ const convertedProspect = await pipeline.get(prospect.id) ?? prospect;
2710
+ const onboardMsg = generateOnboardingMessage(convertedProspect, 'https://www.aiassesstech.com/assess', 'https://www.aiassesstech.com/directory');
2711
+ if (prospect.discoveryChannel === 'moltbook' && moltbook) {
2712
+ await moltbook.createPost('aiassesstech', onboardMsg.subject, onboardMsg.body).catch(() => { });
2713
+ }
2714
+ // Submit first assessment trigger through governance (BB Amendment A9)
2715
+ await governance.propose({
2716
+ actionType: 'self_assessment',
2717
+ description: `Trigger first Grillo assessment for new subscriber ${prospect.agentName} (${prospect.walletAddress})`,
2718
+ riskLevel: 'low',
2719
+ metadata: { targetAgent: prospect.agentName, walletAddress: prospect.walletAddress },
2720
+ }).catch((err) => {
2721
+ console.warn(`[nole] First assessment governance proposal failed: ${err.message}`);
2722
+ });
2723
+ }
2724
+ }
2725
+ catch { /* polling failure is non-fatal */ }
2726
+ }
2727
+ // Veto rate check for Freedom Run kill switch (BB Amendment A2, §11.8)
2728
+ let vetoRateWarning = null;
2729
+ try {
2730
+ const govStats = await governance.getStats();
2731
+ const recentResults = await store.listGovernanceResults(10);
2732
+ const recentVetoes = recentResults.filter(r => r.finalOutcome === 'vetoed').length;
2733
+ if (recentResults.length >= 5) {
2734
+ const last5 = recentResults.slice(0, 5);
2735
+ const last5Vetoes = last5.filter(r => r.finalOutcome === 'vetoed').length;
2736
+ if (last5Vetoes / 5 >= 0.6) {
2737
+ vetoRateWarning = `CRITICAL: 60% veto rate over last 5 proposals — auto-terminating Freedom Run`;
2738
+ await freedomRun.terminate(`Grillo veto rate ${(last5Vetoes / 5 * 100).toFixed(0)}% over 5-pitch window exceeds 60% threshold`, 'mighty-mark');
2739
+ }
2740
+ }
2741
+ if (!vetoRateWarning && recentResults.length >= 10) {
2742
+ const last10 = recentResults.slice(0, 10);
2743
+ const last10Vetoes = last10.filter(r => r.finalOutcome === 'vetoed').length;
2744
+ if (last10Vetoes / 10 >= 0.4) {
2745
+ vetoRateWarning = `WARNING: 40% veto rate over last 10 proposals — auto-pausing Freedom Run`;
2746
+ await freedomRun.pause(`Grillo veto rate ${(last10Vetoes / 10 * 100).toFixed(0)}% over 10-pitch window exceeds 40% threshold`, 'mighty-mark');
2747
+ }
2748
+ }
2749
+ }
2750
+ catch { /* veto rate check is best-effort */ }
2751
+ const suggestedActions = [];
2752
+ if (pipelineSummary.byStage.discovered > 0) {
2753
+ suggestedActions.push(`Qualify ${pipelineSummary.byStage.discovered} discovered prospect(s) — run nole_pipeline action=qualify`);
2754
+ }
2755
+ if (pipelineSummary.byStage.qualified > 0) {
2756
+ suggestedActions.push(`Pitch ${pipelineSummary.byStage.qualified} qualified prospect(s) — run nole_pipeline action=pitch`);
2757
+ }
2758
+ if (pipelineSummary.todayPitched < config.maxDailyPitches) {
2759
+ suggestedActions.push(`${config.maxDailyPitches - pipelineSummary.todayPitched} pitch slots remaining today`);
2760
+ }
2761
+ if (moltbook) {
2762
+ suggestedActions.push('Scan MoltBook for new prospects — run nole_pipeline action=scan');
2763
+ }
2764
+ if (window === 'morning') {
2765
+ return {
2766
+ content: [{
2767
+ type: "text",
2768
+ text: JSON.stringify({
2769
+ window: 'morning',
2770
+ date: today,
2771
+ freedomRun: freedomRun.getStatus().status,
2772
+ pipeline: pipelineSummary,
2773
+ wallet: walletBalance ? { balance: walletBalance, network: wallet.network } : { status: 'unavailable' },
2774
+ conversions: conversions.length > 0 ? conversions : 'none',
2775
+ vetoRateWarning,
2776
+ suggestedActions,
2777
+ }, null, 2),
2778
+ }],
2779
+ };
2780
+ }
2781
+ }
2782
+ // ── Midday window: deliver approved pitches, post content ──
2783
+ if (window === 'midday') {
2784
+ let socialActivityToday = {};
2785
+ try {
2786
+ const interactions = await store.listSocialInteractions(undefined, 200);
2787
+ for (const i of interactions) {
2788
+ if (i.timestamp.startsWith(today)) {
2789
+ const key = `${i.platform}:${i.action}`;
2790
+ socialActivityToday[key] = (socialActivityToday[key] ?? 0) + 1;
2791
+ }
2792
+ }
2793
+ }
2794
+ catch {
2795
+ socialActivityToday = {};
2796
+ }
2797
+ const todayPlan = generateDailyContentPlan(new Date(), { maxDailyXPosts: config.maxDailyXPosts, moltbookScanFrequency: config.moltbookScanFrequency }, { activeSubscribers: pipelineSummary.byStage.onboarded + pipelineSummary.byStage.converted, weeklyConversions: pipelineSummary.todayConverted });
2798
+ return {
2799
+ content: [{
2800
+ type: "text",
2801
+ text: JSON.stringify({
2802
+ window: 'midday',
2803
+ date: today,
2804
+ freedomRun: freedomRun.getStatus().status,
2805
+ socialActivity: socialActivityToday,
2806
+ contentPlan: todayPlan,
2807
+ pitchSlots: Math.max(0, config.maxDailyPitches - pipelineSummary.todayPitched),
2808
+ suggestedActions: [
2809
+ 'Deliver any approved pitches awaiting send',
2810
+ 'Post thought leadership content per content plan',
2811
+ 'Respond to inbound messages',
2812
+ ],
2813
+ }, null, 2),
2814
+ }],
2815
+ };
2816
+ }
2817
+ // ── Afternoon window: nurture, commission sweep, content review ──
2818
+ if (window === 'afternoon') {
2819
+ const allProspects = await pipeline.listAll(200);
2820
+ const atRiskSubscribers = findAtRiskSubscribers(allProspects);
2821
+ let commissionStatus = null;
2822
+ try {
2823
+ const cs = await platform.getCommissionStatus();
2824
+ if (cs.ok)
2825
+ commissionStatus = cs.data;
2826
+ }
2827
+ catch { /* best-effort */ }
2828
+ // Commission sweep via governance — NOT direct claim (BB Amendment A4/A8)
2829
+ let commissionSweepResult = 'No eligible claims';
2830
+ let eligibleEscrows = 0;
2831
+ if (commissionStatus && typeof commissionStatus === 'object' && 'pendingClaims' in commissionStatus) {
2832
+ eligibleEscrows = commissionStatus.pendingClaims ?? 0;
2833
+ }
2834
+ if (eligibleEscrows > 0) {
2835
+ try {
2836
+ const escrows = commissionStatus?.eligibleEscrows ?? [{ escrowId: 'batch', amount: 0, subscriber: 'unknown' }];
2837
+ let proposalsSubmitted = 0;
2838
+ for (const escrow of escrows) {
2839
+ await governance.propose({
2840
+ actionType: 'financial',
2841
+ description: `Claim commission escrow #${escrow.escrowId} — $${escrow.amount} USDC from subscriber ${escrow.subscriber}`,
2842
+ riskLevel: 'critical',
2843
+ metadata: { escrowId: escrow.escrowId, amount: escrow.amount, subscriber: escrow.subscriber },
2844
+ });
2845
+ proposalsSubmitted++;
2846
+ }
2847
+ commissionSweepResult = `${proposalsSubmitted} commission claim proposal(s) submitted for Jessie's approval (risk: critical, NO auto-approval)`;
2848
+ }
2849
+ catch (err) {
2850
+ commissionSweepResult = `Commission sweep error: ${err instanceof Error ? err.message : String(err)}`;
2851
+ }
2852
+ }
2853
+ return {
2854
+ content: [{
2855
+ type: "text",
2856
+ text: JSON.stringify({
2857
+ window: 'afternoon',
2858
+ date: today,
2859
+ freedomRun: freedomRun.getStatus().status,
2860
+ nurture: {
2861
+ atRiskCount: atRiskSubscribers.length,
2862
+ subscribers: atRiskSubscribers.length > 0 ? atRiskSubscribers : undefined,
2863
+ },
2864
+ commission: {
2865
+ status: commissionStatus,
2866
+ sweepResult: commissionSweepResult,
2867
+ note: 'All claims go through governance. Jessie must explicitly approve. No auto-approval timeout.',
2868
+ },
2869
+ suggestedActions: [
2870
+ ...atRiskSubscribers.map(r => `[${r.status.toUpperCase()}] ${r.suggestedAction}`),
2871
+ 'Review content performance metrics',
2872
+ ],
2873
+ }, null, 2),
2874
+ }],
2875
+ };
2876
+ }
2877
+ // ── Evening window: daily report, metrics, report to Jessie ──
2878
+ if (window === 'evening') {
2879
+ const walletBalance = await wallet.getBalance().catch(() => null);
2880
+ const allProspects = await pipeline.listAll(200);
2881
+ const govStats = await governance.getStats();
2882
+ let socialActivityToday = {};
2883
+ try {
2884
+ const interactions = await store.listSocialInteractions(undefined, 200);
2885
+ for (const i of interactions) {
2886
+ if (i.timestamp.startsWith(today)) {
2887
+ const key = `${i.platform}:${i.action}`;
2888
+ socialActivityToday[key] = (socialActivityToday[key] ?? 0) + 1;
2889
+ }
2890
+ }
2891
+ }
2892
+ catch {
2893
+ socialActivityToday = {};
2894
+ }
2895
+ return {
2896
+ content: [{
2897
+ type: "text",
2898
+ text: JSON.stringify({
2899
+ window: 'evening',
2900
+ date: today,
2901
+ freedomRun: freedomRun.getStatus(),
2902
+ dailyReport: {
2903
+ pipeline: pipelineSummary,
2904
+ wallet: walletBalance ? { balance: walletBalance, network: wallet.network } : { status: 'unavailable' },
2905
+ governance: govStats,
2906
+ socialActivity: socialActivityToday,
2907
+ activeProspects: allProspects.filter(p => !['disqualified', 'declined'].includes(p.stage)).length,
2908
+ },
2909
+ suggestedActions: [
2910
+ 'Submit daily report to Jessie (lands in her 17:30 CT afternoon review)',
2911
+ 'Update pipeline metrics for weekly tracking',
2912
+ ],
2913
+ }, null, 2),
2914
+ }],
2915
+ };
2916
+ }
2917
+ // ── Auto mode: full one-shot brief (backward compatible) ──
2651
2918
  const walletBalance = await wallet.getBalance().catch(() => null);
2652
2919
  let commissionStatus = null;
2653
2920
  try {
@@ -2656,28 +2923,12 @@ export default function register(api) {
2656
2923
  commissionStatus = cs.data;
2657
2924
  }
2658
2925
  catch { /* best-effort */ }
2659
- let pendingClaims = 0;
2660
- if (commissionStatus && typeof commissionStatus === 'object' && 'pendingClaims' in commissionStatus) {
2661
- pendingClaims = commissionStatus.pendingClaims ?? 0;
2662
- }
2663
- let commissionClaimResult = null;
2664
- if (pendingClaims > 0) {
2665
- try {
2666
- const claimed = await platform.claimCommission();
2667
- commissionClaimResult = claimed.ok
2668
- ? `Claimed ${pendingClaims} pending commission(s)`
2669
- : `Claim failed: ${claimed.error}`;
2670
- }
2671
- catch {
2672
- commissionClaimResult = 'Claim attempt failed';
2673
- }
2674
- }
2675
- const today = new Date().toISOString().slice(0, 10);
2926
+ const today2 = today;
2676
2927
  let socialActivityToday = {};
2677
2928
  try {
2678
2929
  const interactions = await store.listSocialInteractions(undefined, 200);
2679
2930
  for (const i of interactions) {
2680
- if (i.timestamp.startsWith(today)) {
2931
+ if (i.timestamp.startsWith(today2)) {
2681
2932
  const key = `${i.platform}:${i.action}`;
2682
2933
  socialActivityToday[key] = (socialActivityToday[key] ?? 0) + 1;
2683
2934
  }
@@ -2707,15 +2958,14 @@ export default function register(api) {
2707
2958
  }
2708
2959
  }
2709
2960
  const brief = {
2961
+ window: 'auto',
2710
2962
  date: today,
2963
+ freedomRun: freedomRun.getStatus().status,
2711
2964
  pipeline: pipelineSummary,
2712
2965
  wallet: walletBalance
2713
2966
  ? { balance: walletBalance, network: wallet.network }
2714
2967
  : { status: 'unavailable' },
2715
- commission: {
2716
- status: commissionStatus,
2717
- claimResult: commissionClaimResult,
2718
- },
2968
+ commission: { status: commissionStatus, note: 'Use nole_commission_sweep for governance-gated claims' },
2719
2969
  socialActivity: socialActivityToday,
2720
2970
  suggestedActions,
2721
2971
  nurture: {
@@ -2728,10 +2978,7 @@ export default function register(api) {
2728
2978
  socialPostingMode: config.socialPostingMode,
2729
2979
  },
2730
2980
  };
2731
- const todayPlan = generateDailyContentPlan(new Date(), { maxDailyXPosts: config.maxDailyXPosts, moltbookScanFrequency: config.moltbookScanFrequency }, {
2732
- activeSubscribers: pipelineSummary.byStage.onboarded + pipelineSummary.byStage.converted,
2733
- weeklyConversions: pipelineSummary.todayConverted,
2734
- });
2981
+ const todayPlan = generateDailyContentPlan(new Date(), { maxDailyXPosts: config.maxDailyXPosts, moltbookScanFrequency: config.moltbookScanFrequency }, { activeSubscribers: pipelineSummary.byStage.onboarded + pipelineSummary.byStage.converted, weeklyConversions: pipelineSummary.todayConverted });
2735
2982
  brief.contentPlan = todayPlan;
2736
2983
  if (params.includeProspects) {
2737
2984
  const active = await pipeline.listAll(50);
@@ -2745,6 +2992,81 @@ export default function register(api) {
2745
2992
  }
2746
2993
  },
2747
2994
  });
2995
+ // ── Commission Sweep Tool (BB Amendment A4/A8: governance-gated, no auto-approval) ──
2996
+ api.registerTool({
2997
+ name: "nole_commission_sweep",
2998
+ description: "Sweep eligible commission escrows and submit governance proposals for each claim. " +
2999
+ "Every claim requires Jessie's explicit approval (risk: critical, NO auto-approval timeout). " +
3000
+ "BB Amendment A8: Patent 8 separation of powers.",
3001
+ parameters: {
3002
+ type: "object",
3003
+ properties: {},
3004
+ },
3005
+ async execute(_toolCallId, _params) {
3006
+ if (!freedomRun.canOperate()) {
3007
+ const frStatus = freedomRun.getStatus();
3008
+ return {
3009
+ content: [{
3010
+ type: "text",
3011
+ text: JSON.stringify({
3012
+ tool: 'nole_commission_sweep',
3013
+ blocked: true,
3014
+ freedomRunStatus: frStatus.status,
3015
+ reason: frStatus.reason,
3016
+ }, null, 2),
3017
+ }],
3018
+ };
3019
+ }
3020
+ try {
3021
+ let commissionStatus = null;
3022
+ try {
3023
+ const cs = await platform.getCommissionStatus();
3024
+ if (cs.ok)
3025
+ commissionStatus = cs.data;
3026
+ }
3027
+ catch { /* best-effort */ }
3028
+ let eligibleEscrows = 0;
3029
+ if (commissionStatus && typeof commissionStatus === 'object' && 'pendingClaims' in commissionStatus) {
3030
+ eligibleEscrows = commissionStatus.pendingClaims ?? 0;
3031
+ }
3032
+ if (eligibleEscrows === 0) {
3033
+ return {
3034
+ content: [{
3035
+ type: "text",
3036
+ text: JSON.stringify({ result: 'No eligible commission escrows to claim', eligibleEscrows: 0 }, null, 2),
3037
+ }],
3038
+ };
3039
+ }
3040
+ const escrows = commissionStatus?.eligibleEscrows ?? [{ escrowId: 'batch', amount: 0, subscriber: 'unknown' }];
3041
+ const proposals = [];
3042
+ for (const escrow of escrows) {
3043
+ const result = await governance.propose({
3044
+ actionType: 'financial',
3045
+ description: `Claim commission escrow #${escrow.escrowId} — $${escrow.amount} USDC from subscriber ${escrow.subscriber}`,
3046
+ riskLevel: 'critical',
3047
+ metadata: { escrowId: escrow.escrowId, amount: escrow.amount, subscriber: escrow.subscriber },
3048
+ });
3049
+ proposals.push(result.proposalId);
3050
+ }
3051
+ return {
3052
+ content: [{
3053
+ type: "text",
3054
+ text: JSON.stringify({
3055
+ result: `${proposals.length} commission claim proposal(s) submitted`,
3056
+ proposals,
3057
+ riskTier: 'critical',
3058
+ autoApproval: 'NEVER — Jessie must explicitly approve every financial action (BB Amendment A8)',
3059
+ note: 'Escrows do not expire. Claims will wait for Jessie\'s batch review.',
3060
+ }, null, 2),
3061
+ }],
3062
+ };
3063
+ }
3064
+ catch (err) {
3065
+ const message = err instanceof Error ? err.message : String(err);
3066
+ return { content: [{ type: "text", text: `Commission sweep error: ${message}` }], isError: true };
3067
+ }
3068
+ },
3069
+ });
2748
3070
  // --- Command (matches Grillo/NOAH pattern: sync handler, ctx.args) ---
2749
3071
  api.registerCommand({
2750
3072
  name: "nole",
@@ -2777,7 +3099,8 @@ export default function register(api) {
2777
3099
  "- `nole_setup` — Configuration check\n\n" +
2778
3100
  "**Revenue Pipeline:**\n" +
2779
3101
  "- `nole_pipeline` — Manage prospect pipeline (discover, qualify, pitch, scan, onboard, promote, nurture, directory-scan, content-plan, list, summary)\n" +
2780
- "- `nole_daily_brief` — Daily operations brief with pipeline, wallet, commission, subscriber health, and content plan\n\n" +
3102
+ "- `nole_daily_brief` — Windowed daily brief (morning/midday/afternoon/evening/auto) with Freedom Run gate\n" +
3103
+ "- `nole_commission_sweep` — Governance-gated commission claims (requires Jessie approval)\n\n" +
2781
3104
  "**Social Media:**\n" +
2782
3105
  "- `nole_moltbook_post` — Post to a MoltBook submolt\n" +
2783
3106
  "- `nole_moltbook_comment` — Comment on a MoltBook post\n" +
@@ -2923,6 +3246,42 @@ export default function register(api) {
2923
3246
  }
2924
3247
  if (msg.method === 'cron/scheduled') {
2925
3248
  const { action, jobId } = msg.params ?? {};
3249
+ // Freedom Run daily windows (§11.4 — 4 cron entries aligned with Jessie's EA schedule)
3250
+ const cronWindowMap = {
3251
+ nole_morning_scan: 'morning',
3252
+ nole_midday_outreach: 'midday',
3253
+ nole_afternoon_nurture: 'afternoon',
3254
+ nole_evening_report: 'evening',
3255
+ };
3256
+ if (action in cronWindowMap) {
3257
+ const window = cronWindowMap[action];
3258
+ console.log(`[nole] Cron-triggered ${window} window (job: ${jobId})`);
3259
+ try {
3260
+ await transport.callGateway({
3261
+ method: 'agent',
3262
+ params: {
3263
+ agentId: 'nole',
3264
+ message: `SYSTEM: Freedom Run ${window} window — cron-triggered by Noah. Execute nole_daily_brief with window="${window}".`,
3265
+ deliver: true,
3266
+ label: `cron:${action}`,
3267
+ },
3268
+ });
3269
+ await fleetSend(bus, transport, {
3270
+ to: 'noah',
3271
+ method: 'cron/completed',
3272
+ params: { jobId, action, status: 'completed', timestamp: new Date().toISOString() },
3273
+ }).catch(() => { });
3274
+ }
3275
+ catch (err) {
3276
+ console.error(`[nole] Cron injection failed for ${action}:`, err instanceof Error ? err.message : String(err));
3277
+ await fleetSend(bus, transport, {
3278
+ to: 'noah',
3279
+ method: 'cron/failed',
3280
+ params: { jobId, action, error: err instanceof Error ? err.message : String(err), timestamp: new Date().toISOString() },
3281
+ }).catch(() => { });
3282
+ }
3283
+ return;
3284
+ }
2926
3285
  if (action === 'weekly_strategy_review') {
2927
3286
  console.log(`[nole] Cron-triggered weekly review (job: ${jobId})`);
2928
3287
  try {
@@ -2955,6 +3314,65 @@ export default function register(api) {
2955
3314
  }
2956
3315
  return;
2957
3316
  }
3317
+ // ── Pitch delivery on governance approval (§11.5) ──
3318
+ if (msg.method === 'proposal/approved') {
3319
+ const { actionType, metadata, description } = msg.params ?? {};
3320
+ if (actionType === 'recruitment' || actionType === 'social' || actionType === 'content') {
3321
+ console.log(`[nole] Pitch approved — delivering via social client`);
3322
+ if (!freedomRun.canOperate()) {
3323
+ console.warn('[nole] Pitch delivery skipped — Freedom Run not active');
3324
+ }
3325
+ else {
3326
+ try {
3327
+ const targetPlatform = metadata?.channel ?? metadata?.platform ?? 'moltbook';
3328
+ const pitchContent = metadata?.pitchContent ?? description ?? '';
3329
+ const prospectId = metadata?.prospectId;
3330
+ if (targetPlatform === 'moltbook' && moltbook) {
3331
+ await moltbook.createPost('aiassesstech', 'Nole Outreach', String(pitchContent));
3332
+ }
3333
+ else if (targetPlatform === 'x' && xClient) {
3334
+ await xClient.postTweet(String(pitchContent));
3335
+ }
3336
+ // fleet-bus pitches go via fleetSend in the original proposal handler
3337
+ if (prospectId) {
3338
+ await pipeline.updateStage(String(prospectId), 'pitched', {
3339
+ pitchDeliveredAt: new Date().toISOString(),
3340
+ pitchApproved: true,
3341
+ });
3342
+ }
3343
+ console.log(`[nole] Pitch delivered to ${targetPlatform}`);
3344
+ }
3345
+ catch (err) {
3346
+ const errMsg = err instanceof Error ? err.message : String(err);
3347
+ console.error(`[nole] Pitch delivery failed: ${errMsg}`);
3348
+ // Queue for retry via resilience layer
3349
+ if (metadata?.pitchContent) {
3350
+ postQueue.enqueue(metadata.platform ?? 'moltbook', String(metadata.pitchContent), metadata);
3351
+ }
3352
+ }
3353
+ }
3354
+ }
3355
+ // Commission claim on financial approval (§11.6 — BB Amendment A4/A8)
3356
+ if (actionType === 'financial') {
3357
+ const escrowId = metadata?.escrowId;
3358
+ console.log(`[nole] Financial action approved — claiming commission escrow ${escrowId}`);
3359
+ try {
3360
+ const result = await platform.claimCommission();
3361
+ if (result.ok) {
3362
+ console.log(`[nole] Commission claimed successfully (escrow: ${escrowId})`);
3363
+ }
3364
+ else {
3365
+ console.error(`[nole] Commission claim failed: ${result.error}`);
3366
+ }
3367
+ await governance.auditTrail.record('commission_claimed', `Commission escrow ${escrowId}: ${result.ok ? 'claimed' : `failed — ${result.error}`}`);
3368
+ }
3369
+ catch (err) {
3370
+ const errMsg = err instanceof Error ? err.message : String(err);
3371
+ console.error(`[nole] Commission claim error: ${errMsg}`);
3372
+ }
3373
+ }
3374
+ // Don't return — let the existing fleet-discovery logic below also run
3375
+ }
2958
3376
  if (msg.from && msg.from !== 'nole') {
2959
3377
  const existing = await pipeline.findByAgentName(msg.from);
2960
3378
  if (!existing) {
@@ -3036,7 +3454,7 @@ export default function register(api) {
3036
3454
  `nole_directory, nole_review_pending, nole_approve, nole_veto, nole_fleet_overview, ` +
3037
3455
  `nole_moltbook_post, nole_moltbook_comment, nole_moltbook_feed, nole_moltbook_search, ` +
3038
3456
  `nole_x_post, nole_x_thread, nole_linkedin_draft, nole_social_status, ` +
3039
- `nole_pipeline, nole_daily_brief tools; /nole command`);
3457
+ `nole_pipeline, nole_daily_brief, nole_commission_sweep tools; /nole command`);
3040
3458
  console.log(`[nole] AIAssessTech API: ${platform.isConfigured ? 'configured' : 'not configured — set agentApiKey + platformWalletAddress'} (${config.platformApiUrl})`);
3041
3459
  }
3042
3460
  //# sourceMappingURL=plugin.js.map