synabun 1.3.0 → 2.0.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/CHANGELOG.md +205 -662
- package/LICENSE-COMMERCIAL.md +33 -0
- package/NOTICE +17 -0
- package/README.md +175 -853
- package/THIRD-PARTY-LICENSES.md +136 -0
- package/benchmarks/browser-live-results.json +79 -0
- package/benchmarks/browser-v2-results.json +258 -0
- package/benchmarks/fixtures/browser/README.md +57 -0
- package/benchmarks/fixtures/browser/page-state.json +132 -0
- package/benchmarks/fixtures/browser/social.json +92 -0
- package/benchmarks/fixtures/browser/target.json +319 -0
- package/benchmarks/fixtures/desktop/README.md +58 -0
- package/benchmarks/fixtures/desktop/abstention.json +59 -0
- package/benchmarks/fixtures/desktop/adversarial.json +22 -0
- package/benchmarks/fixtures/desktop/forbidden.json +93 -0
- package/benchmarks/fixtures/desktop/generate.mjs +1023 -0
- package/benchmarks/fixtures/desktop/target.json +136 -0
- package/bin/synabun.js +30 -0
- package/docs/whatsapp.md +325 -0
- package/hooks/claude-code/post-plan.mjs +613 -391
- package/hooks/claude-code/post-remember.mjs +246 -122
- package/hooks/claude-code/pre-compact.mjs +171 -110
- package/hooks/claude-code/pre-task.mjs +133 -0
- package/hooks/claude-code/pre-websearch.mjs +15 -4
- package/hooks/claude-code/prompt-origin.mjs +94 -0
- package/hooks/claude-code/prompt-submit.mjs +1244 -861
- package/hooks/claude-code/session-start.mjs +114 -156
- package/hooks/claude-code/shared.mjs +1228 -368
- package/hooks/claude-code/state.mjs +167 -0
- package/hooks/claude-code/stop.mjs +515 -140
- package/hooks/claude-code/subagent-start.mjs +154 -0
- package/hooks/claude-code/subagent-stop.mjs +102 -0
- package/hooks/claude-code/task-gate.mjs +150 -0
- package/launcher.mjs +40 -0
- package/lib/claude-hooks.js +137 -0
- package/lib/claude-usage-audit.js +141 -0
- package/lib/client-config-repair.js +145 -0
- package/lib/data-home-migration.js +502 -0
- package/lib/desktop-pwa.js +206 -0
- package/lib/external-tools.js +221 -0
- package/lib/first-launch-protection.js +245 -0
- package/lib/packaged-runtime.js +110 -0
- package/lib/paths.d.ts +21 -0
- package/lib/paths.js +103 -0
- package/lib/start-launcher.js +1309 -0
- package/mcp-server/dist/config.d.ts +22 -0
- package/mcp-server/dist/config.d.ts.map +1 -0
- package/mcp-server/dist/config.js +109 -0
- package/mcp-server/dist/config.js.map +1 -0
- package/mcp-server/dist/http.d.ts +30 -0
- package/mcp-server/dist/http.d.ts.map +1 -0
- package/mcp-server/dist/http.js +276 -0
- package/mcp-server/dist/http.js.map +1 -0
- package/mcp-server/dist/index.d.ts +48 -0
- package/mcp-server/dist/index.d.ts.map +1 -0
- package/mcp-server/dist/index.js +538 -0
- package/mcp-server/dist/index.js.map +1 -0
- package/mcp-server/dist/preload-http.d.ts +3 -0
- package/mcp-server/dist/preload-http.d.ts.map +1 -0
- package/mcp-server/dist/preload-http.js +28 -0
- package/mcp-server/dist/preload-http.js.map +1 -0
- package/mcp-server/dist/preload.d.ts +3 -0
- package/mcp-server/dist/preload.d.ts.map +1 -0
- package/mcp-server/dist/preload.js +32 -0
- package/mcp-server/dist/preload.js.map +1 -0
- package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts +19 -0
- package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts.map +1 -0
- package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js +403 -0
- package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js.map +1 -0
- package/mcp-server/dist/scripts/setup-embeddings.d.ts +3 -0
- package/mcp-server/dist/scripts/setup-embeddings.d.ts.map +1 -0
- package/mcp-server/dist/scripts/setup-embeddings.js +15 -0
- package/mcp-server/dist/scripts/setup-embeddings.js.map +1 -0
- package/mcp-server/dist/services/assistant-judgments.d.ts +79 -0
- package/mcp-server/dist/services/assistant-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/assistant-judgments.js +128 -0
- package/mcp-server/dist/services/assistant-judgments.js.map +1 -0
- package/mcp-server/dist/services/browser-assist-gate.d.ts +314 -0
- package/mcp-server/dist/services/browser-assist-gate.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-assist-gate.js +344 -0
- package/mcp-server/dist/services/browser-assist-gate.js.map +1 -0
- package/mcp-server/dist/services/browser-assist.d.ts +104 -0
- package/mcp-server/dist/services/browser-assist.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-assist.js +282 -0
- package/mcp-server/dist/services/browser-assist.js.map +1 -0
- package/mcp-server/dist/services/browser-bench.d.ts +772 -0
- package/mcp-server/dist/services/browser-bench.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-bench.js +424 -0
- package/mcp-server/dist/services/browser-bench.js.map +1 -0
- package/mcp-server/dist/services/browser-judgments.d.ts +196 -0
- package/mcp-server/dist/services/browser-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-judgments.js +363 -0
- package/mcp-server/dist/services/browser-judgments.js.map +1 -0
- package/mcp-server/dist/services/browser-risk.d.ts +166 -0
- package/mcp-server/dist/services/browser-risk.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-risk.js +458 -0
- package/mcp-server/dist/services/browser-risk.js.map +1 -0
- package/mcp-server/dist/services/categories.d.ts +61 -0
- package/mcp-server/dist/services/categories.d.ts.map +1 -0
- package/mcp-server/dist/services/categories.js +377 -0
- package/mcp-server/dist/services/categories.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +49 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.js +346 -0
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -0
- package/mcp-server/dist/services/desktop-assist-gate.d.ts +198 -0
- package/mcp-server/dist/services/desktop-assist-gate.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-assist-gate.js +202 -0
- package/mcp-server/dist/services/desktop-assist-gate.js.map +1 -0
- package/mcp-server/dist/services/desktop-assist.d.ts +104 -0
- package/mcp-server/dist/services/desktop-assist.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-assist.js +266 -0
- package/mcp-server/dist/services/desktop-assist.js.map +1 -0
- package/mcp-server/dist/services/desktop-bench.d.ts +553 -0
- package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-bench.js +311 -0
- package/mcp-server/dist/services/desktop-bench.js.map +1 -0
- package/mcp-server/dist/services/desktop-client.d.ts +23 -0
- package/mcp-server/dist/services/desktop-client.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-client.js +79 -0
- package/mcp-server/dist/services/desktop-client.js.map +1 -0
- package/mcp-server/dist/services/desktop-judgments.d.ts +125 -0
- package/mcp-server/dist/services/desktop-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-judgments.js +177 -0
- package/mcp-server/dist/services/desktop-judgments.js.map +1 -0
- package/mcp-server/dist/services/desktop-risk.d.ts +283 -0
- package/mcp-server/dist/services/desktop-risk.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-risk.js +605 -0
- package/mcp-server/dist/services/desktop-risk.js.map +1 -0
- package/mcp-server/dist/services/discord.d.ts +112 -0
- package/mcp-server/dist/services/discord.d.ts.map +1 -0
- package/mcp-server/dist/services/discord.js +477 -0
- package/mcp-server/dist/services/discord.js.map +1 -0
- package/mcp-server/dist/services/edit-stale.d.ts +81 -0
- package/mcp-server/dist/services/edit-stale.d.ts.map +1 -0
- package/mcp-server/dist/services/edit-stale.js +358 -0
- package/mcp-server/dist/services/edit-stale.js.map +1 -0
- package/mcp-server/dist/services/embedding-service.d.ts +16 -0
- package/mcp-server/dist/services/embedding-service.d.ts.map +1 -0
- package/mcp-server/dist/services/embedding-service.js +126 -0
- package/mcp-server/dist/services/embedding-service.js.map +1 -0
- package/mcp-server/dist/services/embedding-worker.d.ts +2 -0
- package/mcp-server/dist/services/embedding-worker.d.ts.map +1 -0
- package/mcp-server/dist/services/embedding-worker.js +52 -0
- package/mcp-server/dist/services/embedding-worker.js.map +1 -0
- package/mcp-server/dist/services/fb-regions.d.ts +71 -0
- package/mcp-server/dist/services/fb-regions.d.ts.map +1 -0
- package/mcp-server/dist/services/fb-regions.js +211 -0
- package/mcp-server/dist/services/fb-regions.js.map +1 -0
- package/mcp-server/dist/services/file-checksums.d.ts +22 -0
- package/mcp-server/dist/services/file-checksums.d.ts.map +1 -0
- package/mcp-server/dist/services/file-checksums.js +55 -0
- package/mcp-server/dist/services/file-checksums.js.map +1 -0
- package/mcp-server/dist/services/file-excerpt.d.ts +24 -0
- package/mcp-server/dist/services/file-excerpt.d.ts.map +1 -0
- package/mcp-server/dist/services/file-excerpt.js +65 -0
- package/mcp-server/dist/services/file-excerpt.js.map +1 -0
- package/mcp-server/dist/services/identity.d.ts +124 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -0
- package/mcp-server/dist/services/identity.js +241 -0
- package/mcp-server/dist/services/identity.js.map +1 -0
- package/mcp-server/dist/services/local-embeddings.d.ts +2 -0
- package/mcp-server/dist/services/local-embeddings.d.ts.map +1 -0
- package/mcp-server/dist/services/local-embeddings.js +3 -0
- package/mcp-server/dist/services/local-embeddings.js.map +1 -0
- package/mcp-server/dist/services/memory-client.d.ts +4 -0
- package/mcp-server/dist/services/memory-client.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-client.js +27 -0
- package/mcp-server/dist/services/memory-client.js.map +1 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +135 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-judge-backfill.js +510 -0
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -0
- package/mcp-server/dist/services/memory-judgments.d.ts +362 -0
- package/mcp-server/dist/services/memory-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-judgments.js +674 -0
- package/mcp-server/dist/services/memory-judgments.js.map +1 -0
- package/mcp-server/dist/services/memory-maintenance.d.ts +108 -0
- package/mcp-server/dist/services/memory-maintenance.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-maintenance.js +403 -0
- package/mcp-server/dist/services/memory-maintenance.js.map +1 -0
- package/mcp-server/dist/services/memory-retrieval.d.ts +66 -0
- package/mcp-server/dist/services/memory-retrieval.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-retrieval.js +412 -0
- package/mcp-server/dist/services/memory-retrieval.js.map +1 -0
- package/mcp-server/dist/services/memory-schema.d.ts +16 -0
- package/mcp-server/dist/services/memory-schema.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-schema.js +282 -0
- package/mcp-server/dist/services/memory-schema.js.map +1 -0
- package/mcp-server/dist/services/memory-sessions.d.ts +24 -0
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-sessions.js +110 -0
- package/mcp-server/dist/services/memory-sessions.js.map +1 -0
- package/mcp-server/dist/services/memory-triage.d.ts +60 -0
- package/mcp-server/dist/services/memory-triage.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-triage.js +189 -0
- package/mcp-server/dist/services/memory-triage.js.map +1 -0
- package/mcp-server/dist/services/memory-verification.d.ts +24 -0
- package/mcp-server/dist/services/memory-verification.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-verification.js +92 -0
- package/mcp-server/dist/services/memory-verification.js.map +1 -0
- package/mcp-server/dist/services/memory-writer.d.ts +26 -0
- package/mcp-server/dist/services/memory-writer.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-writer.js +126 -0
- package/mcp-server/dist/services/memory-writer.js.map +1 -0
- package/mcp-server/dist/services/neural-interface.d.ts +439 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -0
- package/mcp-server/dist/services/neural-interface.js +1135 -0
- package/mcp-server/dist/services/neural-interface.js.map +1 -0
- package/mcp-server/dist/services/profiles.d.ts +120 -0
- package/mcp-server/dist/services/profiles.d.ts.map +1 -0
- package/mcp-server/dist/services/profiles.js +387 -0
- package/mcp-server/dist/services/profiles.js.map +1 -0
- package/mcp-server/dist/services/secret-gate.d.ts +29 -0
- package/mcp-server/dist/services/secret-gate.d.ts.map +1 -0
- package/mcp-server/dist/services/secret-gate.js +43 -0
- package/mcp-server/dist/services/secret-gate.js.map +1 -0
- package/mcp-server/dist/services/server-instructions.d.ts +10 -0
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -0
- package/mcp-server/dist/services/server-instructions.js +101 -0
- package/mcp-server/dist/services/server-instructions.js.map +1 -0
- package/mcp-server/dist/services/sqlite.d.ts +235 -0
- package/mcp-server/dist/services/sqlite.d.ts.map +1 -0
- package/mcp-server/dist/services/sqlite.js +1185 -0
- package/mcp-server/dist/services/sqlite.js.map +1 -0
- package/mcp-server/dist/services/typesafe-bench.d.ts +99 -0
- package/mcp-server/dist/services/typesafe-bench.d.ts.map +1 -0
- package/mcp-server/dist/services/typesafe-bench.js +218 -0
- package/mcp-server/dist/services/typesafe-bench.js.map +1 -0
- package/mcp-server/dist/services/typesafe-config.d.ts +342 -0
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -0
- package/mcp-server/dist/services/typesafe-config.js +746 -0
- package/mcp-server/dist/services/typesafe-config.js.map +1 -0
- package/mcp-server/dist/services/typesafe.d.ts +213 -0
- package/mcp-server/dist/services/typesafe.d.ts.map +1 -0
- package/mcp-server/dist/services/typesafe.js +330 -0
- package/mcp-server/dist/services/typesafe.js.map +1 -0
- package/mcp-server/dist/services/vector-cache.d.ts +68 -0
- package/mcp-server/dist/services/vector-cache.d.ts.map +1 -0
- package/mcp-server/dist/services/vector-cache.js +271 -0
- package/mcp-server/dist/services/vector-cache.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +305 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.js +666 -0
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -0
- package/mcp-server/dist/tools/agents.d.ts +212 -0
- package/mcp-server/dist/tools/agents.d.ts.map +1 -0
- package/mcp-server/dist/tools/agents.js +407 -0
- package/mcp-server/dist/tools/agents.js.map +1 -0
- package/mcp-server/dist/tools/bluesky.d.ts +39 -0
- package/mcp-server/dist/tools/bluesky.d.ts.map +1 -0
- package/mcp-server/dist/tools/bluesky.js +1144 -0
- package/mcp-server/dist/tools/bluesky.js.map +1 -0
- package/mcp-server/dist/tools/browser-advanced.d.ts +58 -0
- package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-advanced.js +106 -0
- package/mcp-server/dist/tools/browser-advanced.js.map +1 -0
- package/mcp-server/dist/tools/browser-batch.d.ts +99 -0
- package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-batch.js +187 -0
- package/mcp-server/dist/tools/browser-batch.js.map +1 -0
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts +21 -0
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-cheatsheet.js +144 -0
- package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -0
- package/mcp-server/dist/tools/browser-interact.d.ts +239 -0
- package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-interact.js +300 -0
- package/mcp-server/dist/tools/browser-interact.js.map +1 -0
- package/mcp-server/dist/tools/browser-navigate.d.ts +90 -0
- package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-navigate.js +142 -0
- package/mcp-server/dist/tools/browser-navigate.js.map +1 -0
- package/mcp-server/dist/tools/browser-observe.d.ts +561 -0
- package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-observe.js +2009 -0
- package/mcp-server/dist/tools/browser-observe.js.map +1 -0
- package/mcp-server/dist/tools/browser.d.ts +34 -0
- package/mcp-server/dist/tools/browser.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser.js +112 -0
- package/mcp-server/dist/tools/browser.js.map +1 -0
- package/mcp-server/dist/tools/card-tools.d.ts +82 -0
- package/mcp-server/dist/tools/card-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/card-tools.js +137 -0
- package/mcp-server/dist/tools/card-tools.js.map +1 -0
- package/mcp-server/dist/tools/card.d.ts +8 -0
- package/mcp-server/dist/tools/card.d.ts.map +1 -0
- package/mcp-server/dist/tools/card.js +16 -0
- package/mcp-server/dist/tools/card.js.map +1 -0
- package/mcp-server/dist/tools/category.d.ts +32 -0
- package/mcp-server/dist/tools/category.d.ts.map +1 -0
- package/mcp-server/dist/tools/category.js +290 -0
- package/mcp-server/dist/tools/category.js.map +1 -0
- package/mcp-server/dist/tools/choice.d.ts +160 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -0
- package/mcp-server/dist/tools/choice.js +80 -0
- package/mcp-server/dist/tools/choice.js.map +1 -0
- package/mcp-server/dist/tools/computer.d.ts +71 -0
- package/mcp-server/dist/tools/computer.d.ts.map +1 -0
- package/mcp-server/dist/tools/computer.js +174 -0
- package/mcp-server/dist/tools/computer.js.map +1 -0
- package/mcp-server/dist/tools/discord-channel.d.ts +44 -0
- package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-channel.js +236 -0
- package/mcp-server/dist/tools/discord-channel.js.map +1 -0
- package/mcp-server/dist/tools/discord-guild.d.ts +20 -0
- package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-guild.js +138 -0
- package/mcp-server/dist/tools/discord-guild.js.map +1 -0
- package/mcp-server/dist/tools/discord-member.d.ts +26 -0
- package/mcp-server/dist/tools/discord-member.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-member.js +122 -0
- package/mcp-server/dist/tools/discord-member.js.map +1 -0
- package/mcp-server/dist/tools/discord-message.d.ts +36 -0
- package/mcp-server/dist/tools/discord-message.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-message.js +184 -0
- package/mcp-server/dist/tools/discord-message.js.map +1 -0
- package/mcp-server/dist/tools/discord-onboarding.d.ts +32 -0
- package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-onboarding.js +179 -0
- package/mcp-server/dist/tools/discord-onboarding.js.map +1 -0
- package/mcp-server/dist/tools/discord-role.d.ts +34 -0
- package/mcp-server/dist/tools/discord-role.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-role.js +178 -0
- package/mcp-server/dist/tools/discord-role.js.map +1 -0
- package/mcp-server/dist/tools/discord-thread.d.ts +28 -0
- package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-thread.js +88 -0
- package/mcp-server/dist/tools/discord-thread.js.map +1 -0
- package/mcp-server/dist/tools/discord-webhook.d.ts +32 -0
- package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-webhook.js +154 -0
- package/mcp-server/dist/tools/discord-webhook.js.map +1 -0
- package/mcp-server/dist/tools/discord.d.ts +7 -0
- package/mcp-server/dist/tools/discord.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord.js +25 -0
- package/mcp-server/dist/tools/discord.js.map +1 -0
- package/mcp-server/dist/tools/fb-groups.d.ts +98 -0
- package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/fb-groups.js +369 -0
- package/mcp-server/dist/tools/fb-groups.js.map +1 -0
- package/mcp-server/dist/tools/forget.d.ts +14 -0
- package/mcp-server/dist/tools/forget.d.ts.map +1 -0
- package/mcp-server/dist/tools/forget.js +26 -0
- package/mcp-server/dist/tools/forget.js.map +1 -0
- package/mcp-server/dist/tools/git-tools.d.ts +24 -0
- package/mcp-server/dist/tools/git-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/git-tools.js +107 -0
- package/mcp-server/dist/tools/git-tools.js.map +1 -0
- package/mcp-server/dist/tools/git.d.ts +7 -0
- package/mcp-server/dist/tools/git.d.ts.map +1 -0
- package/mcp-server/dist/tools/git.js +11 -0
- package/mcp-server/dist/tools/git.js.map +1 -0
- package/mcp-server/dist/tools/gsc/experience.d.ts +135 -0
- package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/experience.js +351 -0
- package/mcp-server/dist/tools/gsc/experience.js.map +1 -0
- package/mcp-server/dist/tools/gsc/helpers.d.ts +78 -0
- package/mcp-server/dist/tools/gsc/helpers.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/helpers.js +258 -0
- package/mcp-server/dist/tools/gsc/helpers.js.map +1 -0
- package/mcp-server/dist/tools/gsc/indexing.d.ts +121 -0
- package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/indexing.js +357 -0
- package/mcp-server/dist/tools/gsc/indexing.js.map +1 -0
- package/mcp-server/dist/tools/gsc/inspect.d.ts +78 -0
- package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/inspect.js +321 -0
- package/mcp-server/dist/tools/gsc/inspect.js.map +1 -0
- package/mcp-server/dist/tools/gsc/navigate.d.ts +44 -0
- package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/navigate.js +197 -0
- package/mcp-server/dist/tools/gsc/navigate.js.map +1 -0
- package/mcp-server/dist/tools/gsc/performance.d.ts +84 -0
- package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/performance.js +274 -0
- package/mcp-server/dist/tools/gsc/performance.js.map +1 -0
- package/mcp-server/dist/tools/gsc/settings.d.ts +153 -0
- package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/settings.js +425 -0
- package/mcp-server/dist/tools/gsc/settings.js.map +1 -0
- package/mcp-server/dist/tools/gsc.d.ts +7 -0
- package/mcp-server/dist/tools/gsc.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc.js +51 -0
- package/mcp-server/dist/tools/gsc.js.map +1 -0
- package/mcp-server/dist/tools/image-tools.d.ts +24 -0
- package/mcp-server/dist/tools/image-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/image-tools.js +80 -0
- package/mcp-server/dist/tools/image-tools.js.map +1 -0
- package/mcp-server/dist/tools/image.d.ts +10 -0
- package/mcp-server/dist/tools/image.d.ts.map +1 -0
- package/mcp-server/dist/tools/image.js +14 -0
- package/mcp-server/dist/tools/image.js.map +1 -0
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +97 -0
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/leonardo-browser-tools.js +228 -0
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -0
- package/mcp-server/dist/tools/leonardo.d.ts +13 -0
- package/mcp-server/dist/tools/leonardo.d.ts.map +1 -0
- package/mcp-server/dist/tools/leonardo.js +21 -0
- package/mcp-server/dist/tools/leonardo.js.map +1 -0
- package/mcp-server/dist/tools/loop.d.ts +30 -0
- package/mcp-server/dist/tools/loop.d.ts.map +1 -0
- package/mcp-server/dist/tools/loop.js +401 -0
- package/mcp-server/dist/tools/loop.js.map +1 -0
- package/mcp-server/dist/tools/memories.d.ts +56 -0
- package/mcp-server/dist/tools/memories.d.ts.map +1 -0
- package/mcp-server/dist/tools/memories.js +171 -0
- package/mcp-server/dist/tools/memories.js.map +1 -0
- package/mcp-server/dist/tools/morelogin.d.ts +24 -0
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -0
- package/mcp-server/dist/tools/morelogin.js +79 -0
- package/mcp-server/dist/tools/morelogin.js.map +1 -0
- package/mcp-server/dist/tools/profile.d.ts +17 -0
- package/mcp-server/dist/tools/profile.d.ts.map +1 -0
- package/mcp-server/dist/tools/profile.js +114 -0
- package/mcp-server/dist/tools/profile.js.map +1 -0
- package/mcp-server/dist/tools/recall.d.ts +71 -0
- package/mcp-server/dist/tools/recall.d.ts.map +1 -0
- package/mcp-server/dist/tools/recall.js +147 -0
- package/mcp-server/dist/tools/recall.js.map +1 -0
- package/mcp-server/dist/tools/reflect.d.ts +56 -0
- package/mcp-server/dist/tools/reflect.d.ts.map +1 -0
- package/mcp-server/dist/tools/reflect.js +157 -0
- package/mcp-server/dist/tools/reflect.js.map +1 -0
- package/mcp-server/dist/tools/remember.d.ts +62 -0
- package/mcp-server/dist/tools/remember.d.ts.map +1 -0
- package/mcp-server/dist/tools/remember.js +201 -0
- package/mcp-server/dist/tools/remember.js.map +1 -0
- package/mcp-server/dist/tools/response.d.ts +23 -0
- package/mcp-server/dist/tools/response.d.ts.map +1 -0
- package/mcp-server/dist/tools/response.js +24 -0
- package/mcp-server/dist/tools/response.js.map +1 -0
- package/mcp-server/dist/tools/restore.d.ts +14 -0
- package/mcp-server/dist/tools/restore.d.ts.map +1 -0
- package/mcp-server/dist/tools/restore.js +23 -0
- package/mcp-server/dist/tools/restore.js.map +1 -0
- package/mcp-server/dist/tools/style-guide-tools.d.ts +51 -0
- package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/style-guide-tools.js +231 -0
- package/mcp-server/dist/tools/style-guide-tools.js.map +1 -0
- package/mcp-server/dist/tools/style-guide.d.ts +8 -0
- package/mcp-server/dist/tools/style-guide.d.ts.map +1 -0
- package/mcp-server/dist/tools/style-guide.js +12 -0
- package/mcp-server/dist/tools/style-guide.js.map +1 -0
- package/mcp-server/dist/tools/sync.d.ts +20 -0
- package/mcp-server/dist/tools/sync.d.ts.map +1 -0
- package/mcp-server/dist/tools/sync.js +156 -0
- package/mcp-server/dist/tools/sync.js.map +1 -0
- package/mcp-server/dist/tools/tictactoe-tools.d.ts +18 -0
- package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/tictactoe-tools.js +78 -0
- package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -0
- package/mcp-server/dist/tools/tictactoe.d.ts +7 -0
- package/mcp-server/dist/tools/tictactoe.d.ts.map +1 -0
- package/mcp-server/dist/tools/tictactoe.js +11 -0
- package/mcp-server/dist/tools/tictactoe.js.map +1 -0
- package/mcp-server/dist/tools/utils.d.ts +19 -0
- package/mcp-server/dist/tools/utils.d.ts.map +1 -0
- package/mcp-server/dist/tools/utils.js +54 -0
- package/mcp-server/dist/tools/utils.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts +88 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.js +337 -0
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +326 -0
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/whiteboard-tools.js +222 -0
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard.d.ts +8 -0
- package/mcp-server/dist/tools/whiteboard.d.ts.map +1 -0
- package/mcp-server/dist/tools/whiteboard.js +16 -0
- package/mcp-server/dist/tools/whiteboard.js.map +1 -0
- package/mcp-server/dist/tools/youtube/analytics.d.ts +46 -0
- package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/analytics.js +83 -0
- package/mcp-server/dist/tools/youtube/analytics.js.map +1 -0
- package/mcp-server/dist/tools/youtube/dedup.d.ts +35 -0
- package/mcp-server/dist/tools/youtube/dedup.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/dedup.js +56 -0
- package/mcp-server/dist/tools/youtube/dedup.js.map +1 -0
- package/mcp-server/dist/tools/youtube/download.d.ts +31 -0
- package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/download.js +30 -0
- package/mcp-server/dist/tools/youtube/download.js.map +1 -0
- package/mcp-server/dist/tools/youtube/helpers.d.ts +66 -0
- package/mcp-server/dist/tools/youtube/helpers.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/helpers.js +143 -0
- package/mcp-server/dist/tools/youtube/helpers.js.map +1 -0
- package/mcp-server/dist/tools/youtube/media.d.ts +43 -0
- package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/media.js +97 -0
- package/mcp-server/dist/tools/youtube/media.js.map +1 -0
- package/mcp-server/dist/tools/youtube/metadata.d.ts +33 -0
- package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/metadata.js +106 -0
- package/mcp-server/dist/tools/youtube/metadata.js.map +1 -0
- package/mcp-server/dist/tools/youtube/navigate.d.ts +30 -0
- package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/navigate.js +71 -0
- package/mcp-server/dist/tools/youtube/navigate.js.map +1 -0
- package/mcp-server/dist/tools/youtube/playlist.d.ts +29 -0
- package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/playlist.js +128 -0
- package/mcp-server/dist/tools/youtube/playlist.js.map +1 -0
- package/mcp-server/dist/tools/youtube/schedule.d.ts +31 -0
- package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/schedule.js +147 -0
- package/mcp-server/dist/tools/youtube/schedule.js.map +1 -0
- package/mcp-server/dist/tools/youtube/source.d.ts +36 -0
- package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/source.js +85 -0
- package/mcp-server/dist/tools/youtube/source.js.map +1 -0
- package/mcp-server/dist/tools/youtube/upload.d.ts +53 -0
- package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/upload.js +255 -0
- package/mcp-server/dist/tools/youtube/upload.js.map +1 -0
- package/mcp-server/dist/tools/youtube.d.ts +12 -0
- package/mcp-server/dist/tools/youtube.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube.js +38 -0
- package/mcp-server/dist/tools/youtube.js.map +1 -0
- package/mcp-server/dist/tui.d.ts +3 -0
- package/mcp-server/dist/tui.d.ts.map +1 -0
- package/mcp-server/dist/tui.js +806 -0
- package/mcp-server/dist/tui.js.map +1 -0
- package/mcp-server/dist/types.d.ts +77 -0
- package/mcp-server/dist/types.d.ts.map +1 -0
- package/mcp-server/dist/types.js +2 -0
- package/mcp-server/dist/types.js.map +1 -0
- package/mcp-server/package-lock.json +4798 -0
- package/mcp-server/package.json +55 -38
- package/mcp-server/run.mjs +12 -2
- package/mcp-server/src/config.ts +48 -3
- package/mcp-server/src/http.ts +296 -93
- package/mcp-server/src/index.ts +546 -203
- package/mcp-server/src/preload-http.ts +19 -18
- package/mcp-server/src/preload.ts +19 -18
- package/mcp-server/src/scripts/setup-embeddings.ts +9 -0
- package/mcp-server/src/services/assistant-judgments.ts +157 -0
- package/mcp-server/src/services/browser-assist-gate.ts +383 -0
- package/mcp-server/src/services/browser-assist.ts +267 -0
- package/mcp-server/src/services/browser-bench.ts +477 -0
- package/mcp-server/src/services/browser-judgments.ts +414 -0
- package/mcp-server/src/services/browser-risk.ts +484 -0
- package/mcp-server/src/services/categories.ts +36 -19
- package/mcp-server/src/services/codex-config-heal.ts +403 -0
- package/mcp-server/src/services/desktop-assist-gate.ts +251 -0
- package/mcp-server/src/services/desktop-assist.ts +308 -0
- package/mcp-server/src/services/desktop-bench.ts +372 -0
- package/mcp-server/src/services/desktop-client.ts +76 -0
- package/mcp-server/src/services/desktop-judgments.ts +218 -0
- package/mcp-server/src/services/desktop-risk.ts +743 -0
- package/mcp-server/src/services/edit-stale.ts +299 -0
- package/mcp-server/src/services/embedding-service.ts +108 -0
- package/mcp-server/src/services/embedding-worker.ts +46 -0
- package/mcp-server/src/services/fb-regions.ts +221 -0
- package/mcp-server/src/services/file-checksums.ts +26 -9
- package/mcp-server/src/services/file-excerpt.ts +59 -0
- package/mcp-server/src/services/identity.ts +291 -0
- package/mcp-server/src/services/local-embeddings.ts +2 -77
- package/mcp-server/src/services/memory-client.ts +23 -0
- package/mcp-server/src/services/memory-judge-backfill.ts +462 -0
- package/mcp-server/src/services/memory-judgments.ts +901 -0
- package/mcp-server/src/services/memory-maintenance.ts +360 -0
- package/mcp-server/src/services/memory-retrieval.ts +349 -0
- package/mcp-server/src/services/memory-schema.ts +280 -0
- package/mcp-server/src/services/memory-sessions.ts +87 -0
- package/mcp-server/src/services/memory-triage.ts +182 -0
- package/mcp-server/src/services/memory-verification.ts +68 -0
- package/mcp-server/src/services/memory-writer.ts +125 -0
- package/mcp-server/src/services/neural-interface.ts +1217 -73
- package/mcp-server/src/services/profiles.ts +428 -0
- package/mcp-server/src/services/secret-gate.ts +52 -0
- package/mcp-server/src/services/server-instructions.ts +122 -0
- package/mcp-server/src/services/sqlite.ts +1456 -823
- package/mcp-server/src/services/typesafe-bench.ts +247 -0
- package/mcp-server/src/services/typesafe-config.ts +790 -0
- package/mcp-server/src/services/typesafe.ts +390 -0
- package/mcp-server/src/services/vector-cache.ts +255 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +711 -0
- package/mcp-server/src/tools/agents.ts +539 -0
- package/mcp-server/src/tools/bluesky.ts +1186 -0
- package/mcp-server/src/tools/browser-advanced.ts +15 -6
- package/mcp-server/src/tools/browser-batch.ts +178 -0
- package/mcp-server/src/tools/browser-cheatsheet.ts +155 -0
- package/mcp-server/src/tools/browser-interact.ts +362 -255
- package/mcp-server/src/tools/browser-navigate.ts +164 -77
- package/mcp-server/src/tools/browser-observe.ts +2222 -1330
- package/mcp-server/src/tools/browser.ts +181 -107
- package/mcp-server/src/tools/card.ts +7 -5
- package/mcp-server/src/tools/choice.ts +103 -0
- package/mcp-server/src/tools/computer.ts +190 -0
- package/mcp-server/src/tools/discord.ts +10 -23
- package/mcp-server/src/tools/fb-groups.ts +387 -0
- package/mcp-server/src/tools/forget.ts +1 -1
- package/mcp-server/src/tools/git.ts +3 -1
- package/mcp-server/src/tools/gsc/experience.ts +440 -0
- package/mcp-server/src/tools/gsc/helpers.ts +288 -0
- package/mcp-server/src/tools/gsc/indexing.ts +495 -0
- package/mcp-server/src/tools/gsc/inspect.ts +391 -0
- package/mcp-server/src/tools/gsc/navigate.ts +236 -0
- package/mcp-server/src/tools/gsc/performance.ts +352 -0
- package/mcp-server/src/tools/gsc/settings.ts +564 -0
- package/mcp-server/src/tools/gsc.ts +95 -0
- package/mcp-server/src/tools/image.ts +3 -1
- package/mcp-server/src/tools/leonardo-browser-tools.ts +47 -37
- package/mcp-server/src/tools/leonardo.ts +7 -5
- package/mcp-server/src/tools/loop.ts +118 -25
- package/mcp-server/src/tools/memories.ts +65 -5
- package/mcp-server/src/tools/morelogin.ts +81 -0
- package/mcp-server/src/tools/profile.ts +122 -0
- package/mcp-server/src/tools/recall.ts +105 -261
- package/mcp-server/src/tools/reflect.ts +171 -145
- package/mcp-server/src/tools/remember.ts +223 -115
- package/mcp-server/src/tools/style-guide-tools.ts +250 -0
- package/mcp-server/src/tools/style-guide.ts +16 -0
- package/mcp-server/src/tools/sync.ts +66 -9
- package/mcp-server/src/tools/tictactoe.ts +3 -1
- package/mcp-server/src/tools/utils.ts +22 -0
- package/mcp-server/src/tools/whiteboard-format.ts +394 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +191 -183
- package/mcp-server/src/tools/whiteboard.ts +7 -5
- package/mcp-server/src/tools/youtube/analytics.ts +106 -0
- package/mcp-server/src/tools/youtube/dedup.ts +57 -0
- package/mcp-server/src/tools/youtube/download.ts +41 -0
- package/mcp-server/src/tools/youtube/helpers.ts +183 -0
- package/mcp-server/src/tools/youtube/media.ts +109 -0
- package/mcp-server/src/tools/youtube/metadata.ts +112 -0
- package/mcp-server/src/tools/youtube/navigate.ts +81 -0
- package/mcp-server/src/tools/youtube/playlist.ts +134 -0
- package/mcp-server/src/tools/youtube/schedule.ts +158 -0
- package/mcp-server/src/tools/youtube/source.ts +101 -0
- package/mcp-server/src/tools/youtube/upload.ts +296 -0
- package/mcp-server/src/tools/youtube.ts +47 -0
- package/mcp-server/src/tui.ts +8 -9
- package/neural-interface/games/TicTacToe/1.svg +1 -0
- package/neural-interface/games/TicTacToe/2.svg +1 -0
- package/neural-interface/games/TicTacToe/3.svg +1 -0
- package/neural-interface/games/TicTacToe/4.svg +1 -0
- package/neural-interface/games/TicTacToe/5.svg +1 -0
- package/neural-interface/games/TicTacToe/6.svg +1 -0
- package/neural-interface/games/TicTacToe/7.svg +1 -0
- package/neural-interface/games/TicTacToe/8.svg +1 -0
- package/neural-interface/games/TicTacToe/9.svg +1 -0
- package/neural-interface/games/TicTacToe/Board.svg +1 -0
- package/neural-interface/games/TicTacToe/Circle.svg +1 -0
- package/neural-interface/games/TicTacToe/Cross.svg +1 -0
- package/neural-interface/i18n/en.json +7022 -0
- package/neural-interface/i18n/pt-BR.json +7026 -0
- package/neural-interface/lib/assistant-api.js +1005 -0
- package/neural-interface/lib/assistant-attachments.js +121 -0
- package/neural-interface/lib/assistant-brains/claude.js +207 -0
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +43 -0
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +56 -0
- package/neural-interface/lib/assistant-brains/codex.js +213 -0
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +47 -0
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +62 -0
- package/neural-interface/lib/assistant-brains/opencode.js +320 -0
- package/neural-interface/lib/assistant-budget.js +318 -0
- package/neural-interface/lib/assistant-catalog.js +731 -0
- package/neural-interface/lib/assistant-clarify.js +664 -0
- package/neural-interface/lib/assistant-config.js +232 -0
- package/neural-interface/lib/assistant-dispatch.js +2744 -0
- package/neural-interface/lib/assistant-envelope.js +582 -0
- package/neural-interface/lib/assistant-evidence.js +236 -0
- package/neural-interface/lib/assistant-hidden-models.js +272 -0
- package/neural-interface/lib/assistant-jev.js +117 -0
- package/neural-interface/lib/assistant-judgments.js +111 -0
- package/neural-interface/lib/assistant-media.js +335 -0
- package/neural-interface/lib/assistant-memory.js +398 -0
- package/neural-interface/lib/assistant-persona.js +444 -0
- package/neural-interface/lib/assistant-plan-permissions.js +448 -0
- package/neural-interface/lib/assistant-playbooks/design.md +87 -0
- package/neural-interface/lib/assistant-playbooks.js +72 -0
- package/neural-interface/lib/assistant-pricing.js +159 -0
- package/neural-interface/lib/assistant-route-gate-codex.js +163 -0
- package/neural-interface/lib/assistant-route-gate.js +212 -0
- package/neural-interface/lib/assistant-router.js +1324 -0
- package/neural-interface/lib/assistant-runtime.js +3337 -0
- package/neural-interface/lib/assistant-task-prompt.js +472 -0
- package/neural-interface/lib/assistant-usage.js +1688 -0
- package/neural-interface/lib/augmented-path.js +70 -0
- package/neural-interface/lib/automation-storage-migration.js +54 -0
- package/neural-interface/lib/backup-service.js +851 -0
- package/neural-interface/lib/backup-zip-reader.js +262 -0
- package/neural-interface/lib/browser-assist-api.js +41 -0
- package/neural-interface/lib/browser-console.js +176 -0
- package/neural-interface/lib/browser-execution.js +241 -0
- package/neural-interface/lib/browser-extraction.js +191 -0
- package/neural-interface/lib/browser-language.js +83 -0
- package/neural-interface/lib/browser-screenshot.js +399 -0
- package/neural-interface/lib/browser-semantic-context.js +639 -0
- package/neural-interface/lib/browser-snapshots.js +192 -0
- package/neural-interface/lib/browser-tab-recovery.js +119 -0
- package/neural-interface/lib/browser-targets.js +70 -0
- package/neural-interface/lib/browser-tool-policy.js +926 -0
- package/neural-interface/lib/claude-accounts.js +230 -0
- package/neural-interface/lib/claude-agent-bridge.js +3203 -0
- package/neural-interface/lib/claude-bypass-policy.js +99 -0
- package/neural-interface/lib/claude-executable.js +241 -0
- package/neural-interface/lib/claude-history.js +429 -0
- package/neural-interface/lib/claude-model-catalog.js +444 -0
- package/neural-interface/lib/claude-panel-requests.js +165 -0
- package/neural-interface/lib/claude-panel-session.js +272 -0
- package/neural-interface/lib/claude-permission-rules.js +153 -0
- package/neural-interface/lib/claude-session-cleanup.js +71 -0
- package/neural-interface/lib/claude-session-list.js +82 -0
- package/neural-interface/lib/claude-session-ops.js +140 -0
- package/neural-interface/lib/claude-settings-view.js +51 -0
- package/neural-interface/lib/claude-skin-connect.js +79 -0
- package/neural-interface/lib/claude-stall-policy.js +7 -0
- package/neural-interface/lib/claude-temporary.js +435 -0
- package/neural-interface/lib/cli-update-planner.js +312 -0
- package/neural-interface/lib/codex-capabilities.d.ts +41 -0
- package/neural-interface/lib/codex-capabilities.js +148 -0
- package/neural-interface/lib/codex-collaboration-mode.js +17 -0
- package/neural-interface/lib/codex-compat-rpc.js +241 -0
- package/neural-interface/lib/codex-mcp-approvals.js +56 -0
- package/neural-interface/lib/codex-mcp-apps.js +47 -0
- package/neural-interface/lib/codex-model-context.js +202 -0
- package/neural-interface/lib/codex-plan-store.js +71 -0
- package/neural-interface/lib/codex-request-journal.js +242 -0
- package/neural-interface/lib/codex-runtime-path.js +213 -0
- package/neural-interface/lib/codex-session-catalog.js +231 -0
- package/neural-interface/lib/codex-session-settings.js +46 -0
- package/neural-interface/lib/codex-sidepanel-permissions.js +110 -0
- package/neural-interface/lib/codex-thread-history.js +165 -0
- package/neural-interface/lib/codex-user-verification.js +34 -0
- package/neural-interface/lib/codex-writer-lifecycle.js +152 -0
- package/neural-interface/lib/confined-fs.js +256 -0
- package/neural-interface/lib/db.js +922 -0
- package/neural-interface/lib/design-md.js +9 -0
- package/neural-interface/lib/desktop/api.js +92 -0
- package/neural-interface/lib/desktop/audit.js +62 -0
- package/neural-interface/lib/desktop/build.js +423 -0
- package/neural-interface/lib/desktop/config.js +606 -0
- package/neural-interface/lib/desktop/coords.js +76 -0
- package/neural-interface/lib/desktop/fake-helper.js +1192 -0
- package/neural-interface/lib/desktop/frames.js +77 -0
- package/neural-interface/lib/desktop/grants.js +84 -0
- package/neural-interface/lib/desktop/guards.js +170 -0
- package/neural-interface/lib/desktop/helper/SynabunDesktop.swift +3056 -0
- package/neural-interface/lib/desktop/lease.js +51 -0
- package/neural-interface/lib/desktop/manager.js +545 -0
- package/neural-interface/lib/desktop/press-contexts.js +103 -0
- package/neural-interface/lib/desktop/protocol.js +495 -0
- package/neural-interface/lib/desktop/service.js +1202 -0
- package/neural-interface/lib/effort-levels.js +148 -0
- package/neural-interface/lib/event-loop-monitor.js +67 -0
- package/neural-interface/lib/exec-loop-command.js +209 -0
- package/neural-interface/lib/folder-picker.ps1 +77 -0
- package/neural-interface/lib/http-guards.js +287 -0
- package/neural-interface/lib/jsonl-lines.js +94 -0
- package/neural-interface/lib/loop-logger.js +127 -0
- package/neural-interface/lib/mcp-client-config.js +133 -0
- package/neural-interface/lib/mcp-installer.js +487 -0
- package/neural-interface/lib/mcp-profile-refresh-coordinator.js +78 -0
- package/neural-interface/lib/mcp-profile-servers.js +120 -0
- package/neural-interface/lib/memo-cache.js +84 -0
- package/neural-interface/lib/memory-api.js +758 -0
- package/neural-interface/lib/memory-map-api.js +1150 -0
- package/neural-interface/lib/memory-map-layout.js +872 -0
- package/neural-interface/lib/memory-map-worker.js +165 -0
- package/neural-interface/lib/memory-session-capture.js +81 -0
- package/neural-interface/lib/memory-timers.js +62 -0
- package/neural-interface/lib/morelogin.js +301 -0
- package/neural-interface/lib/native-binary-runtime.js +382 -0
- package/neural-interface/lib/native-loop-goal.js +33 -0
- package/neural-interface/lib/native-loop-providers.js +1002 -0
- package/neural-interface/lib/native-loop-runtime.js +747 -0
- package/neural-interface/lib/offline-page.js +54 -0
- package/neural-interface/lib/opencode-hidden-models.js +113 -0
- package/neural-interface/lib/opencode-history.js +253 -0
- package/neural-interface/lib/opencode-runtime-config.js +58 -0
- package/neural-interface/lib/opencode-v2-client.js +486 -0
- package/neural-interface/lib/opencode-v2-ws-requests.js +955 -0
- package/neural-interface/lib/path-confine.js +213 -0
- package/neural-interface/lib/plugin-installer.js +264 -0
- package/neural-interface/lib/project-storage.js +766 -0
- package/neural-interface/lib/pty-host/core.js +603 -0
- package/neural-interface/lib/pty-host/flow.js +43 -0
- package/neural-interface/lib/pty-host/host.js +47 -0
- package/neural-interface/lib/pty-host/manager.js +360 -0
- package/neural-interface/lib/pty-host/protocol.js +81 -0
- package/neural-interface/lib/pty-host/snapshot.js +133 -0
- package/neural-interface/lib/remote-policy.js +582 -0
- package/neural-interface/lib/rulesets/api.js +175 -0
- package/neural-interface/lib/rulesets/fs-safe.js +245 -0
- package/neural-interface/lib/rulesets/installer.js +1453 -0
- package/neural-interface/lib/rulesets/json-edit.js +186 -0
- package/neural-interface/lib/rulesets/lock.js +170 -0
- package/neural-interface/lib/rulesets/managed-text.js +314 -0
- package/neural-interface/lib/rulesets/normalise.js +24 -0
- package/neural-interface/lib/rulesets/render.js +84 -0
- package/neural-interface/lib/schedule-launch-intent.js +40 -0
- package/neural-interface/lib/session-cache-builder.js +524 -0
- package/neural-interface/lib/session-chunker.js +344 -0
- package/neural-interface/lib/session-indexer.js +327 -0
- package/neural-interface/lib/session-title-generator.js +232 -0
- package/neural-interface/lib/skill-prompt-bridge.js +11 -0
- package/neural-interface/lib/style-guide/api.js +375 -0
- package/neural-interface/lib/style-guide/color.js +352 -0
- package/neural-interface/lib/style-guide/export-css.js +99 -0
- package/neural-interface/lib/style-guide/export-dtcg.js +209 -0
- package/neural-interface/lib/style-guide/export-tailwind.js +102 -0
- package/neural-interface/lib/style-guide/fonts.json +227 -0
- package/neural-interface/lib/style-guide/import.js +1268 -0
- package/neural-interface/lib/style-guide/pointers.js +103 -0
- package/neural-interface/lib/style-guide/presets.js +232 -0
- package/neural-interface/lib/style-guide/render-design-md.js +319 -0
- package/neural-interface/lib/style-guide/schema.js +848 -0
- package/neural-interface/lib/style-guide/security.js +43 -0
- package/neural-interface/lib/style-guide/store.js +491 -0
- package/neural-interface/lib/style-guide/summary.js +106 -0
- package/neural-interface/lib/style-guide/tokens.js +170 -0
- package/neural-interface/lib/synabun-update-plan.js +324 -0
- package/neural-interface/lib/synabun-version.js +179 -0
- package/neural-interface/lib/system-restore.js +99 -0
- package/neural-interface/lib/terminal-env.js +86 -0
- package/neural-interface/lib/ui-blob-store.js +106 -0
- package/neural-interface/lib/vector-cache.js +2 -0
- package/neural-interface/lib/virtual-socket.js +60 -0
- package/neural-interface/lib/whatsapp/api.js +146 -0
- package/neural-interface/lib/whatsapp/auth-store.js +389 -0
- package/neural-interface/lib/whatsapp/baileys-adapter.js +457 -0
- package/neural-interface/lib/whatsapp/brain.js +128 -0
- package/neural-interface/lib/whatsapp/bridge.js +1499 -0
- package/neural-interface/lib/whatsapp/cards.js +519 -0
- package/neural-interface/lib/whatsapp/commands.js +92 -0
- package/neural-interface/lib/whatsapp/config.js +291 -0
- package/neural-interface/lib/whatsapp/connector/entry.mjs +25 -0
- package/neural-interface/lib/whatsapp/connector/manifest.json +6 -0
- package/neural-interface/lib/whatsapp/connector/npm-shrinkwrap.json +717 -0
- package/neural-interface/lib/whatsapp/connector/package.json +8 -0
- package/neural-interface/lib/whatsapp/fake-baileys.js +444 -0
- package/neural-interface/lib/whatsapp/format.js +552 -0
- package/neural-interface/lib/whatsapp/host-core.js +1225 -0
- package/neural-interface/lib/whatsapp/host.js +119 -0
- package/neural-interface/lib/whatsapp/identity.js +288 -0
- package/neural-interface/lib/whatsapp/inbound.js +149 -0
- package/neural-interface/lib/whatsapp/installer.js +562 -0
- package/neural-interface/lib/whatsapp/manager.js +638 -0
- package/neural-interface/lib/whatsapp/paths.js +81 -0
- package/neural-interface/lib/whatsapp/protocol.js +327 -0
- package/neural-interface/lib/whatsapp/qr-svg.js +57 -0
- package/neural-interface/lib/whatsapp/redact.js +130 -0
- package/neural-interface/lib/whatsapp/service.js +1305 -0
- package/neural-interface/lib/whatsapp/vendor/qrcodegen.js +848 -0
- package/neural-interface/package-lock.json +4431 -0
- package/neural-interface/package.json +58 -27
- package/neural-interface/public/apple-touch-icon.png +0 -0
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude.svg +1 -0
- package/neural-interface/public/favicon-16x16.png +0 -0
- package/neural-interface/public/favicon-32x32.png +0 -0
- package/neural-interface/public/icon-192.png +0 -0
- package/neural-interface/public/icon-512.png +0 -0
- package/neural-interface/public/index.html +32 -182
- package/neural-interface/public/index2d.html +2 -2
- package/neural-interface/public/install-app.html +31 -0
- package/neural-interface/public/logo-synabun.png +0 -0
- package/neural-interface/public/logoHD.png +0 -0
- package/neural-interface/public/manifest.json +2 -0
- package/neural-interface/public/mcp.svg +4 -0
- package/neural-interface/public/offline.html +670 -299
- package/neural-interface/public/onboarding.html +804 -161
- package/neural-interface/public/opencode-logo-dark-square.svg +1 -0
- package/neural-interface/public/shared/agent-runtime-options.js +596 -0
- package/neural-interface/public/shared/api.js +339 -28
- package/neural-interface/public/shared/assistant/asst-accounts.js +318 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +296 -0
- package/neural-interface/public/shared/assistant/asst-api.js +270 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +744 -0
- package/neural-interface/public/shared/assistant/asst-budget.js +333 -0
- package/neural-interface/public/shared/assistant/asst-composer.js +656 -0
- package/neural-interface/public/shared/assistant/asst-computer.js +803 -0
- package/neural-interface/public/shared/assistant/asst-control.js +1018 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +262 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +313 -0
- package/neural-interface/public/shared/assistant/asst-menu.js +375 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +1845 -0
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +586 -0
- package/neural-interface/public/shared/assistant/asst-render.js +3305 -0
- package/neural-interface/public/shared/assistant/asst-route.js +1653 -0
- package/neural-interface/public/shared/assistant/asst-roving.js +75 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +117 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +488 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +675 -0
- package/neural-interface/public/shared/assistant/asst-slash.js +258 -0
- package/neural-interface/public/shared/assistant/asst-state.js +727 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +1686 -0
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +1017 -0
- package/neural-interface/public/shared/assistant/asst-usage.js +444 -0
- package/neural-interface/public/shared/assistant/asst-ws.js +175 -0
- package/neural-interface/public/shared/automation-context.js +75 -0
- package/neural-interface/public/shared/blob-store.js +99 -0
- package/neural-interface/public/shared/cdx/cdx-capabilities.js +118 -0
- package/neural-interface/public/shared/cdx/cdx-cards.js +37 -0
- package/neural-interface/public/shared/cdx/cdx-commands.js +171 -0
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +402 -0
- package/neural-interface/public/shared/cdx/cdx-context-model.js +220 -0
- package/neural-interface/public/shared/cdx/cdx-features.js +408 -0
- package/neural-interface/public/shared/cdx/cdx-icons.js +89 -0
- package/neural-interface/public/shared/cdx/cdx-mcp-app-host.js +414 -0
- package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.html +6 -0
- package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.js +56 -0
- package/neural-interface/public/shared/cdx/cdx-mcp-app.js +178 -0
- package/neural-interface/public/shared/cdx/cdx-model-catalog.js +120 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +261 -0
- package/neural-interface/public/shared/cdx/cdx-output.js +382 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +1372 -0
- package/neural-interface/public/shared/cdx/cdx-plan.js +266 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +682 -0
- package/neural-interface/public/shared/cdx/cdx-render.js +3381 -0
- package/neural-interface/public/shared/cdx/cdx-requests.js +1136 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +3251 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +7992 -0
- package/neural-interface/public/shared/cdx/cdx-ws.js +363 -0
- package/neural-interface/public/shared/cli-status.js +169 -0
- package/neural-interface/public/shared/command-runner-model.js +30 -0
- package/neural-interface/public/shared/constants.js +22 -5
- package/neural-interface/public/shared/cp/cp-agents.js +186 -0
- package/neural-interface/public/shared/cp/cp-bash.js +252 -0
- package/neural-interface/public/shared/cp/cp-compose.js +159 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +309 -0
- package/neural-interface/public/shared/cp/cp-context-model.js +157 -0
- package/neural-interface/public/shared/cp/cp-context-window.js +80 -0
- package/neural-interface/public/shared/cp/cp-ctx.js +11 -0
- package/neural-interface/public/shared/cp/cp-diff.js +255 -0
- package/neural-interface/public/shared/cp/cp-event-rows.js +637 -0
- package/neural-interface/public/shared/cp/cp-events.js +846 -0
- package/neural-interface/public/shared/cp/cp-markdown.js +104 -0
- package/neural-interface/public/shared/cp/cp-permission-model.js +905 -0
- package/neural-interface/public/shared/cp/cp-permissions.js +626 -0
- package/neural-interface/public/shared/cp/cp-rehydrate.js +113 -0
- package/neural-interface/public/shared/cp/cp-restore.js +224 -0
- package/neural-interface/public/shared/cp/cp-session-model.js +338 -0
- package/neural-interface/public/shared/cp/cp-session-settings.js +254 -0
- package/neural-interface/public/shared/cp/cp-sessions.js +227 -0
- package/neural-interface/public/shared/cp/cp-statusline.js +153 -0
- package/neural-interface/public/shared/cp/cp-styles.js +349 -0
- package/neural-interface/public/shared/cp/cp-tasks-model.js +282 -0
- package/neural-interface/public/shared/cp/cp-tasks.js +107 -0
- package/neural-interface/public/shared/cp/cp-temporary-view.js +56 -0
- package/neural-interface/public/shared/cp/cp-temporary.js +231 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +326 -0
- package/neural-interface/public/shared/cp/cp-tool-results.js +618 -0
- package/neural-interface/public/shared/cp/cp-usage-model.js +124 -0
- package/neural-interface/public/shared/graph-activity.js +68 -0
- package/neural-interface/public/shared/html-shell.js +116 -17
- package/neural-interface/public/shared/i18n.js +132 -34
- package/neural-interface/public/shared/node-positions-store.js +44 -0
- package/neural-interface/public/shared/ocp-hidden-models.js +116 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +355 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-autoaccept-button.js +35 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-automation-ownership.js +72 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +280 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +67 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +179 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes.js +164 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +725 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +75 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +1204 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm-logic.js +117 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm.js +59 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-gauge.js +252 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +478 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +179 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +233 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-icons.js +30 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +1065 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-mention-hints.js +182 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +655 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +2636 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +601 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-profile-inheritance.js +78 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +649 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +35 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +357 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render-logic.js +189 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1869 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-select.js +42 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +117 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +1714 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +468 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-widgets.js +63 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +366 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-settings-link.js +19 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +343 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +746 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +347 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +338 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +3543 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-subagents-logic.js +482 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-tools-logic.js +351 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-tools.js +168 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-variantpicker.js +315 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +545 -0
- package/neural-interface/public/shared/provider-icons.js +30 -0
- package/neural-interface/public/shared/pwa-install-page.js +34 -0
- package/neural-interface/public/shared/pwa-install.css +12 -0
- package/neural-interface/public/shared/pwa-install.js +28 -0
- package/neural-interface/public/shared/session-title.js +100 -0
- package/neural-interface/public/shared/settings/settings-ia.js +389 -0
- package/neural-interface/public/shared/settings/settings-kit.js +234 -0
- package/neural-interface/public/shared/settings/settings-rules.js +160 -0
- package/neural-interface/public/shared/settings/settings-search.js +463 -0
- package/neural-interface/public/shared/settings/settings-shell.js +570 -0
- package/neural-interface/public/shared/start-bridge.js +583 -0
- package/neural-interface/public/shared/state.js +5 -0
- package/neural-interface/public/shared/storage.js +50 -7
- package/neural-interface/public/shared/styleguide/sg-agents.js +42 -0
- package/neural-interface/public/shared/styleguide/sg-brand.js +4 -0
- package/neural-interface/public/shared/styleguide/sg-color-client.js +388 -0
- package/neural-interface/public/shared/styleguide/sg-colors.js +151 -0
- package/neural-interface/public/shared/styleguide/sg-components.js +35 -0
- package/neural-interface/public/shared/styleguide/sg-history.js +41 -0
- package/neural-interface/public/shared/styleguide/sg-icons-logo.js +38 -0
- package/neural-interface/public/shared/styleguide/sg-imagery.js +4 -0
- package/neural-interface/public/shared/styleguide/sg-import-export.js +48 -0
- package/neural-interface/public/shared/styleguide/sg-kit.js +284 -0
- package/neural-interface/public/shared/styleguide/sg-labels.js +318 -0
- package/neural-interface/public/shared/styleguide/sg-layout.js +4 -0
- package/neural-interface/public/shared/styleguide/sg-motion.js +5 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +237 -0
- package/neural-interface/public/shared/styleguide/sg-preview.js +138 -0
- package/neural-interface/public/shared/styleguide/sg-rules.js +9 -0
- package/neural-interface/public/shared/styleguide/sg-shape.js +24 -0
- package/neural-interface/public/shared/styleguide/sg-state.js +132 -0
- package/neural-interface/public/shared/styleguide/sg-typography.js +125 -0
- package/neural-interface/public/shared/styles.css +8614 -1477
- package/neural-interface/public/shared/synabun-mascot.js +1625 -0
- package/neural-interface/public/shared/synabun-ticker.js +376 -0
- package/neural-interface/public/shared/term-ready.js +307 -0
- package/neural-interface/public/shared/term-renderer.js +343 -0
- package/neural-interface/public/shared/ui-assistant-panel.js +10 -0
- package/neural-interface/public/shared/ui-assistant.js +77 -0
- package/neural-interface/public/shared/ui-automation-studio.js +5105 -3573
- package/neural-interface/public/shared/ui-claude-panel.js +11685 -6333
- package/neural-interface/public/shared/ui-codex-panel.js +12 -0
- package/neural-interface/public/shared/ui-command-runner.js +251 -468
- package/neural-interface/public/shared/ui-cost-widget.js +55 -34
- package/neural-interface/public/shared/ui-detail.js +77 -14
- package/neural-interface/public/shared/ui-explorer.js +360 -181
- package/neural-interface/public/shared/ui-file-explorer.js +485 -107
- package/neural-interface/public/shared/ui-help.js +2 -2
- package/neural-interface/public/shared/ui-image-gallery.js +45 -21
- package/neural-interface/public/shared/ui-judgments.js +1346 -0
- package/neural-interface/public/shared/ui-keybinds.js +3 -2
- package/neural-interface/public/shared/ui-link.js +105 -51
- package/neural-interface/public/shared/ui-loading.js +222 -11
- package/neural-interface/public/shared/ui-memory-maintenance.js +111 -0
- package/neural-interface/public/shared/ui-menubar.js +100 -31
- package/neural-interface/public/shared/ui-native-loop-router-state.js +94 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +493 -0
- package/neural-interface/public/shared/ui-native-window-id.js +7 -0
- package/neural-interface/public/shared/ui-navbar.js +357 -222
- package/neural-interface/public/shared/ui-notifications.js +58 -12
- package/neural-interface/public/shared/ui-opencode-panel-v2.js +17 -0
- package/neural-interface/public/shared/ui-panels.js +15 -7
- package/neural-interface/public/shared/ui-resume.js +823 -653
- package/neural-interface/public/shared/ui-schedule-queue.js +172 -0
- package/neural-interface/public/shared/ui-schedules-studio.js +1882 -0
- package/neural-interface/public/shared/ui-scroll-follow.js +194 -0
- package/neural-interface/public/shared/ui-sessions.js +789 -34
- package/neural-interface/public/shared/ui-settings.js +5774 -2194
- package/neural-interface/public/shared/ui-sidepanel-layout.js +109 -0
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +19 -0
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +40 -0
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +221 -0
- package/neural-interface/public/shared/ui-sidepanel-state.js +131 -0
- package/neural-interface/public/shared/ui-sidepanel-tray.js +547 -0
- package/neural-interface/public/shared/ui-sidepanel-windows.js +387 -0
- package/neural-interface/public/shared/ui-skills.js +18 -32
- package/neural-interface/public/shared/ui-styleguide.js +35 -0
- package/neural-interface/public/shared/ui-sync.js +87 -0
- package/neural-interface/public/shared/ui-terminal.js +7863 -6385
- package/neural-interface/public/shared/ui-tool-updates.js +292 -0
- package/neural-interface/public/shared/ui-tooltip.js +156 -15
- package/neural-interface/public/shared/ui-tutorial-draw.js +119 -683
- package/neural-interface/public/shared/ui-tutorial-layout.js +135 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +59 -2209
- package/neural-interface/public/shared/ui-tutorial.css +102 -0
- package/neural-interface/public/shared/ui-tutorial.js +557 -1076
- package/neural-interface/public/shared/ui-update.js +420 -68
- package/neural-interface/public/shared/ui-whatsapp.js +941 -0
- package/neural-interface/public/shared/ui-whiteboard.js +1076 -233
- package/neural-interface/public/shared/ui-workspaces.js +2 -1
- package/neural-interface/public/shared/utils.js +58 -0
- package/neural-interface/public/shared/vterm-buffer.js +15 -5
- package/neural-interface/public/shared/whatsapp/wa-phone.js +65 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +411 -0
- package/neural-interface/public/sidepanel-session.html +18 -0
- package/neural-interface/public/sw.js +10 -2
- package/neural-interface/public/synabun.png +0 -0
- package/neural-interface/public/variant/2d/graph.js +1707 -1635
- package/neural-interface/public/variant/2d/lasso.js +2 -1
- package/neural-interface/public/variant/2d/layout.js +595 -594
- package/neural-interface/public/variant/2d/main.js +557 -521
- package/neural-interface/public/variant/2d/settings-gfx.js +18 -24
- package/neural-interface/public/variant/3d/main.js +517 -500
- package/neural-interface/public/variant/3d/map-controls.js +144 -0
- package/neural-interface/public/variant/3d/map-data.js +251 -0
- package/neural-interface/public/variant/3d/map-labels.js +241 -0
- package/neural-interface/public/variant/3d/map-pick.js +90 -0
- package/neural-interface/public/variant/3d/map-renderer.js +521 -0
- package/neural-interface/public/variant/3d/map-shaders.js +158 -0
- package/neural-interface/public/variant/3d/map-view.js +618 -0
- package/neural-interface/public/vendor/three/manifest.json +3 -0
- package/neural-interface/public/vendor/three/three-0.169.0.min.js +3844 -0
- package/neural-interface/public/vendor/xterm/manifest.json +3 -0
- package/neural-interface/public/vendor/xterm/xterm-6.0.0.css +285 -0
- package/neural-interface/public/vendor/xterm/xterm-6.0.0.min.js +192 -0
- package/neural-interface/scripts/build-vendor.mjs +64 -0
- package/neural-interface/scripts/codex-protocol-fixture.mjs +83 -0
- package/neural-interface/scripts/rebuild-pty.js +39 -22
- package/neural-interface/scripts/vendor-three-entry.js +24 -0
- package/neural-interface/scripts/vendor-xterm-entry.js +8 -0
- package/neural-interface/server.js +33983 -15000
- package/neural-interface/skins/default/skin.css +90 -0
- package/neural-interface/skins/default/skin.json +8 -0
- package/neural-interface/templates/rulesets/README.md +9 -0
- package/neural-interface/templates/rulesets/claude.md +3 -0
- package/neural-interface/templates/rulesets/codex.md +2 -0
- package/neural-interface/templates/rulesets/coexistence.md +6 -0
- package/neural-interface/templates/rulesets/core.md +26 -0
- package/neural-interface/templates/rulesets/cursor.md +2 -0
- package/neural-interface/templates/rulesets/gemini.md +2 -0
- package/neural-interface/templates/rulesets/legacy-hashes.json +227 -0
- package/neural-interface/templates/rulesets/manifest.json +12 -0
- package/neural-interface/templates/rulesets/opencode.md +2 -0
- package/package.json +131 -53
- package/postinstall.js +55 -26
- package/preuninstall.js +187 -0
- package/scripts/restore-data-home-from-backup.mjs +239 -0
- package/setup.js +872 -260
- package/skills/leonardo/SKILL.md +19 -10
- package/skills/leonardo/modules/image-prompter.md +18 -12
- package/skills/leonardo/modules/video-prompter.md +19 -13
- package/skills/synabun/SKILL.md +209 -185
- package/skills/synabun/icon.png +0 -0
- package/skills/synabun/modules/audit.md +380 -374
- package/skills/synabun/modules/changelog.md +15 -6
- package/skills/synabun/modules/idea.md +148 -144
- package/skills/synabun/modules/memorize.md +13 -4
- package/skills/synabun/modules/schedule.md +198 -0
- package/skills/synabun/synabunicon.png +0 -0
- package/updater.cmd +31 -0
- package/updater.mjs +382 -0
- package/updater.sh +29 -0
- package/hooks/claude-code/test-user-learning.mjs +0 -531
- package/neural-interface/public/claude-chat.css +0 -878
- package/neural-interface/public/claude-chat.html +0 -92
- package/neural-interface/public/claude-chat.js +0 -1168
- package/neural-interface/public/index-legacy.html +0 -14677
- package/neural-interface/public/index2d-legacy.html +0 -8193
- package/neural-interface/public/shared/html-term-renderer.js +0 -1022
- package/neural-interface/public/variant/3d/background.js +0 -884
- package/neural-interface/public/variant/3d/camera.js +0 -661
- package/neural-interface/public/variant/3d/gfx.js +0 -116
- package/neural-interface/public/variant/3d/graph.js +0 -2399
- package/neural-interface/public/variant/3d/settings-gfx.js +0 -228
- package/neural-interface/templates/CLAUDE-template.md +0 -211
|
@@ -0,0 +1,1305 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — WhatsApp Link service (the composition root)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// Builds and wires the pieces: the config store (kv `whatsapp_config`), the
|
|
6
|
+
// connector installer and the host manager (Package A), the bridge to the
|
|
7
|
+
// Assistant (Package B) over a transport adapter on the manager, and the
|
|
8
|
+
// /api/whatsapp router (api.js). Nothing here loads Baileys or knows a phone
|
|
9
|
+
// number: the host (a forked child) owns both.
|
|
10
|
+
//
|
|
11
|
+
// The Settings tab reads one state machine:
|
|
12
|
+
// unavailable | not_installed | installing | install_failed | ready |
|
|
13
|
+
// linking (method qr|code; phase starting|waiting|scanned) | link_expired |
|
|
14
|
+
// confirm_owner (kind self|code) | connected | reconnecting | paused |
|
|
15
|
+
// logged_out (reason removed|inactive|banned) | error (code)
|
|
16
|
+
//
|
|
17
|
+
// Secrets keep to one path each: a link QR or pairing code only goes down the
|
|
18
|
+
// NDJSON response of the tab that asked for it, the claim code and wa.me link
|
|
19
|
+
// only down the claim stream, the ALLOW code only in the PUT /config answer.
|
|
20
|
+
// Broadcasts go through toSyncPayload() alone: states and counters, never a
|
|
21
|
+
// QR, SVG, code, number, name or message text. The activity log (a memory
|
|
22
|
+
// ring + DATA_HOME/data/logs/whatsapp-YYYYMMDD.log, 0600, 7 days) passes
|
|
23
|
+
// everything through redactWa and keeps message text only while
|
|
24
|
+
// `activityText` is on.
|
|
25
|
+
|
|
26
|
+
import { randomInt, timingSafeEqual } from 'node:crypto';
|
|
27
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from 'node:fs';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import { createWhatsAppApi } from './api.js';
|
|
30
|
+
import { brainChoiceView, checkBrainChoice, cleanBrainChoice } from './brain.js';
|
|
31
|
+
import { createWhatsAppBridge, outboxPlan } from './bridge.js';
|
|
32
|
+
import { parseCommand } from './commands.js';
|
|
33
|
+
import { createWhatsAppConfigStore, currentLevel, MODES, SERVICE_DEFAULTS, USER_DEFAULTS, USER_FIELDS } from './config.js';
|
|
34
|
+
import { chunk, toWhatsApp } from './format.js';
|
|
35
|
+
import { composePrompt, createCoalescer } from './inbound.js';
|
|
36
|
+
import { createWhatsAppInstaller, NPM_CI_ARGS } from './installer.js';
|
|
37
|
+
import { createWhatsAppManager } from './manager.js';
|
|
38
|
+
import { isInside, resolveWhatsAppPaths } from './paths.js';
|
|
39
|
+
import { renderQrSvg } from './qr-svg.js';
|
|
40
|
+
import { maskNumber, redactWa } from './redact.js';
|
|
41
|
+
import { providerLabel, remoteBrainLimited, remoteBrainNotice } from '../remote-policy.js';
|
|
42
|
+
import { normalizePhone, PHONE_ERRORS } from '../../public/shared/whatsapp/wa-phone.js';
|
|
43
|
+
|
|
44
|
+
const MINUTE = 60_000;
|
|
45
|
+
const HOUR = 60 * MINUTE;
|
|
46
|
+
const DAY = 24 * HOUR;
|
|
47
|
+
|
|
48
|
+
export const SERVICE_LIMITS = Object.freeze({
|
|
49
|
+
linkTimeoutMs: 5 * MINUTE, // a link stream gives up after this
|
|
50
|
+
claimTtlMs: 10 * MINUTE, // a dedicated-mode claim code lives this long
|
|
51
|
+
escalationMs: 5 * MINUTE, // the phone has this long to answer ALLOW <code>
|
|
52
|
+
escalationTries: 3,
|
|
53
|
+
escalationEchoMs: 10 * MINUTE, // a late ALLOW after this is passed on as text
|
|
54
|
+
autonomousMs: 8 * HOUR,
|
|
55
|
+
testEveryMs: 20_000,
|
|
56
|
+
activityMax: 200,
|
|
57
|
+
logRetentionDays: 7,
|
|
58
|
+
broadcastDebounceMs: 150,
|
|
59
|
+
inactiveAfterMs: 13 * DAY, // logged out after this long without a connection reads as "inactive"
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export const TEST_MESSAGE = 'SynaBun test message. If you can read this, WhatsApp is working.';
|
|
63
|
+
|
|
64
|
+
const STATES = Object.freeze(['unavailable', 'not_installed', 'installing', 'install_failed', 'ready', 'linking', 'link_expired', 'confirm_owner', 'connected', 'reconnecting', 'paused', 'logged_out', 'error']);
|
|
65
|
+
export const TAB_STATES = STATES;
|
|
66
|
+
|
|
67
|
+
const globalTimers = {
|
|
68
|
+
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
69
|
+
clearTimeout: (id) => clearTimeout(id),
|
|
70
|
+
setInterval: (fn, ms) => setInterval(fn, ms),
|
|
71
|
+
clearInterval: (id) => clearInterval(id),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** An error the API answers as {ok:false, code, error, field?} with `status`. */
|
|
75
|
+
export function whatsappError(status, code, message, field) {
|
|
76
|
+
const error = new Error(message);
|
|
77
|
+
error.status = status;
|
|
78
|
+
error.code = code;
|
|
79
|
+
if (field) error.field = field;
|
|
80
|
+
return error;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const hhmm = (ms) => new Date(ms).toTimeString().slice(0, 5);
|
|
84
|
+
const ymd = (ms) => { const d = new Date(ms); return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`; };
|
|
85
|
+
const pick = (obj, keys) => Object.fromEntries(keys.map((key) => [key, obj[key]]));
|
|
86
|
+
/** "ABCD1234" → "ABCD-1234" (how WhatsApp shows a pairing code). */
|
|
87
|
+
export function formatPairingCode(code) {
|
|
88
|
+
const clean = String(code || '').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
|
89
|
+
return clean.length === 8 ? `${clean.slice(0, 4)}-${clean.slice(4)}` : clean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {object} deps
|
|
94
|
+
* @param {string} deps.dataHome
|
|
95
|
+
* @param {number|()=>number} deps.port the Neural Interface port (Host/Origin guards)
|
|
96
|
+
* @param {() => object|null} deps.getRuntime the Assistant runtime
|
|
97
|
+
* @param {() => object|null} [deps.getDispatcher]
|
|
98
|
+
* @param {(key:string)=>string|null} deps.getKvConfig
|
|
99
|
+
* @param {(key:string, value:string)=>void} deps.setKvConfig
|
|
100
|
+
* @param {(message:object)=>void} [deps.broadcastSync]
|
|
101
|
+
* @param {(req)=>boolean} [deps.isGuestRequest]
|
|
102
|
+
* @param {Function} [deps.managerFactory] (opts) → createWhatsAppManager-shaped object
|
|
103
|
+
* @param {Function} [deps.installerFactory] (opts) → createWhatsAppInstaller-shaped object
|
|
104
|
+
* @param {Function} [deps.bridgeFactory] (deps) → createWhatsAppBridge-shaped object
|
|
105
|
+
* @param {() => object|null} [deps.getDefaultBrain] the Assistant panel's brain ("Same as the Assistant")
|
|
106
|
+
* @param {() => object|null} [deps.getCatalog] the Assistant's model catalog ({ peek(), full() }): the WhatsApp brain is checked against it
|
|
107
|
+
*/
|
|
108
|
+
export function createWhatsAppService({
|
|
109
|
+
dataHome,
|
|
110
|
+
port = 3344,
|
|
111
|
+
getRuntime = () => null,
|
|
112
|
+
getDispatcher = () => null,
|
|
113
|
+
getKvConfig,
|
|
114
|
+
setKvConfig,
|
|
115
|
+
broadcastSync = () => {},
|
|
116
|
+
isGuestRequest = () => false,
|
|
117
|
+
log = () => {},
|
|
118
|
+
managerFactory = (opts) => createWhatsAppManager(opts),
|
|
119
|
+
installerFactory = (opts) => createWhatsAppInstaller(opts),
|
|
120
|
+
bridgeFactory = (deps) => createWhatsAppBridge(deps),
|
|
121
|
+
// { issue, revoke } of the phone's authority (lib/remote-policy.js createPhoneAuthority), from the composition root.
|
|
122
|
+
phoneAuthority = null,
|
|
123
|
+
env = process.env,
|
|
124
|
+
platform = process.platform,
|
|
125
|
+
paths: pathsOverride = null,
|
|
126
|
+
// Folders a backup copies besides DATA_HOME/data and mcp-data (server.js' additional entries).
|
|
127
|
+
extraBackupRoots = [],
|
|
128
|
+
policy = undefined,
|
|
129
|
+
getDefaultBrain = null,
|
|
130
|
+
getCatalog = null,
|
|
131
|
+
rng = undefined,
|
|
132
|
+
bridgeLimits = undefined,
|
|
133
|
+
now = Date.now,
|
|
134
|
+
timers = globalTimers,
|
|
135
|
+
limits: limitOverrides = {},
|
|
136
|
+
} = {}) {
|
|
137
|
+
if (typeof dataHome !== 'string' || !dataHome) throw new TypeError('createWhatsAppService requires dataHome');
|
|
138
|
+
const L = { ...SERVICE_LIMITS, ...(limitOverrides || {}) };
|
|
139
|
+
const T = { ...globalTimers, ...(timers || {}) };
|
|
140
|
+
const disabled = String(env.SYNABUN_WHATSAPP || '').trim().toLowerCase() === 'off';
|
|
141
|
+
const fake = env.SYNABUN_WHATSAPP_FAKE === '1';
|
|
142
|
+
const paths = pathsOverride || resolveWhatsAppPaths({ dataHome, env, platform, extraBackupRoots });
|
|
143
|
+
const say = (message, level = 'info') => { try { log(`[whatsapp] ${redactWa(message)}`, level); } catch {} };
|
|
144
|
+
// A refused SYNABUN_WHATSAPP_HOME (inside a backed-up folder) says so once, in the server log.
|
|
145
|
+
for (const warning of paths.warnings || []) say(warning, 'warn');
|
|
146
|
+
|
|
147
|
+
const config = createWhatsAppConfigStore({ get: getKvConfig, set: setKvConfig, now, log: (tag, msg) => say(`${tag}: ${msg}`, 'warn') });
|
|
148
|
+
const installer = disabled ? null : installerFactory({ dataHome, paths, env, platform, log: (msg, level) => say(msg, level), now });
|
|
149
|
+
const manager = disabled ? null : managerFactory({ dataHome, paths, env, platform, log: (msg, level) => say(msg, level), now });
|
|
150
|
+
|
|
151
|
+
const state = {
|
|
152
|
+
started: false,
|
|
153
|
+
stopping: false,
|
|
154
|
+
install: null, // { controller, stage, startedAt, update }
|
|
155
|
+
installError: null, // { code, message, at }
|
|
156
|
+
installCache: null, // { at, value }
|
|
157
|
+
link: null, // { method, phase, shown, stream, timer }
|
|
158
|
+
linkOutcome: null, // { state:'link_expired', at } while nothing is linked
|
|
159
|
+
claim: null, // { stream, attemptsLeft }
|
|
160
|
+
fatal: null, // { code, message, at }
|
|
161
|
+
escalation: null, // { code, expiresAt, attemptsLeft, timer }
|
|
162
|
+
escalationEndedAt: 0,
|
|
163
|
+
autonomousTimer: null,
|
|
164
|
+
retentionTimer: null,
|
|
165
|
+
lastConn: null, // last conn snapshot (for transitions)
|
|
166
|
+
lastStateAt: 0,
|
|
167
|
+
connectedAt: 0,
|
|
168
|
+
reconnectAttempt: 0,
|
|
169
|
+
wasConnected: false,
|
|
170
|
+
testAt: 0,
|
|
171
|
+
notesSkipped: 0, // self-chat notes without the "sb" trigger (never logged)
|
|
172
|
+
bridge: null,
|
|
173
|
+
rev: 0,
|
|
174
|
+
lastSignature: '',
|
|
175
|
+
broadcastTimer: null,
|
|
176
|
+
logFile: null,
|
|
177
|
+
offs: [],
|
|
178
|
+
};
|
|
179
|
+
const activity = [];
|
|
180
|
+
|
|
181
|
+
// ── small readers ──────────────────────────────────────────────────────────
|
|
182
|
+
function installerStatus() {
|
|
183
|
+
if (!installer) return { installed: false, version: null, pinned: null, outdated: false, path: null, installedAt: null, approxSizeMB: null };
|
|
184
|
+
if (state.installCache && now() - state.installCache.at < 2000) return state.installCache.value;
|
|
185
|
+
let value;
|
|
186
|
+
try { value = installer.status(); } catch (error) { value = { installed: false, version: null, pinned: null, outdated: false, error: error?.message }; }
|
|
187
|
+
state.installCache = { at: now(), value };
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
function managerStatus() {
|
|
191
|
+
const empty = { host: { state: 'stopped', pid: null, restarts: 0, lastError: null }, conn: { state: 'idle', mode: null, registered: false, me: null, owner: null, lastDisconnect: null, retryInMs: null, counters: {}, paused: false, awaitingConfirmUntil: null, claim: null, lastError: null }, runtime: { loaded: false, version: null, fake: false, error: null } };
|
|
192
|
+
if (!manager) return empty;
|
|
193
|
+
try { const s = manager.status(); return { host: { ...empty.host, ...(s?.host || {}) }, conn: { ...empty.conn, ...(s?.conn || {}) }, runtime: { ...empty.runtime, ...(s?.runtime || {}) } }; }
|
|
194
|
+
catch { return empty; }
|
|
195
|
+
}
|
|
196
|
+
const hostRunning = () => ['starting', 'running', 'backoff'].includes(managerStatus().host.state);
|
|
197
|
+
const connectedNow = () => managerStatus().conn.state === 'open';
|
|
198
|
+
/** A device is linked (the host says so, or the config remembers a link this process has not seen yet). */
|
|
199
|
+
function isLinked(cfg = config.read(), conn = managerStatus().conn) {
|
|
200
|
+
if (conn.registered === true && !['unlinked', 'logged_out', 'link_expired'].includes(conn.state)) return true;
|
|
201
|
+
if (['unlinked', 'logged_out', 'forbidden', 'link_expired'].includes(conn.state) && state.lastConn) return false;
|
|
202
|
+
return !!cfg.owner || !!cfg.setup?.linkedAt;
|
|
203
|
+
}
|
|
204
|
+
// The host's own "sb" filter stays off (selfTrigger 'all'): it would drop /stop,
|
|
205
|
+
// /status and card answers too. selfTrigger 'prefix' is applied here (applyTrigger).
|
|
206
|
+
function prefsOf(cfg) { return { mode: cfg.mode, selfTrigger: 'all' }; }
|
|
207
|
+
function configureHost(cfg = config.read()) {
|
|
208
|
+
if (!manager) return;
|
|
209
|
+
Promise.resolve(manager.configure?.(prefsOf(cfg))).catch(() => {});
|
|
210
|
+
}
|
|
211
|
+
function replyPrefix(cfg = config.read()) {
|
|
212
|
+
if (cfg.replyLabel) return cfg.replyLabel;
|
|
213
|
+
return cfg.mode === 'dedicated' ? '' : 'SynaBun:';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── the tab's state machine ────────────────────────────────────────────────
|
|
217
|
+
function logoutReason(cfg) {
|
|
218
|
+
const last = Math.max(state.connectedAt || 0, Number(cfg.setup?.connectedAt) || 0);
|
|
219
|
+
return last && now() - last > L.inactiveAfterMs ? 'inactive' : 'removed';
|
|
220
|
+
}
|
|
221
|
+
function errorCodeOf(conn) {
|
|
222
|
+
if (state.fatal?.code && state.fatal.code !== 'LOGGED_OUT') return state.fatal.code;
|
|
223
|
+
const reason = String(conn.lastError || '');
|
|
224
|
+
if (reason === 'reconnect_limit') return 'RECONNECT_LIMIT';
|
|
225
|
+
if (reason === 'pairing_code_failed') return 'PAIRING_FAILED';
|
|
226
|
+
return 'SESSION_ERROR';
|
|
227
|
+
}
|
|
228
|
+
function tabState() {
|
|
229
|
+
if (disabled) return { state: 'unavailable', reason: 'env' };
|
|
230
|
+
if (state.install) return { state: 'installing', stage: state.install.stage || 'checking' };
|
|
231
|
+
const inst = installerStatus();
|
|
232
|
+
if (!inst.installed) return state.installError ? { state: 'install_failed', code: state.installError.code } : { state: 'not_installed' };
|
|
233
|
+
if (state.link) return { state: 'linking', method: state.link.method, phase: state.link.phase };
|
|
234
|
+
const cfg = config.read();
|
|
235
|
+
const { host, conn } = managerStatus();
|
|
236
|
+
if (host.state === 'held') return { state: 'error', code: 'HOST_HELD' };
|
|
237
|
+
if (conn.paused === true && conn.state === 'open') return { state: 'error', code: 'OWNER_ANOMALY' };
|
|
238
|
+
switch (conn.state) {
|
|
239
|
+
case 'link_expired': return { state: 'link_expired' };
|
|
240
|
+
case 'awaiting_confirm': return { state: 'confirm_owner', kind: 'self' };
|
|
241
|
+
case 'open':
|
|
242
|
+
if (cfg.mode === 'dedicated' && !conn.owner?.bound) return { state: 'confirm_owner', kind: 'code' };
|
|
243
|
+
if (cfg.paused || !cfg.enabled) return { state: 'paused' };
|
|
244
|
+
return { state: 'connected' };
|
|
245
|
+
case 'connecting':
|
|
246
|
+
case 'reconnecting':
|
|
247
|
+
return { state: 'reconnecting' };
|
|
248
|
+
case 'logged_out': return { state: 'logged_out', reason: cfg.setup?.lastEnd?.reason || logoutReason(cfg) };
|
|
249
|
+
case 'forbidden': return { state: 'logged_out', reason: 'banned' };
|
|
250
|
+
case 'replaced': return { state: 'error', code: 'REPLACED' };
|
|
251
|
+
case 'locked': return { state: 'error', code: 'LOCKED' };
|
|
252
|
+
case 'error': return { state: 'error', code: errorCodeOf(conn) };
|
|
253
|
+
default: break;
|
|
254
|
+
}
|
|
255
|
+
if (isLinked(cfg, conn)) {
|
|
256
|
+
if (!cfg.enabled || cfg.paused) return { state: 'paused' };
|
|
257
|
+
if (state.fatal && !hostRunning()) return { state: 'error', code: state.fatal.code };
|
|
258
|
+
return { state: 'reconnecting' };
|
|
259
|
+
}
|
|
260
|
+
if (state.linkOutcome?.state === 'link_expired') return { state: 'link_expired' };
|
|
261
|
+
const end = cfg.setup?.lastEnd;
|
|
262
|
+
if (end?.state === 'logged_out') return { state: 'logged_out', reason: end.reason };
|
|
263
|
+
return { state: 'ready' };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ── broadcasts ─────────────────────────────────────────────────────────────
|
|
267
|
+
/** The only thing that ever leaves through broadcastSync. */
|
|
268
|
+
function toSyncPayload() {
|
|
269
|
+
const cfg = config.read();
|
|
270
|
+
const tab = tabState();
|
|
271
|
+
const inst = installerStatus();
|
|
272
|
+
const conn = managerStatus().conn;
|
|
273
|
+
const level = currentLevel(cfg, now());
|
|
274
|
+
const counters = {};
|
|
275
|
+
for (const [key, value] of Object.entries(conn.counters || {})) if (Number.isFinite(value)) counters[key] = value;
|
|
276
|
+
return {
|
|
277
|
+
type: 'whatsapp:status',
|
|
278
|
+
v: 1,
|
|
279
|
+
state: tab.state,
|
|
280
|
+
phase: tab.phase || null,
|
|
281
|
+
mode: cfg.mode,
|
|
282
|
+
level,
|
|
283
|
+
levelExpiresAt: level === 'autonomous' ? cfg.autonomousUntil : null,
|
|
284
|
+
paused: cfg.paused,
|
|
285
|
+
pausedBy: cfg.pausedBy,
|
|
286
|
+
// The owner's switch for computer use from WhatsApp (what it allows per level: remoteComputerUse).
|
|
287
|
+
computerUse: cfg.computerUse === true,
|
|
288
|
+
// Which brain runs the WhatsApp conversation: null = same as the Assistant.
|
|
289
|
+
brain: cfg.brain ? { provider: cfg.brain.provider, model: cfg.brain.model, effort: cfg.brain.effort || null } : null,
|
|
290
|
+
rev: state.rev,
|
|
291
|
+
connector: { installed: !!inst.installed, stage: state.install?.stage || null, updateAvailable: !!inst.outdated, errorCode: state.installError?.code || null },
|
|
292
|
+
counters,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function flushBroadcast() {
|
|
296
|
+
state.broadcastTimer = null;
|
|
297
|
+
if (state.stopping) return;
|
|
298
|
+
const payload = toSyncPayload();
|
|
299
|
+
const { rev: _rev, ...rest } = payload;
|
|
300
|
+
const signature = JSON.stringify(rest);
|
|
301
|
+
if (signature === state.lastSignature) return;
|
|
302
|
+
state.lastSignature = signature;
|
|
303
|
+
state.rev += 1;
|
|
304
|
+
payload.rev = state.rev;
|
|
305
|
+
try { broadcastSync(payload); } catch (error) { say(`broadcast failed: ${error?.message || error}`, 'warn'); }
|
|
306
|
+
}
|
|
307
|
+
function notify() {
|
|
308
|
+
if (state.broadcastTimer || state.stopping) return;
|
|
309
|
+
state.broadcastTimer = T.setTimeout(flushBroadcast, L.broadcastDebounceMs);
|
|
310
|
+
state.broadcastTimer?.unref?.();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ── activity log ───────────────────────────────────────────────────────────
|
|
314
|
+
function appendLogLine(entry) {
|
|
315
|
+
try {
|
|
316
|
+
mkdirSync(paths.logsDir, { recursive: true, mode: 0o700 });
|
|
317
|
+
const file = path.join(paths.logsDir, `whatsapp-${ymd(entry.at)}.log`);
|
|
318
|
+
const text = entry.text ? ` | ${entry.text.replace(/\r?\n/g, ' ⏎ ')}` : '';
|
|
319
|
+
appendFileSync(file, `${new Date(entry.at).toISOString()} ${entry.kind} ${entry.detail}${text}\n`, { mode: 0o600 });
|
|
320
|
+
if (state.logFile !== file) { state.logFile = file; try { chmodSync(file, 0o600); } catch {} }
|
|
321
|
+
} catch {}
|
|
322
|
+
}
|
|
323
|
+
/** One activity row. `text` (a message body) is kept only while activityText is on. */
|
|
324
|
+
function record(kind, detail, text = null) {
|
|
325
|
+
const entry = { at: now(), kind, detail: redactWa(String(detail || '')).slice(0, 300) };
|
|
326
|
+
if (text && config.read().activityText) entry.text = redactWa(String(text)).slice(0, 2000);
|
|
327
|
+
activity.push(entry);
|
|
328
|
+
if (activity.length > L.activityMax) activity.splice(0, activity.length - L.activityMax);
|
|
329
|
+
appendLogLine(entry);
|
|
330
|
+
return entry;
|
|
331
|
+
}
|
|
332
|
+
function logFiles() {
|
|
333
|
+
try { return readdirSync(paths.logsDir).filter((name) => /^whatsapp-\d{8}\.log$/.test(name)); } catch { return []; }
|
|
334
|
+
}
|
|
335
|
+
function pruneLogs() {
|
|
336
|
+
const cutoff = ymd(now() - L.logRetentionDays * DAY);
|
|
337
|
+
for (const name of logFiles()) {
|
|
338
|
+
if (name.slice(9, 17) < cutoff) { try { unlinkSync(path.join(paths.logsDir, name)); } catch {} }
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function describeInbound(message) {
|
|
342
|
+
const parts = [];
|
|
343
|
+
if (String(message.text || '').trim()) parts.push('text');
|
|
344
|
+
const images = Array.isArray(message.images) ? message.images.length : 0;
|
|
345
|
+
if (images) parts.push(`${images} image${images === 1 ? '' : 's'}`);
|
|
346
|
+
if (message.unsupported) parts.push(`${String(message.unsupported.type || 'unsupported')} (not supported)`);
|
|
347
|
+
const flags = [message.forwarded ? 'forwarded' : null, message.quoted ? (message.quoted.fromBot ? 'reply to SynaBun' : 'quote') : null].filter(Boolean);
|
|
348
|
+
return `Message from you: ${parts.join(', ') || 'empty'}${flags.length ? ` (${flags.join(', ')})` : ''}`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── transport + bridge ─────────────────────────────────────────────────────
|
|
352
|
+
// Why a refused reply was dropped (the bridge's outboxPlan decides; the phone cannot be told).
|
|
353
|
+
const DROPPED_BECAUSE = {
|
|
354
|
+
PAUSED: 'sending is paused because the owner looked different than expected. See Setup & link',
|
|
355
|
+
HELD: 'the WhatsApp connector kept crashing and was stopped. Press Reconnect',
|
|
356
|
+
};
|
|
357
|
+
const transport = {
|
|
358
|
+
send: async (text, opts = {}) => {
|
|
359
|
+
const result = await manager.send(text, opts);
|
|
360
|
+
if (result?.ok) record('reply', `Sent a message (${String(text).length} characters)`, text);
|
|
361
|
+
else {
|
|
362
|
+
const code = String(result?.code || 'unknown');
|
|
363
|
+
const plan = outboxPlan(code);
|
|
364
|
+
// THROTTLED: the throttled event already says when sending resumes.
|
|
365
|
+
if (plan === 'connect') record('state', `A reply waits until WhatsApp is back (${code})`);
|
|
366
|
+
else if (plan === 'drop') record('error', `A reply was dropped: ${DROPPED_BECAUSE[code] || 'WhatsApp refused it'} (${code})`);
|
|
367
|
+
}
|
|
368
|
+
notify();
|
|
369
|
+
return result;
|
|
370
|
+
},
|
|
371
|
+
react: (id, name) => manager.react(id, name),
|
|
372
|
+
presence: (value) => manager.presence(value),
|
|
373
|
+
markRead: (ids) => manager.markRead(ids),
|
|
374
|
+
connected: () => connectedNow(),
|
|
375
|
+
};
|
|
376
|
+
const bridgeConfig = { read: () => config.read(), write: (patch) => config.write(patch) };
|
|
377
|
+
// Every bridge built gets a fresh capability, which ends the one before; a bridge that is gone
|
|
378
|
+
// leaves none behind. The capability goes straight to the bridge and is never kept here.
|
|
379
|
+
const revokeAuthority = () => { try { phoneAuthority?.revoke?.(); } catch {} };
|
|
380
|
+
function ensureBridge() {
|
|
381
|
+
if (state.bridge || disabled || state.stopping) return state.bridge;
|
|
382
|
+
try {
|
|
383
|
+
state.bridge = bridgeFactory({
|
|
384
|
+
getRuntime, getDispatcher, transport, config: bridgeConfig,
|
|
385
|
+
authority: typeof phoneAuthority?.issue === 'function' ? phoneAuthority.issue() : null,
|
|
386
|
+
format: { toWhatsApp, chunk }, inbound: { composePrompt, createCoalescer },
|
|
387
|
+
// No policy of our own: the bridge uses the process-wide registry (defaultRemotePolicyRegistry).
|
|
388
|
+
...(policy ? { policy } : {}), ...(typeof getDefaultBrain === 'function' ? { getDefaultBrain } : {}),
|
|
389
|
+
...(typeof getCatalog === 'function' ? { getCatalog } : {}),
|
|
390
|
+
...(rng ? { rng } : {}), ...(bridgeLimits ? { limits: bridgeLimits } : {}), now, timers: T,
|
|
391
|
+
log: (tag, message) => say(`${tag} ${message}`),
|
|
392
|
+
});
|
|
393
|
+
} catch (error) {
|
|
394
|
+
say(`the bridge did not start: ${error?.message || error}`, 'warn');
|
|
395
|
+
state.bridge = null;
|
|
396
|
+
revokeAuthority();
|
|
397
|
+
}
|
|
398
|
+
return state.bridge;
|
|
399
|
+
}
|
|
400
|
+
async function dropBridge() {
|
|
401
|
+
const bridge = state.bridge;
|
|
402
|
+
// First the Mac: live computer control of the WhatsApp sessions ends (lib/whatsapp/bridge.js
|
|
403
|
+
// endComputerUse, synchronous) while the bridge still exists. Only then is it dropped.
|
|
404
|
+
try { bridge?.endComputerUse?.('bridge dropped'); } catch (error) { say(`ending computer use: ${error?.message || error}`, 'warn'); }
|
|
405
|
+
state.bridge = null;
|
|
406
|
+
revokeAuthority();
|
|
407
|
+
if (!bridge) return;
|
|
408
|
+
try { await bridge.shutdown?.(); } catch (error) { say(`bridge shutdown: ${error?.message || error}`, 'warn'); }
|
|
409
|
+
}
|
|
410
|
+
function bridgeStatus() { try { return state.bridge?.status?.() || null; } catch { return null; } }
|
|
411
|
+
function refreshBridge() { try { state.bridge?.refresh?.(); } catch {} }
|
|
412
|
+
/** A notice from SynaBun itself (escalation, autonomous expiry): straight to the phone, labelled like the bridge's replies. */
|
|
413
|
+
async function notice(text) {
|
|
414
|
+
if (!manager || !connectedNow()) return false;
|
|
415
|
+
const prefix = replyPrefix();
|
|
416
|
+
const result = await transport.send(prefix ? `${prefix} ${text}` : text).catch(() => null);
|
|
417
|
+
return !!result?.ok;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ── autonomous: the phone's ALLOW code, the 8 h window ────────────────────
|
|
421
|
+
function endEscalation(reason) {
|
|
422
|
+
const esc = state.escalation;
|
|
423
|
+
if (!esc) return;
|
|
424
|
+
T.clearTimeout(esc.timer);
|
|
425
|
+
state.escalation = null;
|
|
426
|
+
state.escalationEndedAt = now();
|
|
427
|
+
if (reason !== 'allowed') record('security', `Autonomous was not turned on (${reason})`);
|
|
428
|
+
notify();
|
|
429
|
+
}
|
|
430
|
+
function startEscalation() {
|
|
431
|
+
if (state.escalation) endEscalation('replaced');
|
|
432
|
+
const code = String(randomInt(0, 10_000)).padStart(4, '0');
|
|
433
|
+
const esc = { code, expiresAt: now() + L.escalationMs, attemptsLeft: L.escalationTries, timer: null };
|
|
434
|
+
esc.timer = T.setTimeout(() => {
|
|
435
|
+
if (state.escalation !== esc) return;
|
|
436
|
+
endEscalation('expired');
|
|
437
|
+
notice('The ALLOW code expired: SynaBun stays on its current level.').catch(() => {});
|
|
438
|
+
}, L.escalationMs);
|
|
439
|
+
esc.timer?.unref?.();
|
|
440
|
+
state.escalation = esc;
|
|
441
|
+
record('security', 'Autonomous requested on this computer; waiting for the ALLOW code from the phone');
|
|
442
|
+
notice('Autonomous was requested on your computer. To turn it on for 8 hours, reply ALLOW followed by the 4-digit code shown in SynaBun, within 5 minutes. Ignore this message if it was not you.').catch(() => {});
|
|
443
|
+
notify();
|
|
444
|
+
return { code, expiresAt: esc.expiresAt, attemptsLeft: esc.attemptsLeft };
|
|
445
|
+
}
|
|
446
|
+
function armAutonomousTimer() {
|
|
447
|
+
T.clearTimeout(state.autonomousTimer);
|
|
448
|
+
state.autonomousTimer = null;
|
|
449
|
+
const cfg = config.read();
|
|
450
|
+
if (cfg.level !== 'autonomous') return;
|
|
451
|
+
const left = (cfg.autonomousUntil || 0) - now();
|
|
452
|
+
if (left <= 0) { expireAutonomous(); return; }
|
|
453
|
+
// setTimeout caps at 2^31-1 ms; 8 h is far below it.
|
|
454
|
+
state.autonomousTimer = T.setTimeout(expireAutonomous, left);
|
|
455
|
+
state.autonomousTimer?.unref?.();
|
|
456
|
+
}
|
|
457
|
+
function expireAutonomous() {
|
|
458
|
+
state.autonomousTimer = null;
|
|
459
|
+
const cfg = config.read();
|
|
460
|
+
if (cfg.level !== 'autonomous') return;
|
|
461
|
+
config.write({ level: 'ask', autonomousUntil: null });
|
|
462
|
+
refreshBridge();
|
|
463
|
+
record('security', 'Autonomous ended; back to Ask on my phone');
|
|
464
|
+
notice('Autonomous ended: I ask on your phone again before acting.').catch(() => {});
|
|
465
|
+
notify();
|
|
466
|
+
}
|
|
467
|
+
const sameCode = (a, b) => { const x = Buffer.from(String(a)); const y = Buffer.from(String(b)); return x.length === y.length && timingSafeEqual(x, y); };
|
|
468
|
+
/** "ALLOW 1234" from the owner: complete, refuse or explain. → true when consumed. */
|
|
469
|
+
function handleAllow(message) {
|
|
470
|
+
const match = /^\s*allow\s+(\d{4})\s*$/i.exec(String(message.text || ''));
|
|
471
|
+
if (!match || message.forwarded || (Array.isArray(message.images) && message.images.length)) return false;
|
|
472
|
+
const esc = state.escalation;
|
|
473
|
+
if (!esc) {
|
|
474
|
+
if (state.escalationEndedAt && now() - state.escalationEndedAt < L.escalationEchoMs) {
|
|
475
|
+
notice('Nothing is waiting for an ALLOW code any more. Ask again from SynaBun on your computer.').catch(() => {});
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
if (now() > esc.expiresAt) {
|
|
481
|
+
endEscalation('expired');
|
|
482
|
+
notice('That ALLOW code expired. Ask again from SynaBun on your computer.').catch(() => {});
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
if (!sameCode(match[1], esc.code)) {
|
|
486
|
+
esc.attemptsLeft -= 1;
|
|
487
|
+
if (esc.attemptsLeft <= 0) {
|
|
488
|
+
endEscalation('wrong code');
|
|
489
|
+
notice('Wrong code three times: Autonomous stays off.').catch(() => {});
|
|
490
|
+
} else {
|
|
491
|
+
notice(`That code does not match. ${esc.attemptsLeft} ${esc.attemptsLeft === 1 ? 'try' : 'tries'} left.`).catch(() => {});
|
|
492
|
+
notify();
|
|
493
|
+
}
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
const until = now() + L.autonomousMs;
|
|
497
|
+
config.write({ level: 'autonomous', autonomousUntil: until });
|
|
498
|
+
endEscalation('allowed');
|
|
499
|
+
refreshBridge();
|
|
500
|
+
armAutonomousTimer();
|
|
501
|
+
record('security', `Autonomous turned on from the phone until ${hhmm(until)}`);
|
|
502
|
+
notice(`Autonomous is on until ${hhmm(until)}. I act without asking for 8 hours, then go back to Ask. Send /pause to stop everything.`).catch(() => {});
|
|
503
|
+
notify();
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ── manager events ─────────────────────────────────────────────────────────
|
|
508
|
+
function onState(snapshot) {
|
|
509
|
+
const conn = snapshot?.conn || {};
|
|
510
|
+
const prev = state.lastConn;
|
|
511
|
+
state.lastConn = conn;
|
|
512
|
+
state.lastStateAt = now();
|
|
513
|
+
const cfg = config.read();
|
|
514
|
+
if (conn.state === 'open' && prev?.state !== 'open') {
|
|
515
|
+
state.connectedAt = now();
|
|
516
|
+
state.reconnectAttempt = 0;
|
|
517
|
+
state.fatal = null;
|
|
518
|
+
config.write({ setup: { ...(cfg.setup || {}), connectedAt: now(), lastEnd: null, ...(conn.registered && !cfg.setup?.linkedAt ? { linkedAt: now() } : {}) } });
|
|
519
|
+
record('state', 'Connected to WhatsApp');
|
|
520
|
+
}
|
|
521
|
+
if (conn.state === 'reconnecting' && prev?.state !== 'reconnecting') {
|
|
522
|
+
state.reconnectAttempt += 1;
|
|
523
|
+
if (prev?.state === 'open') record('state', 'Connection lost; reconnecting');
|
|
524
|
+
}
|
|
525
|
+
// The link's phase follows the host: a QR/code on screen is "waiting", the phone accepting it is "scanned".
|
|
526
|
+
// Terminal states only count once the host took the link request (a fresh host starts out "unlinked").
|
|
527
|
+
const link = state.link;
|
|
528
|
+
if (link?.requested) {
|
|
529
|
+
if (['connecting', 'qr', 'pairing'].includes(conn.state)) link.progressed = true;
|
|
530
|
+
if (link.shown && conn.state === 'connecting' && link.phase !== 'scanned') {
|
|
531
|
+
link.phase = 'scanned';
|
|
532
|
+
link.stream.write({ type: 'state', state: 'linking', phase: 'scanned', method: link.method });
|
|
533
|
+
} else if (link.progressed && ['link_expired', 'error', 'unlinked', 'logged_out', 'forbidden'].includes(conn.state)) {
|
|
534
|
+
finishLink('failed', conn.state === 'link_expired' || conn.state === 'unlinked' ? { type: 'state', state: 'link_expired' } : { type: 'error', code: conn.state === 'forbidden' ? 'FORBIDDEN' : 'LINK_FAILED' });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (state.claim) {
|
|
538
|
+
const claim = conn.claim;
|
|
539
|
+
if (claim && Number.isFinite(claim.attemptsLeft) && claim.attemptsLeft !== state.claim.attemptsLeft) {
|
|
540
|
+
state.claim.attemptsLeft = claim.attemptsLeft;
|
|
541
|
+
state.claim.stream.write({ type: 'claim', attemptsLeft: claim.attemptsLeft, expiresAt: claim.expiresAt });
|
|
542
|
+
}
|
|
543
|
+
if (!claim?.active && !conn.owner?.bound && prev?.claim?.active) endClaim({ type: 'expired' });
|
|
544
|
+
}
|
|
545
|
+
if (['logged_out', 'forbidden'].includes(conn.state) && prev?.state !== conn.state) onLoggedOut(conn.state === 'forbidden' ? 'banned' : logoutReason(cfg));
|
|
546
|
+
if (conn.state === 'unlinked' && prev && prev.state !== 'unlinked' && !state.link) {
|
|
547
|
+
// Confirmation refused or timed out, or the host wiped the link.
|
|
548
|
+
config.write({ owner: null, setup: cfg.setup ? { ...cfg.setup, linkedAt: null } : null });
|
|
549
|
+
}
|
|
550
|
+
// Every state change reaches the bridge (it sends the restart note and flushes its outbox on connect).
|
|
551
|
+
const connected = conn.state === 'open';
|
|
552
|
+
state.wasConnected = connected;
|
|
553
|
+
const bridge = connected ? ensureBridge() : state.bridge;
|
|
554
|
+
try { const r = bridge?.onConnection?.({ connected, state: conn.state }); if (r?.catch) r.catch(() => {}); } catch {}
|
|
555
|
+
notify();
|
|
556
|
+
}
|
|
557
|
+
function onLoggedOut(reason) {
|
|
558
|
+
const cfg = config.read();
|
|
559
|
+
config.write({ owner: null, setup: { ...(cfg.setup || {}), linkedAt: null, lastEnd: { state: 'logged_out', reason, at: now() } } });
|
|
560
|
+
record('security', reason === 'banned' ? 'WhatsApp refused this account (it may be banned)' : reason === 'inactive' ? 'WhatsApp logged SynaBun out after a long time offline' : 'WhatsApp logged SynaBun out (removed from the phone)');
|
|
561
|
+
endEscalation('logged out');
|
|
562
|
+
dropBridge().catch(() => {});
|
|
563
|
+
// The host wiped the credentials; nothing is left for it to do.
|
|
564
|
+
T.setTimeout(() => { if (!isLinked()) manager.stop().catch?.(() => {}); }, 1000)?.unref?.();
|
|
565
|
+
}
|
|
566
|
+
function onQr({ qr, expiresAt } = {}) {
|
|
567
|
+
const link = state.link;
|
|
568
|
+
if (!link || typeof qr !== 'string' || !qr) return;
|
|
569
|
+
let svg;
|
|
570
|
+
try { svg = renderQrSvg(qr, { label: 'WhatsApp link QR code' }); } catch (error) { say(`could not draw the link QR: ${error?.message || error}`, 'warn'); return; }
|
|
571
|
+
link.shown = true;
|
|
572
|
+
link.phase = 'waiting';
|
|
573
|
+
link.stream.write({ type: 'qr', svg, expiresAt });
|
|
574
|
+
notify();
|
|
575
|
+
}
|
|
576
|
+
function onPairingCode({ code, expiresAt } = {}) {
|
|
577
|
+
const link = state.link;
|
|
578
|
+
if (!link || !code) return;
|
|
579
|
+
link.shown = true;
|
|
580
|
+
link.phase = 'waiting';
|
|
581
|
+
link.stream.write({ type: 'pairing_code', code: formatPairingCode(code), expiresAt });
|
|
582
|
+
notify();
|
|
583
|
+
}
|
|
584
|
+
function onLinked({ mode, me } = {}) {
|
|
585
|
+
const cfg = config.read();
|
|
586
|
+
const linkedMode = MODES.includes(mode) ? mode : cfg.mode;
|
|
587
|
+
config.write({ mode: linkedMode, enabled: true, owner: null, paused: false, pausedBy: null, setup: { ...(cfg.setup || {}), mode: linkedMode, linkedAt: now(), lastEnd: null } });
|
|
588
|
+
state.linkOutcome = null;
|
|
589
|
+
record('state', `Linked a WhatsApp account${me?.masked ? ` (${me.masked})` : ''} in ${linkedMode === 'self' ? '"Message yourself"' : 'second-number'} mode`);
|
|
590
|
+
finishLink('linked', { type: 'linked', mode: linkedMode });
|
|
591
|
+
configureHost();
|
|
592
|
+
ensureBridge();
|
|
593
|
+
notify();
|
|
594
|
+
}
|
|
595
|
+
function onOwnerBound({ masked, via } = {}) {
|
|
596
|
+
const cfg = config.read();
|
|
597
|
+
config.write({ owner: { masked: String(masked || '••••').slice(0, 32), boundAt: now(), via: via === 'claim' ? 'claim' : 'self_confirm' }, enabled: true, setup: { ...(cfg.setup || {}), linkedAt: cfg.setup?.linkedAt || now() } });
|
|
598
|
+
record('security', via === 'claim' ? `Owner confirmed with the claim code (${masked})` : `You confirmed the linked account (${masked})`);
|
|
599
|
+
endClaim({ type: 'bound', masked });
|
|
600
|
+
ensureBridge();
|
|
601
|
+
notify();
|
|
602
|
+
}
|
|
603
|
+
const TRIGGER_RE = /^\s*sb(?:\s+|:\s*|$)/i; // "sb hi", "sb:hi", or a bare "sb" caption on a picture
|
|
604
|
+
const ALLOW_RE = /^\s*allow\s+\d{4}\s*$/i;
|
|
605
|
+
/**
|
|
606
|
+
* "Message yourself" with selfTrigger 'prefix': only messages that start with
|
|
607
|
+
* "sb " or "sb:" are for SynaBun (the prefix is stripped). Commands (/stop,
|
|
608
|
+
* /status …), the ALLOW answer and replies quoting SynaBun's own messages
|
|
609
|
+
* always pass. Anything else is a note to self: dropped here, never logged.
|
|
610
|
+
* → the message to act on, or null.
|
|
611
|
+
*/
|
|
612
|
+
function applyTrigger(message) {
|
|
613
|
+
const cfg = config.read();
|
|
614
|
+
if (cfg.mode !== 'self' || cfg.selfTrigger !== 'prefix') return message;
|
|
615
|
+
const text = typeof message.text === 'string' ? message.text : String(message.text ?? '');
|
|
616
|
+
const hasImages = Array.isArray(message.images) && message.images.length > 0;
|
|
617
|
+
const match = TRIGGER_RE.exec(text);
|
|
618
|
+
if (match) {
|
|
619
|
+
const rest = text.slice(match[0].length);
|
|
620
|
+
return rest.trim() || hasImages ? { ...message, text: rest } : null;
|
|
621
|
+
}
|
|
622
|
+
if (!message.forwarded && ALLOW_RE.test(text)) return message;
|
|
623
|
+
const command = parseCommand(message);
|
|
624
|
+
if (command && (command.kind === 'command' || command.kind === 'refused')) return message;
|
|
625
|
+
if (message.quoted?.fromBot === true) return message;
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
/** The manager hands over the InboundMessage itself (a {message} wrapper is accepted too). */
|
|
629
|
+
function onInbound(event) {
|
|
630
|
+
const incoming = event?.message && typeof event.message === 'object' && !('owner' in event) ? event.message : event;
|
|
631
|
+
if (!incoming || incoming.owner !== true) return;
|
|
632
|
+
const message = applyTrigger(incoming);
|
|
633
|
+
if (!message) { state.notesSkipped += 1; return; }
|
|
634
|
+
// An ALLOW answer is consumed before anything is logged: its code never
|
|
635
|
+
// reaches the activity log, whatever "Keep message text" says.
|
|
636
|
+
if (handleAllow(message)) { record('inbound', 'Message from you: an ALLOW code (not kept)'); notify(); return; }
|
|
637
|
+
record('inbound', describeInbound(message), message.text);
|
|
638
|
+
const bridge = ensureBridge();
|
|
639
|
+
try { const r = bridge?.onInbound?.(message); if (r?.catch) r.catch((error) => say(`inbound: ${error?.message || error}`, 'warn')); }
|
|
640
|
+
catch (error) { say(`inbound: ${error?.message || error}`, 'warn'); }
|
|
641
|
+
notify();
|
|
642
|
+
}
|
|
643
|
+
function onIgnored({ counts } = {}) {
|
|
644
|
+
const total = Object.values(counts || {}).reduce((sum, n) => sum + (Number(n) || 0), 0);
|
|
645
|
+
if (total) record('ignored', `Ignored ${total} message${total === 1 ? '' : 's'} that were not yours to answer (other chats, groups, strangers)`);
|
|
646
|
+
notify();
|
|
647
|
+
}
|
|
648
|
+
function onThrottled({ retryAfterMs } = {}) {
|
|
649
|
+
record('state', `SynaBun's WhatsApp send limit was reached; replies wait ${Math.ceil((Number(retryAfterMs) || 0) / 1000)} s and are sent then`);
|
|
650
|
+
notify();
|
|
651
|
+
}
|
|
652
|
+
function onFatal({ code, message } = {}) {
|
|
653
|
+
state.fatal = { code: String(code || 'ERROR'), message: message ? redactWa(message) : null, at: now() };
|
|
654
|
+
const labels = {
|
|
655
|
+
LOGGED_OUT: 'WhatsApp logged SynaBun out', REPLACED: 'Another copy of this link took over the session', FORBIDDEN: 'WhatsApp refused the connection',
|
|
656
|
+
CONFIRM_TIMEOUT: 'The linked account was not confirmed within 10 minutes and was logged out', RECONNECT_LIMIT: 'Too many reconnects: SynaBun stopped trying',
|
|
657
|
+
HOST_HELD: 'The WhatsApp connector kept crashing and was stopped', OWNER_ANOMALY: 'Sending paused: the owner looked different than expected', LOCKED: 'Another SynaBun is using the WhatsApp session files',
|
|
658
|
+
AUTH_PERMS: 'The WhatsApp session files could not be made private (fix the folder permissions, then reconnect)',
|
|
659
|
+
};
|
|
660
|
+
record('error', labels[state.fatal.code] || `WhatsApp connector: ${state.fatal.code}`);
|
|
661
|
+
if (state.link) finishLink('failed', { type: 'error', code: state.fatal.code });
|
|
662
|
+
notify();
|
|
663
|
+
}
|
|
664
|
+
function wireManager() {
|
|
665
|
+
if (!manager || state.offs.length) return;
|
|
666
|
+
const on = (name, fn) => { try { state.offs.push(manager.on(name, (payload) => { try { fn(payload); } catch (error) { say(`${name} handler: ${error?.message || error}`, 'warn'); } })); } catch {} };
|
|
667
|
+
on('state', onState);
|
|
668
|
+
on('qr', onQr);
|
|
669
|
+
on('pairing_code', onPairingCode);
|
|
670
|
+
on('linked', onLinked);
|
|
671
|
+
on('owner_bound', onOwnerBound);
|
|
672
|
+
on('inbound', onInbound);
|
|
673
|
+
on('ignored', onIgnored);
|
|
674
|
+
on('throttled', onThrottled);
|
|
675
|
+
on('fatal', onFatal);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
679
|
+
function requireAvailable() {
|
|
680
|
+
if (disabled) throw whatsappError(409, 'UNAVAILABLE', 'WhatsApp is turned off on this computer (SYNABUN_WHATSAPP=off).');
|
|
681
|
+
}
|
|
682
|
+
/** Start the host and the linked session (autostart, resume, reconnect). */
|
|
683
|
+
async function run() {
|
|
684
|
+
const cfg = config.read();
|
|
685
|
+
wireManager();
|
|
686
|
+
const started = await manager.start(prefsOf(cfg));
|
|
687
|
+
if (!started?.ok) {
|
|
688
|
+
state.fatal = { code: started?.code || 'HOST_UNAVAILABLE', message: started?.message ? redactWa(started.message) : null, at: now() };
|
|
689
|
+
notify();
|
|
690
|
+
return started || { ok: false, code: 'HOST_UNAVAILABLE' };
|
|
691
|
+
}
|
|
692
|
+
const result = await manager.connect({ purpose: 'run', mode: cfg.mode });
|
|
693
|
+
if (!result?.ok) {
|
|
694
|
+
if (result?.code === 'NOT_LINKED') config.write({ owner: null, setup: cfg.setup ? { ...cfg.setup, linkedAt: null } : null });
|
|
695
|
+
else state.fatal = { code: result?.code || 'CONNECT_FAILED', message: result?.message ? redactWa(result.message) : null, at: now() };
|
|
696
|
+
} else {
|
|
697
|
+
ensureBridge();
|
|
698
|
+
}
|
|
699
|
+
notify();
|
|
700
|
+
return result;
|
|
701
|
+
}
|
|
702
|
+
async function start() {
|
|
703
|
+
if (state.started) return status();
|
|
704
|
+
state.started = true;
|
|
705
|
+
if (disabled) { notify(); return status(); }
|
|
706
|
+
wireManager();
|
|
707
|
+
pruneLogs();
|
|
708
|
+
state.retentionTimer = T.setInterval(pruneLogs, 6 * HOUR);
|
|
709
|
+
state.retentionTimer?.unref?.();
|
|
710
|
+
armAutonomousTimer();
|
|
711
|
+
const cfg = config.read();
|
|
712
|
+
configureHost(cfg);
|
|
713
|
+
if (installerStatus().installed && cfg.enabled && isLinked(cfg)) {
|
|
714
|
+
try { await run(); } catch (error) { say(`autostart failed: ${error?.message || error}`, 'warn'); }
|
|
715
|
+
}
|
|
716
|
+
notify();
|
|
717
|
+
return status();
|
|
718
|
+
}
|
|
719
|
+
async function shutdown({ timeoutMs = 5000 } = {}) {
|
|
720
|
+
state.stopping = true;
|
|
721
|
+
for (const timer of [state.broadcastTimer, state.autonomousTimer, state.escalation?.timer, state.link?.timer]) if (timer) T.clearTimeout(timer);
|
|
722
|
+
if (state.retentionTimer) T.clearInterval(state.retentionTimer);
|
|
723
|
+
try { state.install?.controller?.abort(); } catch {}
|
|
724
|
+
if (state.link) { state.link.stream.end({ type: 'error', code: 'SHUTTING_DOWN' }); state.link = null; }
|
|
725
|
+
if (state.claim) { state.claim.stream.end({ type: 'error', code: 'SHUTTING_DOWN' }); state.claim = null; }
|
|
726
|
+
// Bridge first (it may still say goodbye through the manager), then the host.
|
|
727
|
+
await dropBridge();
|
|
728
|
+
for (const off of state.offs.splice(0)) { try { off?.(); } catch {} }
|
|
729
|
+
if (manager) { try { await manager.stop({ timeoutMs }); } catch (error) { say(`stop: ${error?.message || error}`, 'warn'); } }
|
|
730
|
+
}
|
|
731
|
+
function killNow() {
|
|
732
|
+
state.stopping = true;
|
|
733
|
+
const bridge = state.bridge;
|
|
734
|
+
try { bridge?.endComputerUse?.('process exit'); } catch {}
|
|
735
|
+
state.bridge = null;
|
|
736
|
+
revokeAuthority();
|
|
737
|
+
try { const r = bridge?.shutdown?.(); if (r?.catch) r.catch(() => {}); } catch {}
|
|
738
|
+
try { manager?.killNow(); } catch {}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ── linking (NDJSON to the tab that asked) ─────────────────────────────────
|
|
742
|
+
function finishLink(outcome, lastEvent) {
|
|
743
|
+
const link = state.link;
|
|
744
|
+
if (!link) return;
|
|
745
|
+
state.link = null;
|
|
746
|
+
T.clearTimeout(link.timer);
|
|
747
|
+
link.stream.end(lastEvent);
|
|
748
|
+
if (outcome !== 'linked') {
|
|
749
|
+
state.linkOutcome = { state: 'link_expired', at: now() };
|
|
750
|
+
record('state', lastEvent?.code ? `Linking failed (${lastEvent.code})` : 'The link code expired before a phone used it');
|
|
751
|
+
// Nothing linked: stop the host (and Baileys) until the next attempt.
|
|
752
|
+
const r = manager.disconnect?.();
|
|
753
|
+
Promise.resolve(r).finally(() => { if (!isLinked() && !state.link) Promise.resolve(manager.stop?.()).catch(() => {}); }).catch(() => {});
|
|
754
|
+
}
|
|
755
|
+
notify();
|
|
756
|
+
}
|
|
757
|
+
async function link(body = {}, openStream) {
|
|
758
|
+
requireAvailable();
|
|
759
|
+
const method = body.method === 'code' ? 'code' : body.method === 'qr' || body.method === undefined ? 'qr' : null;
|
|
760
|
+
if (!method) throw whatsappError(400, 'BAD_METHOD', 'method must be "qr" or "code"', 'method');
|
|
761
|
+
let phoneDigits = null;
|
|
762
|
+
if (method === 'code') {
|
|
763
|
+
const phone = normalizePhone(body.phone);
|
|
764
|
+
if (!phone.ok) throw whatsappError(400, 'BAD_PHONE', phoneMessage(phone.code), 'phone');
|
|
765
|
+
phoneDigits = phone.digits;
|
|
766
|
+
}
|
|
767
|
+
if (state.link) throw whatsappError(409, 'LINK_BUSY', 'Another SynaBun tab is already linking WhatsApp. Finish or close it first.');
|
|
768
|
+
if (state.install) throw whatsappError(409, 'INSTALL_BUSY', 'The WhatsApp connector is still installing.');
|
|
769
|
+
if (!installerStatus().installed) throw whatsappError(409, 'NOT_INSTALLED', 'Install the WhatsApp connector first.');
|
|
770
|
+
const cfg = config.read();
|
|
771
|
+
if (isLinked(cfg)) throw whatsappError(409, 'ALREADY_LINKED', 'A WhatsApp account is already linked. Unlink it first.');
|
|
772
|
+
if (body.mode !== undefined) {
|
|
773
|
+
if (!MODES.includes(body.mode)) throw whatsappError(400, 'BAD_MODE', 'mode must be "self" or "dedicated"', 'mode');
|
|
774
|
+
if (body.mode !== cfg.mode) config.write({ mode: body.mode });
|
|
775
|
+
}
|
|
776
|
+
const mode = config.read().mode;
|
|
777
|
+
const stream = openStream();
|
|
778
|
+
const current = { method, phase: 'starting', shown: false, requested: false, progressed: false, stream, timer: null };
|
|
779
|
+
state.link = current;
|
|
780
|
+
state.linkOutcome = null;
|
|
781
|
+
state.fatal = null;
|
|
782
|
+
if (cfg.setup?.lastEnd) config.write({ setup: { ...cfg.setup, lastEnd: null } });
|
|
783
|
+
stream.onAbort(() => {
|
|
784
|
+
if (state.link !== current) return;
|
|
785
|
+
state.link = null;
|
|
786
|
+
T.clearTimeout(current.timer);
|
|
787
|
+
record('state', 'Linking cancelled (the settings tab closed)');
|
|
788
|
+
Promise.resolve(manager.disconnect?.()).finally(() => { if (!isLinked() && !state.link) Promise.resolve(manager.stop?.()).catch(() => {}); }).catch(() => {});
|
|
789
|
+
notify();
|
|
790
|
+
});
|
|
791
|
+
stream.write({ type: 'state', state: 'linking', phase: 'starting', method });
|
|
792
|
+
notify();
|
|
793
|
+
wireManager();
|
|
794
|
+
let started;
|
|
795
|
+
let result;
|
|
796
|
+
try {
|
|
797
|
+
started = await manager.start(prefsOf({ ...config.read(), mode }));
|
|
798
|
+
if (state.link !== current) return;
|
|
799
|
+
if (!started?.ok) { finishLink('failed', { type: 'error', code: started?.code || 'HOST_UNAVAILABLE', message: started?.message ? redactWa(started.message) : undefined }); return; }
|
|
800
|
+
current.requested = true;
|
|
801
|
+
result = await manager.connect({ purpose: 'link', mode, link: { method, ...(phoneDigits ? { phone: phoneDigits } : {}) } });
|
|
802
|
+
} catch (error) {
|
|
803
|
+
result = { ok: false, code: 'LINK_FAILED', message: error?.message || String(error) };
|
|
804
|
+
}
|
|
805
|
+
if (state.link !== current) return;
|
|
806
|
+
if (!result?.ok) { finishLink('failed', { type: 'error', code: result?.code || 'LINK_FAILED', message: result?.message ? redactWa(result.message) : undefined }); return; }
|
|
807
|
+
current.timer = T.setTimeout(() => { if (state.link === current) finishLink('failed', { type: 'state', state: 'link_expired' }); }, L.linkTimeoutMs);
|
|
808
|
+
current.timer?.unref?.();
|
|
809
|
+
record('state', `Linking started (${method === 'qr' ? 'QR code' : 'phone number code'})`);
|
|
810
|
+
}
|
|
811
|
+
const phoneMessage = (code) => PHONE_ERRORS[code] || 'That phone number does not look right.';
|
|
812
|
+
|
|
813
|
+
// ── owner ──────────────────────────────────────────────────────────────────
|
|
814
|
+
async function confirmOwner(body = {}) {
|
|
815
|
+
requireAvailable();
|
|
816
|
+
if (typeof body.accept !== 'boolean') throw whatsappError(400, 'BAD_REQUEST', 'accept must be true or false', 'accept');
|
|
817
|
+
const result = await manager.confirmOwner(body.accept);
|
|
818
|
+
if (!result?.ok) throw whatsappError(409, result?.code || 'NOT_AWAITING', result?.message ? redactWa(result.message) : 'Nothing is waiting for confirmation.');
|
|
819
|
+
if (!body.accept) {
|
|
820
|
+
const cfg = config.read();
|
|
821
|
+
config.write({ owner: null, setup: cfg.setup ? { ...cfg.setup, linkedAt: null } : null });
|
|
822
|
+
record('security', 'You said the linked account is not yours; SynaBun logged it out');
|
|
823
|
+
await dropBridge();
|
|
824
|
+
Promise.resolve(manager.stop?.()).catch(() => {});
|
|
825
|
+
}
|
|
826
|
+
notify();
|
|
827
|
+
return { ok: true, status: status() };
|
|
828
|
+
}
|
|
829
|
+
function endClaim(lastEvent) {
|
|
830
|
+
const claim = state.claim;
|
|
831
|
+
if (!claim) return;
|
|
832
|
+
state.claim = null;
|
|
833
|
+
claim.stream.end(lastEvent);
|
|
834
|
+
}
|
|
835
|
+
async function claimOwner(body = {}, openStream) {
|
|
836
|
+
requireAvailable();
|
|
837
|
+
const cfg = config.read();
|
|
838
|
+
const conn = managerStatus().conn;
|
|
839
|
+
if (cfg.mode !== 'dedicated') throw whatsappError(409, 'WRONG_MODE', 'Claim codes are for the second-number mode.');
|
|
840
|
+
if (conn.state !== 'open') throw whatsappError(409, 'NOT_CONNECTED', 'WhatsApp is not connected.');
|
|
841
|
+
if (conn.owner?.bound) throw whatsappError(409, 'OWNER_BOUND', 'An owner is already set. Reset it first.');
|
|
842
|
+
const result = await manager.claimStart({ ttlMs: L.claimTtlMs });
|
|
843
|
+
if (!result?.ok) throw whatsappError(409, result?.code || 'CLAIM_FAILED', result?.message ? redactWa(result.message) : 'Could not start a claim code.');
|
|
844
|
+
if (state.claim) endClaim({ type: 'cancelled' });
|
|
845
|
+
const digits = /wa\.me\/(\d{6,15})/.exec(String(result.link || ''))?.[1] || null;
|
|
846
|
+
const stream = openStream();
|
|
847
|
+
const current = { stream, attemptsLeft: managerStatus().conn.claim?.attemptsLeft ?? null };
|
|
848
|
+
state.claim = current;
|
|
849
|
+
stream.onAbort(() => {
|
|
850
|
+
if (state.claim !== current) return;
|
|
851
|
+
state.claim = null;
|
|
852
|
+
Promise.resolve(manager.claimCancel?.()).catch(() => {});
|
|
853
|
+
notify();
|
|
854
|
+
});
|
|
855
|
+
let qrSvg = null;
|
|
856
|
+
if (result.link) { try { qrSvg = renderQrSvg(result.link, { label: 'QR code that opens the chat with the code filled in' }); } catch {} }
|
|
857
|
+
stream.write({ type: 'claim', code: result.code, sendTo: digits ? maskNumber(digits) : null, waMeUrl: result.link || null, qrSvg, expiresAt: result.expiresAt, attemptsLeft: current.attemptsLeft });
|
|
858
|
+
record('security', 'Claim code shown on this computer; waiting for the owner to send it');
|
|
859
|
+
notify();
|
|
860
|
+
}
|
|
861
|
+
async function resetOwner() {
|
|
862
|
+
requireAvailable();
|
|
863
|
+
if (config.read().mode !== 'dedicated') throw whatsappError(409, 'WRONG_MODE', 'In "Message yourself" the owner is the linked account. Unlink to link another one.');
|
|
864
|
+
const result = await manager.ownerReset();
|
|
865
|
+
if (!result?.ok) throw whatsappError(409, result?.code || 'RESET_FAILED', result?.message ? redactWa(result.message) : 'Could not reset the owner.');
|
|
866
|
+
// The second account stays linked: keep that on record while the owner is unbound.
|
|
867
|
+
const cfg = config.read();
|
|
868
|
+
config.write({ owner: null, setup: { ...(cfg.setup || {}), linkedAt: cfg.setup?.linkedAt || cfg.owner?.boundAt || now() } });
|
|
869
|
+
endEscalation('owner reset');
|
|
870
|
+
record('security', 'Owner reset; confirm the owner again');
|
|
871
|
+
notify();
|
|
872
|
+
return { ok: true, status: status() };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// ── connector ──────────────────────────────────────────────────────────────
|
|
876
|
+
function startInstall({ update = false } = {}) {
|
|
877
|
+
const controller = new AbortController();
|
|
878
|
+
const job = { controller, stage: 'checking', startedAt: now(), update };
|
|
879
|
+
state.install = job;
|
|
880
|
+
state.installError = null;
|
|
881
|
+
state.installCache = null;
|
|
882
|
+
record('state', update ? 'Updating the WhatsApp connector' : 'Installing the WhatsApp connector');
|
|
883
|
+
notify();
|
|
884
|
+
(async () => {
|
|
885
|
+
// The host holds the runtime's files open (Windows locks them): stop it first, restart after.
|
|
886
|
+
if (hostRunning()) await dropBridge();
|
|
887
|
+
try { await manager.stop(); } catch {}
|
|
888
|
+
let result;
|
|
889
|
+
try {
|
|
890
|
+
result = await installer.install({
|
|
891
|
+
signal: controller.signal,
|
|
892
|
+
onProgress: ({ stage } = {}) => { if (stage && stage !== job.stage) { job.stage = stage; notify(); } },
|
|
893
|
+
});
|
|
894
|
+
} catch (error) {
|
|
895
|
+
result = { ok: false, code: 'UNKNOWN', message: error?.message || String(error) };
|
|
896
|
+
}
|
|
897
|
+
if (state.install === job) state.install = null;
|
|
898
|
+
state.installCache = null;
|
|
899
|
+
if (result?.ok) {
|
|
900
|
+
record('state', `WhatsApp connector ${result.version || ''} installed`.replace(/\s+/g, ' '));
|
|
901
|
+
} else {
|
|
902
|
+
state.installError = { code: result?.code || 'UNKNOWN', message: result?.message ? redactWa(result.message) : null, at: now() };
|
|
903
|
+
record('error', result?.code === 'ABORTED' ? 'Connector install cancelled' : `Connector install failed (${state.installError.code})`);
|
|
904
|
+
if (result?.code === 'ABORTED') state.installError = null;
|
|
905
|
+
}
|
|
906
|
+
const cfg = config.read();
|
|
907
|
+
if (!state.stopping && installerStatus().installed && cfg.enabled && isLinked(cfg)) await run().catch(() => {});
|
|
908
|
+
notify();
|
|
909
|
+
})().catch((error) => { say(`install job: ${error?.message || error}`, 'warn'); state.install = null; notify(); });
|
|
910
|
+
return job;
|
|
911
|
+
}
|
|
912
|
+
async function installConnector(body = {}) {
|
|
913
|
+
requireAvailable();
|
|
914
|
+
if (state.install) throw whatsappError(409, 'INSTALL_BUSY', 'The connector is already being installed.');
|
|
915
|
+
const inst = installerStatus();
|
|
916
|
+
const update = body.update === true || body.reinstall === true;
|
|
917
|
+
if (inst.installed && !update) return { ok: true, already: true, status: status() };
|
|
918
|
+
startInstall({ update });
|
|
919
|
+
return { ok: true, started: true, status: status() };
|
|
920
|
+
}
|
|
921
|
+
async function cancelInstall() {
|
|
922
|
+
requireAvailable();
|
|
923
|
+
if (!state.install) return { ok: true, cancelled: false, status: status() };
|
|
924
|
+
try { state.install.controller.abort(); } catch {}
|
|
925
|
+
return { ok: true, cancelled: true, status: status() };
|
|
926
|
+
}
|
|
927
|
+
async function removeConnector() {
|
|
928
|
+
requireAvailable();
|
|
929
|
+
if (state.install) throw whatsappError(409, 'INSTALL_BUSY', 'The connector is being installed.');
|
|
930
|
+
if (isLinked()) throw whatsappError(409, 'STILL_LINKED', 'Unlink WhatsApp before removing the connector.');
|
|
931
|
+
try { await manager.stop(); } catch {}
|
|
932
|
+
const result = await installer.uninstall();
|
|
933
|
+
state.installCache = null;
|
|
934
|
+
state.installError = null;
|
|
935
|
+
if (!result?.ok) throw whatsappError(409, result?.code || 'UNINSTALL_FAILED', result?.message ? redactWa(result.message) : 'Could not remove the connector.');
|
|
936
|
+
record('state', 'WhatsApp connector removed');
|
|
937
|
+
notify();
|
|
938
|
+
return { ok: true, status: status() };
|
|
939
|
+
}
|
|
940
|
+
function connectorLog({ limit = 200 } = {}) {
|
|
941
|
+
requireAvailable();
|
|
942
|
+
const n = Math.max(1, Math.min(1000, Number(limit) || 200));
|
|
943
|
+
let lines = [];
|
|
944
|
+
try { lines = installer.logTail(n) || []; } catch { lines = []; }
|
|
945
|
+
return { ok: true, lines: lines.map((line) => redactWa(line)) };
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ── setup, test, pause, resume, reconnect, unlink, remove ──────────────────
|
|
949
|
+
async function setup(body = {}) {
|
|
950
|
+
requireAvailable();
|
|
951
|
+
const mode = body.mode === undefined ? 'self' : body.mode;
|
|
952
|
+
if (!MODES.includes(mode)) throw whatsappError(400, 'BAD_MODE', 'mode must be "self" or "dedicated"', 'mode');
|
|
953
|
+
const method = body.method === undefined ? 'qr' : body.method;
|
|
954
|
+
if (method !== 'qr' && method !== 'code') throw whatsappError(400, 'BAD_METHOD', 'method must be "qr" or "code"', 'method');
|
|
955
|
+
if (method === 'code') {
|
|
956
|
+
const phone = normalizePhone(body.phone);
|
|
957
|
+
if (!phone.ok) throw whatsappError(400, 'BAD_PHONE', phoneMessage(phone.code), 'phone');
|
|
958
|
+
}
|
|
959
|
+
const cfg = config.read();
|
|
960
|
+
if (isLinked(cfg)) {
|
|
961
|
+
if (cfg.mode !== mode) throw whatsappError(409, 'ALREADY_LINKED', `WhatsApp is already linked in ${cfg.mode === 'self' ? '"Message yourself"' : 'second-number'} mode. Unlink it to switch.`);
|
|
962
|
+
if (!cfg.enabled) config.write({ enabled: true });
|
|
963
|
+
return { ok: true, next: 'none', status: status() };
|
|
964
|
+
}
|
|
965
|
+
// The intent (never the phone number: the tab keeps that for /link).
|
|
966
|
+
config.write({ mode, enabled: true, setup: { ...(cfg.setup || {}), mode, method, requestedAt: now(), lastEnd: null } });
|
|
967
|
+
state.linkOutcome = null;
|
|
968
|
+
record('state', `Setup started: ${mode === 'self' ? '"Message yourself"' : 'second number'}, ${method === 'qr' ? 'QR code' : 'phone number code'}`);
|
|
969
|
+
let next = 'link';
|
|
970
|
+
if (state.install) next = 'install';
|
|
971
|
+
else if (!installerStatus().installed) { startInstall(); next = 'install'; }
|
|
972
|
+
notify();
|
|
973
|
+
return { ok: true, next, status: status() };
|
|
974
|
+
}
|
|
975
|
+
async function test() {
|
|
976
|
+
requireAvailable();
|
|
977
|
+
if (!connectedNow()) throw whatsappError(409, 'NOT_CONNECTED', 'WhatsApp is not connected.');
|
|
978
|
+
const wait = state.testAt + L.testEveryMs - now();
|
|
979
|
+
if (wait > 0) {
|
|
980
|
+
const error = whatsappError(429, 'RATE_LIMITED', 'Wait a few seconds before sending another test.');
|
|
981
|
+
error.retryAfterMs = wait;
|
|
982
|
+
throw error;
|
|
983
|
+
}
|
|
984
|
+
state.testAt = now();
|
|
985
|
+
const result = await transport.send(TEST_MESSAGE);
|
|
986
|
+
if (!result?.ok) throw whatsappError(502, result?.code || 'SEND_FAILED', result?.message ? redactWa(result.message) : 'The test message could not be sent.');
|
|
987
|
+
return { ok: true };
|
|
988
|
+
}
|
|
989
|
+
function pause({ by = 'desktop' } = {}) {
|
|
990
|
+
requireAvailable();
|
|
991
|
+
// An agent's pause counts as the desktop's: the phone cannot undo it.
|
|
992
|
+
const bridge = state.bridge;
|
|
993
|
+
if (bridge?.pause) { try { bridge.pause('desktop'); } catch {} }
|
|
994
|
+
config.write({ paused: true, pausedBy: 'desktop' });
|
|
995
|
+
refreshBridge();
|
|
996
|
+
record('security', by === 'agent' ? 'Paused by an agent on this computer' : 'Paused from this computer');
|
|
997
|
+
notify();
|
|
998
|
+
return { ok: true, status: status() };
|
|
999
|
+
}
|
|
1000
|
+
async function resume() {
|
|
1001
|
+
requireAvailable();
|
|
1002
|
+
const cfg = config.read();
|
|
1003
|
+
config.write({ paused: false, pausedBy: null, ...(cfg.enabled ? {} : { enabled: true }) });
|
|
1004
|
+
try { state.bridge?.resume?.('desktop'); } catch {}
|
|
1005
|
+
refreshBridge();
|
|
1006
|
+
record('security', 'Resumed from this computer');
|
|
1007
|
+
if (isLinked() && !hostRunning() && installerStatus().installed) await run().catch(() => {});
|
|
1008
|
+
notify();
|
|
1009
|
+
return { ok: true, status: status() };
|
|
1010
|
+
}
|
|
1011
|
+
async function reconnect() {
|
|
1012
|
+
requireAvailable();
|
|
1013
|
+
if (!installerStatus().installed) throw whatsappError(409, 'NOT_INSTALLED', 'Install the WhatsApp connector first.');
|
|
1014
|
+
if (!isLinked()) throw whatsappError(409, 'NOT_LINKED', 'No WhatsApp account is linked.');
|
|
1015
|
+
const cfg = config.read();
|
|
1016
|
+
if (!cfg.enabled) config.write({ enabled: true });
|
|
1017
|
+
state.fatal = null;
|
|
1018
|
+
record('state', 'Reconnect requested from this computer');
|
|
1019
|
+
const result = await run();
|
|
1020
|
+
return { ok: !!result?.ok, code: result?.ok ? undefined : result?.code, status: status() };
|
|
1021
|
+
}
|
|
1022
|
+
/** Forget the link: log the device out of WhatsApp when the host can, wipe, reset the service-owned config. */
|
|
1023
|
+
async function unlinkInternal() {
|
|
1024
|
+
if (state.link) finishLink('failed', { type: 'error', code: 'UNLINKED' });
|
|
1025
|
+
endClaim({ type: 'cancelled' });
|
|
1026
|
+
endEscalation('unlinked');
|
|
1027
|
+
T.clearTimeout(state.autonomousTimer);
|
|
1028
|
+
state.autonomousTimer = null;
|
|
1029
|
+
await dropBridge();
|
|
1030
|
+
let loggedOut = false;
|
|
1031
|
+
let code = null;
|
|
1032
|
+
if (manager && installerStatus().installed) {
|
|
1033
|
+
if (!hostRunning()) { const started = await manager.start(prefsOf(config.read())); if (!started?.ok) code = started?.code || 'HOST_UNAVAILABLE'; }
|
|
1034
|
+
if (!code) {
|
|
1035
|
+
const result = await manager.logout();
|
|
1036
|
+
loggedOut = !!result?.ok;
|
|
1037
|
+
if (!result?.ok) code = result?.code || 'LOGOUT_FAILED';
|
|
1038
|
+
}
|
|
1039
|
+
try { await manager.stop(); } catch {}
|
|
1040
|
+
}
|
|
1041
|
+
const cfg = config.read();
|
|
1042
|
+
const { version: _version, ...serviceDefaults } = SERVICE_DEFAULTS;
|
|
1043
|
+
config.write({ ...serviceDefaults, previousSessionIds: [], enabled: false, level: cfg.level === 'autonomous' ? 'ask' : cfg.level, setup: null, bridgeState: null });
|
|
1044
|
+
state.linkOutcome = null;
|
|
1045
|
+
state.fatal = null;
|
|
1046
|
+
state.lastConn = null;
|
|
1047
|
+
state.wasConnected = false;
|
|
1048
|
+
return { loggedOut, code };
|
|
1049
|
+
}
|
|
1050
|
+
async function unlink() {
|
|
1051
|
+
requireAvailable();
|
|
1052
|
+
const result = await unlinkInternal();
|
|
1053
|
+
record('security', result.loggedOut ? 'Unlinked: SynaBun logged out of WhatsApp and deleted the session' : 'Unlinked on this computer; also remove SynaBun under WhatsApp → Linked devices on your phone');
|
|
1054
|
+
notify();
|
|
1055
|
+
return { ok: true, loggedOut: result.loggedOut, code: result.code || undefined, status: status() };
|
|
1056
|
+
}
|
|
1057
|
+
function removeWaHome() {
|
|
1058
|
+
const home = paths.waHome;
|
|
1059
|
+
const dataDir = path.join(path.resolve(dataHome), 'data');
|
|
1060
|
+
if (!home || path.basename(home) !== 'whatsapp' || isInside(home, dataDir) || !existsSync(home)) return false;
|
|
1061
|
+
try { rmSync(home, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 }); return true; } catch { return false; }
|
|
1062
|
+
}
|
|
1063
|
+
async function remove(body = {}) {
|
|
1064
|
+
requireAvailable();
|
|
1065
|
+
const deleteConversation = body.deleteConversation === true;
|
|
1066
|
+
if (state.install) { try { state.install.controller.abort(); } catch {} }
|
|
1067
|
+
const before = config.read();
|
|
1068
|
+
const sessionIds = [...new Set([bridgeStatus()?.sessionId, before.sessionId, ...(before.previousSessionIds || [])].filter(Boolean))];
|
|
1069
|
+
const unlinked = await unlinkInternal();
|
|
1070
|
+
try { await manager?.stop(); } catch {}
|
|
1071
|
+
const uninstall = installer ? await installer.uninstall().catch((error) => ({ ok: false, code: 'UNKNOWN', message: error?.message })) : { ok: true };
|
|
1072
|
+
state.installCache = null;
|
|
1073
|
+
state.installError = null;
|
|
1074
|
+
const wiped = removeWaHome();
|
|
1075
|
+
let deleted = 0;
|
|
1076
|
+
if (deleteConversation) {
|
|
1077
|
+
let rt = null;
|
|
1078
|
+
try { rt = getRuntime(); } catch { rt = null; }
|
|
1079
|
+
for (const id of sessionIds) { try { if (await rt?.destroySession?.(id)) deleted += 1; } catch {} }
|
|
1080
|
+
}
|
|
1081
|
+
activity.length = 0;
|
|
1082
|
+
for (const name of logFiles()) { try { unlinkSync(path.join(paths.logsDir, name)); } catch {} }
|
|
1083
|
+
state.logFile = null;
|
|
1084
|
+
const { version: _version, ...serviceDefaults } = SERVICE_DEFAULTS;
|
|
1085
|
+
config.write({ ...USER_DEFAULTS, ...serviceDefaults, previousSessionIds: [], setup: null, bridgeState: null });
|
|
1086
|
+
record('security', `WhatsApp removed from SynaBun${deleteConversation ? `; ${deleted} conversation${deleted === 1 ? '' : 's'} deleted` : ''}`);
|
|
1087
|
+
notify();
|
|
1088
|
+
return { ok: true, loggedOut: unlinked.loggedOut, uninstalled: !!uninstall?.ok, wipedSession: wiped, deletedConversations: deleted, status: status() };
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// ── config, activity, session, fake ────────────────────────────────────────
|
|
1092
|
+
const userView = (cfg) => pick(cfg, USER_FIELDS);
|
|
1093
|
+
function getConfig() {
|
|
1094
|
+
const cfg = config.read();
|
|
1095
|
+
return { ok: true, config: userView(cfg), version: cfg.version, level: currentLevel(cfg, now()), levelExpiresAt: currentLevel(cfg, now()) === 'autonomous' ? cfg.autonomousUntil : null, escalation: escalationView() };
|
|
1096
|
+
}
|
|
1097
|
+
function escalationView() {
|
|
1098
|
+
const esc = state.escalation;
|
|
1099
|
+
return esc ? { pending: true, expiresAt: esc.expiresAt, attemptsLeft: esc.attemptsLeft } : null;
|
|
1100
|
+
}
|
|
1101
|
+
async function putConfig(body = {}) {
|
|
1102
|
+
requireAvailable();
|
|
1103
|
+
const control = new Set(['config', 'expectedVersion', 'confirmEscalation']);
|
|
1104
|
+
const sent = body && typeof body.config === 'object' && body.config !== null ? body.config : Object.fromEntries(Object.entries(body || {}).filter(([key]) => !control.has(key)));
|
|
1105
|
+
const patch = { ...sent };
|
|
1106
|
+
// Which brain runs WhatsApp: only a model the Assistant's own catalog lists (and the user
|
|
1107
|
+
// has not disabled) with an effort that model runs is ever stored; anything else is a 400.
|
|
1108
|
+
if (Object.hasOwn(patch, 'brain') && patch.brain !== undefined) {
|
|
1109
|
+
// The shape first (a 400 needs no catalog); then the model and effort against a fresh catalog.
|
|
1110
|
+
const shaped = cleanBrainChoice(patch.brain);
|
|
1111
|
+
const checked = checkBrainChoice(patch.brain, shaped ? await catalogValue({ fresh: true }) : null);
|
|
1112
|
+
if (!checked.ok) throw whatsappError(checked.status, checked.code, checked.message, checked.field);
|
|
1113
|
+
patch.brain = checked.brain;
|
|
1114
|
+
}
|
|
1115
|
+
const before = config.read();
|
|
1116
|
+
const wantsAutonomous = patch?.level === 'autonomous' && currentLevel(before, now()) !== 'autonomous';
|
|
1117
|
+
if (wantsAutonomous && body.confirmEscalation === true && !connectedNow()) {
|
|
1118
|
+
throw whatsappError(409, 'PHONE_REQUIRED', 'Connect WhatsApp first: your phone confirms Autonomous with an ALLOW code.', 'level');
|
|
1119
|
+
}
|
|
1120
|
+
const result = config.update(patch, { expectedVersion: body.expectedVersion ?? null, confirmEscalation: body.confirmEscalation === true });
|
|
1121
|
+
const after = result.config;
|
|
1122
|
+
let pending = null;
|
|
1123
|
+
if (result.pending) pending = startEscalation();
|
|
1124
|
+
else if (patch && Object.hasOwn(patch, 'level') && patch.level !== 'autonomous') endEscalation('level lowered');
|
|
1125
|
+
if (before.level !== after.level || before.autonomousUntil !== after.autonomousUntil) {
|
|
1126
|
+
armAutonomousTimer();
|
|
1127
|
+
record('security', `Level set to ${after.level === 'read-only' ? 'Read-only' : after.level === 'ask' ? 'Ask on my phone' : 'Autonomous'} from this computer`);
|
|
1128
|
+
}
|
|
1129
|
+
if (before.computerUse !== after.computerUse) record('security', after.computerUse ? 'Computer use from WhatsApp turned on from this computer' : 'Computer use from WhatsApp turned off from this computer');
|
|
1130
|
+
if (before.activityText !== after.activityText) record('settings', after.activityText ? 'Message text is now kept in this activity log' : 'Message text is no longer kept in this activity log');
|
|
1131
|
+
if (before.selfTrigger !== after.selfTrigger) record('settings', after.selfTrigger === 'prefix' ? 'Only messages that start with sb go to SynaBun now' : 'Every message in "Message yourself" goes to SynaBun now');
|
|
1132
|
+
if (JSON.stringify(before.brain) !== JSON.stringify(after.brain)) {
|
|
1133
|
+
record('settings', after.brain ? `WhatsApp now runs on ${providerLabel(after.brain.provider)} / ${after.brain.model}${after.brain.effort ? ` (${after.brain.effort})` : ''}, from the next message` : 'WhatsApp now runs on the same brain as the Assistant, from the next message');
|
|
1134
|
+
}
|
|
1135
|
+
refreshBridge();
|
|
1136
|
+
notify();
|
|
1137
|
+
return { ok: true, config: userView(after), version: after.version, pending };
|
|
1138
|
+
}
|
|
1139
|
+
function listActivity({ limit = 50 } = {}) {
|
|
1140
|
+
const n = Math.max(1, Math.min(L.activityMax, Number(limit) || 50));
|
|
1141
|
+
return { ok: true, entries: activity.slice(-n).reverse(), total: activity.length, textKept: config.read().activityText, counters: { ...(managerStatus().conn.counters || {}) } };
|
|
1142
|
+
}
|
|
1143
|
+
function clearActivity() {
|
|
1144
|
+
activity.length = 0;
|
|
1145
|
+
for (const name of logFiles()) { try { unlinkSync(path.join(paths.logsDir, name)); } catch {} }
|
|
1146
|
+
state.logFile = null;
|
|
1147
|
+
return { ok: true };
|
|
1148
|
+
}
|
|
1149
|
+
async function newSession() {
|
|
1150
|
+
requireAvailable();
|
|
1151
|
+
if (!isLinked()) throw whatsappError(409, 'NOT_LINKED', 'Link WhatsApp first.');
|
|
1152
|
+
const bridge = ensureBridge();
|
|
1153
|
+
if (!bridge?.newSession) throw whatsappError(409, 'BRIDGE_UNAVAILABLE', 'The WhatsApp conversation is not available right now.');
|
|
1154
|
+
let id;
|
|
1155
|
+
try { id = await bridge.newSession('desktop'); } catch (error) { throw whatsappError(409, error?.code || 'SESSION_FAILED', error?.message || 'Could not start a new conversation.'); }
|
|
1156
|
+
record('state', 'Started a fresh WhatsApp conversation from this computer');
|
|
1157
|
+
notify();
|
|
1158
|
+
return { ok: true, session: sessionView(id) };
|
|
1159
|
+
}
|
|
1160
|
+
function sessionView(id) {
|
|
1161
|
+
if (!id) return null;
|
|
1162
|
+
let title = null;
|
|
1163
|
+
try { title = getRuntime()?.getSession?.(id, { transcript: false })?.title || null; } catch { title = null; }
|
|
1164
|
+
return { id, title };
|
|
1165
|
+
}
|
|
1166
|
+
// ── which brain runs WhatsApp (lib/whatsapp/brain.js) ──────────────────────
|
|
1167
|
+
function catalogRef() { try { return typeof getCatalog === 'function' ? getCatalog() || null : null; } catch { return null; } }
|
|
1168
|
+
/** The Assistant's catalog as last built, or null. `build`: when there is none yet, start one for the next reader. */
|
|
1169
|
+
function catalogPeek({ build = false } = {}) {
|
|
1170
|
+
const catalog = catalogRef();
|
|
1171
|
+
let value = null;
|
|
1172
|
+
try { value = catalog?.peek?.() || null; } catch { value = null; }
|
|
1173
|
+
if (!value && build && catalog?.full) Promise.resolve().then(() => catalog.full()).catch(() => {});
|
|
1174
|
+
return value;
|
|
1175
|
+
}
|
|
1176
|
+
/** The catalog value, built when needed (`fresh`: what is enabled right now, for a save). null when there is none. */
|
|
1177
|
+
async function catalogValue({ fresh = false } = {}) {
|
|
1178
|
+
const catalog = catalogRef();
|
|
1179
|
+
if (!catalog) return null;
|
|
1180
|
+
try { if (catalog.full) return (await catalog.full(fresh ? { force: true } : {})) || null; } catch { /* fall through to what is cached */ }
|
|
1181
|
+
try { return catalog.peek?.() || null; } catch { return null; }
|
|
1182
|
+
}
|
|
1183
|
+
function panelBrain() { try { return typeof getDefaultBrain === 'function' ? getDefaultBrain() || null : null; } catch { return null; } }
|
|
1184
|
+
/** The stored choice, what a conversation runs on now, and why a stored choice is not used (status.brainChoice). */
|
|
1185
|
+
function brainChoice(cfg = config.read()) {
|
|
1186
|
+
// Only a stored choice needs checking: the default never builds the catalog from here.
|
|
1187
|
+
return brainChoiceView(cfg.brain || null, { catalog: catalogPeek({ build: !!cfg.brain }), panelBrain: panelBrain() });
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Ask and Autonomous hold on a Claude brain only (lib/remote-policy.js): the
|
|
1191
|
+
* WhatsApp conversation's brain (or the one a new conversation would get)
|
|
1192
|
+
* when it is Codex or OpenCode and the level asks for more than read-only.
|
|
1193
|
+
*/
|
|
1194
|
+
function brainLimitView(cfg, sessionId) {
|
|
1195
|
+
if (currentLevel(cfg, now()) === 'read-only') return null;
|
|
1196
|
+
let provider = null;
|
|
1197
|
+
try { provider = sessionId ? getRuntime()?.getSession?.(sessionId, { transcript: false })?.brain?.provider || null : null; } catch { provider = null; }
|
|
1198
|
+
if (!provider) provider = brainChoice(cfg).effective?.provider || null;
|
|
1199
|
+
if (!remoteBrainLimited(provider)) return null;
|
|
1200
|
+
return { provider, label: providerLabel(provider), text: remoteBrainNotice(provider) };
|
|
1201
|
+
}
|
|
1202
|
+
const fakeEnabled = () => fake && !disabled;
|
|
1203
|
+
async function fakeAction(body = {}) {
|
|
1204
|
+
if (!fakeEnabled()) throw whatsappError(404, 'NOT_FOUND', 'Not found');
|
|
1205
|
+
const action = String(body.action || '');
|
|
1206
|
+
const cfg = config.read();
|
|
1207
|
+
const call = async (name, args = {}) => {
|
|
1208
|
+
const result = await manager.fake(name, args);
|
|
1209
|
+
if (result?.ok === false) throw whatsappError(409, result.code || 'FAKE_FAILED', result.message || `fake ${name} failed`);
|
|
1210
|
+
return result;
|
|
1211
|
+
};
|
|
1212
|
+
switch (action) {
|
|
1213
|
+
case 'scan': await call('scan'); break;
|
|
1214
|
+
case 'inbound': {
|
|
1215
|
+
const from = body.from === 'stranger' ? 'stranger' : cfg.mode === 'dedicated' ? 'owner' : 'self';
|
|
1216
|
+
const text = String(body.text ?? '').slice(0, 4000);
|
|
1217
|
+
// offline: WhatsApp delivers it after an offline spell (messages.upsert 'append'), sent ageMs ago.
|
|
1218
|
+
const ageMs = Math.min(Math.max(0, Number(body.ageMs) || 0), 7 * DAY);
|
|
1219
|
+
const held = body.offline === true ? { type: 'append', at: now() - ageMs } : {};
|
|
1220
|
+
await call('inbound', { from, text, ...(body.forwarded === true ? { forwarded: true } : {}), ...held });
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
case 'drop': await call('disconnect', { reason: 'connectionLost' }); break;
|
|
1224
|
+
case 'logout': await call('disconnect', { reason: 'loggedOut' }); break;
|
|
1225
|
+
case 'ban': await call('forbidden'); break;
|
|
1226
|
+
case 'crash': await call('crash'); break;
|
|
1227
|
+
case 'state': break;
|
|
1228
|
+
default: throw whatsappError(400, 'BAD_ACTION', 'action must be one of scan, inbound, drop, logout, ban, crash, state', 'action');
|
|
1229
|
+
}
|
|
1230
|
+
let sent = [];
|
|
1231
|
+
try {
|
|
1232
|
+
const snap = await manager.fake('snapshot');
|
|
1233
|
+
sent = (Array.isArray(snap?.sent) ? snap.sent : []).slice(-50).map((row) => ({ kind: row.kind, text: row.kind === 'text' ? String(row.text ?? '') : null, react: row.react?.text ?? null, at: row.at ?? null }));
|
|
1234
|
+
} catch { sent = []; }
|
|
1235
|
+
return { ok: true, action, sent, status: status() };
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// ── status ─────────────────────────────────────────────────────────────────
|
|
1239
|
+
function status() {
|
|
1240
|
+
const cfg = config.read();
|
|
1241
|
+
const tab = tabState();
|
|
1242
|
+
const { conn, host, runtime } = managerStatus();
|
|
1243
|
+
const inst = installerStatus();
|
|
1244
|
+
const level = currentLevel(cfg, now());
|
|
1245
|
+
const bridge = bridgeStatus();
|
|
1246
|
+
const sessionId = bridge?.sessionId || cfg.sessionId || null;
|
|
1247
|
+
let confirm = null;
|
|
1248
|
+
if (tab.state === 'confirm_owner') {
|
|
1249
|
+
confirm = tab.kind === 'self'
|
|
1250
|
+
? { kind: 'self', expiresAt: conn.awaitingConfirmUntil ?? null, attemptsLeft: null }
|
|
1251
|
+
: { kind: 'code', expiresAt: conn.claim?.active ? conn.claim.expiresAt : null, attemptsLeft: conn.claim?.active ? conn.claim.attemptsLeft : null };
|
|
1252
|
+
}
|
|
1253
|
+
const owner = cfg.owner ? { masked: cfg.owner.masked, boundAt: cfg.owner.boundAt } : conn.owner?.bound ? { masked: conn.owner.masked || '••••', boundAt: null } : null;
|
|
1254
|
+
return {
|
|
1255
|
+
ok: true,
|
|
1256
|
+
v: 1,
|
|
1257
|
+
...tab,
|
|
1258
|
+
mode: cfg.mode,
|
|
1259
|
+
level,
|
|
1260
|
+
levelExpiresAt: level === 'autonomous' ? cfg.autonomousUntil : null,
|
|
1261
|
+
enabled: cfg.enabled,
|
|
1262
|
+
paused: cfg.paused,
|
|
1263
|
+
pausedBy: cfg.pausedBy,
|
|
1264
|
+
computerUse: cfg.computerUse === true,
|
|
1265
|
+
fake: fakeEnabled(),
|
|
1266
|
+
connector: {
|
|
1267
|
+
installed: !!inst.installed, version: inst.version ?? null, pinned: inst.pinned ?? null, outdated: !!inst.outdated, updateAvailable: !!inst.outdated,
|
|
1268
|
+
installedAt: inst.installedAt ?? null, approxSizeMB: inst.approxSizeMB ?? null, stage: state.install?.stage || null,
|
|
1269
|
+
errorCode: state.installError?.code || null, errorMessage: state.installError?.message || null, runtime: runtime.loaded ? { version: runtime.version, fake: runtime.fake } : null,
|
|
1270
|
+
},
|
|
1271
|
+
host: { state: host.state, restarts: host.restarts || 0 },
|
|
1272
|
+
account: conn.me ? { masked: conn.me.masked, name: conn.me.name ?? null } : null,
|
|
1273
|
+
owner,
|
|
1274
|
+
confirm,
|
|
1275
|
+
session: sessionView(sessionId),
|
|
1276
|
+
brainLimit: brainLimitView(cfg, sessionId),
|
|
1277
|
+
brainChoice: brainChoice(cfg),
|
|
1278
|
+
bridge: bridge ? { running: !!bridge.running, queued: bridge.queued || 0, pendingCards: bridge.pendingCards || 0, lastReplyAt: bridge.lastReplyAt || null } : null,
|
|
1279
|
+
connection: tab.state === 'reconnecting' ? { attempt: state.reconnectAttempt, nextRetryAt: Number.isFinite(conn.retryInMs) ? state.lastStateAt + conn.retryInMs : null } : null,
|
|
1280
|
+
counters: { ...(conn.counters || {}) },
|
|
1281
|
+
config: userView(cfg),
|
|
1282
|
+
version: cfg.version,
|
|
1283
|
+
escalation: escalationView(),
|
|
1284
|
+
manualCommand: `npm ${NPM_CI_ARGS.join(' ')}`,
|
|
1285
|
+
paths: { session: paths.authDir, connector: paths.runtimeDir, logs: paths.logsDir, installLog: paths.installLogPath },
|
|
1286
|
+
rev: state.rev,
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const ops = {
|
|
1291
|
+
status, setup, installConnector, cancelInstall, removeConnector, connectorLog,
|
|
1292
|
+
link, confirmOwner, claimOwner, resetOwner,
|
|
1293
|
+
test, pause, resume, reconnect, unlink, remove,
|
|
1294
|
+
getConfig, putConfig, listActivity, clearActivity, newSession,
|
|
1295
|
+
fakeEnabled, fake: fakeAction,
|
|
1296
|
+
};
|
|
1297
|
+
const router = createWhatsAppApi({ service: ops, isGuestRequest, port });
|
|
1298
|
+
|
|
1299
|
+
return {
|
|
1300
|
+
router, start, shutdown, killNow, status,
|
|
1301
|
+
toSyncPayload,
|
|
1302
|
+
_internals: { state, config, ops, activity, record, tabState, handleAllow, flushBroadcast, manager, installer, paths, brainChoice },
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
|