@agentprojectcontext/apx 1.80.2 → 1.89.1
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/package.json +9 -8
- package/skills/apc-context/SKILL.md +14 -7
- package/skills/apx/SKILL.md +62 -2
- package/skills/apx-mcp/SKILL.md +42 -15
- package/src/core/agent/a2a/peers.js +161 -0
- package/src/core/agent/a2a/reply.js +379 -27
- package/src/core/agent/abort.js +25 -0
- package/src/core/agent/agent-model.js +58 -0
- package/src/core/agent/agent-tools.js +101 -0
- package/src/core/agent/author-line.js +110 -0
- package/src/core/agent/build-agent-system.js +52 -18
- package/src/core/agent/channels/voice-context.js +17 -2
- package/src/core/agent/closing-floor.js +117 -0
- package/src/core/agent/constants.js +119 -11
- package/src/core/agent/engine-call.js +56 -0
- package/src/core/agent/group/run-group-turn.js +218 -0
- package/src/core/agent/group/turn-resolver.js +165 -0
- package/src/core/agent/judge.js +138 -11
- package/src/core/agent/loop/side-effects.js +106 -3
- package/src/core/agent/memory.js +31 -18
- package/src/core/agent/model-capabilities.js +47 -0
- package/src/core/agent/model-router.js +11 -40
- package/src/core/agent/prompt-builder.js +47 -0
- package/src/core/agent/prompts/channels/a2a.md +12 -0
- package/src/core/agent/prompts/channels/code.md +3 -1
- package/src/core/agent/prompts/channels/telegram.md +2 -0
- package/src/core/agent/prompts/channels/web_code.md +1 -1
- package/src/core/agent/prompts/channels/whatsapp.md +37 -0
- package/src/core/agent/prompts/core/agent-base.md +2 -0
- package/src/core/agent/prompts/discipline/action.md +1 -0
- package/src/core/agent/prompts/discipline/two-segment.md +2 -1
- package/src/core/agent/prompts/modes/mobility.md +36 -0
- package/src/core/agent/retry.js +42 -0
- package/src/core/agent/run-agent.js +322 -25
- package/src/core/agent/run-turn.js +253 -0
- package/src/core/agent/self-memory.js +18 -52
- package/src/core/agent/skills/agent-skills.js +118 -0
- package/src/core/agent/skills/catalog.js +1 -1
- package/src/core/agent/skills/declared.js +13 -0
- package/src/core/agent/skills/index-store.js +8 -1
- package/src/core/agent/skills/inspector.js +20 -3
- package/src/core/agent/skills/loader.js +18 -3
- package/src/core/agent/skills/media.js +121 -0
- package/src/core/agent/skills/origin.js +61 -0
- package/src/core/agent/skills/sections.js +136 -0
- package/src/core/agent/super-agent.js +54 -14
- package/src/core/agent/tools/handlers/_calendar.js +50 -0
- package/src/core/agent/tools/handlers/add-mcp.js +103 -0
- package/src/core/agent/tools/handlers/asana-create-task.js +3 -1
- package/src/core/agent/tools/handlers/attach-media.js +56 -0
- package/src/core/agent/tools/handlers/calendar-create-event.js +38 -0
- package/src/core/agent/tools/handlers/calendar-find-slot.js +39 -0
- package/src/core/agent/tools/handlers/calendar-list-events.js +39 -0
- package/src/core/agent/tools/handlers/calendar-update-event.js +35 -0
- package/src/core/agent/tools/handlers/call-agent.js +14 -6
- package/src/core/agent/tools/handlers/call-runtime.js +27 -49
- package/src/core/agent/tools/handlers/comment-task.js +78 -0
- package/src/core/agent/tools/handlers/complete-task.js +60 -0
- package/src/core/agent/tools/handlers/configure-agent.js +70 -0
- package/src/core/agent/tools/handlers/create-agent.js +68 -0
- package/src/core/agent/tools/handlers/create-task.js +41 -3
- package/src/core/agent/tools/handlers/import-agent.js +5 -5
- package/src/core/agent/tools/handlers/list-mcp-tools.js +83 -0
- package/src/core/agent/tools/handlers/list-routines.js +46 -0
- package/src/core/agent/tools/handlers/list-tasks.js +8 -1
- package/src/core/agent/tools/handlers/mark-commitment.js +57 -0
- package/src/core/agent/tools/handlers/read-skill.js +64 -0
- package/src/core/agent/tools/handlers/remember-routine.js +2 -8
- package/src/core/agent/tools/handlers/remember.js +82 -6
- package/src/core/agent/tools/handlers/remove-agent.js +42 -0
- package/src/core/agent/tools/handlers/run-routine.js +94 -0
- package/src/core/agent/tools/handlers/run-shell.js +44 -2
- package/src/core/agent/tools/handlers/send-telegram.js +41 -19
- package/src/core/agent/tools/handlers/set-agent-prompt.js +44 -0
- package/src/core/agent/tools/handlers/view-media.js +69 -0
- package/src/core/agent/tools/handlers/write-agent-memory.js +52 -0
- package/src/core/agent/tools/helpers.js +36 -4
- package/src/core/agent/tools/names.js +64 -0
- package/src/core/agent/tools/registry-bridge.js +24 -1
- package/src/core/agent/tools/registry.js +79 -0
- package/src/core/agent/tools/tool-call-parser.js +136 -13
- package/src/core/agent/vision-bridge.js +69 -0
- package/src/core/apc/agent-identity.js +74 -0
- package/src/core/apc/agent-rename-refs.js +187 -0
- package/src/core/apc/agent-write.js +312 -0
- package/src/core/apc/agents-vault.js +1 -1
- package/src/core/apc/blob-keys.js +42 -0
- package/src/core/apc/frontmatter.js +166 -10
- package/src/core/apc/parser.js +33 -83
- package/src/core/apc/paths.js +4 -6
- package/src/core/apc/project-memory.js +44 -0
- package/src/core/apc/projects-helpers.js +33 -0
- package/src/core/apc/scaffold.js +36 -16
- package/src/core/channels/telegram/ask-callbacks.js +103 -0
- package/src/core/channels/telegram/dispatch.js +154 -30
- package/src/core/channels/telegram/helpers.js +59 -7
- package/src/core/channels/telegram/inbound/audio.js +26 -30
- package/src/core/channels/telegram/inbound/file.js +22 -32
- package/src/core/channels/telegram/inbound/photo.js +24 -28
- package/src/core/channels/telegram/progress-gate.js +76 -0
- package/src/core/channels/telegram/reply.js +151 -43
- package/src/core/channels/telegram/voice-note.js +150 -0
- package/src/core/config/index.js +53 -54
- package/src/core/config/paths.js +117 -31
- package/src/core/config/redact.js +89 -4
- package/src/core/config/secret-values.js +2 -4
- package/src/core/constants/actors.js +5 -0
- package/src/core/constants/channels.js +3 -1
- package/src/core/constants/task-categories.js +73 -0
- package/src/core/constants/task-fields.js +28 -0
- package/src/core/engines/_globs.js +33 -0
- package/src/core/engines/anthropic.js +19 -1
- package/src/core/engines/catalog.js +20 -0
- package/src/core/engines/gemini.js +84 -16
- package/src/core/engines/index.js +19 -3
- package/src/core/engines/mock.js +145 -1
- package/src/core/engines/openai-compatible.js +150 -15
- package/src/core/engines/presets.js +36 -9
- package/src/core/engines/zen.js +131 -0
- package/src/core/events/bus.js +140 -0
- package/src/core/events/mascot-notify.js +156 -0
- package/src/core/http-tools/browser.js +366 -36
- package/src/core/http-tools/catalog.js +206 -22
- package/src/core/http-tools/grep.js +62 -7
- package/src/core/http-tools/registry.js +3 -1
- package/src/core/i18n/en.js +34 -3
- package/src/core/i18n/es.js +36 -4
- package/src/core/i18n/index.js +5 -5
- package/src/core/i18n/pt.js +32 -3
- package/src/core/identity/self.js +14 -1
- package/src/core/images/engines/a1111.js +219 -0
- package/src/core/images/engines/index.js +187 -0
- package/src/core/images/engines/mock.js +74 -0
- package/src/core/images/engines/openai.js +130 -0
- package/src/core/images/engines/sdcpp.js +204 -0
- package/src/core/images/engines/shared.js +218 -0
- package/src/core/images/generate.js +260 -0
- package/src/core/integrations/catalog.js +8 -3
- package/src/core/integrations/plugins/_google-oauth.js +179 -0
- package/src/core/integrations/plugins/asana.js +7 -3
- package/src/core/integrations/plugins/calendar.js +404 -0
- package/src/core/integrations/store.js +2 -0
- package/src/core/memory/broker.js +16 -1
- package/src/core/memory/compactor.js +19 -3
- package/src/core/memory/consolidate.js +2 -2
- package/src/core/memory/dated-log.js +66 -0
- package/src/core/memory/embed-engines/custom.js +59 -0
- package/src/core/memory/embed-engines/gemini.js +9 -3
- package/src/core/memory/embed-engines/index.js +87 -15
- package/src/core/memory/embeddings.js +52 -24
- package/src/core/memory/index.js +13 -3
- package/src/core/memory/indexer.js +83 -27
- package/src/core/memory/prune.js +182 -0
- package/src/core/memory/store.js +22 -0
- package/src/core/memory/summarizer.js +72 -8
- package/src/core/mobility/answer.js +80 -0
- package/src/core/mobility/geofence.js +667 -0
- package/src/core/mobility/osm-route.js +282 -0
- package/src/core/mobility/preferences.js +91 -0
- package/src/core/mobility/state.js +245 -0
- package/src/core/mobility/trip-event.js +384 -0
- package/src/core/net/tailscale.js +168 -0
- package/src/core/profiles/bundled/secretary/PROFILE.md +32 -42
- package/src/core/profiles/bundled/secretary/config.schema.es.json +68 -0
- package/src/core/profiles/bundled/secretary/config.schema.json +30 -2
- package/src/core/profiles/bundled/secretary/profile.json +3 -1
- package/src/core/profiles/bundled/secretary/routines/a2a-sweep.json +20 -0
- package/src/core/profiles/bundled/secretary/routines/day-close.json +4 -1
- package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -1
- package/src/core/profiles/bundled/secretary/routines/watch.json +13 -2
- package/src/core/profiles/lifecycle.js +243 -19
- package/src/core/profiles/paths.js +12 -0
- package/src/core/profiles/store.js +47 -0
- package/src/core/routines/active-runs.js +213 -0
- package/src/core/routines/delivery.js +612 -0
- package/src/core/routines/header.js +88 -0
- package/src/core/routines/index.js +26 -0
- package/src/core/routines/run-log.js +78 -0
- package/src/core/routines/runner.js +629 -66
- package/src/core/routines/signals.js +75 -0
- package/src/core/runtime-skills/apc-context/SKILL.md +14 -7
- package/src/core/runtime-skills/apx/SKILL.md +93 -1
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +2 -2
- package/src/core/runtime-skills/apx-agent/SKILL.md +134 -19
- package/src/core/runtime-skills/apx-commitment/SKILL.md +134 -0
- package/src/core/runtime-skills/apx-image/SKILL.md +60 -0
- package/src/core/runtime-skills/apx-integrations/SKILL.md +82 -0
- package/src/core/runtime-skills/apx-mcp/SKILL.md +45 -14
- package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +4 -4
- package/src/core/runtime-skills/apx-profile/SKILL.md +27 -2
- package/src/core/runtime-skills/apx-project/SKILL.md +23 -2
- package/src/core/runtime-skills/apx-routine/SKILL.md +40 -17
- package/src/core/runtime-skills/apx-runtime/SKILL.md +124 -14
- package/src/core/runtime-skills/apx-sessions/SKILL.md +7 -6
- package/src/core/runtime-skills/apx-skill-builder/SKILL.md +15 -13
- package/src/core/runtime-skills/apx-task/SKILL.md +79 -26
- package/src/core/runtime-skills/apx-telegram/SKILL.md +12 -12
- package/src/core/runtime-skills/apx-transcribe/SKILL.md +56 -0
- package/src/core/runtime-skills/apx-voice/SKILL.md +27 -8
- package/src/core/runtime-skills/claude-code/SKILL.md +8 -0
- package/src/core/runtime-skills/openrouter/SKILL.md +8 -2
- package/src/core/runtimes/_spawn.js +9 -1
- package/src/core/runtimes/claude-code.js +11 -2
- package/src/core/runtimes/codex.js +44 -10
- package/src/core/runtimes/index.js +17 -2
- package/src/core/runtimes/opencode.js +88 -9
- package/src/core/runtimes/outcome.js +119 -0
- package/src/core/sessions/index.js +193 -1
- package/src/core/stores/agent-inbox.js +109 -23
- package/src/core/stores/code-sessions.js +21 -0
- package/src/core/stores/commitments.js +27 -3
- package/src/core/stores/conversations.js +220 -20
- package/src/core/stores/deliveries.js +177 -0
- package/src/core/stores/media-archive.js +228 -0
- package/src/core/stores/messages.js +1096 -112
- package/src/core/stores/organization.js +34 -0
- package/src/core/stores/project-memory.js +71 -0
- package/src/core/stores/routine-memory.js +16 -8
- package/src/core/stores/routines.js +27 -49
- package/src/core/stores/sessions.js +4 -8
- package/src/core/stores/tasks.js +212 -18
- package/src/core/stores/turn-record.js +209 -0
- package/src/core/tasks/columns.js +136 -0
- package/src/core/tasks/comment-turn.js +258 -0
- package/src/core/util/time.js +13 -0
- package/src/core/voice/engines/index.js +54 -1
- package/src/core/voice/engines/openai.js +50 -4
- package/src/core/voice/opus.js +67 -0
- package/src/core/voice/pronounceable.js +43 -0
- package/src/core/voice/transcription.js +4 -0
- package/src/core/voice/tts.js +114 -24
- package/src/host/daemon/active-turns.js +188 -0
- package/src/host/daemon/api/admin-config.js +9 -0
- package/src/host/daemon/api/agents.js +99 -90
- package/src/host/daemon/api/artifact-preview.js +7 -6
- package/src/host/daemon/api/artifacts.js +3 -2
- package/src/host/daemon/api/code.js +55 -9
- package/src/host/daemon/api/commitments.js +11 -0
- package/src/host/daemon/api/confirm.js +3 -2
- package/src/host/daemon/api/conversations.js +543 -35
- package/src/host/daemon/api/deck.js +9 -8
- package/src/host/daemon/api/deliveries.js +33 -0
- package/src/host/daemon/api/desktop.js +5 -4
- package/src/host/daemon/api/embeddings.js +15 -6
- package/src/host/daemon/api/engines.js +59 -8
- package/src/host/daemon/api/exec.js +437 -121
- package/src/host/daemon/api/groups.js +215 -0
- package/src/host/daemon/api/images.js +107 -0
- package/src/host/daemon/api/inbox.js +123 -2
- package/src/host/daemon/api/integrations.js +64 -2
- package/src/host/daemon/api/mcps.js +7 -6
- package/src/host/daemon/api/media.js +283 -0
- package/src/host/daemon/api/mobility.js +177 -0
- package/src/host/daemon/api/net.js +182 -0
- package/src/host/daemon/api/pairing.js +38 -5
- package/src/host/daemon/api/profiles.js +30 -2
- package/src/host/daemon/api/routines.js +36 -4
- package/src/host/daemon/api/runtimes.js +74 -39
- package/src/host/daemon/api/sessions-search.js +7 -2
- package/src/host/daemon/api/sessions.js +44 -2
- package/src/host/daemon/api/shared.js +127 -20
- package/src/host/daemon/api/skills.js +11 -4
- package/src/host/daemon/api/super-agent.js +384 -53
- package/src/host/daemon/api/tasks.js +180 -10
- package/src/host/daemon/api/telegram.js +11 -10
- package/src/host/daemon/api/thread-faces.js +159 -0
- package/src/host/daemon/api/top-level.js +3 -3
- package/src/host/daemon/api/transcribe.js +11 -10
- package/src/host/daemon/api/tts.js +97 -4
- package/src/host/daemon/api/turn-abort.js +23 -0
- package/src/host/daemon/api/turns.js +49 -0
- package/src/host/daemon/api/voice.js +5 -4
- package/src/host/daemon/api/web.js +98 -0
- package/src/host/daemon/api.js +39 -0
- package/src/host/daemon/delivery-sweep.js +58 -0
- package/src/host/daemon/desktop-ws.js +5 -23
- package/src/host/daemon/events-ws.js +273 -0
- package/src/host/daemon/index.js +59 -15
- package/src/host/daemon/plugins/desktop/index.js +17 -4
- package/src/host/daemon/plugins/telegram/index.js +78 -40
- package/src/host/daemon/pty-bridge.py +99 -0
- package/src/host/daemon/routines-scheduler.js +1 -0
- package/src/host/daemon/smoke.js +1 -2
- package/src/host/daemon/terminal-ws.js +132 -0
- package/src/host/daemon/tts-keepwarm.js +87 -0
- package/src/host/daemon/wakeup.js +11 -6
- package/src/host/daemon/whisper-server.js +44 -0
- package/src/host/daemon/whisper-server.py +70 -13
- package/src/host/daemon/ws-auth.js +28 -0
- package/src/interfaces/android/app/build.gradle +42 -0
- package/src/interfaces/android/app/proguard-rules.pro +4 -0
- package/src/interfaces/android/app/src/main/AndroidManifest.xml +139 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/AlertScreen.java +139 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/AndroidAutoDetector.java +106 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ApxCarAppService.java +57 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ApxPreferences.java +165 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/CarAlertStore.java +84 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/CarMessageActionReceiver.java +47 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/CarMessageNotification.java +152 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/DaemonAddress.java +35 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/DaemonClient.java +339 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/DeviceLocation.java +38 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ErrandScreen.java +127 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MainActivity.java +872 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MapsNavigationDetector.java +63 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MapsNavigationListenerService.java +453 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MapsShareIntentParser.java +93 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MascotBlobCatalog.java +66 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MascotOverlayService.java +448 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MascotView.java +249 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MessageFrameParser.java +240 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/NotifyChannels.java +69 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ProximityActionReceiver.java +78 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ProximityNotification.java +161 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/StartupReceiver.java +57 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TravelEventGate.java +62 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TravelStatusBanner.java +116 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TripLocationService.java +272 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TripPlacesSheet.java +203 -0
- package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TripScreen.java +182 -0
- package/src/interfaces/android/app/src/main/res/drawable/app_background.xml +12 -0
- package/src/interfaces/android/app/src/main/res/drawable/ic_apx_app.xml +10 -0
- package/src/interfaces/android/app/src/main/res/drawable/ic_apx_launcher_foreground.xml +12 -0
- package/src/interfaces/android/app/src/main/res/drawable/ic_apx_notification.xml +22 -0
- package/src/interfaces/android/app/src/main/res/drawable/ic_trip_car.xml +37 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/apx_logo.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_amatista.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_aqua.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_brasa.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_campana.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_caracol.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_carbon.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_cobalto.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_cometa.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_coral.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_cubi.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_eclipse.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_faro.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_gajo.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_iris.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_kiwi.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_laguna.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_lila.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_menta.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_nimbo.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_noche.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_onyx.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_orbita.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_ovni.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_papa.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_parche.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_peon.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_perla.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_pino.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_quad.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_rubi.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_saturno.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_trebol.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_trino.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_volcan.png +0 -0
- package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_zafiro.png +0 -0
- package/src/interfaces/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/src/interfaces/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
- package/src/interfaces/android/app/src/main/res/raw/apx_notification.mp3 +0 -0
- package/src/interfaces/android/app/src/main/res/values/colors.xml +4 -0
- package/src/interfaces/android/app/src/main/res/values/strings.xml +25 -0
- package/src/interfaces/android/app/src/main/res/values/styles.xml +10 -0
- package/src/interfaces/android/app/src/main/res/values-v31/styles.xml +14 -0
- package/src/interfaces/android/app/src/main/res/xml/automotive_app_desc.xml +9 -0
- package/src/interfaces/android/app/src/main/res/xml/network_security_config.xml +10 -0
- package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/AndroidAutoDetectorTest.java +85 -0
- package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/CarAlertStoreTest.java +104 -0
- package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/DaemonAddressTest.java +23 -0
- package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/MapsNavigationDetectorTest.java +69 -0
- package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/MapsShareIntentParserTest.java +31 -0
- package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/MessageFrameParserTest.java +180 -0
- package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/TravelEventGateTest.java +64 -0
- package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/TripPlacesSheetTest.java +66 -0
- package/src/interfaces/android/build.gradle +3 -0
- package/src/interfaces/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/src/interfaces/android/gradle/wrapper/gradle-wrapper.properties +9 -0
- package/src/interfaces/android/gradle.properties +3 -0
- package/src/interfaces/android/gradlew +248 -0
- package/src/interfaces/android/gradlew.bat +82 -0
- package/src/interfaces/android/settings.gradle +18 -0
- package/src/interfaces/cli/commands/a2a.js +145 -4
- package/src/interfaces/cli/commands/agent.js +247 -22
- package/src/interfaces/cli/commands/code.js +1 -1
- package/src/interfaces/cli/commands/commitment.js +5 -0
- package/src/interfaces/cli/commands/config.js +85 -6
- package/src/interfaces/cli/commands/exec.js +90 -1
- package/src/interfaces/cli/commands/image.js +314 -0
- package/src/interfaces/cli/commands/mcp.js +87 -23
- package/src/interfaces/cli/commands/memory.js +35 -22
- package/src/interfaces/cli/commands/panel.js +94 -0
- package/src/interfaces/cli/commands/profile.js +14 -0
- package/src/interfaces/cli/commands/project.js +29 -0
- package/src/interfaces/cli/commands/routine.js +45 -10
- package/src/interfaces/cli/commands/runtime.js +30 -7
- package/src/interfaces/cli/commands/session.js +90 -38
- package/src/interfaces/cli/commands/sessions.js +35 -2
- package/src/interfaces/cli/commands/skills.js +15 -9
- package/src/interfaces/cli/commands/status.js +2 -3
- package/src/interfaces/cli/commands/task.js +54 -5
- package/src/interfaces/cli/commands/telegram.js +12 -4
- package/src/interfaces/cli/commands/transcribe.js +177 -0
- package/src/interfaces/cli/help/index.js +311 -72
- package/src/interfaces/cli/http.js +61 -13
- package/src/interfaces/cli/index.js +1 -1
- package/src/interfaces/cli/routes/agent.js +5 -4
- package/src/interfaces/cli/routes/commitment.js +3 -2
- package/src/interfaces/cli/routes/config.js +7 -2
- package/src/interfaces/cli/routes/image.js +11 -0
- package/src/interfaces/cli/routes/index.js +2 -0
- package/src/interfaces/cli/routes/log.js +1 -1
- package/src/interfaces/cli/routes/memory.js +3 -2
- package/src/interfaces/cli/routes/panel.js +15 -2
- package/src/interfaces/cli/routes/profile.js +3 -2
- package/src/interfaces/cli/routes/transcribe.js +10 -0
- package/src/interfaces/cli/stdin.js +29 -0
- package/src/interfaces/desktop/assets/blobs/amatista.png +0 -0
- package/src/interfaces/desktop/assets/blobs/aqua.png +0 -0
- package/src/interfaces/desktop/assets/blobs/brasa.png +0 -0
- package/src/interfaces/desktop/assets/blobs/campana.png +0 -0
- package/src/interfaces/desktop/assets/blobs/caracol.png +0 -0
- package/src/interfaces/desktop/assets/blobs/carbon.png +0 -0
- package/src/interfaces/desktop/assets/blobs/cobalto.png +0 -0
- package/src/interfaces/desktop/assets/blobs/cometa.png +0 -0
- package/src/interfaces/desktop/assets/blobs/coral.png +0 -0
- package/src/interfaces/desktop/assets/blobs/cubi.png +0 -0
- package/src/interfaces/desktop/assets/blobs/eclipse.png +0 -0
- package/src/interfaces/desktop/assets/blobs/faro.png +0 -0
- package/src/interfaces/desktop/assets/blobs/gajo.png +0 -0
- package/src/interfaces/desktop/assets/blobs/iris.png +0 -0
- package/src/interfaces/desktop/assets/blobs/kiwi.png +0 -0
- package/src/interfaces/desktop/assets/blobs/laguna.png +0 -0
- package/src/interfaces/desktop/assets/blobs/lila.png +0 -0
- package/src/interfaces/desktop/assets/blobs/menta.png +0 -0
- package/src/interfaces/desktop/assets/blobs/nimbo.png +0 -0
- package/src/interfaces/desktop/assets/blobs/noche.png +0 -0
- package/src/interfaces/desktop/assets/blobs/onyx.png +0 -0
- package/src/interfaces/desktop/assets/blobs/orbita.png +0 -0
- package/src/interfaces/desktop/assets/blobs/ovni.png +0 -0
- package/src/interfaces/desktop/assets/blobs/papa.png +0 -0
- package/src/interfaces/desktop/assets/blobs/parche.png +0 -0
- package/src/interfaces/desktop/assets/blobs/peon.png +0 -0
- package/src/interfaces/desktop/assets/blobs/perla.png +0 -0
- package/src/interfaces/desktop/assets/blobs/pino.png +0 -0
- package/src/interfaces/desktop/assets/blobs/quad.png +0 -0
- package/src/interfaces/desktop/assets/blobs/rubi.png +0 -0
- package/src/interfaces/desktop/assets/blobs/saturno.png +0 -0
- package/src/interfaces/desktop/assets/blobs/trebol.png +0 -0
- package/src/interfaces/desktop/assets/blobs/trino.png +0 -0
- package/src/interfaces/desktop/assets/blobs/volcan.png +0 -0
- package/src/interfaces/desktop/assets/blobs/zafiro.png +0 -0
- package/src/interfaces/desktop/assets/notification.mp3 +0 -0
- package/src/interfaces/desktop/main.js +473 -29
- package/src/interfaces/desktop/mascot-blobs.js +41 -0
- package/src/interfaces/desktop/mascot-preload.js +32 -0
- package/src/interfaces/desktop/mascot.css +186 -0
- package/src/interfaces/desktop/mascot.html +40 -0
- package/src/interfaces/desktop/mascot.js +163 -0
- package/src/interfaces/desktop/preload.js +10 -0
- package/src/interfaces/desktop/renderer.js +308 -48
- package/src/interfaces/desktop/style.css +34 -0
- package/src/interfaces/mcp-server/index.js +87 -0
- package/src/interfaces/tui/_shims/util-locale.ts +0 -2
- package/src/interfaces/tui/app.tsx +0 -6
- package/src/interfaces/tui/attach.ts +0 -1
- package/src/interfaces/tui/context/sdk-apx.tsx +0 -4
- package/src/interfaces/tui/plugin/api.tsx +0 -3
- package/src/interfaces/tui/plugin/runtime.ts +0 -3
- package/src/interfaces/tui/thread.ts +0 -16
- package/src/interfaces/web/README.md +22 -14
- package/src/interfaces/web/dist/assets/claude-XtpLmGtF.webp +0 -0
- package/src/interfaces/web/dist/assets/codex-DExPUgvh.webp +0 -0
- package/src/interfaces/web/dist/assets/index-BEoRgIqd.css +32 -0
- package/src/interfaces/web/dist/assets/index-BEoRgIqd.css.br +0 -0
- package/src/interfaces/web/dist/assets/index-BEoRgIqd.css.gz +0 -0
- package/src/interfaces/web/dist/assets/index-C6fXHc1p.js +1074 -0
- package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.br +0 -0
- package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.gz +0 -0
- package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.map.br +0 -0
- package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.map.gz +0 -0
- package/src/interfaces/web/dist/favicon/dark/site.webmanifest +12 -6
- package/src/interfaces/web/dist/favicon/white/site.webmanifest +12 -6
- package/src/interfaces/web/dist/index.html +22 -6
- package/src/interfaces/web/dist/index.html.br +0 -0
- package/src/interfaces/web/dist/index.html.gz +0 -0
- package/src/interfaces/web/dist/manifest.webmanifest +25 -0
- package/src/interfaces/web/dist/modules/blobs/amatista.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/aqua.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/brasa.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/campana.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/caracol.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/carbon.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/cobalto.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/cometa.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/coral.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/cubi.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/eclipse.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/faro.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/gajo.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/iris.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/kiwi.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/laguna.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/lila.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/menta.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/nimbo.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/noche.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/onyx.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/orbita.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/ovni.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/papa.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/parche.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/peon.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/perla.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/pino.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/quad.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/rubi.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/saturno.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/trebol.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/trino.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/volcan.png +0 -0
- package/src/interfaces/web/dist/modules/blobs/zafiro.png +0 -0
- package/src/interfaces/web/dist/sw.js +106 -0
- package/src/interfaces/web/dist/sw.js.br +0 -0
- package/src/interfaces/web/dist/sw.js.gz +0 -0
- package/src/interfaces/web/eslint.config.js +83 -0
- package/src/interfaces/web/index.html +20 -4
- package/src/interfaces/web/package-lock.json +1651 -1280
- package/src/interfaces/web/package.json +9 -10
- package/src/interfaces/web/pnpm-lock.yaml +867 -869
- package/src/interfaces/web/public/favicon/dark/site.webmanifest +12 -6
- package/src/interfaces/web/public/favicon/white/site.webmanifest +12 -6
- package/src/interfaces/web/public/manifest.webmanifest +25 -0
- package/src/interfaces/web/public/modules/blobs/amatista.png +0 -0
- package/src/interfaces/web/public/modules/blobs/aqua.png +0 -0
- package/src/interfaces/web/public/modules/blobs/brasa.png +0 -0
- package/src/interfaces/web/public/modules/blobs/campana.png +0 -0
- package/src/interfaces/web/public/modules/blobs/caracol.png +0 -0
- package/src/interfaces/web/public/modules/blobs/carbon.png +0 -0
- package/src/interfaces/web/public/modules/blobs/cobalto.png +0 -0
- package/src/interfaces/web/public/modules/blobs/cometa.png +0 -0
- package/src/interfaces/web/public/modules/blobs/coral.png +0 -0
- package/src/interfaces/web/public/modules/blobs/cubi.png +0 -0
- package/src/interfaces/web/public/modules/blobs/eclipse.png +0 -0
- package/src/interfaces/web/public/modules/blobs/faro.png +0 -0
- package/src/interfaces/web/public/modules/blobs/gajo.png +0 -0
- package/src/interfaces/web/public/modules/blobs/iris.png +0 -0
- package/src/interfaces/web/public/modules/blobs/kiwi.png +0 -0
- package/src/interfaces/web/public/modules/blobs/laguna.png +0 -0
- package/src/interfaces/web/public/modules/blobs/lila.png +0 -0
- package/src/interfaces/web/public/modules/blobs/menta.png +0 -0
- package/src/interfaces/web/public/modules/blobs/nimbo.png +0 -0
- package/src/interfaces/web/public/modules/blobs/noche.png +0 -0
- package/src/interfaces/web/public/modules/blobs/onyx.png +0 -0
- package/src/interfaces/web/public/modules/blobs/orbita.png +0 -0
- package/src/interfaces/web/public/modules/blobs/ovni.png +0 -0
- package/src/interfaces/web/public/modules/blobs/papa.png +0 -0
- package/src/interfaces/web/public/modules/blobs/parche.png +0 -0
- package/src/interfaces/web/public/modules/blobs/peon.png +0 -0
- package/src/interfaces/web/public/modules/blobs/perla.png +0 -0
- package/src/interfaces/web/public/modules/blobs/pino.png +0 -0
- package/src/interfaces/web/public/modules/blobs/quad.png +0 -0
- package/src/interfaces/web/public/modules/blobs/rubi.png +0 -0
- package/src/interfaces/web/public/modules/blobs/saturno.png +0 -0
- package/src/interfaces/web/public/modules/blobs/trebol.png +0 -0
- package/src/interfaces/web/public/modules/blobs/trino.png +0 -0
- package/src/interfaces/web/public/modules/blobs/volcan.png +0 -0
- package/src/interfaces/web/public/modules/blobs/zafiro.png +0 -0
- package/src/interfaces/web/public/sw.js +106 -0
- package/src/interfaces/web/src/App.tsx +106 -10
- package/src/interfaces/web/src/assets/cli/claude.webp +0 -0
- package/src/interfaces/web/src/assets/cli/codex.webp +0 -0
- package/src/interfaces/web/src/assets/cli/cursor.svg +1 -0
- package/src/interfaces/web/src/assets/cli/opencode.png +0 -0
- package/src/interfaces/web/src/components/Combobox.tsx +201 -0
- package/src/interfaces/web/src/components/MobileHint.tsx +95 -0
- package/src/interfaces/web/src/components/MobileLinkDialog.tsx +156 -0
- package/src/interfaces/web/src/components/ModelCombobox.tsx +21 -113
- package/src/interfaces/web/src/components/{routines/ReadOnlyBlock.tsx → ReadOnlyBlock.tsx} +13 -4
- package/src/interfaces/web/src/components/Roby.tsx +5 -3
- package/src/interfaces/web/src/components/RobyBubble.tsx +23 -5
- package/src/interfaces/web/src/components/RowMenu.tsx +46 -0
- package/src/interfaces/web/src/components/TimezoneSelect.tsx +4 -1
- package/src/interfaces/web/src/components/Toast.tsx +50 -14
- package/src/interfaces/web/src/components/UiSelect.tsx +12 -5
- package/src/interfaces/web/src/components/agents/AgentAvatar.tsx +171 -0
- package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +42 -11
- package/src/interfaces/web/src/components/agents/AgentModelBadge.tsx +157 -0
- package/src/interfaces/web/src/components/agents/AgentModelSelect.tsx +57 -0
- package/src/interfaces/web/src/components/agents/AgentSkillsPicker.tsx +115 -0
- package/src/interfaces/web/src/components/agents/BlobAvatar.tsx +50 -0
- package/src/interfaces/web/src/components/agents/blobPresets.ts +50 -0
- package/src/interfaces/web/src/components/agents/modelCatalog.ts +66 -0
- package/src/interfaces/web/src/components/chat/ActionGroup.tsx +162 -0
- package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +4 -3
- package/src/interfaces/web/src/components/chat/AskQuestionsCard.tsx +1 -1
- package/src/interfaces/web/src/components/chat/Attachment.tsx +220 -0
- package/src/interfaces/web/src/components/chat/ChatList.tsx +226 -51
- package/src/interfaces/web/src/components/chat/ChatRowActivity.tsx +45 -0
- package/src/interfaces/web/src/components/chat/Composer.tsx +672 -23
- package/src/interfaces/web/src/components/chat/ComposerCommands.tsx +175 -0
- package/src/interfaces/web/src/components/chat/ComposerSuggest.tsx +215 -0
- package/src/interfaces/web/src/components/chat/ContextBar.tsx +133 -65
- package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +107 -47
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +460 -119
- package/src/interfaces/web/src/components/chat/MessageList.tsx +165 -18
- package/src/interfaces/web/src/components/chat/ReasoningBlock.tsx +58 -0
- package/src/interfaces/web/src/components/chat/SessionPicker.tsx +182 -0
- package/src/interfaces/web/src/components/chat/SkillTrace.tsx +183 -0
- package/src/interfaces/web/src/components/chat/ToolCall.tsx +11 -11
- package/src/interfaces/web/src/components/code/CodeContextTab.tsx +87 -53
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +11 -52
- package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +5 -4
- package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +31 -7
- package/src/interfaces/web/src/components/code/CodeSessionList.tsx +32 -11
- package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +33 -3
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +5 -4
- package/src/interfaces/web/src/components/code/NewCodeSessionDialog.tsx +164 -0
- package/src/interfaces/web/src/components/commitments/CommitmentDetail.tsx +162 -0
- package/src/interfaces/web/src/components/commitments/CommitmentFormDialog.tsx +158 -0
- package/src/interfaces/web/src/components/commitments/CommitmentList.tsx +194 -0
- package/src/interfaces/web/src/components/commitments/commitmentState.tsx +46 -0
- package/src/interfaces/web/src/components/common/BulkActionBar.tsx +60 -0
- package/src/interfaces/web/src/components/common/ConfirmDialog.tsx +4 -1
- package/src/interfaces/web/src/components/common/SelectCheckbox.tsx +40 -0
- package/src/interfaces/web/src/components/common/TabNav.tsx +1 -1
- package/src/interfaces/web/src/components/cron/CronPicker.tsx +1 -1
- package/src/interfaces/web/src/components/files/FileBrowser.tsx +1 -1
- package/src/interfaces/web/src/components/files/FileTree.tsx +6 -4
- package/src/interfaces/web/src/components/files/FileViewer.tsx +9 -5
- package/src/interfaces/web/src/components/files/MarkdownPreview.tsx +45 -9
- package/src/interfaces/web/src/components/images/ImageDefaultsCard.tsx +93 -0
- package/src/interfaces/web/src/components/images/ImageProviderList.tsx +142 -0
- package/src/interfaces/web/src/components/images/ImageProviderModal.tsx +263 -0
- package/src/interfaces/web/src/components/images/ImageTestCard.tsx +142 -0
- package/src/interfaces/web/src/components/inbox/ChannelFilter.tsx +178 -0
- package/src/interfaces/web/src/components/inbox/InboxList.tsx +77 -81
- package/src/interfaces/web/src/components/inbox/InboxRowItem.tsx +140 -0
- package/src/interfaces/web/src/components/integrations/ComingSoonPlugin.tsx +2 -2
- package/src/interfaces/web/src/components/integrations/PluginConnect.tsx +208 -30
- package/src/interfaces/web/src/components/integrations/PluginToolsSection.tsx +1 -1
- package/src/interfaces/web/src/components/layout/ProjectAvatar.tsx +39 -24
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +253 -58
- package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +94 -47
- package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +202 -90
- package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +28 -6
- package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +16 -5
- package/src/interfaces/web/src/components/routines/RoutineList.tsx +49 -9
- package/src/interfaces/web/src/components/routines/VarTextarea.tsx +1 -1
- package/src/interfaces/web/src/components/routines/runChat.ts +121 -0
- package/src/interfaces/web/src/components/sessions/SessionDetail.tsx +148 -0
- package/src/interfaces/web/src/components/sessions/SessionTerminal.tsx +98 -0
- package/src/interfaces/web/src/components/settings/AccessPanel.tsx +205 -0
- package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +246 -101
- package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +36 -2
- package/src/interfaces/web/src/components/settings/DevicesPanel.tsx +19 -6
- package/src/interfaces/web/src/components/settings/EmbedProviderList.tsx +274 -0
- package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +26 -6
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +118 -119
- package/src/interfaces/web/src/components/settings/NudgePanel.tsx +4 -3
- package/src/interfaces/web/src/components/settings/PairDeviceDialog.tsx +15 -4
- package/src/interfaces/web/src/components/settings/PanelPrefs.tsx +374 -0
- package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +70 -2
- package/src/interfaces/web/src/components/settings/RoutingPanel.tsx +2 -1
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +102 -43
- package/src/interfaces/web/src/components/settings/SkillsManager.tsx +76 -21
- package/src/interfaces/web/src/components/settings/SkillsSettings.tsx +10 -3
- package/src/interfaces/web/src/components/settings/SuperAgentPanel.tsx +8 -0
- package/src/interfaces/web/src/components/settings/TelegramChannelsPanel.tsx +11 -2
- package/src/interfaces/web/src/components/settings/TelegramContactsPanel.tsx +19 -8
- package/src/interfaces/web/src/components/settings/TelegramRolesPanel.tsx +12 -2
- package/src/interfaces/web/src/components/settings/WebPanel.tsx +22 -31
- package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +15 -3
- package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +97 -66
- package/src/interfaces/web/src/components/settings/providers/ProviderTestDialog.tsx +150 -0
- package/src/interfaces/web/src/components/settings/providers/typeStyles.ts +67 -25
- package/src/interfaces/web/src/components/settings/providers/types.ts +3 -0
- package/src/interfaces/web/src/components/tasks/TaskBoard.tsx +201 -0
- package/src/interfaces/web/src/components/tasks/TaskColumnsDialog.tsx +243 -0
- package/src/interfaces/web/src/components/tasks/TaskComments.tsx +167 -0
- package/src/interfaces/web/src/components/tasks/TaskDetail.tsx +335 -0
- package/src/interfaces/web/src/components/tasks/TaskFormDialog.tsx +406 -0
- package/src/interfaces/web/src/components/tasks/TaskList.tsx +258 -0
- package/src/interfaces/web/src/components/tasks/TaskSubtasks.tsx +126 -0
- package/src/interfaces/web/src/components/tasks/columns.ts +51 -0
- package/src/interfaces/web/src/components/tasks/taskStatus.tsx +159 -34
- package/src/interfaces/web/src/components/tasks/taskViewPrefs.ts +73 -0
- package/src/interfaces/web/src/components/tasks/useTaskColumns.ts +31 -0
- package/src/interfaces/web/src/components/ui/chat-input.tsx +136 -12
- package/src/interfaces/web/src/components/ui/context-menu.tsx +15 -198
- package/src/interfaces/web/src/components/ui/dialog.tsx +3 -2
- package/src/interfaces/web/src/components/ui/filter-chips.tsx +4 -0
- package/src/interfaces/web/src/components/ui/popover.tsx +14 -49
- package/src/interfaces/web/src/components/ui/sheet.tsx +2 -1
- package/src/interfaces/web/src/components/ui/spinner.tsx +2 -1
- package/src/interfaces/web/src/components/ui.tsx +62 -23
- package/src/interfaces/web/src/components/voice/VoiceProviderList.tsx +2 -1
- package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +66 -8
- package/src/interfaces/web/src/components/voice/VoiceQvoxInstallCard.tsx +63 -0
- package/src/interfaces/web/src/components/voice/VoiceQvoxStatusCard.tsx +51 -0
- package/src/interfaces/web/src/components/voice/VoiceSttCard.tsx +4 -3
- package/src/interfaces/web/src/components/voice/VoiceTestCard.tsx +38 -4
- package/src/interfaces/web/src/constants/index.ts +1 -0
- package/src/interfaces/web/src/hooks/useChannelPrefs.ts +39 -0
- package/src/interfaces/web/src/hooks/useChat.ts +1092 -85
- package/src/interfaces/web/src/hooks/useChatActivity.ts +24 -0
- package/src/interfaces/web/src/hooks/useInbox.ts +26 -4
- package/src/interfaces/web/src/hooks/useLiveMessages.ts +16 -0
- package/src/interfaces/web/src/hooks/useOllamaModels.ts +79 -0
- package/src/interfaces/web/src/hooks/useRecorder.ts +198 -0
- package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +23 -6
- package/src/interfaces/web/src/i18n/en.ts +832 -175
- package/src/interfaces/web/src/i18n/es.ts +841 -188
- package/src/interfaces/web/src/lib/api/agents.ts +23 -4
- package/src/interfaces/web/src/lib/api/code.ts +17 -0
- package/src/interfaces/web/src/lib/api/commitments.ts +7 -1
- package/src/interfaces/web/src/lib/api/conversations.ts +38 -11
- package/src/interfaces/web/src/lib/api/embeddings.ts +3 -0
- package/src/interfaces/web/src/lib/api/engines.ts +14 -0
- package/src/interfaces/web/src/lib/api/groups.ts +93 -0
- package/src/interfaces/web/src/lib/api/images.ts +193 -0
- package/src/interfaces/web/src/lib/api/inbox.ts +29 -5
- package/src/interfaces/web/src/lib/api/integrations.ts +9 -0
- package/src/interfaces/web/src/lib/api/media.ts +126 -0
- package/src/interfaces/web/src/lib/api/net.ts +44 -0
- package/src/interfaces/web/src/lib/api/profiles.ts +12 -0
- package/src/interfaces/web/src/lib/api/projects.ts +9 -1
- package/src/interfaces/web/src/lib/api/routines.ts +9 -1
- package/src/interfaces/web/src/lib/api/sessions.ts +18 -0
- package/src/interfaces/web/src/lib/api/skills.ts +8 -0
- package/src/interfaces/web/src/lib/api/super_agent.ts +3 -0
- package/src/interfaces/web/src/lib/api/tasks.ts +46 -6
- package/src/interfaces/web/src/lib/api/tools.ts +2 -0
- package/src/interfaces/web/src/lib/api/transcribe.ts +31 -0
- package/src/interfaces/web/src/lib/api/turns.ts +20 -0
- package/src/interfaces/web/src/lib/api/voice.ts +72 -2
- package/src/interfaces/web/src/lib/api.ts +3 -0
- package/src/interfaces/web/src/lib/channels.ts +214 -0
- package/src/interfaces/web/src/lib/chat-activity.ts +175 -0
- package/src/interfaces/web/src/lib/chat-prefs.ts +48 -0
- package/src/interfaces/web/src/lib/code-context.ts +0 -2
- package/src/interfaces/web/src/lib/http.ts +51 -17
- package/src/interfaces/web/src/lib/inbox-selection.ts +29 -0
- package/src/interfaces/web/src/lib/live.ts +252 -0
- package/src/interfaces/web/src/lib/net.ts +211 -0
- package/src/interfaces/web/src/lib/notify.ts +331 -0
- package/src/interfaces/web/src/lib/projectNav.ts +40 -0
- package/src/interfaces/web/src/lib/pwa.ts +115 -0
- package/src/interfaces/web/src/lib/qvox.ts +24 -0
- package/src/interfaces/web/src/lib/slug.ts +3 -0
- package/src/interfaces/web/src/lib/tone.ts +150 -0
- package/src/interfaces/web/src/main.tsx +15 -4
- package/src/interfaces/web/src/screens/ApxAdminScreen.tsx +33 -11
- package/src/interfaces/web/src/screens/InboxScreen.tsx +126 -71
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -4
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +7 -4
- package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +30 -9
- package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +150 -168
- package/src/interfaces/web/src/screens/base/LogsTab.tsx +8 -7
- package/src/interfaces/web/src/screens/base/SessionsTab.tsx +99 -37
- package/src/interfaces/web/src/screens/base/WorkspacesTab.tsx +1 -1
- package/src/interfaces/web/src/screens/mobile/MobileChat.tsx +85 -0
- package/src/interfaces/web/src/screens/mobile/MobileChatList.tsx +244 -0
- package/src/interfaces/web/src/screens/mobile/MobileCommitments.tsx +350 -0
- package/src/interfaces/web/src/screens/mobile/MobileScreen.tsx +138 -0
- package/src/interfaces/web/src/screens/mobile/MobileTabBar.tsx +105 -0
- package/src/interfaces/web/src/screens/mobile/MobileTasks.tsx +407 -0
- package/src/interfaces/web/src/screens/mobile/NewChatSheet.tsx +313 -0
- package/src/interfaces/web/src/screens/mobile/mobileList.tsx +194 -0
- package/src/interfaces/web/src/screens/mobile/routes.ts +239 -0
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +135 -64
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +0 -1
- package/src/interfaces/web/src/screens/modules/ImagesScreen.tsx +172 -0
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +36 -4
- package/src/interfaces/web/src/screens/project/AgentBrainGraph.tsx +117 -30
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +537 -92
- package/src/interfaces/web/src/screens/project/AgentsTab.tsx +344 -93
- package/src/interfaces/web/src/screens/project/ArtifactsTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +1160 -94
- package/src/interfaces/web/src/screens/project/IntegrationsTab.tsx +3 -2
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +62 -14
- package/src/interfaces/web/src/screens/project/Overview.tsx +36 -15
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +93 -8
- package/src/interfaces/web/src/screens/project/SkillsTab.tsx +4 -1
- package/src/interfaces/web/src/screens/project/StructureTab.tsx +5 -4
- package/src/interfaces/web/src/screens/project/TasksTab.tsx +351 -129
- package/src/interfaces/web/src/screens/project/VarsTab.tsx +17 -5
- package/src/interfaces/web/src/styles.css +106 -31
- package/src/interfaces/web/src/types/daemon.ts +246 -2
- package/src/interfaces/web/src/vite-env.d.ts +20 -0
- package/src/interfaces/tui/plugin/command-shim.ts +0 -109
- package/src/interfaces/web/components.json +0 -25
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js +0 -849
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +0 -1
- package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +0 -142
- package/src/interfaces/web/src/components/ui/accordion.tsx +0 -72
- package/src/interfaces/web/src/components/ui/alert-dialog.tsx +0 -187
- package/src/interfaces/web/src/components/ui/alert.tsx +0 -76
- package/src/interfaces/web/src/components/ui/aspect-ratio.tsx +0 -22
- package/src/interfaces/web/src/components/ui/avatar.tsx +0 -107
- package/src/interfaces/web/src/components/ui/breadcrumb.tsx +0 -125
- package/src/interfaces/web/src/components/ui/button-group.tsx +0 -87
- package/src/interfaces/web/src/components/ui/calendar.tsx +0 -221
- package/src/interfaces/web/src/components/ui/card.tsx +0 -103
- package/src/interfaces/web/src/components/ui/carousel.tsx +0 -242
- package/src/interfaces/web/src/components/ui/chart.tsx +0 -371
- package/src/interfaces/web/src/components/ui/checkbox.tsx +0 -29
- package/src/interfaces/web/src/components/ui/collapsible.tsx +0 -19
- package/src/interfaces/web/src/components/ui/combobox.tsx +0 -295
- package/src/interfaces/web/src/components/ui/command.tsx +0 -196
- package/src/interfaces/web/src/components/ui/direction.tsx +0 -4
- package/src/interfaces/web/src/components/ui/drawer.tsx +0 -134
- package/src/interfaces/web/src/components/ui/empty.tsx +0 -104
- package/src/interfaces/web/src/components/ui/field.tsx +0 -236
- package/src/interfaces/web/src/components/ui/hover-card.tsx +0 -51
- package/src/interfaces/web/src/components/ui/input-otp.tsx +0 -85
- package/src/interfaces/web/src/components/ui/item.tsx +0 -201
- package/src/interfaces/web/src/components/ui/menubar.tsx +0 -280
- package/src/interfaces/web/src/components/ui/native-select.tsx +0 -61
- package/src/interfaces/web/src/components/ui/navigation-menu.tsx +0 -168
- package/src/interfaces/web/src/components/ui/pagination.tsx +0 -130
- package/src/interfaces/web/src/components/ui/progress.tsx +0 -83
- package/src/interfaces/web/src/components/ui/radio-group.tsx +0 -36
- package/src/interfaces/web/src/components/ui/resizable.tsx +0 -50
- package/src/interfaces/web/src/components/ui/scroll-area.tsx +0 -53
- package/src/interfaces/web/src/components/ui/sidebar.tsx +0 -726
- package/src/interfaces/web/src/components/ui/slider.tsx +0 -52
- package/src/interfaces/web/src/components/ui/sonner.tsx +0 -49
- package/src/interfaces/web/src/components/ui/table.tsx +0 -116
- package/src/interfaces/web/src/components/ui/toggle-group.tsx +0 -87
- package/src/interfaces/web/src/components/ui/toggle.tsx +0 -45
- package/src/interfaces/web/src/hooks/use-mobile.ts +0 -19
- package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +0 -161
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// Attachment bytes, both directions.
|
|
2
|
+
//
|
|
3
|
+
// GET /media?path=<abs> stream a stored attachment
|
|
4
|
+
// POST /media/upload?name=… store one the web composer is about to send
|
|
5
|
+
//
|
|
6
|
+
// A photo, voice note or document that arrives over a channel is downloaded to
|
|
7
|
+
// ~/.apx/media and its path recorded on the stored turn (dispatch folds it into
|
|
8
|
+
// the one inbound record). This streams those bytes back so a viewer can show
|
|
9
|
+
// the actual file instead of the text marker the agent was handed — a thread
|
|
10
|
+
// that read "[document received: … saved to /Users/…]" told the user nothing.
|
|
11
|
+
//
|
|
12
|
+
// Sandboxed to that directory, like /voice/tts: the path arrives from a stored
|
|
13
|
+
// record, so it is treated as untrusted input. The content type is derived from
|
|
14
|
+
// the extension only — never from the request — so a stray file cannot be
|
|
15
|
+
// served back as HTML.
|
|
16
|
+
//
|
|
17
|
+
// The web composer writes into the same directory (subdir `web/`) so a file
|
|
18
|
+
// picked, pasted or dropped in the browser reaches the agent by exactly the
|
|
19
|
+
// route a Telegram photo does: bytes on disk, path on the turn.
|
|
20
|
+
import crypto from "node:crypto";
|
|
21
|
+
import express from "express";
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { APX_HOME } from "#core/config/index.js";
|
|
25
|
+
|
|
26
|
+
const MIME = {
|
|
27
|
+
".oga": "audio/ogg", ".ogg": "audio/ogg", ".opus": "audio/ogg",
|
|
28
|
+
// A voice note recorded in the browser is Opus in a WebM container. Without
|
|
29
|
+
// its own extension it would land as ".webm" → video/webm → a <video> element
|
|
30
|
+
// with no picture. `.weba` is the audio-only spelling of the same container.
|
|
31
|
+
".weba": "audio/webm",
|
|
32
|
+
".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".aac": "audio/aac",
|
|
33
|
+
".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".gif": "image/gif",
|
|
34
|
+
".webp": "image/webp", ".heic": "image/heic",
|
|
35
|
+
".mp4": "video/mp4", ".mov": "video/quicktime", ".webm": "video/webm",
|
|
36
|
+
".pdf": "application/pdf",
|
|
37
|
+
".txt": "text/plain", ".md": "text/markdown", ".csv": "text/csv",
|
|
38
|
+
".json": "application/json", ".log": "text/plain",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// What the composer may hand over. The gate is the extension — the request's
|
|
42
|
+
// own content type is never trusted here either — so the set stays small and
|
|
43
|
+
// purposeful: images a multimodal engine can see, audio it can transcribe,
|
|
44
|
+
// documents its file tools can open. Nothing executable, and nothing a browser
|
|
45
|
+
// would run if it ever escaped the Content-Disposition below.
|
|
46
|
+
const UPLOAD_EXT = new Set([
|
|
47
|
+
".jpg", ".jpeg", ".png", ".gif", ".webp", ".heic",
|
|
48
|
+
".oga", ".ogg", ".opus", ".weba", ".mp3", ".wav", ".m4a", ".aac",
|
|
49
|
+
".mp4", ".mov", ".webm",
|
|
50
|
+
".pdf", ".txt", ".md", ".csv", ".json", ".log",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// 25 MB. Above this the base64 an image turn carries starts to dominate the
|
|
54
|
+
// model's context window, and the point of the cap is the turn, not the disk.
|
|
55
|
+
const MAX_UPLOAD = 25 * 1024 * 1024;
|
|
56
|
+
|
|
57
|
+
// An extension the allowlist trusts must be backed by the bytes it claims:
|
|
58
|
+
// otherwise ".png" is just a four-character promise, and the file that lands in
|
|
59
|
+
// the media dir is whatever the caller wanted to put there. Only the formats
|
|
60
|
+
// with a stable signature are checked; the rest pass on the extension alone.
|
|
61
|
+
const MAGIC = [
|
|
62
|
+
{ ext: [".jpg", ".jpeg"], at: 0, bytes: [0xff, 0xd8, 0xff] },
|
|
63
|
+
{ ext: [".png"], at: 0, bytes: [0x89, 0x50, 0x4e, 0x47] },
|
|
64
|
+
{ ext: [".gif"], at: 0, bytes: [0x47, 0x49, 0x46, 0x38] },
|
|
65
|
+
{ ext: [".webp"], at: 8, bytes: [0x57, 0x45, 0x42, 0x50] },
|
|
66
|
+
{ ext: [".pdf"], at: 0, bytes: [0x25, 0x50, 0x44, 0x46] },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
function magicMatches(ext, buf) {
|
|
70
|
+
const rule = MAGIC.find((m) => m.ext.includes(ext));
|
|
71
|
+
if (!rule) return true;
|
|
72
|
+
if (buf.length < rule.at + rule.bytes.length) return false;
|
|
73
|
+
return rule.bytes.every((b, i) => buf[rule.at + i] === b);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The media dir itself. One root for the read and the write side. */
|
|
77
|
+
function mediaRoot() {
|
|
78
|
+
return path.resolve(APX_HOME, "media");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** `photo` | `audio` | `video` | `document`, from the resolved mime. */
|
|
82
|
+
function kindFromMime(mime) {
|
|
83
|
+
if (mime.startsWith("image/")) return "photo";
|
|
84
|
+
if (mime.startsWith("audio/")) return "audio";
|
|
85
|
+
if (mime.startsWith("video/")) return "video";
|
|
86
|
+
return "document";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mimeFor(file) {
|
|
90
|
+
return MIME[path.extname(file).toLowerCase()] || "application/octet-stream";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A filename fit to store and to show. The caller's name is a display string
|
|
95
|
+
* from an untrusted client: only its basename survives, separators and control
|
|
96
|
+
* characters included, and the stem is capped so a 300-character name cannot
|
|
97
|
+
* push the stored path past the filesystem's limit.
|
|
98
|
+
*/
|
|
99
|
+
function safeName(raw) {
|
|
100
|
+
const base = path.basename(String(raw || "")).replace(/[^\w.\- ]+/g, "_").trim();
|
|
101
|
+
const ext = path.extname(base).toLowerCase();
|
|
102
|
+
const stem = base.slice(0, base.length - ext.length).slice(0, 60).trim() || "file";
|
|
103
|
+
return { name: `${stem}${ext}`, ext, stem };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Shown in the browser rather than pushed to Downloads. Everything else gets
|
|
107
|
+
// `attachment`, so an unknown type can never render in the app's own origin.
|
|
108
|
+
const INLINE = /^(audio|image|video)\//;
|
|
109
|
+
|
|
110
|
+
function tryRealpath(p) {
|
|
111
|
+
try {
|
|
112
|
+
return fs.realpathSync(p);
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The real path of a file inside ~/.apx/media, or null if it escapes. */
|
|
119
|
+
function resolveInMediaDir(raw) {
|
|
120
|
+
const root = mediaRoot();
|
|
121
|
+
const asked = path.resolve(raw);
|
|
122
|
+
// Lexical guard first, so a path from outside the media dir never reaches the
|
|
123
|
+
// filesystem at all. Both spellings of the root count: what a stored turn
|
|
124
|
+
// carries is the resolved path, and on a home behind a symlink (a macOS temp
|
|
125
|
+
// dir, a relocated ~) the unresolved root would refuse our own file.
|
|
126
|
+
const roots = [root, tryRealpath(root)].filter(Boolean);
|
|
127
|
+
if (!roots.some((r) => asked === r || asked.startsWith(r + path.sep))) return null;
|
|
128
|
+
if (!fs.existsSync(asked)) return null;
|
|
129
|
+
// Symlinks are resolved on both sides: a link inside the media dir must not
|
|
130
|
+
// become a window onto the rest of the disk.
|
|
131
|
+
const real = fs.realpathSync(asked);
|
|
132
|
+
const realRoot = fs.realpathSync(root);
|
|
133
|
+
if (!real.startsWith(realRoot + path.sep)) return null;
|
|
134
|
+
return real;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The files a turn is being sent with, resolved from the media dir.
|
|
139
|
+
*
|
|
140
|
+
* Returns what each consumer needs: `attachments` for the model (images only —
|
|
141
|
+
* a multimodal engine renders them, the others ignore the field), `markers` for
|
|
142
|
+
* the prompt (so an engine without vision still knows a file arrived and where
|
|
143
|
+
* it is), and `media` shaped as the ledger meta a stored turn carries, so a
|
|
144
|
+
* reopened thread shows the file instead of the marker. Mirrors what the
|
|
145
|
+
* Telegram inbound handlers hand to dispatch.
|
|
146
|
+
*
|
|
147
|
+
* Anything that does not resolve inside the media dir is dropped in silence:
|
|
148
|
+
* the paths come back from a client, and a turn is not the place to explain a
|
|
149
|
+
* path that a caller had no business sending.
|
|
150
|
+
*/
|
|
151
|
+
export function readTurnAttachments(raw) {
|
|
152
|
+
const attachments = [];
|
|
153
|
+
const markers = [];
|
|
154
|
+
let media = null;
|
|
155
|
+
for (const item of Array.isArray(raw) ? raw : []) {
|
|
156
|
+
let resolved = null;
|
|
157
|
+
try {
|
|
158
|
+
resolved = resolveInMediaDir(String(item?.path || ""));
|
|
159
|
+
} catch {
|
|
160
|
+
resolved = null;
|
|
161
|
+
}
|
|
162
|
+
if (!resolved) continue;
|
|
163
|
+
const mime = mimeFor(resolved);
|
|
164
|
+
const name = item?.name ? safeName(item.name).name : path.basename(resolved);
|
|
165
|
+
let size = null;
|
|
166
|
+
try {
|
|
167
|
+
size = fs.statSync(resolved).size;
|
|
168
|
+
} catch {
|
|
169
|
+
/* the stat is metadata; a readable file without it still goes through */
|
|
170
|
+
}
|
|
171
|
+
if (mime.startsWith("image/")) {
|
|
172
|
+
try {
|
|
173
|
+
attachments.push({
|
|
174
|
+
kind: "image",
|
|
175
|
+
mime,
|
|
176
|
+
data: fs.readFileSync(resolved).toString("base64"),
|
|
177
|
+
path: resolved,
|
|
178
|
+
});
|
|
179
|
+
} catch {
|
|
180
|
+
/* unreadable: the marker below still names the path */
|
|
181
|
+
}
|
|
182
|
+
markers.push(`[image attached — saved to ${resolved}]`);
|
|
183
|
+
} else {
|
|
184
|
+
markers.push(
|
|
185
|
+
`[file attached: ${name}, ${mime} — saved to ${resolved}. You can open it with your file tools.]`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
// One attachment per stored turn, as on every other channel: the ledger row
|
|
189
|
+
// holds a single file, so a turn sent with several records the first.
|
|
190
|
+
media ??= {
|
|
191
|
+
media_kind: kindFromMime(mime),
|
|
192
|
+
local_path: resolved,
|
|
193
|
+
file_name: name,
|
|
194
|
+
mime_type: mime,
|
|
195
|
+
file_size: size,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return { attachments, markers, media };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Body parser for the upload: raw bytes, not multipart (one file per request,
|
|
202
|
+
// so a boundary parser would earn nothing) and not base64 in JSON (which would
|
|
203
|
+
// inflate every image by a third and fight the 2 MB global JSON limit). Its own
|
|
204
|
+
// errors are answered here as JSON — the default is an HTML error page, which a
|
|
205
|
+
// fetch() caller can only report as gibberish.
|
|
206
|
+
const rawBody = express.raw({ type: "*/*", limit: MAX_UPLOAD });
|
|
207
|
+
function readRawBody(req, res, next) {
|
|
208
|
+
rawBody(req, res, (err) => {
|
|
209
|
+
if (!err) return next();
|
|
210
|
+
if (err.type === "entity.too.large") {
|
|
211
|
+
return res.status(413).json({ error: `file too large (max ${MAX_UPLOAD / (1024 * 1024)} MB)` });
|
|
212
|
+
}
|
|
213
|
+
res.status(400).json({ error: err.message || "upload failed" });
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function register(api) {
|
|
218
|
+
// Store one attachment the composer is about to send. The name rides in the
|
|
219
|
+
// query because the body is the file itself; the extension it carries is the
|
|
220
|
+
// only thing that decides what this is willing to keep.
|
|
221
|
+
api.post("/media/upload", readRawBody, (req, res) => {
|
|
222
|
+
const buf = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
|
|
223
|
+
if (!buf.length) return res.status(400).json({ error: "empty upload" });
|
|
224
|
+
|
|
225
|
+
const { name, ext, stem } = safeName(req.query.name);
|
|
226
|
+
if (!ext || !UPLOAD_EXT.has(ext)) {
|
|
227
|
+
return res.status(415).json({ error: `file type not allowed: ${ext || "no extension"}` });
|
|
228
|
+
}
|
|
229
|
+
if (!magicMatches(ext, buf)) {
|
|
230
|
+
return res.status(415).json({ error: `the file's bytes do not match ${ext}` });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const dir = path.join(mediaRoot(), "web");
|
|
235
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
236
|
+
// The stored name is ours, never the caller's: a random stem keeps two
|
|
237
|
+
// uploads of "image.png" apart and keeps a guessed path from reaching a
|
|
238
|
+
// file that is not the guesser's.
|
|
239
|
+
const stored = path.join(dir, `${crypto.randomUUID().slice(0, 8)}-${stem}${ext}`);
|
|
240
|
+
fs.writeFileSync(stored, buf);
|
|
241
|
+
// The real path is the canonical one: it is what the turn side resolves
|
|
242
|
+
// to (symlinks and all), so client and daemon name the same file the same
|
|
243
|
+
// way — on a macOS temp dir, /var and /private/var are the same file and
|
|
244
|
+
// two different strings.
|
|
245
|
+
const real = fs.realpathSync(stored);
|
|
246
|
+
const mime = mimeFor(real);
|
|
247
|
+
res.status(201).json({
|
|
248
|
+
path: real,
|
|
249
|
+
name,
|
|
250
|
+
mime,
|
|
251
|
+
kind: kindFromMime(mime),
|
|
252
|
+
size: buf.length,
|
|
253
|
+
});
|
|
254
|
+
} catch (e) {
|
|
255
|
+
res.status(500).json({ error: e?.message || "upload failed" });
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
api.get("/media", (req, res) => {
|
|
260
|
+
const raw = String(req.query.path || "");
|
|
261
|
+
if (!raw) return res.status(400).json({ error: "path required" });
|
|
262
|
+
let resolved;
|
|
263
|
+
try {
|
|
264
|
+
resolved = resolveInMediaDir(raw);
|
|
265
|
+
} catch (e) {
|
|
266
|
+
return res.status(500).json({ error: e?.message || "media read failed" });
|
|
267
|
+
}
|
|
268
|
+
if (!resolved) return res.status(404).json({ error: "not found" });
|
|
269
|
+
|
|
270
|
+
const mime = MIME[path.extname(resolved).toLowerCase()] || "application/octet-stream";
|
|
271
|
+
const name = path.basename(resolved).replace(/["\\]/g, "");
|
|
272
|
+
res.setHeader("Content-Type", mime);
|
|
273
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
274
|
+
res.setHeader(
|
|
275
|
+
"Content-Disposition",
|
|
276
|
+
`${INLINE.test(mime) ? "inline" : "attachment"}; filename="${name}"`,
|
|
277
|
+
);
|
|
278
|
+
res.setHeader("Cache-Control", "private, max-age=300");
|
|
279
|
+
const stream = fs.createReadStream(resolved);
|
|
280
|
+
stream.on("error", () => res.destroy());
|
|
281
|
+
stream.pipe(res);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acceptMobilityEvent,
|
|
3
|
+
dispatchMobilityEvent,
|
|
4
|
+
dispatchMobilityPosition,
|
|
5
|
+
} from "#core/mobility/trip-event.js";
|
|
6
|
+
import { acceptMobilityPosition, lockTripErrand, tripErrand, tripPlaces } from "#core/mobility/geofence.js";
|
|
7
|
+
import { answerMobilityAlert, MOBILITY_ANSWERS } from "#core/mobility/answer.js";
|
|
8
|
+
import { doneTask } from "#core/stores/tasks.js";
|
|
9
|
+
import { resolveLang } from "#core/i18n/index.js";
|
|
10
|
+
import {
|
|
11
|
+
listMobilityAlerts,
|
|
12
|
+
mobilityContext,
|
|
13
|
+
recordMobilityAlert,
|
|
14
|
+
updateMobilityAlert,
|
|
15
|
+
} from "#core/mobility/state.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Close the task a proximity alert was about.
|
|
19
|
+
*
|
|
20
|
+
* The daemon's half of core/mobility/answer.js: the alert records WHICH project
|
|
21
|
+
* the task came from, so this resolves that one project rather than searching
|
|
22
|
+
* every registered one — two projects can hold tasks whose short ids share a
|
|
23
|
+
* prefix. Mirrors closeAlertTask() in the Telegram adapter, which does the same
|
|
24
|
+
* thing with the plugin's own registry handle.
|
|
25
|
+
*/
|
|
26
|
+
function closeAlertTaskFor(ctx, alert) {
|
|
27
|
+
if (!alert?.task_id) return false;
|
|
28
|
+
let storagePath = null;
|
|
29
|
+
try {
|
|
30
|
+
storagePath = ctx.projects?.get(alert.project_id)?.storagePath || null;
|
|
31
|
+
} catch {
|
|
32
|
+
storagePath = null;
|
|
33
|
+
}
|
|
34
|
+
if (!storagePath) return false;
|
|
35
|
+
try {
|
|
36
|
+
return Boolean(doneTask(storagePath, alert.task_id, "mobility"));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.warn(`[mobility] could not close task ${alert.task_id}: ${error.message}`);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function register(api, ctx) {
|
|
44
|
+
// What the phone's trip banner shows when it is tapped: the current trip and
|
|
45
|
+
// the errands APX is watching for on it. Read-only and deliberately separate
|
|
46
|
+
// from the alert path — a driver looking at the list is not being
|
|
47
|
+
// interrupted by it, so nothing here fires a reminder or spends a one-shot.
|
|
48
|
+
api.get("/mobility/trip", (req, res) => {
|
|
49
|
+
const state = mobilityContext();
|
|
50
|
+
const trip = state.trip?.active ? state.trip : null;
|
|
51
|
+
res.json({
|
|
52
|
+
trip,
|
|
53
|
+
places: trip ? tripPlaces(trip.trip_id) : [],
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// A button on the NATIVE card — the phone's notification, or the head unit.
|
|
58
|
+
//
|
|
59
|
+
// This is the other half of the alert no longer depending on Telegram: the
|
|
60
|
+
// card is pushed over the events socket (core/events/bus.js) and answered
|
|
61
|
+
// here, so a install with no Telegram plugin has a complete round trip.
|
|
62
|
+
// `navigate` and `add_stop` land on the same branch as "voy": the driver did
|
|
63
|
+
// not promise to go, they started going.
|
|
64
|
+
api.post("/mobility/alerts/:id/answer", (req, res) => {
|
|
65
|
+
const action = String(req.body?.action || "").trim();
|
|
66
|
+
if (!MOBILITY_ANSWERS.includes(action)) {
|
|
67
|
+
return res.status(400).json({ error: `action must be one of ${MOBILITY_ANSWERS.join(", ")}` });
|
|
68
|
+
}
|
|
69
|
+
const result = answerMobilityAlert(req.params.id, action, {
|
|
70
|
+
lang: resolveLang(ctx.config),
|
|
71
|
+
// Closing the task needs the project registry, which core does not have.
|
|
72
|
+
closeTask: (alert) => closeAlertTaskFor(ctx, alert),
|
|
73
|
+
});
|
|
74
|
+
if (!result.ok) {
|
|
75
|
+
return res.status(result.reason === "unknown-alert" ? 404 : 400).json({ error: result.reason });
|
|
76
|
+
}
|
|
77
|
+
res.json({ ok: true, ack: result.ack, alert: result.alert });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// "Voy" / "Hoy no" pressed on the phone's own list instead of on the
|
|
81
|
+
// Telegram card. Same answer, same record, same follow-up after the trip —
|
|
82
|
+
// the surface is different, the promise is not. Answering here also burns
|
|
83
|
+
// the one-shot, which is the point: the owner has already decided, so the
|
|
84
|
+
// proximity alert for that errand has nothing left to ask.
|
|
85
|
+
api.post("/mobility/errands/answer", (req, res) => {
|
|
86
|
+
const state = mobilityContext();
|
|
87
|
+
const trip = state.trip?.active ? state.trip : null;
|
|
88
|
+
if (!trip) return res.status(409).json({ error: "no trip is running" });
|
|
89
|
+
const taskId = String(req.body?.task_id || "").trim();
|
|
90
|
+
const answer = String(req.body?.answer || "").trim();
|
|
91
|
+
if (!taskId) return res.status(400).json({ error: "task_id required" });
|
|
92
|
+
// "next" is the phone's half of «avisar en la siguiente»: same three
|
|
93
|
+
// answers the Telegram card offers, because the docs promise the two
|
|
94
|
+
// surfaces ask the same question. "skip" stays for the older APK.
|
|
95
|
+
// "done" is the car's "ya la hice": the owner read the list on the head
|
|
96
|
+
// unit and is closing the errand from there, without waiting to be asked.
|
|
97
|
+
// It closes the TASK, which is the one answer here that does.
|
|
98
|
+
if (!["go", "next", "skip", "done"].includes(answer)) {
|
|
99
|
+
return res.status(400).json({ error: "answer must be go, next, skip or done" });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const existing = listMobilityAlerts()
|
|
103
|
+
.filter((alert) => alert.trip_id === trip.trip_id && alert.task_id === taskId)
|
|
104
|
+
.pop();
|
|
105
|
+
// No alert yet means the owner is answering BEFORE being asked — reading
|
|
106
|
+
// the list and deciding on their own. That still deserves a record, or the
|
|
107
|
+
// decision would be forgotten the moment the reminder fired anyway.
|
|
108
|
+
const alert = existing || (() => {
|
|
109
|
+
const errand = tripErrand(trip.trip_id, taskId);
|
|
110
|
+
return errand ? recordMobilityAlert(errand) : null;
|
|
111
|
+
})();
|
|
112
|
+
if (!alert) return res.status(404).json({ error: "that errand is not on this trip" });
|
|
113
|
+
|
|
114
|
+
const answered_at = new Date().toISOString();
|
|
115
|
+
// "next" declines the PLACE: the errand stops counting as announced, so a
|
|
116
|
+
// different shop can raise it again later in the drive, and the outcome
|
|
117
|
+
// closes it for the after-trip follow-up — nothing was promised here.
|
|
118
|
+
// "skip" is still the whole errand, for the rest of the trip.
|
|
119
|
+
if (answer === "done") {
|
|
120
|
+
// Through core, like every other answer, so closing from the car records
|
|
121
|
+
// the same outcome as closing from the after-trip follow-up.
|
|
122
|
+
const result = answerMobilityAlert(alert.id, "done", {
|
|
123
|
+
lang: resolveLang(ctx.config),
|
|
124
|
+
closeTask: (target) => closeAlertTaskFor(ctx, target),
|
|
125
|
+
});
|
|
126
|
+
return res.json({ ok: true, alert_id: alert.id, answer, ack: result.ack });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const patch = {
|
|
130
|
+
go: { answer: "go", answered_at },
|
|
131
|
+
next: { answer: "next", answered_at, outcome: "skipped_place" },
|
|
132
|
+
skip: { answer: "skip", answered_at, outcome: "skipped" },
|
|
133
|
+
}[answer];
|
|
134
|
+
updateMobilityAlert(alert.id, patch);
|
|
135
|
+
if (answer === "go") lockTripErrand(trip.trip_id, taskId, "accepted");
|
|
136
|
+
res.json({ ok: true, alert_id: alert.id, answer });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
api.post("/mobility/events", (req, res) => {
|
|
140
|
+
let event;
|
|
141
|
+
try {
|
|
142
|
+
event = acceptMobilityEvent(req.body);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return res.status(400).json({ error: error.message });
|
|
145
|
+
}
|
|
146
|
+
if (event.duplicate) return res.json({ accepted: true, duplicate: true, event_id: event.event_id });
|
|
147
|
+
res.status(202).json({ accepted: true, event_id: event.event_id });
|
|
148
|
+
const dispatch = ctx.mobilityDispatch || dispatchMobilityEvent;
|
|
149
|
+
setImmediate(() => {
|
|
150
|
+
Promise.resolve(dispatch(event, ctx)).catch((error) => {
|
|
151
|
+
console.error(`[mobility] ${error?.message || error}`);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// The live GPS stream while a trip is running. Answered before any work for
|
|
157
|
+
// the same reason the event route is: the phone is on mobile data in a
|
|
158
|
+
// moving car, and a request it has to hold open until a place search and a
|
|
159
|
+
// Telegram send have finished is a request that times out. Proximity is
|
|
160
|
+
// evaluated after the ack, and the one-alert-per-place guarantee lives in
|
|
161
|
+
// core/mobility/geofence.js, not here.
|
|
162
|
+
api.post("/mobility/positions", (req, res) => {
|
|
163
|
+
let position;
|
|
164
|
+
try {
|
|
165
|
+
position = acceptMobilityPosition(req.body);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return res.status(400).json({ error: error.message });
|
|
168
|
+
}
|
|
169
|
+
res.status(202).json({ accepted: true, trip_id: position.trip_id });
|
|
170
|
+
const dispatch = ctx.mobilityPositionDispatch || dispatchMobilityPosition;
|
|
171
|
+
setImmediate(() => {
|
|
172
|
+
Promise.resolve(dispatch(position, ctx)).catch((error) => {
|
|
173
|
+
console.error(`[mobility] ${error?.message || error}`);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// GET /net/endpoints every URL this daemon can be reached at
|
|
2
|
+
// POST /net/tailscale/serve publish it over HTTPS on the tailnet
|
|
3
|
+
// DELETE /net/tailscale/serve stop publishing
|
|
4
|
+
//
|
|
5
|
+
// One daemon, several addresses: loopback for the toolchain, a LAN address for
|
|
6
|
+
// a phone on the same Wi-Fi, and — when Tailscale is up — a name that keeps
|
|
7
|
+
// working from a train. The panel needs the whole list for two reasons: to show
|
|
8
|
+
// you which URL to install the app from, and so a client whose address stopped
|
|
9
|
+
// answering can try the others instead of just failing.
|
|
10
|
+
//
|
|
11
|
+
// The HTTPS one is not interchangeable with the others. Service workers (the
|
|
12
|
+
// installable app), getUserMedia (voice notes) and the clipboard are all gated
|
|
13
|
+
// on a secure context, so over http://192.168.x.x the browser switches them
|
|
14
|
+
// off. `tailscale serve` is the only way to a real certificate here that does
|
|
15
|
+
// not involve buying a domain.
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import { effectivePort, effectiveHost, readConfig } from "#core/config/index.js";
|
|
18
|
+
import { detectLanAddresses, isLoopback, isWildcard } from "#core/net/lan.js";
|
|
19
|
+
import {
|
|
20
|
+
disableTailscaleServe,
|
|
21
|
+
enableTailscaleServe,
|
|
22
|
+
tailscaleServe,
|
|
23
|
+
tailscaleStatus,
|
|
24
|
+
} from "#core/net/tailscale.js";
|
|
25
|
+
import { asyncRoute } from "./shared.js";
|
|
26
|
+
|
|
27
|
+
/** A Tailscale address, so a LAN scan does not report the tailnet as Wi-Fi. */
|
|
28
|
+
function isTailscaleAddress(address, iface) {
|
|
29
|
+
if (/^utun|^tailscale/i.test(String(iface || ""))) return true;
|
|
30
|
+
const p = String(address || "").split(".").map(Number);
|
|
31
|
+
// 100.64.0.0/10 — the CGNAT range Tailscale hands out.
|
|
32
|
+
return p[0] === 100 && p[1] >= 64 && p[1] <= 127;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every base URL that reaches this daemon, best first.
|
|
37
|
+
*
|
|
38
|
+
* "Best" is ordered by how long it keeps working: the HTTPS tailnet name (from
|
|
39
|
+
* anywhere, and a secure context), then the tailnet IP, then the LAN, then
|
|
40
|
+
* loopback, which only ever helps the machine it runs on.
|
|
41
|
+
*/
|
|
42
|
+
export async function reachableEndpoints({ port, host }) {
|
|
43
|
+
const out = [];
|
|
44
|
+
const ts = await tailscaleStatus();
|
|
45
|
+
const serve = ts.running ? await tailscaleServe(port) : { serving: false, url: null };
|
|
46
|
+
|
|
47
|
+
if (serve.serving && serve.url) {
|
|
48
|
+
out.push({ url: serve.url, kind: "tailscale-https", label: ts.dnsName || "tailscale", secure: true });
|
|
49
|
+
}
|
|
50
|
+
if (ts.running && ts.ipv4) {
|
|
51
|
+
out.push({ url: `http://${ts.ipv4}:${port}`, kind: "tailscale", label: ts.hostname || "tailscale", secure: false });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A daemon bound to one specific address is only reachable there; a wildcard
|
|
55
|
+
// (or loopback-only, which still runs the LAN listener when shared) is
|
|
56
|
+
// reachable at every interface it holds.
|
|
57
|
+
const bound = host && !isLoopback(host) && !isWildcard(host) ? host : null;
|
|
58
|
+
if (bound && !out.some((e) => e.url.includes(bound))) {
|
|
59
|
+
out.push({ url: `http://${bound}:${port}`, kind: "lan", label: "LAN", secure: false });
|
|
60
|
+
}
|
|
61
|
+
if (!bound) {
|
|
62
|
+
for (const a of detectLanAddresses()) {
|
|
63
|
+
if (isTailscaleAddress(a.address, a.iface)) continue;
|
|
64
|
+
out.push({ url: `http://${a.address}:${port}`, kind: "lan", label: a.iface, secure: false });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
out.push({ url: `http://127.0.0.1:${port}`, kind: "loopback", label: os.hostname(), secure: true });
|
|
69
|
+
// Deduplicate while keeping the first (best) spelling of each URL.
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
return out.filter((e) => (seen.has(e.url) ? false : seen.add(e.url)));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Hostnames that are this daemon under another name — the CORS allowlist. */
|
|
75
|
+
export async function selfOrigins({ port, host }) {
|
|
76
|
+
const eps = await reachableEndpoints({ port, host });
|
|
77
|
+
return new Set(eps.map((e) => new URL(e.url).host));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── CORS, narrowly: this daemon under one of its OWN other names ───────────
|
|
81
|
+
//
|
|
82
|
+
// The panel installed from one address (say the LAN) can end up talking to
|
|
83
|
+
// another (the tailnet) when the first stops answering. That is a cross-origin
|
|
84
|
+
// request to the same process, and it needs a header to be allowed — but only
|
|
85
|
+
// for hosts that ARE us. Anything else, including a page on the same port at
|
|
86
|
+
// some other hostname, gets nothing.
|
|
87
|
+
//
|
|
88
|
+
// The allowlist is cached: building it asks Tailscale, which is far too slow to
|
|
89
|
+
// do per request. Loopback and the LAN are known synchronously and seeded
|
|
90
|
+
// immediately, so the first request is answered correctly even before the
|
|
91
|
+
// asynchronous half lands.
|
|
92
|
+
const CORS_TTL_MS = 30_000;
|
|
93
|
+
let allowCache = { at: 0, hosts: null, refreshing: false };
|
|
94
|
+
|
|
95
|
+
function knownHostsSync({ port, host }) {
|
|
96
|
+
const hosts = new Set([`127.0.0.1:${port}`, `localhost:${port}`]);
|
|
97
|
+
if (host && !isLoopback(host) && !isWildcard(host)) hosts.add(`${host}:${port}`);
|
|
98
|
+
for (const a of detectLanAddresses()) hosts.add(`${a.address}:${port}`);
|
|
99
|
+
return hosts;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function refreshAllowed(ctx) {
|
|
103
|
+
if (allowCache.refreshing) return;
|
|
104
|
+
allowCache.refreshing = true;
|
|
105
|
+
selfOrigins(ctx)
|
|
106
|
+
.then((hosts) => {
|
|
107
|
+
allowCache = { at: Date.now(), hosts, refreshing: false };
|
|
108
|
+
})
|
|
109
|
+
.catch(() => {
|
|
110
|
+
allowCache = { ...allowCache, at: Date.now(), refreshing: false };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Express middleware allowing cross-origin calls between our own addresses. */
|
|
115
|
+
export function corsBetweenOwnAddresses({ port, host }) {
|
|
116
|
+
const ctx = { port, host };
|
|
117
|
+
return function cors(req, res, next) {
|
|
118
|
+
const origin = req.headers.origin;
|
|
119
|
+
if (!origin) return next();
|
|
120
|
+
if (!allowCache.hosts || Date.now() - allowCache.at > CORS_TTL_MS) {
|
|
121
|
+
if (!allowCache.hosts) allowCache = { ...allowCache, hosts: knownHostsSync(ctx) };
|
|
122
|
+
refreshAllowed(ctx);
|
|
123
|
+
}
|
|
124
|
+
let originHost = "";
|
|
125
|
+
try {
|
|
126
|
+
originHost = new URL(origin).host;
|
|
127
|
+
} catch {
|
|
128
|
+
return next();
|
|
129
|
+
}
|
|
130
|
+
if (!allowCache.hosts.has(originHost)) return next();
|
|
131
|
+
res.setHeader("access-control-allow-origin", origin);
|
|
132
|
+
res.setHeader("vary", "Origin");
|
|
133
|
+
res.setHeader("access-control-allow-credentials", "true");
|
|
134
|
+
if (req.method === "OPTIONS") {
|
|
135
|
+
res.setHeader("access-control-allow-methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS");
|
|
136
|
+
res.setHeader(
|
|
137
|
+
"access-control-allow-headers",
|
|
138
|
+
req.headers["access-control-request-headers"] || "authorization,content-type",
|
|
139
|
+
);
|
|
140
|
+
res.setHeader("access-control-max-age", "600");
|
|
141
|
+
return res.status(204).end();
|
|
142
|
+
}
|
|
143
|
+
return next();
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function register(api) {
|
|
148
|
+
api.get("/net/endpoints", asyncRoute(async (_req, res) => {
|
|
149
|
+
const cfg = readConfig();
|
|
150
|
+
const port = effectivePort(cfg);
|
|
151
|
+
const host = effectiveHost(cfg);
|
|
152
|
+
const ts = await tailscaleStatus();
|
|
153
|
+
const serve = ts.installed ? await tailscaleServe(port) : { serving: false, url: null, error: null };
|
|
154
|
+
res.json({
|
|
155
|
+
port,
|
|
156
|
+
bind: host,
|
|
157
|
+
shared: !isLoopback(host),
|
|
158
|
+
endpoints: await reachableEndpoints({ port, host }),
|
|
159
|
+
tailscale: { ...ts, serving: serve.serving, serve_url: serve.url, serve_error: serve.error },
|
|
160
|
+
});
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
api.post("/net/tailscale/serve", asyncRoute(async (_req, res) => {
|
|
164
|
+
const port = effectivePort(readConfig());
|
|
165
|
+
try {
|
|
166
|
+
const out = await enableTailscaleServe(port);
|
|
167
|
+
res.json({ ok: true, ...out });
|
|
168
|
+
} catch (e) {
|
|
169
|
+
res.status(400).json({ ok: false, error: e.message });
|
|
170
|
+
}
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
api.delete("/net/tailscale/serve", asyncRoute(async (_req, res) => {
|
|
174
|
+
const port = effectivePort(readConfig());
|
|
175
|
+
try {
|
|
176
|
+
const out = await disableTailscaleServe(port);
|
|
177
|
+
res.json({ ok: true, ...out });
|
|
178
|
+
} catch (e) {
|
|
179
|
+
res.status(400).json({ ok: false, error: e.message });
|
|
180
|
+
}
|
|
181
|
+
}));
|
|
182
|
+
}
|