thumbgate 0.9.9
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/.claude-plugin/README.md +134 -0
- package/.claude-plugin/bundle/icon.png +0 -0
- package/.claude-plugin/bundle/icon.svg +18 -0
- package/.claude-plugin/bundle/server/index.js +24 -0
- package/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +21 -0
- package/.well-known/mcp/server-card.json +231 -0
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/adapters/README.md +9 -0
- package/adapters/amp/skills/rlhf-feedback/SKILL.md +22 -0
- package/adapters/chatgpt/INSTALL.md +83 -0
- package/adapters/chatgpt/openapi.yaml +1281 -0
- package/adapters/claude/.mcp.json +14 -0
- package/adapters/codex/config.toml +9 -0
- package/adapters/gemini/function-declarations.json +224 -0
- package/adapters/mcp/server-stdio.js +788 -0
- package/adapters/opencode/opencode.json +15 -0
- package/bin/cli.js +1483 -0
- package/bin/memory.sh +64 -0
- package/bin/obsidian-sync.sh +20 -0
- package/bin/postinstall.js +37 -0
- package/config/build-metadata.json +4 -0
- package/config/e2e-critical-flows.json +45 -0
- package/config/gate-templates.json +77 -0
- package/config/gates/claim-verification.json +29 -0
- package/config/gates/computer-use.json +39 -0
- package/config/gates/default.json +117 -0
- package/config/github-about.json +25 -0
- package/config/mcp-allowlists.json +135 -0
- package/config/model-tiers.json +33 -0
- package/config/partner-routing.json +132 -0
- package/config/policy-bundles/constrained-v1.json +64 -0
- package/config/policy-bundles/default-v1.json +91 -0
- package/config/rubrics/default-v1.json +52 -0
- package/config/skill-packs/react-testing.json +23 -0
- package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
- package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
- package/config/skill-specs/pr-reviewer.json +9 -0
- package/config/skill-specs/release-status.json +9 -0
- package/config/skill-specs/ticket-triage.json +9 -0
- package/config/subagent-profiles.json +32 -0
- package/config/tessl-tiles.json +29 -0
- package/config/thumbgate-settings.managed.json +12 -0
- package/openapi/openapi.yaml +1281 -0
- package/package.json +286 -0
- package/plugins/amp-skill/INSTALL.md +52 -0
- package/plugins/amp-skill/SKILL.md +64 -0
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
- package/plugins/claude-codex-bridge/.mcp.json +12 -0
- package/plugins/claude-codex-bridge/INSTALL.md +43 -0
- package/plugins/claude-codex-bridge/README.md +46 -0
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
- package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
- package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
- package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
- package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
- package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
- package/plugins/claude-skill/INSTALL.md +55 -0
- package/plugins/claude-skill/SKILL.md +46 -0
- package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
- package/plugins/codex-profile/.mcp.json +12 -0
- package/plugins/codex-profile/AGENTS.md +20 -0
- package/plugins/codex-profile/INSTALL.md +66 -0
- package/plugins/codex-profile/README.md +37 -0
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
- package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
- package/plugins/cursor-marketplace/LICENSE +21 -0
- package/plugins/cursor-marketplace/README.md +124 -0
- package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
- package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
- package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
- package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
- package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
- package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
- package/plugins/cursor-marketplace/mcp.json +12 -0
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
- package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
- package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
- package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
- package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
- package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
- package/plugins/gemini-extension/INSTALL.md +92 -0
- package/plugins/gemini-extension/gemini_prompt.txt +14 -0
- package/plugins/gemini-extension/tool_contract.json +45 -0
- package/plugins/opencode-profile/INSTALL.md +57 -0
- package/public/assets/instagram-card.png +0 -0
- package/public/assets/tiktok-agent-memory.mp4 +0 -0
- package/public/blog.html +400 -0
- package/public/dashboard.html +1093 -0
- package/public/guide.html +317 -0
- package/public/index.html +1195 -0
- package/public/learn/agent-harness-pattern.html +180 -0
- package/public/learn/ai-agent-persistent-memory.html +202 -0
- package/public/learn/learn.css +45 -0
- package/public/learn/mcp-pre-action-gates-explained.html +172 -0
- package/public/learn/stop-ai-agent-force-push.html +134 -0
- package/public/learn/vibe-coding-safety-net.html +142 -0
- package/public/learn.html +213 -0
- package/public/lessons.html +650 -0
- package/public/vercel.json +8 -0
- package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
- package/scripts/a2ui-engine.js +73 -0
- package/scripts/access-anomaly-detector.js +12 -0
- package/scripts/adk-consolidator.js +266 -0
- package/scripts/agent-readiness.js +220 -0
- package/scripts/agent-security-hardening.js +227 -0
- package/scripts/agentic-data-pipeline.js +847 -0
- package/scripts/analytics-report.js +328 -0
- package/scripts/analytics-window.js +158 -0
- package/scripts/async-job-runner.js +1001 -0
- package/scripts/audit-trail.js +398 -0
- package/scripts/auto-promote-gates.js +293 -0
- package/scripts/auto-wire-hooks.js +316 -0
- package/scripts/autonomous-sales-agent.js +39 -0
- package/scripts/autoresearch-runner.js +216 -0
- package/scripts/background-agent-governance.js +237 -0
- package/scripts/behavioral-extraction.js +93 -0
- package/scripts/belief-update.js +84 -0
- package/scripts/billing.js +2438 -0
- package/scripts/bot-detector.js +50 -0
- package/scripts/budget-guard.js +173 -0
- package/scripts/build-claude-mcpb.js +189 -0
- package/scripts/build-metadata.js +97 -0
- package/scripts/check-congruence.js +322 -0
- package/scripts/cli-feedback.js +135 -0
- package/scripts/cli-telemetry.js +87 -0
- package/scripts/cloudflare-dynamic-sandbox.js +315 -0
- package/scripts/code-reasoning.js +350 -0
- package/scripts/codegraph-context.js +466 -0
- package/scripts/commercial-offer.js +56 -0
- package/scripts/computer-use-firewall.js +250 -0
- package/scripts/context-engine.js +694 -0
- package/scripts/contextfs.js +1287 -0
- package/scripts/conversation-context.js +119 -0
- package/scripts/creator-campaigns.js +239 -0
- package/scripts/daemon-manager.js +108 -0
- package/scripts/daily-digest.js +11 -0
- package/scripts/dashboard-render-spec.js +395 -0
- package/scripts/dashboard.js +1058 -0
- package/scripts/data-governance.js +173 -0
- package/scripts/delegation-runtime.js +900 -0
- package/scripts/deploy-gcp.sh +44 -0
- package/scripts/deploy-policy.js +231 -0
- package/scripts/disagreement-mining.js +315 -0
- package/scripts/dispatch-brief.js +159 -0
- package/scripts/distribution-surfaces.js +44 -0
- package/scripts/dpo-optimizer.js +206 -0
- package/scripts/ensure-repo-bootstrap.js +129 -0
- package/scripts/ephemeral-agent-store.js +219 -0
- package/scripts/eval-harness.js +56 -0
- package/scripts/evolution-state.js +241 -0
- package/scripts/experiment-tracker.js +267 -0
- package/scripts/export-databricks-bundle.js +242 -0
- package/scripts/export-dpo-pairs.js +344 -0
- package/scripts/export-kto-pairs.js +309 -0
- package/scripts/export-training.js +450 -0
- package/scripts/failure-diagnostics.js +558 -0
- package/scripts/feedback-attribution.js +313 -0
- package/scripts/feedback-fallback.js +110 -0
- package/scripts/feedback-history-distiller.js +391 -0
- package/scripts/feedback-inbox-read.js +162 -0
- package/scripts/feedback-loop.js +1887 -0
- package/scripts/feedback-paths.js +145 -0
- package/scripts/feedback-quality.js +139 -0
- package/scripts/feedback-root-consolidator.js +238 -0
- package/scripts/feedback-schema.js +426 -0
- package/scripts/feedback-session.js +286 -0
- package/scripts/feedback-to-memory.js +185 -0
- package/scripts/feedback-to-rules.js +164 -0
- package/scripts/filesystem-search.js +405 -0
- package/scripts/funnel-analytics.js +35 -0
- package/scripts/gate-satisfy.js +42 -0
- package/scripts/gate-stats.js +116 -0
- package/scripts/gate-templates.js +70 -0
- package/scripts/gates-engine.js +816 -0
- package/scripts/generate-paperbanana-diagrams.sh +99 -0
- package/scripts/generate-pretool-hook.sh +40 -0
- package/scripts/github-about.js +350 -0
- package/scripts/github-outreach.js +65 -0
- package/scripts/gtm-revenue-loop.js +520 -0
- package/scripts/hallucination-detector.js +226 -0
- package/scripts/hf-papers.js +317 -0
- package/scripts/history-distiller.js +200 -0
- package/scripts/hook-auto-capture.sh +100 -0
- package/scripts/hook-stop-pr-thread-check.sh +68 -0
- package/scripts/hook-stop-self-score.sh +51 -0
- package/scripts/hook-stop-verify-deploy.sh +31 -0
- package/scripts/hook-thumbgate-cache-updater.js +48 -0
- package/scripts/hook-verify-before-done.sh +20 -0
- package/scripts/hosted-config.js +156 -0
- package/scripts/hybrid-feedback-context.js +675 -0
- package/scripts/install-mcp.js +159 -0
- package/scripts/intent-router.js +392 -0
- package/scripts/internal-agent-bootstrap.js +490 -0
- package/scripts/jsonl-watcher.js +155 -0
- package/scripts/lesson-db.js +613 -0
- package/scripts/lesson-inference.js +310 -0
- package/scripts/lesson-retrieval.js +95 -0
- package/scripts/lesson-rotation.js +137 -0
- package/scripts/lesson-search.js +644 -0
- package/scripts/lesson-synthesis.js +196 -0
- package/scripts/license.js +50 -0
- package/scripts/local-model-profile.js +384 -0
- package/scripts/markdown-escape.js +12 -0
- package/scripts/marketing-experiment.js +671 -0
- package/scripts/mcp-config.js +149 -0
- package/scripts/mcp-policy.js +99 -0
- package/scripts/memalign-recall.js +111 -0
- package/scripts/memory-firewall.js +222 -0
- package/scripts/memory-migration.js +296 -0
- package/scripts/meta-policy.js +190 -0
- package/scripts/metered-billing.js +16 -0
- package/scripts/model-tier-router.js +301 -0
- package/scripts/money-watcher.js +71 -0
- package/scripts/multi-hop-recall.js +240 -0
- package/scripts/natural-language-harness.js +330 -0
- package/scripts/obsidian-export.js +713 -0
- package/scripts/operational-dashboard.js +103 -0
- package/scripts/operational-summary.js +93 -0
- package/scripts/optimize-context.js +17 -0
- package/scripts/org-dashboard.js +201 -0
- package/scripts/partner-orchestration.js +146 -0
- package/scripts/per-step-scoring.js +165 -0
- package/scripts/perplexity-marketing.js +466 -0
- package/scripts/pii-scanner.js +153 -0
- package/scripts/plan-gate.js +154 -0
- package/scripts/post-everywhere.js +308 -0
- package/scripts/post-to-x-retry.sh +22 -0
- package/scripts/post-to-x.js +369 -0
- package/scripts/pr-manager.js +236 -0
- package/scripts/predictive-insights.js +356 -0
- package/scripts/principle-extractor.js +162 -0
- package/scripts/pro-features.js +40 -0
- package/scripts/pro-local-dashboard.js +174 -0
- package/scripts/problem-detail.js +53 -0
- package/scripts/product-feedback.js +134 -0
- package/scripts/profile-router.js +245 -0
- package/scripts/prompt-dlp.js +221 -0
- package/scripts/prompt-guard.js +83 -0
- package/scripts/prove-adapters.js +863 -0
- package/scripts/prove-attribution.js +365 -0
- package/scripts/prove-automation.js +653 -0
- package/scripts/prove-autoresearch.js +304 -0
- package/scripts/prove-claim-verification.js +277 -0
- package/scripts/prove-cloudflare-sandbox.js +163 -0
- package/scripts/prove-data-pipeline.js +410 -0
- package/scripts/prove-data-quality.js +227 -0
- package/scripts/prove-evolution.js +352 -0
- package/scripts/prove-harnesses.js +287 -0
- package/scripts/prove-intelligence.js +259 -0
- package/scripts/prove-lancedb.js +371 -0
- package/scripts/prove-local-intelligence.js +342 -0
- package/scripts/prove-loop-closure.js +263 -0
- package/scripts/prove-predictive-insights.js +357 -0
- package/scripts/prove-runtime.js +350 -0
- package/scripts/prove-seo-gsd.js +234 -0
- package/scripts/prove-settings.js +279 -0
- package/scripts/prove-subway-upgrades.js +277 -0
- package/scripts/prove-tessl.js +229 -0
- package/scripts/prove-training-export.js +327 -0
- package/scripts/prove-workflow-contract.js +116 -0
- package/scripts/prove-xmemory.js +332 -0
- package/scripts/publish-decision.js +133 -0
- package/scripts/pulse.js +80 -0
- package/scripts/rate-limiter.js +125 -0
- package/scripts/reddit-dm-outreach.js +182 -0
- package/scripts/reddit-monitor-cron.sh +26 -0
- package/scripts/reflector-agent.js +221 -0
- package/scripts/reminder-engine.js +132 -0
- package/scripts/revenue-status.js +472 -0
- package/scripts/risk-scorer.js +459 -0
- package/scripts/rlaif-self-audit.js +129 -0
- package/scripts/rlhf_session_start.sh +32 -0
- package/scripts/rubric-engine.js +230 -0
- package/scripts/schedule-manager.js +251 -0
- package/scripts/secret-scanner.js +414 -0
- package/scripts/self-heal.js +147 -0
- package/scripts/self-healing-check.js +188 -0
- package/scripts/semantic-layer.js +98 -0
- package/scripts/seo-gsd.js +1153 -0
- package/scripts/settings-hierarchy.js +214 -0
- package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
- package/scripts/skill-exporter.js +262 -0
- package/scripts/skill-generator.js +446 -0
- package/scripts/skill-materializer.js +134 -0
- package/scripts/skill-packs.js +136 -0
- package/scripts/skill-proposer.js +99 -0
- package/scripts/skill-quality-tracker.js +282 -0
- package/scripts/slo-alert-engine.js +14 -0
- package/scripts/slow-loop.js +72 -0
- package/scripts/social-analytics/db/schema.sql +32 -0
- package/scripts/social-analytics/db/social-analytics.db +0 -0
- package/scripts/social-analytics/digest.js +256 -0
- package/scripts/social-analytics/generate-instagram-card.js +97 -0
- package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
- package/scripts/social-analytics/load-env.js +46 -0
- package/scripts/social-analytics/mcp-server.js +289 -0
- package/scripts/social-analytics/normalizer.js +580 -0
- package/scripts/social-analytics/notify.js +162 -0
- package/scripts/social-analytics/poll-all.js +92 -0
- package/scripts/social-analytics/pollers/github.js +195 -0
- package/scripts/social-analytics/pollers/instagram.js +253 -0
- package/scripts/social-analytics/pollers/linkedin.js +330 -0
- package/scripts/social-analytics/pollers/plausible.js +247 -0
- package/scripts/social-analytics/pollers/reddit.js +306 -0
- package/scripts/social-analytics/pollers/threads.js +233 -0
- package/scripts/social-analytics/pollers/tiktok.js +203 -0
- package/scripts/social-analytics/pollers/x.js +227 -0
- package/scripts/social-analytics/pollers/youtube.js +304 -0
- package/scripts/social-analytics/pollers/zernio.js +183 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
- package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
- package/scripts/social-analytics/publishers/devto.js +122 -0
- package/scripts/social-analytics/publishers/instagram.js +317 -0
- package/scripts/social-analytics/publishers/linkedin.js +294 -0
- package/scripts/social-analytics/publishers/reddit.js +390 -0
- package/scripts/social-analytics/publishers/threads.js +275 -0
- package/scripts/social-analytics/publishers/tiktok.js +217 -0
- package/scripts/social-analytics/publishers/x.js +259 -0
- package/scripts/social-analytics/publishers/youtube.js +223 -0
- package/scripts/social-analytics/publishers/zernio.js +378 -0
- package/scripts/social-analytics/run-digest.js +34 -0
- package/scripts/social-analytics/store.js +257 -0
- package/scripts/social-analytics/utm.js +143 -0
- package/scripts/social-pipeline.js +2628 -0
- package/scripts/social-quality-gate.js +18 -0
- package/scripts/social-reply-monitor.js +445 -0
- package/scripts/status-dashboard.js +155 -0
- package/scripts/statusline-lesson.js +16 -0
- package/scripts/statusline-tower.js +8 -0
- package/scripts/statusline.sh +116 -0
- package/scripts/stripe-live-status.js +115 -0
- package/scripts/subagent-profiles.js +79 -0
- package/scripts/sync-gh-secrets-from-env.sh +70 -0
- package/scripts/sync-github-about.js +52 -0
- package/scripts/sync-version.js +447 -0
- package/scripts/synthetic-dpo.js +234 -0
- package/scripts/telemetry-analytics.js +821 -0
- package/scripts/tessl-export.js +371 -0
- package/scripts/test-coverage.js +120 -0
- package/scripts/thompson-sampling.js +417 -0
- package/scripts/thumbgate-search.js +189 -0
- package/scripts/tool-kpi-tracker.js +12 -0
- package/scripts/tool-registry.js +811 -0
- package/scripts/train_from_feedback.py +933 -0
- package/scripts/user-profile.js +78 -0
- package/scripts/validate-feedback.js +581 -0
- package/scripts/validate-workflow-contract.js +287 -0
- package/scripts/vector-store.js +197 -0
- package/scripts/verification-loop.js +291 -0
- package/scripts/verify-obsidian-setup.sh +269 -0
- package/scripts/verify-run.js +269 -0
- package/scripts/webhook-delivery.js +62 -0
- package/scripts/weekly-auto-post.js +124 -0
- package/scripts/workflow-runs.js +154 -0
- package/scripts/workflow-sprint-intake.js +475 -0
- package/scripts/workspace-evolver.js +374 -0
- package/scripts/x-autonomous-marketing.js +139 -0
- package/scripts/xmemory-lite.js +405 -0
- package/skills/agent-memory/SKILL.md +97 -0
- package/skills/rlhf-feedback/SKILL.md +49 -0
- package/skills/solve-architecture-autonomy/SKILL.md +17 -0
- package/skills/solve-architecture-autonomy/tool.js +33 -0
- package/skills/thumbgate/SKILL.md +114 -0
- package/src/api/server.js +4206 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* reddit-dm-outreach.js
|
|
6
|
+
* Send direct messages to engaged Reddit users via OAuth2 password grant flow.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/reddit-dm-outreach.js
|
|
10
|
+
*
|
|
11
|
+
* Requires env vars:
|
|
12
|
+
* REDDIT_CLIENT_ID
|
|
13
|
+
* REDDIT_CLIENT_SECRET
|
|
14
|
+
* REDDIT_USERNAME
|
|
15
|
+
* REDDIT_PASSWORD
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const https = require('https');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
// Parse .env credentials line-by-line
|
|
23
|
+
function parseEnv(filePath) {
|
|
24
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
25
|
+
const result = {};
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
31
|
+
|
|
32
|
+
const eqIdx = trimmed.indexOf('=');
|
|
33
|
+
if (eqIdx === -1) continue;
|
|
34
|
+
|
|
35
|
+
const key = trimmed.substring(0, eqIdx).trim();
|
|
36
|
+
const value = trimmed.substring(eqIdx + 1).trim();
|
|
37
|
+
result[key] = value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const envPath = path.join(__dirname, '..', '.env');
|
|
44
|
+
const env = parseEnv(envPath);
|
|
45
|
+
|
|
46
|
+
const REDDIT_CLIENT_ID = env.REDDIT_CLIENT_ID;
|
|
47
|
+
const REDDIT_CLIENT_SECRET = env.REDDIT_CLIENT_SECRET;
|
|
48
|
+
const REDDIT_USERNAME = env.REDDIT_USERNAME;
|
|
49
|
+
const REDDIT_PASSWORD = env.REDDIT_PASSWORD;
|
|
50
|
+
|
|
51
|
+
if (!REDDIT_CLIENT_ID || !REDDIT_CLIENT_SECRET || !REDDIT_USERNAME || !REDDIT_PASSWORD) {
|
|
52
|
+
console.error('❌ Missing Reddit credentials in .env');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log('📧 Reddit DM Outreach');
|
|
57
|
+
console.log('---');
|
|
58
|
+
|
|
59
|
+
// OAuth2 password grant flow
|
|
60
|
+
function authenticate() {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const authHeader = 'Basic ' + Buffer.from(
|
|
63
|
+
REDDIT_CLIENT_ID + ':' + REDDIT_CLIENT_SECRET
|
|
64
|
+
).toString('base64');
|
|
65
|
+
|
|
66
|
+
const postData = [
|
|
67
|
+
'grant_type=password',
|
|
68
|
+
`username=${encodeURIComponent(REDDIT_USERNAME)}`,
|
|
69
|
+
`password=${encodeURIComponent(REDDIT_PASSWORD)}`,
|
|
70
|
+
'duration=permanent'
|
|
71
|
+
].join('&');
|
|
72
|
+
|
|
73
|
+
const authOptions = {
|
|
74
|
+
hostname: 'www.reddit.com',
|
|
75
|
+
port: 443,
|
|
76
|
+
path: '/api/v1/access_token',
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
'Authorization': authHeader,
|
|
80
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
81
|
+
'User-Agent': 'ThumbGate-Testimonial/1.0',
|
|
82
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const authReq = https.request(authOptions, (res) => {
|
|
87
|
+
let data = '';
|
|
88
|
+
res.on('data', chunk => { data += chunk; });
|
|
89
|
+
res.on('end', () => {
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(data);
|
|
92
|
+
if (parsed.access_token) {
|
|
93
|
+
console.log('✅ Authenticated as', REDDIT_USERNAME);
|
|
94
|
+
resolve(parsed.access_token);
|
|
95
|
+
} else {
|
|
96
|
+
reject(new Error('Auth failed: ' + JSON.stringify(parsed)));
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
reject(new Error('Parse error: ' + e.message));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
authReq.on('error', reject);
|
|
105
|
+
authReq.write(postData);
|
|
106
|
+
authReq.end();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Send a single DM
|
|
111
|
+
function sendDM(accessToken, to, subject, text) {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
const dmData = JSON.stringify({ to, subject, text });
|
|
114
|
+
|
|
115
|
+
const dmOptions = {
|
|
116
|
+
hostname: 'oauth.reddit.com',
|
|
117
|
+
port: 443,
|
|
118
|
+
path: '/api/compose',
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: {
|
|
121
|
+
'Authorization': 'Bearer ' + accessToken,
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
'User-Agent': 'ThumbGate-Testimonial/1.0',
|
|
124
|
+
'Content-Length': Buffer.byteLength(dmData)
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const dmReq = https.request(dmOptions, (res) => {
|
|
129
|
+
let data = '';
|
|
130
|
+
res.on('data', chunk => { data += chunk; });
|
|
131
|
+
res.on('end', () => {
|
|
132
|
+
if (res.statusCode === 200) {
|
|
133
|
+
resolve();
|
|
134
|
+
} else {
|
|
135
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 200)}`));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
dmReq.on('error', reject);
|
|
141
|
+
dmReq.write(dmData);
|
|
142
|
+
dmReq.end();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Main
|
|
147
|
+
async function main() {
|
|
148
|
+
try {
|
|
149
|
+
const accessToken = await authenticate();
|
|
150
|
+
|
|
151
|
+
const messages = [
|
|
152
|
+
{
|
|
153
|
+
to: 'game-of-kton',
|
|
154
|
+
subject: 'ThumbGate Pro — Try it free, get the hook',
|
|
155
|
+
text: 'Hey, thanks for the thoughtful comments on the Cursor thread about agent memory. You clearly get the hooks-based enforcement approach. Would you be open to trying ThumbGate Pro for free? I\'d love to get your honest take — if you like it, a one-sentence quote I can use on the landing page would be huge. No strings attached either way. Here\'s the repo: https://github.com/IgorGanapolsky/ThumbGate'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
to: 'Deep_Ad1959',
|
|
159
|
+
subject: 'ThumbGate Pro — Context-dependent blocking (your idea)',
|
|
160
|
+
text: 'Hey, your point about context-dependent blocking was spot on — it\'s exactly why we use Thompson Sampling instead of hard binary blocks. Would you be interested in trying ThumbGate Pro for free? If you find it useful, I\'d appreciate a quick testimonial quote for the site. No obligation. Repo: https://github.com/IgorGanapolsky/ThumbGate'
|
|
161
|
+
}
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
console.log(`\n📨 Sending ${messages.length} direct messages...\n`);
|
|
165
|
+
|
|
166
|
+
for (const msg of messages) {
|
|
167
|
+
try {
|
|
168
|
+
await sendDM(accessToken, msg.to, msg.subject, msg.text);
|
|
169
|
+
console.log(`✅ DM sent to u/${msg.to}`);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`❌ Failed to send DM to u/${msg.to}:`, err.message);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log(`\n✅ Outreach complete (${messages.length}/${messages.length} sent)`);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error('❌ Error:', err.message);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
main();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
REPO_DIR="/Users/igorganapolsky/workspace/git/igor/rlhf"
|
|
5
|
+
LOG_FILE="${REPO_DIR}/.thumbgate/reddit-monitor.log"
|
|
6
|
+
|
|
7
|
+
mkdir -p "${REPO_DIR}/.rlhf"
|
|
8
|
+
|
|
9
|
+
# Load environment
|
|
10
|
+
if [ -f "${REPO_DIR}/.env" ]; then
|
|
11
|
+
while IFS='=' read -r key value; do
|
|
12
|
+
# Skip comments and empty lines
|
|
13
|
+
[[ -z "$key" || "$key" =~ ^# ]] && continue
|
|
14
|
+
# Strip leading/trailing whitespace from key
|
|
15
|
+
key=$(echo "$key" | xargs)
|
|
16
|
+
# Export the variable (value preserved as-is)
|
|
17
|
+
export "$key=$value"
|
|
18
|
+
done < "${REPO_DIR}/.env"
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] Reddit monitor run starting" >> "$LOG_FILE"
|
|
22
|
+
|
|
23
|
+
cd "$REPO_DIR"
|
|
24
|
+
/opt/homebrew/bin/node scripts/social-reply-monitor.js >> "$LOG_FILE" 2>&1
|
|
25
|
+
|
|
26
|
+
echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] Reddit monitor run complete" >> "$LOG_FILE"
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reflector Agent — Self-Healing Brain
|
|
3
|
+
*
|
|
4
|
+
* On negative feedback, analyzes the conversation window to:
|
|
5
|
+
* 1. Identify what the assistant did wrong
|
|
6
|
+
* 2. Check if this is a recurring mistake
|
|
7
|
+
* 3. Propose a specific, actionable rule
|
|
8
|
+
* 4. Return the proposal for user confirmation
|
|
9
|
+
*
|
|
10
|
+
* This transforms ThumbGate from "Manual Guardrail" to "Self-Healing Brain"
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const { retrieveRelevantLessons } = require('./lesson-retrieval');
|
|
16
|
+
const {
|
|
17
|
+
extractFilePaths,
|
|
18
|
+
extractToolCalls,
|
|
19
|
+
extractErrors,
|
|
20
|
+
normalizeConversationWindow,
|
|
21
|
+
} = require('./conversation-context');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run a post-mortem analysis on a negative feedback event.
|
|
25
|
+
* @param {object} params
|
|
26
|
+
* @param {Array} params.conversationWindow - Last N conversation turns
|
|
27
|
+
* @param {string} params.context - One-line context from the caller
|
|
28
|
+
* @param {string} params.whatWentWrong - What the caller said went wrong
|
|
29
|
+
* @param {object} params.structuredRule - IF/THEN rule from lesson-inference
|
|
30
|
+
* @param {object} params.feedbackEvent - The stored feedback event
|
|
31
|
+
* @returns {object} Reflection result with proposed rule and recurrence info
|
|
32
|
+
*/
|
|
33
|
+
function reflect(params) {
|
|
34
|
+
const { conversationWindow, context, whatWentWrong, structuredRule, feedbackEvent } = params;
|
|
35
|
+
|
|
36
|
+
// 1. Extract what happened from the conversation
|
|
37
|
+
const analysis = analyzeConversation(conversationWindow || []);
|
|
38
|
+
|
|
39
|
+
// 2. Check for recurrence — has this mistake happened before?
|
|
40
|
+
const recurrence = checkRecurrence(analysis, feedbackEvent);
|
|
41
|
+
|
|
42
|
+
// 3. Generate a human-readable proposed rule
|
|
43
|
+
const proposedRule = generateProposedRule(analysis, structuredRule, recurrence, whatWentWrong);
|
|
44
|
+
|
|
45
|
+
// 4. Determine severity based on recurrence
|
|
46
|
+
const severity = recurrence.count >= 3 ? 'critical' : recurrence.count >= 1 ? 'warning' : 'info';
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
status: 'reflection_complete',
|
|
50
|
+
analysis: {
|
|
51
|
+
userIntent: analysis.userIntent,
|
|
52
|
+
assistantAction: analysis.assistantAction,
|
|
53
|
+
errorDetected: analysis.errorDetected,
|
|
54
|
+
toolsUsed: analysis.toolsUsed,
|
|
55
|
+
filesInvolved: analysis.filesInvolved,
|
|
56
|
+
},
|
|
57
|
+
recurrence: {
|
|
58
|
+
isRecurring: recurrence.count > 0,
|
|
59
|
+
count: recurrence.count,
|
|
60
|
+
previousLessons: recurrence.previousLessons.map(l => ({
|
|
61
|
+
id: l.id,
|
|
62
|
+
title: l.title,
|
|
63
|
+
timestamp: l.timestamp,
|
|
64
|
+
})),
|
|
65
|
+
},
|
|
66
|
+
proposedRule: proposedRule,
|
|
67
|
+
severity: severity,
|
|
68
|
+
message: formatReflectionMessage(proposedRule, recurrence, analysis),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function analyzeConversation(window) {
|
|
73
|
+
const normalizedWindow = normalizeConversationWindow(window);
|
|
74
|
+
const userMsgs = normalizedWindow.filter(m => m.role === 'user');
|
|
75
|
+
const assistantMsgs = normalizedWindow.filter(m => m.role === 'assistant');
|
|
76
|
+
|
|
77
|
+
const lastUser = userMsgs[userMsgs.length - 1]?.content || '';
|
|
78
|
+
const lastAssistant = assistantMsgs[assistantMsgs.length - 1]?.content || '';
|
|
79
|
+
const corrections = extractCorrections(userMsgs);
|
|
80
|
+
const toolsUsed = extractToolCalls(normalizedWindow);
|
|
81
|
+
const filesInvolved = extractFilePaths(normalizedWindow).slice(0, 10);
|
|
82
|
+
const errors = extractErrors(normalizedWindow).slice(0, 5);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
userIntent: lastUser.slice(0, 300),
|
|
86
|
+
assistantAction: lastAssistant.slice(0, 300),
|
|
87
|
+
corrections,
|
|
88
|
+
errorDetected: errors.length > 0,
|
|
89
|
+
errors,
|
|
90
|
+
toolsUsed,
|
|
91
|
+
filesInvolved,
|
|
92
|
+
messageCount: normalizedWindow.length,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function checkRecurrence(analysis, feedbackEvent) {
|
|
97
|
+
// Search existing lessons for similar mistakes
|
|
98
|
+
let previousLessons = [];
|
|
99
|
+
try {
|
|
100
|
+
const context = `${analysis.userIntent} ${analysis.assistantAction} ${analysis.corrections.join(' ')}`;
|
|
101
|
+
const toolName = analysis.toolsUsed[0] || 'unknown';
|
|
102
|
+
previousLessons = retrieveRelevantLessons(toolName, context, { maxResults: 5 });
|
|
103
|
+
// Filter to only negative lessons
|
|
104
|
+
previousLessons = previousLessons.filter(l => l.signal === 'negative');
|
|
105
|
+
} catch (_err) {
|
|
106
|
+
// Non-critical — recurrence check is best-effort
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
count: previousLessons.length,
|
|
111
|
+
previousLessons,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function generateProposedRule(analysis, structuredRule, recurrence, whatWentWrong) {
|
|
116
|
+
// Build the most specific rule we can from available data
|
|
117
|
+
const parts = [];
|
|
118
|
+
|
|
119
|
+
// Use corrections from conversation as the strongest signal
|
|
120
|
+
if (analysis.corrections.length > 0) {
|
|
121
|
+
const correction = analysis.corrections[0];
|
|
122
|
+
parts.push({
|
|
123
|
+
type: 'constraint',
|
|
124
|
+
rule: `NEVER ${correction}`,
|
|
125
|
+
source: 'user-correction',
|
|
126
|
+
confidence: 0.95,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Use structured rule if available
|
|
131
|
+
if (structuredRule?.trigger && structuredRule?.action) {
|
|
132
|
+
parts.push({
|
|
133
|
+
type: structuredRule.action.type === 'avoid' ? 'constraint' : 'preference',
|
|
134
|
+
rule: `IF ${structuredRule.trigger.condition} THEN ${structuredRule.action.description}`,
|
|
135
|
+
source: 'inferred',
|
|
136
|
+
confidence: structuredRule.confidence || 0.7,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Use whatWentWrong as fallback
|
|
141
|
+
if (parts.length === 0 && whatWentWrong) {
|
|
142
|
+
parts.push({
|
|
143
|
+
type: 'lesson',
|
|
144
|
+
rule: `AVOID: ${whatWentWrong}`,
|
|
145
|
+
source: 'user-provided',
|
|
146
|
+
confidence: 0.8,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Use analysis as last resort
|
|
151
|
+
if (parts.length === 0) {
|
|
152
|
+
parts.push({
|
|
153
|
+
type: 'observation',
|
|
154
|
+
rule: `Review approach when: ${analysis.userIntent.slice(0, 80)}`,
|
|
155
|
+
source: 'conversation-analysis',
|
|
156
|
+
confidence: 0.5,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Pick highest confidence rule
|
|
161
|
+
const best = parts.sort((a, b) => b.confidence - a.confidence)[0];
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
...best,
|
|
165
|
+
isRecurring: recurrence.count > 0,
|
|
166
|
+
recurrenceCount: recurrence.count,
|
|
167
|
+
scope: analysis.filesInvolved.length > 0 ? 'project' : 'global',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function formatReflectionMessage(proposedRule, recurrence, analysis) {
|
|
172
|
+
const prefix = recurrence.count > 0
|
|
173
|
+
? `I've made this mistake ${recurrence.count + 1} time(s) now. `
|
|
174
|
+
: '';
|
|
175
|
+
|
|
176
|
+
const ruleText = proposedRule.rule;
|
|
177
|
+
|
|
178
|
+
const correction = analysis.corrections.length > 0
|
|
179
|
+
? ` I noticed you corrected me: "${analysis.corrections[0].slice(0, 80)}".`
|
|
180
|
+
: '';
|
|
181
|
+
|
|
182
|
+
const fileContext = analysis.filesInvolved.length > 0
|
|
183
|
+
? ` (in ${analysis.filesInvolved.slice(0, 3).join(', ')})`
|
|
184
|
+
: '';
|
|
185
|
+
|
|
186
|
+
return `${prefix}${correction} I've recorded a rule${fileContext}: "${ruleText}". Correct?`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function extractCorrections(userMessages) {
|
|
190
|
+
const results = [];
|
|
191
|
+
const phraseSets = [
|
|
192
|
+
['don\'t ', 'do not ', 'never ', 'stop '],
|
|
193
|
+
['wrong ', 'incorrect ', 'that\'s not ', 'no, '],
|
|
194
|
+
['i said ', 'i told you ', 'i already '],
|
|
195
|
+
['use ', 'switch to ', 'change to '],
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
for (const message of userMessages) {
|
|
199
|
+
const content = String(message.content || '').trim();
|
|
200
|
+
const lower = content.toLowerCase();
|
|
201
|
+
if (!lower) continue;
|
|
202
|
+
|
|
203
|
+
for (const phrases of phraseSets) {
|
|
204
|
+
for (const phrase of phrases) {
|
|
205
|
+
const index = lower.indexOf(phrase);
|
|
206
|
+
if (index === -1) continue;
|
|
207
|
+
let detail = content.slice(index + phrase.length).trim();
|
|
208
|
+
const insteadIndex = detail.toLowerCase().indexOf(' instead');
|
|
209
|
+
if (insteadIndex >= 0) {
|
|
210
|
+
detail = detail.slice(0, insteadIndex).trim();
|
|
211
|
+
}
|
|
212
|
+
if (detail) results.push(detail.slice(0, 100));
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return results;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = { reflect, analyzeConversation, checkRecurrence, generateProposedRule, formatReflectionMessage };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
7
|
+
const DEFAULT_STATE_PATH = path.join(PROJECT_ROOT, '.rlhf', 'reminder-state.json');
|
|
8
|
+
|
|
9
|
+
const REMINDER_TEMPLATES = {
|
|
10
|
+
guardrail_spike: 'Safety guardrails triggered {{count}} times. Re-apply rule: {{rule}}',
|
|
11
|
+
iteration_limit: 'Approaching max iterations ({{count}}/{{limit}}). Prioritize essential actions only.',
|
|
12
|
+
tool_misuse: 'Tool misuse detected {{count}} times for: {{tools}}. Verify tool schemas before calling.',
|
|
13
|
+
error_cascade: 'Repeated errors ({{count}}). Switch strategy: {{suggestion}}',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DEFAULT_THRESHOLDS = {
|
|
17
|
+
guardrail_spike: 3,
|
|
18
|
+
iteration_limit: 1,
|
|
19
|
+
tool_misuse: 2,
|
|
20
|
+
error_cascade: 3,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function getStatePath(stateFile) {
|
|
24
|
+
return stateFile || DEFAULT_STATE_PATH;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function loadState(stateFile) {
|
|
28
|
+
const p = getStatePath(stateFile);
|
|
29
|
+
try {
|
|
30
|
+
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
31
|
+
} catch {
|
|
32
|
+
// corrupted — start fresh
|
|
33
|
+
}
|
|
34
|
+
return { counts: {} };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function saveState(state, stateFile) {
|
|
38
|
+
const p = getStatePath(stateFile);
|
|
39
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
40
|
+
fs.writeFileSync(p, JSON.stringify(state, null, 2));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Increment the event counter for a given event type.
|
|
45
|
+
* @param {string} eventType - One of the keys in REMINDER_TEMPLATES
|
|
46
|
+
* @param {string} [stateFile] - Path to state JSON (default: .thumbgate/reminder-state.json)
|
|
47
|
+
* @returns {number} New count after incrementing
|
|
48
|
+
*/
|
|
49
|
+
function trackEvent(eventType, stateFile) {
|
|
50
|
+
const state = loadState(stateFile);
|
|
51
|
+
state.counts[eventType] = (state.counts[eventType] || 0) + 1;
|
|
52
|
+
saveState(state, stateFile);
|
|
53
|
+
return state.counts[eventType];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the current event count without modifying state.
|
|
58
|
+
* @param {string} eventType
|
|
59
|
+
* @param {string} [stateFile]
|
|
60
|
+
* @returns {number}
|
|
61
|
+
*/
|
|
62
|
+
function getEventCount(eventType, stateFile) {
|
|
63
|
+
return loadState(stateFile).counts[eventType] || 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Return true if the event count meets or exceeds its threshold.
|
|
68
|
+
* @param {string} eventType
|
|
69
|
+
* @param {number} [threshold] - Defaults to DEFAULT_THRESHOLDS[eventType] or 3
|
|
70
|
+
* @param {string} [stateFile]
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
function shouldInjectReminder(eventType, threshold, stateFile) {
|
|
74
|
+
const t = typeof threshold === 'number' ? threshold : (DEFAULT_THRESHOLDS[eventType] || 3);
|
|
75
|
+
return getEventCount(eventType, stateFile) >= t;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render a reminder template with context variable substitution.
|
|
80
|
+
* @param {string} eventType
|
|
81
|
+
* @param {object} ctx - Variables to substitute into {{var}} placeholders
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
function renderTemplate(eventType, ctx) {
|
|
85
|
+
const template = REMINDER_TEMPLATES[eventType];
|
|
86
|
+
if (!template) return `[Reminder] Event: ${eventType}`;
|
|
87
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => (ctx && ctx[key] !== undefined ? ctx[key] : `{${key}}`));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Append a system reminder to a turns array without modifying state.
|
|
92
|
+
* Callers are responsible for calling trackEvent before/after as needed.
|
|
93
|
+
* @param {object[]} turns - Existing turns array
|
|
94
|
+
* @param {string} eventType
|
|
95
|
+
* @param {object} ctx - Template variables (count will be added automatically)
|
|
96
|
+
* @param {string} [stateFile]
|
|
97
|
+
* @returns {object[]} New turns array with reminder appended
|
|
98
|
+
*/
|
|
99
|
+
function injectReminder(turns, eventType, ctx, stateFile) {
|
|
100
|
+
const count = getEventCount(eventType, stateFile);
|
|
101
|
+
const message = renderTemplate(eventType, { ...ctx, count });
|
|
102
|
+
const reminder = {
|
|
103
|
+
role: 'user',
|
|
104
|
+
content: `[System Reminder] ${message}`,
|
|
105
|
+
injectedAt: new Date().toISOString(),
|
|
106
|
+
eventType,
|
|
107
|
+
};
|
|
108
|
+
return [...turns, reminder];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reset the event counter for a given event type (e.g., after a reminder is acted on).
|
|
113
|
+
* @param {string} eventType
|
|
114
|
+
* @param {string} [stateFile]
|
|
115
|
+
*/
|
|
116
|
+
function resetEvent(eventType, stateFile) {
|
|
117
|
+
const state = loadState(stateFile);
|
|
118
|
+
state.counts[eventType] = 0;
|
|
119
|
+
saveState(state, stateFile);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
REMINDER_TEMPLATES,
|
|
124
|
+
DEFAULT_THRESHOLDS,
|
|
125
|
+
DEFAULT_STATE_PATH,
|
|
126
|
+
trackEvent,
|
|
127
|
+
getEventCount,
|
|
128
|
+
shouldInjectReminder,
|
|
129
|
+
renderTemplate,
|
|
130
|
+
injectReminder,
|
|
131
|
+
resetEvent,
|
|
132
|
+
};
|