@maudecode/talaria-web 1.17.2 → 1.19.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/_release.json +8 -8
- package/dist/api/auth-raw.d.ts +1 -1
- package/dist/api/auth-raw.js +20 -20
- package/dist/api/auth-raw.js.map +1 -1
- package/dist/api/automation-raw.d.ts +7 -1
- package/dist/api/automation-raw.js +74 -27
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +128 -23
- package/dist/api/automation-router.js +29 -13
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +1845 -40
- package/dist/api/chat-router.js +196 -114
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +37 -3
- package/dist/api/git-router.js +47 -48
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +9 -1
- package/dist/api/raw-routes.js +286 -73
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +16 -6
- package/dist/api/router.js +28 -26
- package/dist/api/router.js.map +1 -1
- package/dist/api/session-visibility.d.ts +4 -4
- package/dist/api/session-visibility.js +26 -16
- package/dist/api/session-visibility.js.map +1 -1
- package/dist/api/sessions-router.d.ts +3312 -131
- package/dist/api/sessions-router.js +287 -157
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +457 -8
- package/dist/api/settings-router.js +54 -33
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +7 -2
- package/dist/api/sse-routes.js +109 -62
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.d.ts +5 -0
- package/dist/api/tools-raw.js +63 -55
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +998 -85
- package/dist/api/tools-router.js +447 -61
- package/dist/api/tools-router.js.map +1 -1
- package/dist/api/transcribe-stream.d.ts +16 -0
- package/dist/api/transcribe-stream.js +172 -0
- package/dist/api/transcribe-stream.js.map +1 -0
- package/dist/app.d.ts +4 -1
- package/dist/app.js +98 -59
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +15 -11
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/oidc.d.ts +5 -2
- package/dist/auth/oidc.js +8 -7
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/passkeys.d.ts +15 -14
- package/dist/auth/passkeys.js +89 -91
- package/dist/auth/passkeys.js.map +1 -1
- package/dist/auth/store.d.ts +17 -7
- package/dist/auth/store.js +93 -52
- package/dist/auth/store.js.map +1 -1
- package/dist/bin/talaria-web.js +12 -81
- package/dist/bin/talaria-web.js.map +1 -1
- package/dist/cli/ctl.js +2 -1
- package/dist/cli/ctl.js.map +1 -1
- package/dist/cli/dotenv.d.ts +5 -0
- package/dist/cli/dotenv.js +15 -2
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/cli/launcher.js +5 -5
- package/dist/cli/launcher.js.map +1 -1
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +85 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/supervise.d.ts +2 -0
- package/dist/cli/supervise.js +67 -4
- package/dist/cli/supervise.js.map +1 -1
- package/dist/config/agent-config.d.ts +34 -11
- package/dist/config/agent-config.js +116 -60
- package/dist/config/agent-config.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +19 -6
- package/dist/config.js.map +1 -1
- package/dist/fs/atomic.d.ts +1 -0
- package/dist/fs/atomic.js +4 -1
- package/dist/fs/atomic.js.map +1 -1
- package/dist/fs/stat.d.ts +1 -0
- package/dist/fs/stat.js +11 -0
- package/dist/fs/stat.js.map +1 -0
- package/dist/http/context.d.ts +31 -12
- package/dist/http/context.js +60 -24
- package/dist/http/context.js.map +1 -1
- package/dist/http/csp.d.ts +2 -1
- package/dist/http/csp.js +3 -2
- package/dist/http/csp.js.map +1 -1
- package/dist/onboarding.d.ts +2 -2
- package/dist/onboarding.js +10 -8
- package/dist/onboarding.js.map +1 -1
- package/dist/profiles/profiles.d.ts +20 -4
- package/dist/profiles/profiles.js +115 -30
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/projects.d.ts +15 -6
- package/dist/projects.js +55 -47
- package/dist/projects.js.map +1 -1
- package/dist/providers/catalog.d.ts +84 -14
- package/dist/providers/catalog.js +474 -151
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/env-file.d.ts +10 -3
- package/dist/providers/env-file.js +24 -75
- package/dist/providers/env-file.js.map +1 -1
- package/dist/providers/runtime-env.d.ts +4 -2
- package/dist/providers/runtime-env.js +9 -6
- package/dist/providers/runtime-env.js.map +1 -1
- package/dist/providers/tables.d.ts +3 -1
- package/dist/providers/tables.js +4 -2
- package/dist/providers/tables.js.map +1 -1
- package/dist/raster-image.d.ts +7 -0
- package/dist/raster-image.js +267 -0
- package/dist/raster-image.js.map +1 -0
- package/dist/redact.js +342 -46
- package/dist/redact.js.map +1 -1
- package/dist/release.d.ts +7 -0
- package/dist/release.js +14 -0
- package/dist/release.js.map +1 -1
- package/dist/runtime.d.ts +5 -1
- package/dist/runtime.js +305 -96
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +9 -3
- package/dist/sessions/anchor.js +28 -36
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-events.d.ts +2 -0
- package/dist/sessions/background-events.js +10 -0
- package/dist/sessions/background-events.js.map +1 -0
- package/dist/sessions/background-tasks.d.ts +26 -27
- package/dist/sessions/background-tasks.js +89 -97
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/background-updates.d.ts +15 -8
- package/dist/sessions/background-updates.js +101 -25
- package/dist/sessions/background-updates.js.map +1 -1
- package/dist/sessions/cli-sessions.d.ts +27 -6
- package/dist/sessions/cli-sessions.js +152 -42
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +77 -9
- package/dist/sessions/completions.js +482 -45
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +8 -0
- package/dist/sessions/compress.js +2 -2
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/drafts.d.ts +14 -10
- package/dist/sessions/drafts.js +38 -43
- package/dist/sessions/drafts.js.map +1 -1
- package/dist/sessions/export.d.ts +5 -0
- package/dist/sessions/export.js +30 -0
- package/dist/sessions/export.js.map +1 -1
- package/dist/sessions/gateway-sessions.d.ts +46 -0
- package/dist/sessions/gateway-sessions.js +378 -0
- package/dist/sessions/gateway-sessions.js.map +1 -0
- package/dist/sessions/gateway-watcher.d.ts +7 -3
- package/dist/sessions/gateway-watcher.js +31 -19
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/goals.d.ts +31 -0
- package/dist/sessions/goals.js +75 -0
- package/dist/sessions/goals.js.map +1 -0
- package/dist/sessions/handoff.d.ts +32 -0
- package/dist/sessions/handoff.js +114 -0
- package/dist/sessions/handoff.js.map +1 -0
- package/dist/sessions/journal.d.ts +5 -79
- package/dist/sessions/journal.js +15 -606
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +31 -16
- package/dist/sessions/list.js +139 -129
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +69 -13
- package/dist/sessions/merge.js +265 -64
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/notices.d.ts +22 -0
- package/dist/sessions/notices.js +72 -0
- package/dist/sessions/notices.js.map +1 -0
- package/dist/sessions/pending.d.ts +10 -4
- package/dist/sessions/pending.js +83 -7
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/prefix-index.d.ts +82 -0
- package/dist/sessions/prefix-index.js +232 -0
- package/dist/sessions/prefix-index.js.map +1 -0
- package/dist/sessions/recovery.d.ts +19 -0
- package/dist/sessions/recovery.js +322 -0
- package/dist/sessions/recovery.js.map +1 -0
- package/dist/sessions/relay.d.ts +38 -19
- package/dist/sessions/relay.js +302 -79
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +260 -55
- package/dist/sessions/service.js +1448 -462
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +24 -2
- package/dist/sessions/session.js +69 -7
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.d.ts +7 -6
- package/dist/sessions/shares.js +22 -45
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/source-kind.js +1 -0
- package/dist/sessions/source-kind.js.map +1 -1
- package/dist/sessions/state-db-identity.d.ts +18 -0
- package/dist/sessions/state-db-identity.js +67 -0
- package/dist/sessions/state-db-identity.js.map +1 -0
- package/dist/sessions/state-db-worker.d.ts +1 -0
- package/dist/sessions/state-db-worker.js +35 -0
- package/dist/sessions/state-db-worker.js.map +1 -0
- package/dist/sessions/state-db.d.ts +113 -7
- package/dist/sessions/state-db.js +861 -44
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/storage/backend.d.ts +162 -0
- package/dist/sessions/storage/backend.js +19 -0
- package/dist/sessions/storage/backend.js.map +1 -0
- package/dist/sessions/storage/conformance.d.ts +20 -0
- package/dist/sessions/storage/conformance.js +237 -0
- package/dist/sessions/storage/conformance.js.map +1 -0
- package/dist/sessions/storage/document.d.ts +16 -0
- package/dist/sessions/storage/document.js +44 -0
- package/dist/sessions/storage/document.js.map +1 -0
- package/dist/sessions/storage/json-documents.d.ts +23 -0
- package/dist/sessions/storage/json-documents.js +233 -0
- package/dist/sessions/storage/json-documents.js.map +1 -0
- package/dist/sessions/storage/json-files.d.ts +12 -0
- package/dist/sessions/storage/json-files.js +124 -0
- package/dist/sessions/storage/json-files.js.map +1 -0
- package/dist/sessions/storage/json-installation.d.ts +3 -0
- package/dist/sessions/storage/json-installation.js +31 -0
- package/dist/sessions/storage/json-installation.js.map +1 -0
- package/dist/sessions/storage/json-journal.d.ts +114 -0
- package/dist/sessions/storage/json-journal.js +686 -0
- package/dist/sessions/storage/json-journal.js.map +1 -0
- package/dist/sessions/storage/json-recovery.d.ts +4 -0
- package/dist/sessions/storage/json-recovery.js +195 -0
- package/dist/sessions/storage/json-recovery.js.map +1 -0
- package/dist/sessions/storage/json.d.ts +68 -0
- package/dist/sessions/storage/json.js +258 -0
- package/dist/sessions/storage/json.js.map +1 -0
- package/dist/sessions/storage/row.d.ts +6 -0
- package/dist/sessions/storage/row.js +49 -0
- package/dist/sessions/storage/row.js.map +1 -0
- package/dist/sessions/storage/search-text.d.ts +4 -0
- package/dist/sessions/storage/search-text.js +16 -0
- package/dist/sessions/storage/search-text.js.map +1 -0
- package/dist/sessions/store.d.ts +65 -68
- package/dist/sessions/store.js +305 -602
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +4 -0
- package/dist/sessions/streams.js +9 -1
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/titles.d.ts +2 -0
- package/dist/sessions/titles.js +5 -1
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/todo.d.ts +5 -0
- package/dist/sessions/todo.js +28 -21
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +5 -4
- package/dist/sessions/tool-display.js +30 -50
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn-context.d.ts +11 -2
- package/dist/sessions/turn-context.js +18 -11
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +122 -17
- package/dist/sessions/turn.js +939 -358
- package/dist/sessions/turn.js.map +1 -1
- package/dist/sessions/wakeup-pause.d.ts +54 -0
- package/dist/sessions/wakeup-pause.js +163 -0
- package/dist/sessions/wakeup-pause.js.map +1 -0
- package/dist/sessions/window.js +13 -12
- package/dist/sessions/window.js.map +1 -1
- package/dist/settings.d.ts +19 -7
- package/dist/settings.js +178 -184
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/client.d.ts +17 -3
- package/dist/sidecar/client.js +78 -8
- package/dist/sidecar/client.js.map +1 -1
- package/dist/sidecar/discover.d.ts +2 -1
- package/dist/sidecar/discover.js +93 -10
- package/dist/sidecar/discover.js.map +1 -1
- package/dist/sidecar/env.d.ts +4 -0
- package/dist/sidecar/env.js +3 -0
- package/dist/sidecar/env.js.map +1 -0
- package/dist/sidecar/fake.d.ts +2 -2
- package/dist/sidecar/fake.js +19 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/startup.d.ts +13 -5
- package/dist/startup.js +48 -0
- package/dist/startup.js.map +1 -1
- package/dist/test/env-file.d.ts +5 -0
- package/dist/test/env-file.js +88 -0
- package/dist/test/env-file.js.map +1 -0
- package/dist/test/harness.d.ts +1 -0
- package/dist/test/harness.js +1 -1
- package/dist/test/harness.js.map +1 -1
- package/dist/test/passkeys.d.ts +22 -0
- package/dist/test/passkeys.js +62 -0
- package/dist/test/passkeys.js.map +1 -0
- package/dist/test/storage.d.ts +15 -0
- package/dist/test/storage.js +26 -0
- package/dist/test/storage.js.map +1 -0
- package/dist/test/ts-worker-hooks.d.ts +1 -0
- package/dist/test/ts-worker-hooks.js +24 -0
- package/dist/test/ts-worker-hooks.js.map +1 -0
- package/dist/text/diff.d.ts +9 -14
- package/dist/text/diff.js +74 -108
- package/dist/text/diff.js.map +1 -1
- package/dist/tools/commands.d.ts +6 -1
- package/dist/tools/commands.js +8 -2
- package/dist/tools/commands.js.map +1 -1
- package/dist/tools/connectors.d.ts +33 -0
- package/dist/tools/connectors.js +121 -0
- package/dist/tools/connectors.js.map +1 -0
- package/dist/tools/crons.d.ts +38 -1
- package/dist/tools/crons.js +122 -18
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/dashboard-plugins.d.ts +53 -0
- package/dist/tools/dashboard-plugins.js +188 -0
- package/dist/tools/dashboard-plugins.js.map +1 -0
- package/dist/tools/extensions.d.ts +18 -5
- package/dist/tools/extensions.js +344 -266
- package/dist/tools/extensions.js.map +1 -1
- package/dist/tools/gateway-restart.d.ts +62 -0
- package/dist/tools/gateway-restart.js +142 -0
- package/dist/tools/gateway-restart.js.map +1 -0
- package/dist/tools/health.d.ts +44 -0
- package/dist/tools/health.js +201 -46
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/hygiene.d.ts +4 -3
- package/dist/tools/hygiene.js +23 -9
- package/dist/tools/hygiene.js.map +1 -1
- package/dist/tools/kanban.d.ts +4 -2
- package/dist/tools/kanban.js +28 -19
- package/dist/tools/kanban.js.map +1 -1
- package/dist/tools/mcp.d.ts +13 -0
- package/dist/tools/mcp.js +88 -0
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/memory-masks.d.ts +5 -0
- package/dist/tools/memory-masks.js +256 -0
- package/dist/tools/memory-masks.js.map +1 -0
- package/dist/tools/memory.d.ts +29 -3
- package/dist/tools/memory.js +69 -27
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/plugin-settings.d.ts +15 -0
- package/dist/tools/plugin-settings.js +90 -0
- package/dist/tools/plugin-settings.js.map +1 -0
- package/dist/tools/project-os.d.ts +11 -0
- package/dist/tools/project-os.js +267 -0
- package/dist/tools/project-os.js.map +1 -0
- package/dist/tools/skills.d.ts +14 -5
- package/dist/tools/skills.js +49 -16
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +34 -15
- package/dist/tools/update-notifications.js +141 -57
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/update-rollback.d.ts +11 -0
- package/dist/tools/update-rollback.js +28 -0
- package/dist/tools/update-rollback.js.map +1 -0
- package/dist/tools/updates.d.ts +110 -32
- package/dist/tools/updates.js +1112 -294
- package/dist/tools/updates.js.map +1 -1
- package/dist/tools/wiki.d.ts +68 -0
- package/dist/tools/wiki.js +413 -0
- package/dist/tools/wiki.js.map +1 -0
- package/dist/util.d.ts +2 -0
- package/dist/util.js +5 -0
- package/dist/util.js.map +1 -1
- package/dist/workspace/escape.d.ts +61 -0
- package/dist/workspace/escape.js +149 -0
- package/dist/workspace/escape.js.map +1 -0
- package/dist/workspace/extract.d.ts +22 -0
- package/dist/workspace/extract.js +308 -0
- package/dist/workspace/extract.js.map +1 -0
- package/dist/workspace/fs.d.ts +12 -0
- package/dist/workspace/fs.js +92 -23
- package/dist/workspace/fs.js.map +1 -1
- package/dist/workspace/git.d.ts +34 -1
- package/dist/workspace/git.js +328 -35
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media.d.ts +16 -13
- package/dist/workspace/media.js +43 -61
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/rollback.d.ts +36 -22
- package/dist/workspace/rollback.js +178 -272
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/unzip.js +4 -1
- package/dist/workspace/unzip.js.map +1 -1
- package/dist/workspace/upload.d.ts +7 -4
- package/dist/workspace/upload.js +5 -2
- package/dist/workspace/upload.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +15 -19
- package/dist/workspace/workspaces.js +55 -127
- package/dist/workspace/workspaces.js.map +1 -1
- package/dist/workspace/worktrees.d.ts +2 -1
- package/dist/workspace/worktrees.js +179 -42
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +7 -3
- package/sidecar/agent_dependency.json +4 -4
- package/sidecar/agent_support.json +26 -0
- package/sidecar/scripts/record-fixtures.py +12 -3
- package/sidecar/scripts/replay_sidecar.py +32 -0
- package/sidecar/scripts/suite_runner.py +302 -0
- package/sidecar/scripts/test.sh +12 -4
- package/sidecar/talaria_sidecar/__init__.py +6 -1
- package/sidecar/talaria_sidecar/__main__.py +4 -2
- package/sidecar/talaria_sidecar/agent_mutation.py +975 -0
- package/sidecar/talaria_sidecar/agent_services.py +1053 -0
- package/sidecar/talaria_sidecar/dependency_sync.py +85 -0
- package/sidecar/talaria_sidecar/home.py +56 -3
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +166 -24
- package/sidecar/talaria_sidecar/methods/chat.py +852 -135
- package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
- package/sidecar/talaria_sidecar/methods/commands.py +294 -4
- package/sidecar/talaria_sidecar/methods/config.py +123 -4
- package/sidecar/talaria_sidecar/methods/connectors.py +343 -0
- package/sidecar/talaria_sidecar/methods/cron.py +395 -36
- package/sidecar/talaria_sidecar/methods/cron_execution.py +284 -0
- package/sidecar/talaria_sidecar/methods/gateway.py +212 -55
- package/sidecar/talaria_sidecar/methods/gateway_sessions.py +234 -0
- package/sidecar/talaria_sidecar/methods/goals.py +81 -41
- package/sidecar/talaria_sidecar/methods/kanban.py +108 -34
- package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
- package/sidecar/talaria_sidecar/methods/memory.py +84 -0
- package/sidecar/talaria_sidecar/methods/oauth.py +5 -2
- package/sidecar/talaria_sidecar/methods/process.py +218 -15
- package/sidecar/talaria_sidecar/methods/profiles.py +121 -13
- package/sidecar/talaria_sidecar/methods/providers.py +217 -28
- package/sidecar/talaria_sidecar/methods/runtime.py +69 -1
- package/sidecar/talaria_sidecar/methods/skills.py +67 -4
- package/sidecar/talaria_sidecar/methods/state_db.py +316 -75
- package/sidecar/talaria_sidecar/methods/stt.py +159 -78
- package/sidecar/talaria_sidecar/methods/text.py +14 -1
- package/sidecar/talaria_sidecar/methods/toolsets.py +49 -0
- package/sidecar/talaria_sidecar/methods/usage.py +113 -14
- package/sidecar/talaria_sidecar/methods/worktree.py +26 -19
- package/sidecar/talaria_sidecar/methods/write_approval.py +181 -0
- package/sidecar/talaria_sidecar/rpc.py +45 -4
- package/sidecar/talaria_sidecar/runtime.py +98 -13
- package/sidecar/talaria_sidecar/speed.py +35 -0
- package/static/dist/FILES.txt +126 -126
- package/static/dist/assets/AppShell-cey33v9s.js +8 -0
- package/static/dist/assets/ChatPage-i8wnugr7.js +18 -0
- package/static/dist/assets/ConnectTargets-hg19553x.js +1 -0
- package/static/dist/assets/ExtensionRoute-cly4apm5.js +1 -0
- package/static/dist/assets/Field-j8uqoigz.js +9 -0
- package/static/dist/assets/HubRoute-e4bgz41j.js +2 -0
- package/static/dist/assets/Markdown-lahuichm.js +598 -0
- package/static/dist/assets/RightPanel-lbo4i0fn.js +1 -0
- package/static/dist/assets/Select-gqatm2e0.js +1 -0
- package/static/dist/assets/SessionListPanel-mfqayfjh.js +1 -0
- package/static/dist/assets/SettingsLayout-b8980u7k.js +1 -0
- package/static/dist/assets/States-e7dkgtxf.js +1 -0
- package/static/dist/assets/Toaster-bfqqy786.js +1 -0
- package/static/dist/assets/_app-n1n6pfx1.js +1 -0
- package/static/dist/assets/{_app.ext._extensionId-opyh9y0v.js → _app.ext._extensionId-buxjxav3.js} +1 -1
- package/static/dist/assets/_app.index-jt8zwiu2.js +1 -0
- package/static/dist/assets/_app.insights-cv76ml71.js +1 -0
- package/static/dist/assets/_app.kanban-h6ts53j7.js +1 -0
- package/static/dist/assets/_app.logs-dm873z90.js +1 -0
- package/static/dist/assets/_app.memory-glk5y6m8.js +1 -0
- package/static/dist/assets/_app.profiles-ka6zpstq.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-bfd4h3id.js → _app.session._sessionId-g2vzghvc.js} +1 -1
- package/static/dist/assets/_app.settings-hgjdhzcd.js +1 -0
- package/static/dist/assets/_app.settings._section-jf2kmhae.js +3 -0
- package/static/dist/assets/_app.skills-cii852yq.js +1 -0
- package/static/dist/assets/_app.tasks-fp9q7atm.js +1 -0
- package/static/dist/assets/_app.todos-dw94qzue.js +1 -0
- package/static/dist/assets/_app.workspaces-inyygzze.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-b2x1lqot.js → abnfDiagram-VCTEODGH-gjayq3ra.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +1 -0
- package/static/dist/assets/{architectureDiagram-5GKGNRK7-h38o5l7b.js → architectureDiagram-5GKGNRK7-nwb3xovn.js} +1 -1
- package/static/dist/assets/archive-b1j004vn.js +1 -0
- package/static/dist/assets/back-g74gkozu.js +1 -0
- package/static/dist/assets/{blockDiagram-I7D4REHJ-bqquxv2u.js → blockDiagram-I7D4REHJ-joedghi3.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-f3fb8w6z.js → c4Diagram-7LVT6UL2-hyrjrohe.js} +1 -1
- package/static/dist/assets/channel-es95o4y8.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-bzfyyzp3.js → chunk-4HAMMTFA-koouqbut.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-ly99rfx5.js → chunk-75Z2AOVW-bl4bhdnt.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-bg6ecfqf.js → chunk-DU6HZSFF-gpedttsp.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-e8k7mmqw.js → chunk-F27PBJKO-d8xxi0zi.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-cmalnoaj.js → chunk-GMAD6QVW-jro0hl73.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-gmif5o01.js → chunk-GVQU2GXP-djn6gbyu.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-f1nwwh6u.js → chunk-IMKFNOWR-vw4p5p7o.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-oktfh5v7.js → chunk-L3NEJ4N5-n1hj77q8.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-b1sdx96z.js → chunk-OSK3NFVY-oy0gwn2n.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-fhu4nzxt.js → chunk-P2QGCYS3-onf1hz6c.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-em31rnef.js → chunk-POPQ4Y6H-m8sqn7cs.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-bxhhyftw.js → chunk-PWAF6VOD-ek4smc8x.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-fmrvwcav.js → chunk-SHT3W25Y-g9gfvefo.js} +3 -3
- package/static/dist/assets/{chunk-SVP7TREG-lymukl2j.js → chunk-SVP7TREG-f9nfv2cu.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-lmza6k23.js → chunk-TICWLB2K-bnk3m1mq.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +1 -0
- package/static/dist/assets/copied-bli9tbcx.js +1 -0
- package/static/dist/assets/{copy-fw5b1pc6.js → copy-jamtd1pf.js} +1 -1
- package/static/dist/assets/{cynefin-OW5HDTMX-czlhbyhs.js → cynefin-OW5HDTMX-f8xfdd3f.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-gq7mvswb.js → cynefinDiagram-5FMLGOSQ-n9lb99h9.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-cqdbf7dr.js → dagre-GXQ25YYZ-dgailddi.js} +4 -4
- package/static/dist/assets/{diagram-S7CK7UJ4-o6arxa58.js → diagram-S7CK7UJ4-m6aeoe7c.js} +1 -1
- package/static/dist/assets/{diagram-UQ7AKVKN-nhnd68tz.js → diagram-UQ7AKVKN-jall3j0x.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-dk2sclzn.js → diagram-VSXAHHWV-hbawi7hn.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-n3k5nyx3.js → diagram-VX7I27RA-hendl2tt.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-m6q9cwdh.js → diagram-Z3DM3KII-igwc35p3.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-lsa5641p.js → ebnfDiagram-PWID7BFC-c64knz08.js} +1 -1
- package/static/dist/assets/{erDiagram-RLTQ6QDP-fo2ckbe6.js → erDiagram-RLTQ6QDP-brdu9lcm.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +1 -0
- package/static/dist/assets/folder-plus-lvt9kudb.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-nzqz6x9g.js → ganttDiagram-EL5Y4UJY-ma6jxj5i.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-gukvcxe6.js → gitGraph-4MIJSDKK-naa9w6xa.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-lqhdp8f0.js → gitGraphDiagram-WWUBYQGX-hzltvsod.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-igw6cp39.js → highlighted-body-KPVGNVTW-ghoavj8d.js} +1 -1
- package/static/dist/assets/host-hzm8n85n.js +1 -0
- package/static/dist/assets/index-drdcaqeq.css +2 -0
- package/static/dist/assets/index-jzm844he.js +11 -0
- package/static/dist/assets/{info-A6RAGUB7-gvu7a93l.js → info-A6RAGUB7-mn2ut5ko.js} +1 -1
- package/static/dist/assets/{infoDiagram-27XIBGKW-m8t8g6w1.js → infoDiagram-27XIBGKW-h6kuqvsb.js} +1 -1
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-l07vkbgj.js → ishikawaDiagram-5VMMS53U-e7bnwq7h.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-hybkgj8l.js → journeyDiagram-3NMN7TZE-etd1bi6p.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-by13uvtn.js → kanban-definition-UXKFOSKX-emb1g9dz.js} +4 -4
- package/static/dist/assets/lazyRouteComponent-o816ytzl.js +1 -0
- package/static/dist/assets/{line-osxq6fq7.js → line-h0ojzsvs.js} +1 -1
- package/static/dist/assets/link-k01ryaf9.js +1 -0
- package/static/dist/assets/login-bsae49ew.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-i91oxmou.js → mermaid-parser.core-bnre27u8.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-m82mx8ww.js → mindmap-definition-YA3MSWOX-o1vathns.js} +1 -1
- package/static/dist/assets/onboarding-c65tysya.js +1 -0
- package/static/dist/assets/onboarding_model_label-gpn28uix.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-nbtgge2o.js → packet-AYTQ26CC-hatq7m2e.js} +1 -1
- package/static/dist/assets/pcmTap.worklet-74EKjWAZ.js +1 -0
- package/static/dist/assets/{pegDiagram-XKGWAZYB-f1zxzga1.js → pegDiagram-XKGWAZYB-gwisz8jv.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-of8vuhxs.js → pie-WAS4IAKB-fpftf0mk.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-o7y8ftio.js → pieDiagram-E7YTZNPT-khm1o9a6.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-iqiwia1t.js → quadrantDiagram-AXDQQJYC-owqc4uqn.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-delnsy2v.js → radar-RG4KPBEZ-cntfwjpq.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-nspnyq9u.js → railroad-74A4TZTK-a7t5eibp.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-z41tqx5z.js → railroadDiagram-O6MQD6OU-etuh0qa0.js} +1 -1
- package/static/dist/assets/{refresh-cw-nt1dtsx4.js → refresh-cw-mqbkv2a3.js} +1 -1
- package/static/dist/assets/refresh-l29ljp76.js +1 -0
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-bdvif194.js → requirementDiagram-BXWQKSXE-oq2ulk22.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-h73e0rol.js → sankeyDiagram-P5KCCOFB-iqc02emv.js} +1 -1
- package/static/dist/assets/saved-ndd4t2xb.js +1 -0
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-iv95mb1z.js → sequenceDiagram-WJ2MYXX4-i0v116q0.js} +2 -2
- package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +1 -0
- package/static/dist/assets/share._token-c1ri0hy1.js +2 -0
- package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-k417ixmf.js → stateDiagram-D77RDMKH-dnpt1ato.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-esola70w.js → swimlanes-42K2YHIH-loqbdazb.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-getrz2y8.js → timeline-definition-24CTP7MA-ol2hnvma.js} +1 -1
- package/static/dist/assets/toast-i3h34v6o.js +64 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-hc7ap1q7.js → treeView-Q6P3EWNA-idrj6iox.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-b7k1syy5.js → treemap-WGGIJYW6-lzr49vv5.js} +1 -1
- package/static/dist/assets/untitled-oaoya4qj.js +1 -0
- package/static/dist/assets/useForm-nfaxgmsk.js +1 -0
- package/static/dist/assets/{useParams-lhg566ew.js → useParams-gjc70qru.js} +1 -1
- package/static/dist/assets/{useSearch-lbrh6ejn.js → useSearch-e6ae539u.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-n6pcy5qb.js → vennDiagram-4TSXK5OY-bvwr9o2z.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-lpa18vz1.js → wardley-WFR3VGLG-31ui8tma.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-kpclwwg7.js → wardleyDiagram-VM6X3IG4-k5wqtx4b.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-paa2n8y0.js → xychartDiagram-S5SC5T6Z-aqbgvujg.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-fb2td9zi.js +0 -1
- package/static/dist/assets/ChatPage-cj96kiy9.js +0 -25
- package/static/dist/assets/Dialog-kxcg7ard.js +0 -1
- package/static/dist/assets/ExtensionRoute-jf7h0754.js +0 -1
- package/static/dist/assets/HubRoute-gmanpd5y.js +0 -2
- package/static/dist/assets/Markdown-mdwlvj3w.js +0 -598
- package/static/dist/assets/RightPanel-ftvrjlcp.js +0 -1
- package/static/dist/assets/Select-bilaypej.js +0 -1
- package/static/dist/assets/SessionListPanel-og8n36qs.js +0 -1
- package/static/dist/assets/SettingsLayout-jonjefmj.js +0 -1
- package/static/dist/assets/States-nmejrwrm.js +0 -1
- package/static/dist/assets/Toaster-nnl8zkt9.js +0 -1
- package/static/dist/assets/_app-gdidzd94.js +0 -1
- package/static/dist/assets/_app.index-hbarzbjx.js +0 -1
- package/static/dist/assets/_app.insights-luywlarz.js +0 -1
- package/static/dist/assets/_app.kanban-kincnbr8.js +0 -1
- package/static/dist/assets/_app.logs-oi2gr3gs.js +0 -1
- package/static/dist/assets/_app.memory-b6jfivt6.js +0 -1
- package/static/dist/assets/_app.profiles-lpvixmqp.js +0 -1
- package/static/dist/assets/_app.settings-vowx3iq0.js +0 -1
- package/static/dist/assets/_app.settings._section-cnntg1tu.js +0 -3
- package/static/dist/assets/_app.skills-fb57lpau.js +0 -1
- package/static/dist/assets/_app.tasks-eszpx1q7.js +0 -2
- package/static/dist/assets/_app.todos-nr05ac6w.js +0 -1
- package/static/dist/assets/_app.workspaces-d2luomu6.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +0 -1
- package/static/dist/assets/back-bemaer09.js +0 -1
- package/static/dist/assets/cancel-drd0g6d9.js +0 -1
- package/static/dist/assets/channel-cpdgf867.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +0 -1
- package/static/dist/assets/copied-ima3pysb.js +0 -1
- package/static/dist/assets/endpoints-d33siokx.js +0 -64
- package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +0 -1
- package/static/dist/assets/index-cxq58xb1.js +0 -11
- package/static/dist/assets/index-hgtx08n5.css +0 -2
- package/static/dist/assets/lazyRouteComponent-af0lvgjx.js +0 -1
- package/static/dist/assets/link-jyxicdhg.js +0 -1
- package/static/dist/assets/login-ec21t39x.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +0 -1
- package/static/dist/assets/onboarding-btnltvyz.js +0 -1
- package/static/dist/assets/onboarding_model_label-dp7xffoh.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +0 -1
- package/static/dist/assets/refresh-b4uams1i.js +0 -1
- package/static/dist/assets/saved-kg52csy7.js +0 -1
- package/static/dist/assets/settings_section_extensions_title-ej0iapxe.js +0 -1
- package/static/dist/assets/share._token-f97e94r0.js +0 -2
- package/static/dist/assets/sliders-horizontal-fj18it47.js +0 -1
- package/static/dist/assets/sse-fwpqtzbg.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +0 -8
- package/static/dist/assets/todoStore-eginf9h4.js +0 -1
- package/static/dist/assets/tts-lxdxnmjd.js +0 -1
- package/static/dist/assets/untitled-ovpfthg4.js +0 -1
- package/static/dist/assets/useForm-bcblv1rx.js +0 -1
- package/static/dist/assets/useLocale-fi5jb8ma.js +0 -1
package/dist/tools/extensions.js
CHANGED
|
@@ -5,10 +5,12 @@ import { resolvePathLikePython } from '../workspace/paths.js';
|
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { closeSync, existsSync, fstatSync, lstatSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
8
|
-
import {
|
|
8
|
+
import { InvalidDocument } from '../sessions/storage/backend.js';
|
|
9
|
+
import { readDocument } from '../sessions/storage/document.js';
|
|
9
10
|
import { readZip } from '../workspace/unzip.js';
|
|
10
11
|
import { isDict } from '../config/agent-config.js';
|
|
11
12
|
import { str } from '../util.js';
|
|
13
|
+
import { SKIN_TOKEN_NAMES, SKIN_VALUE_RE } from '@maudecode/talaria-web-contracts';
|
|
12
14
|
export class ExtensionError extends Error {
|
|
13
15
|
status;
|
|
14
16
|
constructor(message, status = 400) {
|
|
@@ -34,6 +36,9 @@ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
|
|
|
34
36
|
const ALLOWED_ASSET_PREFIXES = ['/extensions/', '/static/'];
|
|
35
37
|
const ALLOWED_DOTFILES = new Set(['.gitkeep', '.gitignore', '.gitattributes', '.env.example']);
|
|
36
38
|
export const EXTENSION_MIME = { css: 'text/css', js: 'application/javascript', html: 'text/html', svg: 'image/svg+xml', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', ico: 'image/x-icon', gif: 'image/gif', webp: 'image/webp', woff: 'font/woff', woff2: 'font/woff2', ttf: 'font/ttf', otf: 'font/otf', wasm: 'application/wasm' };
|
|
39
|
+
const SKIN_TOKENS = new Set(SKIN_TOKEN_NAMES);
|
|
40
|
+
/** Skin keys and TTS engine ids share the frontend contract's slug. */
|
|
41
|
+
const DECLARATION_ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
|
|
37
42
|
const TEXT_MIME = new Set(['text/css', 'application/javascript', 'text/html', 'image/svg+xml', 'text/plain']);
|
|
38
43
|
export const validId = (v) => typeof v === 'string' && ID_RE.test(v.trim());
|
|
39
44
|
const warn = (d, code, source) => { if (d && !d.warnings.some((w) => w.code === code && w.source === source))
|
|
@@ -83,6 +88,41 @@ function isSafeAssetUrl(value) {
|
|
|
83
88
|
return isSafeRelativePath(decoded.slice(prefix.length));
|
|
84
89
|
return false;
|
|
85
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Read `<root>/<rel>` for a static route: `rel` must be a clean relative path (no dot segments or dotfiles), and the
|
|
93
|
+
* read is an anchored, symlink-free open, so a link inside the root cannot expose its target. Python
|
|
94
|
+
* `(root / rel).resolve()`: a symlinked file is served when its target stays inside the root, never otherwise.
|
|
95
|
+
*/
|
|
96
|
+
export function readContainedFile(root, rel) {
|
|
97
|
+
if (!isSafeRelativePath(rel))
|
|
98
|
+
return null;
|
|
99
|
+
const anchor = resolvePathLikePython(root);
|
|
100
|
+
let file = resolve(anchor, rel);
|
|
101
|
+
if (!file.startsWith(anchor + sep))
|
|
102
|
+
return null;
|
|
103
|
+
const real = resolvePathLikePython(file);
|
|
104
|
+
if (real !== file) {
|
|
105
|
+
if (!real.startsWith(anchor + sep))
|
|
106
|
+
return null;
|
|
107
|
+
file = real;
|
|
108
|
+
}
|
|
109
|
+
let fd;
|
|
110
|
+
try {
|
|
111
|
+
fd = openAnchoredFd(anchor, file, { wantDir: false });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return fstatSync(fd).isFile() ? { body: readFileSync(fd), file } : null;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
closeSync(fd);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
86
126
|
/**
|
|
87
127
|
* Python `_normalize_loopback_sidecar_origin` (`urlsplit`, not WHATWG): scheme + literal loopback host + optional
|
|
88
128
|
* numeric port, nothing else — no path (not even `/`), no query, no fragment, no userinfo, no host aliases.
|
|
@@ -144,7 +184,7 @@ export function normalizeProxyPath(value) {
|
|
|
144
184
|
const segments = decoded.split('/').slice(1);
|
|
145
185
|
return segments.length && segments.every((s) => s && s !== '.' && s !== '..') ? candidate : null;
|
|
146
186
|
}
|
|
147
|
-
const text = (v, max = 160) => (typeof v === 'string' ? v.replaceAll(/[\x00-\x1f\x7f]/g, '').trim().slice(0, max) : '');
|
|
187
|
+
export const text = (v, max = 160) => (typeof v === 'string' ? v.replaceAll(/[\x00-\x1f\x7f]/g, '').trim().slice(0, max) : '');
|
|
148
188
|
const entryText = (e, k) => (typeof e[k] === 'string' ? e[k].trim() : '');
|
|
149
189
|
const storageOwned = (e) => isDict(e.permissions) && isDict(e.permissions.storage) && e.permissions.storage.owned === true;
|
|
150
190
|
function settingsSchema(entry) {
|
|
@@ -268,6 +308,28 @@ function sidecarFromEntry(entry, d) {
|
|
|
268
308
|
}
|
|
269
309
|
return { id: entryText(entry, 'id'), name: entryText(entry, 'name'), type: 'loopback', origin, health_path: healthPath, health_url: `${origin}${healthPath}`, proxy_auth: proxyAuth };
|
|
270
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* A manifest `theme` reduced to the frontend skin contract: allowlisted tokens whose values pass the shared value check,
|
|
313
|
+
* keyed `<extension-id>:<key>` so it never collides with a built-in skin or another extension's skin.
|
|
314
|
+
*/
|
|
315
|
+
function themeFromEntry(raw, id) {
|
|
316
|
+
if (!isDict(raw) || typeof raw.key !== 'string' || !DECLARATION_ID_RE.test(raw.key))
|
|
317
|
+
return null;
|
|
318
|
+
const name = text(raw.name, 40);
|
|
319
|
+
const tokens = isDict(raw.tokens) ? Object.fromEntries(Object.entries(raw.tokens).filter(([k, v]) => SKIN_TOKENS.has(k) && typeof v === 'string' && SKIN_VALUE_RE.test(v))) : {};
|
|
320
|
+
if (!name || !Object.keys(tokens).length)
|
|
321
|
+
return null;
|
|
322
|
+
const colors = Array.isArray(raw.colors) ? raw.colors.filter((c) => typeof c === 'string' && SKIN_VALUE_RE.test(c)).slice(0, 3) : null;
|
|
323
|
+
return { key: `${id}:${raw.key}`, name, ...(raw.scheme === 'light' || raw.scheme === 'dark' ? { scheme: raw.scheme } : {}), ...(colors ? { colors } : {}), tokens };
|
|
324
|
+
}
|
|
325
|
+
function ttsFromEntry(raw) {
|
|
326
|
+
if (!isDict(raw) || typeof raw.id !== 'string' || !DECLARATION_ID_RE.test(raw.id))
|
|
327
|
+
return null;
|
|
328
|
+
const label = text(raw.label, 60);
|
|
329
|
+
return label ? { id: raw.id, label } : null;
|
|
330
|
+
}
|
|
331
|
+
/** Entries the gallery loader read from `<root>/<id>/`, keyed by object so a manifest can never claim gallery provenance. */
|
|
332
|
+
const galleryFolders = new WeakMap();
|
|
271
333
|
export class ExtensionService {
|
|
272
334
|
deps;
|
|
273
335
|
registryCache = null;
|
|
@@ -278,8 +340,7 @@ export class ExtensionService {
|
|
|
278
340
|
this.deps = deps;
|
|
279
341
|
}
|
|
280
342
|
get defaultRoot() { return join(this.deps.stateDir, 'extensions'); }
|
|
281
|
-
|
|
282
|
-
get installFile() { return join(this.deps.stateDir, 'extension-install-manifest.json'); }
|
|
343
|
+
revisions = new Map();
|
|
283
344
|
root() {
|
|
284
345
|
const raw = (this.deps.env.HERMES_WEBUI_EXTENSION_DIR ?? '').trim();
|
|
285
346
|
if (raw) {
|
|
@@ -304,22 +365,25 @@ export class ExtensionService {
|
|
|
304
365
|
return null;
|
|
305
366
|
}
|
|
306
367
|
}
|
|
307
|
-
loadState(d) {
|
|
368
|
+
async loadState(d) {
|
|
308
369
|
const empty = { version: 1, disabled_extensions: [], sidecar_proxy_consents: {} };
|
|
309
370
|
const src = 'extension_state';
|
|
310
371
|
let parsed;
|
|
311
372
|
try {
|
|
312
|
-
|
|
373
|
+
const record = await readDocument(this.deps.documents, 'extensions', 'overrides', { replaceInvalid: true, maxBytes: MAX_STATE_BYTES });
|
|
374
|
+
this.revisions.set('overrides', record?.revision ?? null);
|
|
375
|
+
if (!record)
|
|
313
376
|
return empty;
|
|
314
|
-
|
|
315
|
-
if (raw.length > MAX_STATE_BYTES) {
|
|
377
|
+
if (Buffer.byteLength(JSON.stringify(record.value)) > MAX_STATE_BYTES) {
|
|
316
378
|
warn(d, 'extension_state_oversized', src);
|
|
317
379
|
return empty;
|
|
318
380
|
}
|
|
319
|
-
parsed =
|
|
381
|
+
parsed = record.value;
|
|
320
382
|
}
|
|
321
|
-
catch {
|
|
322
|
-
|
|
383
|
+
catch (error) {
|
|
384
|
+
if (error instanceof InvalidDocument && error.reason === 'too-large')
|
|
385
|
+
this.revisions.set('overrides', error.revision);
|
|
386
|
+
warn(d, error instanceof InvalidDocument && error.reason === 'too-large' ? 'extension_state_oversized' : 'extension_state_unreadable', src);
|
|
323
387
|
return empty;
|
|
324
388
|
}
|
|
325
389
|
if (!isDict(parsed)) {
|
|
@@ -379,19 +443,17 @@ export class ExtensionService {
|
|
|
379
443
|
warn(d, 'extension_state_invalid_entries', src);
|
|
380
444
|
return { version: 1, disabled_extensions: disabled, sidecar_proxy_consents: consents };
|
|
381
445
|
}
|
|
382
|
-
writeState(state) {
|
|
383
|
-
|
|
384
|
-
atomicWriteText(this.stateFile, JSON.stringify({ version: 1, disabled_extensions: [...new Set(state.disabled_extensions.filter(validId))].sort(), sidecar_proxy_consents: state.sidecar_proxy_consents }, null, 2));
|
|
446
|
+
async writeState(state) {
|
|
447
|
+
this.revisions.set('overrides', await this.deps.documents.put('extensions', 'overrides', { version: 1, disabled_extensions: [...new Set(state.disabled_extensions.filter(validId))].sort(), sidecar_proxy_consents: state.sidecar_proxy_consents }, this.revisions.get('overrides') ?? null));
|
|
385
448
|
}
|
|
386
|
-
loadInstallManifest() {
|
|
449
|
+
async loadInstallManifest() {
|
|
387
450
|
const empty = { version: 1, installed: {} };
|
|
388
451
|
try {
|
|
389
|
-
|
|
452
|
+
const record = await readDocument(this.deps.documents, 'extensions', 'install-manifest', { replaceInvalid: true, maxBytes: MAX_INSTALL_MANIFEST_BYTES });
|
|
453
|
+
this.revisions.set('install-manifest', record?.revision ?? null);
|
|
454
|
+
if (!record || Buffer.byteLength(JSON.stringify(record.value)) > MAX_INSTALL_MANIFEST_BYTES)
|
|
390
455
|
return empty;
|
|
391
|
-
const
|
|
392
|
-
if (raw.length > MAX_INSTALL_MANIFEST_BYTES)
|
|
393
|
-
return empty;
|
|
394
|
-
const parsed = JSON.parse(raw.toString('utf8'));
|
|
456
|
+
const parsed = record.value;
|
|
395
457
|
if (!isDict(parsed) || !isDict(parsed.installed))
|
|
396
458
|
return empty;
|
|
397
459
|
const installed = {};
|
|
@@ -404,13 +466,14 @@ export class ExtensionService {
|
|
|
404
466
|
}
|
|
405
467
|
return { version: 1, installed };
|
|
406
468
|
}
|
|
407
|
-
catch {
|
|
469
|
+
catch (error) {
|
|
470
|
+
if (error instanceof InvalidDocument && error.reason === 'too-large')
|
|
471
|
+
this.revisions.set('install-manifest', error.revision);
|
|
408
472
|
return empty;
|
|
409
473
|
}
|
|
410
474
|
}
|
|
411
|
-
writeInstallManifest(manifest) {
|
|
412
|
-
|
|
413
|
-
atomicWriteText(this.installFile, JSON.stringify(manifest, null, 2));
|
|
475
|
+
async writeInstallManifest(manifest) {
|
|
476
|
+
this.revisions.set('install-manifest', await this.deps.documents.put('extensions', 'install-manifest', manifest, this.revisions.get('install-manifest') ?? null));
|
|
414
477
|
}
|
|
415
478
|
manifestPath(root) {
|
|
416
479
|
const raw = (this.deps.env.HERMES_WEBUI_EXTENSION_MANIFEST ?? '').trim();
|
|
@@ -431,16 +494,17 @@ export class ExtensionService {
|
|
|
431
494
|
return [null, 'invalid_path'];
|
|
432
495
|
return [manifest, 'configured'];
|
|
433
496
|
}
|
|
434
|
-
galleryManifest(root, d) {
|
|
435
|
-
const installed = this.loadInstallManifest().installed;
|
|
497
|
+
async galleryManifest(root, d) {
|
|
498
|
+
const installed = (await this.loadInstallManifest()).installed;
|
|
436
499
|
const entries = [];
|
|
437
500
|
for (const id of Object.keys(installed).sort()) {
|
|
438
501
|
if (!validId(id))
|
|
439
502
|
continue;
|
|
440
|
-
|
|
503
|
+
// `manifest.json` wins; a gallery zip may ship only `extension.json` (docs/EXTENSIONS.md).
|
|
441
504
|
let manifest;
|
|
442
505
|
try {
|
|
443
|
-
|
|
506
|
+
const file = ['manifest.json', 'extension.json'].map((name) => join(root, id, name)).find((p) => existsSync(p) && statSync(p).isFile());
|
|
507
|
+
if (!file) {
|
|
444
508
|
warn(d, 'gallery_manifest_missing', 'gallery');
|
|
445
509
|
continue;
|
|
446
510
|
}
|
|
@@ -457,17 +521,26 @@ export class ExtensionService {
|
|
|
457
521
|
}
|
|
458
522
|
const withBase = (entry) => {
|
|
459
523
|
const copy = { ...entry };
|
|
524
|
+
// Paths already rooted at the id (the `extensions.json` shape, as in the shipped example) are not prefixed twice.
|
|
525
|
+
const rebase = (v) => {
|
|
526
|
+
if (typeof v !== 'string' || !v.trim() || /^[a-z]+:|^\/\/|^\//i.test(v.trim()))
|
|
527
|
+
return v;
|
|
528
|
+
return v.trim().startsWith(`${id}/`) ? v.trim() : `${id}/${v.trim()}`;
|
|
529
|
+
};
|
|
460
530
|
for (const key of ['scripts', 'stylesheets'])
|
|
461
531
|
if (Array.isArray(copy[key]))
|
|
462
|
-
copy[key] = copy[key].map(
|
|
532
|
+
copy[key] = copy[key].map(rebase);
|
|
533
|
+
if (typeof copy.panel === 'string')
|
|
534
|
+
copy.panel = rebase(copy.panel.trim().replace(/^(\.\/)+/, ''));
|
|
535
|
+
galleryFolders.set(copy, id);
|
|
463
536
|
return copy;
|
|
464
537
|
};
|
|
465
538
|
if (isDict(manifest)) {
|
|
466
539
|
const top = { id };
|
|
467
|
-
for (const key of ['name', 'enabled', 'scripts', 'stylesheets', 'sidecar', 'permissions', 'settings_schema'])
|
|
540
|
+
for (const key of ['name', 'enabled', 'version', 'description', 'panel', 'nav', 'capabilities', 'scripts', 'stylesheets', 'sidecar', 'theme', 'tts', 'permissions', 'settings_schema'])
|
|
468
541
|
if (key in manifest)
|
|
469
542
|
top[key] = manifest[key];
|
|
470
|
-
if (['scripts', 'stylesheets', 'sidecar', 'permissions', 'settings_schema'].some((k) => k in top))
|
|
543
|
+
if (['panel', 'scripts', 'stylesheets', 'sidecar', 'theme', 'tts', 'permissions', 'settings_schema'].some((k) => k in top))
|
|
471
544
|
entries.push(withBase(top));
|
|
472
545
|
}
|
|
473
546
|
for (const entry of manifestEntries(manifest)) {
|
|
@@ -479,14 +552,14 @@ export class ExtensionService {
|
|
|
479
552
|
}
|
|
480
553
|
return entries.length ? { extensions: entries } : null;
|
|
481
554
|
}
|
|
482
|
-
loadManifest(root, d) {
|
|
555
|
+
async loadManifest(root, d) {
|
|
483
556
|
const [file, pathStatus] = this.manifestPath(root);
|
|
484
557
|
const status = { configured: pathStatus !== 'not_configured', loaded: false, status: pathStatus, _asset_base: '', entry_count: 0, script_count: 0, stylesheet_count: 0, sidecar_count: 0 };
|
|
485
558
|
if (!file) {
|
|
486
559
|
if (pathStatus === 'invalid_path')
|
|
487
560
|
warn(d, 'manifest_invalid_path', 'manifest');
|
|
488
561
|
else if (pathStatus === 'not_configured') {
|
|
489
|
-
const gallery = this.galleryManifest(root, d);
|
|
562
|
+
const gallery = (await this.galleryManifest(root, d));
|
|
490
563
|
if (gallery)
|
|
491
564
|
return [gallery, { ...status, loaded: true, status: 'gallery_installed' }];
|
|
492
565
|
}
|
|
@@ -650,9 +723,9 @@ export class ExtensionService {
|
|
|
650
723
|
break;
|
|
651
724
|
}
|
|
652
725
|
}
|
|
653
|
-
const { records } = await this.sidecarRecords(manifest, disabled, state, d);
|
|
726
|
+
const { records, byId } = await this.sidecarRecords(manifest, disabled, state, d);
|
|
654
727
|
Object.assign(status, { loaded: true, status: str(status.status) || 'loaded', script_count: scripts.length, stylesheet_count: stylesheets.length, sidecar_count: records.length });
|
|
655
|
-
return { scripts, stylesheets, sidecars: records };
|
|
728
|
+
return { scripts, stylesheets, sidecars: records, sidecarsById: byId };
|
|
656
729
|
}
|
|
657
730
|
/** Python `get_extension_status`. */
|
|
658
731
|
/** Python `get_extension_config()["enabled"]` for the bootstrap feature flag: a valid root with a loadable manifest. */
|
|
@@ -661,33 +734,37 @@ export class ExtensionService {
|
|
|
661
734
|
return this.root() !== null;
|
|
662
735
|
}
|
|
663
736
|
async status() {
|
|
737
|
+
return (await this.snapshot()).status;
|
|
738
|
+
}
|
|
739
|
+
/** The status payload plus the manifest and sidecar records it was built from, so `manifests()` reads the same load. */
|
|
740
|
+
async snapshot() {
|
|
664
741
|
const d = { warnings: [] };
|
|
665
742
|
const envDir = (this.deps.env.HERMES_WEBUI_EXTENSION_DIR ?? '').trim();
|
|
666
743
|
const root = this.root();
|
|
667
|
-
const state = this.loadState(d);
|
|
744
|
+
const state = (await this.loadState(d));
|
|
668
745
|
const disabled = new Set(state.disabled_extensions);
|
|
669
746
|
const manifestConfigured = Boolean((this.deps.env.HERMES_WEBUI_EXTENSION_MANIFEST ?? '').trim());
|
|
670
747
|
if (envDir && !root)
|
|
671
748
|
warn(d, 'extension_dir_unavailable', 'extension_dir');
|
|
672
749
|
if (!root) {
|
|
673
|
-
return { enabled: false, extension_dir_configured: true, extension_dir_valid: false, script_urls: [], stylesheet_urls: [], sidecars: [], counts: { script_urls: 0, stylesheet_urls: 0, sidecars: 0, manifest_extensions: 0, user_disabled: 0 }, manifest: { configured: manifestConfigured, loaded: false, status: manifestConfigured ? 'extension_disabled' : 'not_configured', entry_count: 0, script_count: 0, stylesheet_count: 0, sidecar_count: 0 }, extensions: [], warnings: d.warnings };
|
|
750
|
+
return { manifest: null, assetBase: '', sidecarsById: new Map(), status: { enabled: false, extension_dir_configured: true, extension_dir_valid: false, script_urls: [], stylesheet_urls: [], sidecars: [], counts: { script_urls: 0, stylesheet_urls: 0, sidecars: 0, manifest_extensions: 0, user_disabled: 0 }, manifest: { configured: manifestConfigured, loaded: false, status: manifestConfigured ? 'extension_disabled' : 'not_configured', entry_count: 0, script_count: 0, stylesheet_count: 0, sidecar_count: 0 }, extensions: [], warnings: d.warnings } };
|
|
674
751
|
}
|
|
675
|
-
const [manifest, status] = this.loadManifest(root, d);
|
|
752
|
+
const [manifest, status] = (await this.loadManifest(root, d));
|
|
676
753
|
const consentIds = new Set(Object.keys(state.sidecar_proxy_consents));
|
|
677
754
|
const ext = manifest !== null ? this.extensionState(manifest, disabled, d, consentIds) : { extensions: [], knownIds: new Set(), manifestDisabled: new Set() };
|
|
678
|
-
const urls = manifest !== null ? await this.manifestUrls(manifest, status, disabled, state, d) : { scripts: [], stylesheets: [], sidecars: [] };
|
|
755
|
+
const urls = manifest !== null ? await this.manifestUrls(manifest, status, disabled, state, d) : { scripts: [], stylesheets: [], sidecars: [], sidecarsById: new Map() };
|
|
679
756
|
const scriptUrls = this.envUrls('HERMES_WEBUI_EXTENSION_SCRIPT_URLS', urls.scripts.length ? urls.scripts : null, d);
|
|
680
757
|
const stylesheetUrls = this.envUrls('HERMES_WEBUI_EXTENSION_STYLESHEET_URLS', urls.stylesheets.length ? urls.stylesheets : null, d);
|
|
681
758
|
const publicStatus = Object.fromEntries(Object.entries(status).filter(([k]) => !k.startsWith('_')));
|
|
682
|
-
return {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
759
|
+
return { manifest, assetBase: str(status._asset_base), sidecarsById: urls.sidecarsById, status: {
|
|
760
|
+
enabled: true, extension_dir_configured: true, extension_dir_valid: true, script_urls: scriptUrls, stylesheet_urls: stylesheetUrls, sidecars: urls.sidecars,
|
|
761
|
+
counts: { script_urls: scriptUrls.length, stylesheet_urls: stylesheetUrls.length, sidecars: urls.sidecars.length, manifest_extensions: ext.extensions.length, user_disabled: [...disabled].filter((id) => ext.knownIds.has(id)).length },
|
|
762
|
+
manifest: publicStatus, extensions: ext.extensions, gallery_installed: (await this.loadInstallManifest()).installed, warnings: d.warnings,
|
|
763
|
+
} };
|
|
687
764
|
}
|
|
688
|
-
loaded(root, d) {
|
|
689
|
-
const state = this.loadState(d);
|
|
690
|
-
const [manifest, status] = this.loadManifest(root, d);
|
|
765
|
+
async loaded(root, d) {
|
|
766
|
+
const state = (await this.loadState(d));
|
|
767
|
+
const [manifest, status] = (await this.loadManifest(root, d));
|
|
691
768
|
if (manifest === null || status.loaded !== true)
|
|
692
769
|
throw new ExtensionError('Extension manifest is not loaded', 409);
|
|
693
770
|
return { manifest, state, disabled: new Set(state.disabled_extensions) };
|
|
@@ -702,14 +779,14 @@ export class ExtensionService {
|
|
|
702
779
|
throw new ExtensionError('Invalid extension id', 400);
|
|
703
780
|
if (typeof enabled !== 'boolean')
|
|
704
781
|
throw new ExtensionError('enabled must be a boolean', 400);
|
|
705
|
-
return this.mutateState(async () => this.setEnabledLocked(idRaw.trim(), enabled));
|
|
782
|
+
return this.mutateState(async () => (await this.setEnabledLocked(idRaw.trim(), enabled)));
|
|
706
783
|
}
|
|
707
784
|
async setEnabledLocked(id, enabled) {
|
|
708
785
|
const root = this.root();
|
|
709
786
|
if (!root)
|
|
710
787
|
throw new ExtensionError('Extensions are not configured', 404);
|
|
711
788
|
const d = { warnings: [] };
|
|
712
|
-
const { manifest, state, disabled } = this.loaded(root, d);
|
|
789
|
+
const { manifest, state, disabled } = (await this.loaded(root, d));
|
|
713
790
|
const ext = this.extensionState(manifest, disabled, d, new Set(Object.keys(state.sidecar_proxy_consents)));
|
|
714
791
|
if (!ext.knownIds.has(id))
|
|
715
792
|
throw new ExtensionError('Extension not found', 404);
|
|
@@ -719,7 +796,7 @@ export class ExtensionService {
|
|
|
719
796
|
disabled.delete(id);
|
|
720
797
|
else
|
|
721
798
|
disabled.add(id);
|
|
722
|
-
this.writeState({ disabled_extensions: [...disabled], sidecar_proxy_consents: Object.fromEntries(Object.entries(state.sidecar_proxy_consents).filter(([k]) => ext.knownIds.has(k))) });
|
|
799
|
+
await this.writeState({ disabled_extensions: [...disabled], sidecar_proxy_consents: Object.fromEntries(Object.entries(state.sidecar_proxy_consents).filter(([k]) => ext.knownIds.has(k))) });
|
|
723
800
|
return this.status();
|
|
724
801
|
}
|
|
725
802
|
setConsent(idRaw, approved) {
|
|
@@ -727,14 +804,14 @@ export class ExtensionService {
|
|
|
727
804
|
throw new ExtensionError('Invalid extension id', 400);
|
|
728
805
|
if (typeof approved !== 'boolean')
|
|
729
806
|
throw new ExtensionError('approved must be a boolean', 400);
|
|
730
|
-
return this.mutateState(() => this.setConsentLocked(idRaw.trim(), approved));
|
|
807
|
+
return this.mutateState(async () => (await this.setConsentLocked(idRaw.trim(), approved)));
|
|
731
808
|
}
|
|
732
809
|
async setConsentLocked(id, approved) {
|
|
733
810
|
const root = this.root();
|
|
734
811
|
if (!root)
|
|
735
812
|
throw new ExtensionError('Extensions are not configured', 404);
|
|
736
813
|
const d = { warnings: [] };
|
|
737
|
-
const { manifest, state, disabled } = this.loaded(root, d);
|
|
814
|
+
const { manifest, state, disabled } = (await this.loaded(root, d));
|
|
738
815
|
const consents = { ...state.sidecar_proxy_consents };
|
|
739
816
|
const ext = this.extensionState(manifest, disabled, d, new Set(Object.keys(consents)));
|
|
740
817
|
if (!ext.knownIds.has(id))
|
|
@@ -752,7 +829,7 @@ export class ExtensionService {
|
|
|
752
829
|
}
|
|
753
830
|
else
|
|
754
831
|
Reflect.deleteProperty(consents, id);
|
|
755
|
-
this.writeState({ disabled_extensions: [...disabled], sidecar_proxy_consents: Object.fromEntries(Object.entries(consents).filter(([k]) => ext.knownIds.has(k))) });
|
|
832
|
+
await this.writeState({ disabled_extensions: [...disabled], sidecar_proxy_consents: Object.fromEntries(Object.entries(consents).filter(([k]) => ext.knownIds.has(k))) });
|
|
756
833
|
return this.status();
|
|
757
834
|
}
|
|
758
835
|
/** Python `resolve_extension_sidecar_proxy_target` (legacy proxy auth only). */
|
|
@@ -767,7 +844,7 @@ export class ExtensionService {
|
|
|
767
844
|
throw new ExtensionError('Extensions are not configured', 404);
|
|
768
845
|
const id = idRaw.trim();
|
|
769
846
|
const d = { warnings: [] };
|
|
770
|
-
const { manifest, state, disabled } = this.loaded(root, d);
|
|
847
|
+
const { manifest, state, disabled } = (await this.loaded(root, d));
|
|
771
848
|
const ext = this.extensionState(manifest, disabled, d, new Set(Object.keys(state.sidecar_proxy_consents)));
|
|
772
849
|
if (!ext.knownIds.has(id))
|
|
773
850
|
throw new ExtensionError('Extension not found', 404);
|
|
@@ -833,233 +910,219 @@ export class ExtensionService {
|
|
|
833
910
|
}
|
|
834
911
|
}
|
|
835
912
|
async install(idRaw, downloadUrl, sha256) {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
host = new URL(downloadUrl).hostname;
|
|
844
|
-
}
|
|
845
|
-
catch {
|
|
846
|
-
host = '';
|
|
847
|
-
}
|
|
848
|
-
if (!ALLOWED_DOWNLOAD_HOSTS.has(host))
|
|
849
|
-
throw new ExtensionError('Invalid download URL');
|
|
850
|
-
if (typeof sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(sha256))
|
|
851
|
-
throw new ExtensionError('Invalid sha256');
|
|
852
|
-
const root = this.writableRoot();
|
|
853
|
-
if (!root)
|
|
854
|
-
throw new ExtensionError('Extensions not configured', 404);
|
|
855
|
-
let raw;
|
|
856
|
-
try {
|
|
857
|
-
const res = await this.galleryFetch(downloadUrl, 30_000);
|
|
858
|
-
if (!res.ok)
|
|
859
|
-
throw new Error(`download ${String(res.status)}`);
|
|
860
|
-
const capped = await readCapped(res, MAX_ZIP_BYTES);
|
|
861
|
-
if (!capped)
|
|
862
|
-
throw new ExtensionError('Download too large');
|
|
863
|
-
raw = capped;
|
|
864
|
-
}
|
|
865
|
-
catch (error) {
|
|
866
|
-
throw new ExtensionError(error instanceof ExtensionError ? error.message : 'Download failed', error instanceof ExtensionError ? error.status : 502);
|
|
867
|
-
}
|
|
868
|
-
if (createHash('sha256').update(raw).digest('hex') !== sha256)
|
|
869
|
-
throw new ExtensionError('SHA-256 mismatch');
|
|
870
|
-
let entries;
|
|
871
|
-
try {
|
|
872
|
-
entries = readZip(raw);
|
|
873
|
-
}
|
|
874
|
-
catch {
|
|
875
|
-
throw new ExtensionError('Invalid zip archive');
|
|
876
|
-
}
|
|
877
|
-
const files = entries.filter((e) => e.name && !e.isDir);
|
|
878
|
-
if (files.reduce((acc, e) => acc + e.size, 0) > MAX_ZIP_BYTES * 10)
|
|
879
|
-
throw new ExtensionError('Archive uncompressed size exceeds limit');
|
|
880
|
-
if (files.length > 1024)
|
|
881
|
-
throw new ExtensionError('Archive contains too many files');
|
|
882
|
-
const prefix = files.every((e) => e.name.startsWith(`${id}/`)) ? `${id}/` : '';
|
|
883
|
-
const stripped = (name) => (prefix && name.startsWith(prefix) ? name.slice(prefix.length) : name);
|
|
884
|
-
const anchor = resolvePathLikePython(root);
|
|
885
|
-
const extDir = resolve(anchor, id);
|
|
886
|
-
for (const e of files) {
|
|
887
|
-
const decoded = fullyUnquote(stripped(e.name));
|
|
888
|
-
if (!decoded || !isSafeArchiveMember(decoded))
|
|
889
|
-
throw new ExtensionError('Unsafe archive member');
|
|
890
|
-
const target = resolve(extDir, decoded);
|
|
891
|
-
if (!target.startsWith(anchor + sep) || !target.startsWith(extDir + sep))
|
|
892
|
-
throw new ExtensionError('Zip-slip detected');
|
|
893
|
-
}
|
|
894
|
-
let version = 'unknown';
|
|
895
|
-
for (const vfile of ['extension.json', 'manifest.json']) {
|
|
896
|
-
const entry = entries.find((e) => e.name === prefix + vfile);
|
|
897
|
-
if (!entry)
|
|
898
|
-
continue;
|
|
913
|
+
return await this.mutateState(async () => {
|
|
914
|
+
if (!validId(idRaw))
|
|
915
|
+
throw new ExtensionError('Invalid extension id');
|
|
916
|
+
const id = idRaw.trim();
|
|
917
|
+
if (typeof downloadUrl !== 'string' || !downloadUrl.startsWith('https://'))
|
|
918
|
+
throw new ExtensionError('Invalid download URL');
|
|
919
|
+
let host = '';
|
|
899
920
|
try {
|
|
900
|
-
|
|
901
|
-
if (isDict(m) && typeof m.version === 'string') {
|
|
902
|
-
version = m.version;
|
|
903
|
-
break;
|
|
904
|
-
}
|
|
921
|
+
host = new URL(downloadUrl).hostname;
|
|
905
922
|
}
|
|
906
|
-
catch {
|
|
907
|
-
|
|
908
|
-
try {
|
|
909
|
-
if (lstatSync(extDir).isSymbolicLink())
|
|
910
|
-
throw new ExtensionError('Extension directory is a symlink', 400);
|
|
911
|
-
}
|
|
912
|
-
catch (error) {
|
|
913
|
-
if (error instanceof ExtensionError)
|
|
914
|
-
throw error;
|
|
915
|
-
}
|
|
916
|
-
try {
|
|
917
|
-
makeAnchoredDir(anchor, extDir);
|
|
918
|
-
}
|
|
919
|
-
catch {
|
|
920
|
-
throw new ExtensionError('Extension directory is a symlink', 400);
|
|
921
|
-
}
|
|
922
|
-
const written = [];
|
|
923
|
-
const rollback = () => {
|
|
924
|
-
for (const p of written) {
|
|
925
|
-
try {
|
|
926
|
-
unlinkAnchored(anchor, p);
|
|
927
|
-
}
|
|
928
|
-
catch { /* gone */ }
|
|
923
|
+
catch {
|
|
924
|
+
host = '';
|
|
929
925
|
}
|
|
926
|
+
if (!ALLOWED_DOWNLOAD_HOSTS.has(host))
|
|
927
|
+
throw new ExtensionError('Invalid download URL');
|
|
928
|
+
if (typeof sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(sha256))
|
|
929
|
+
throw new ExtensionError('Invalid sha256');
|
|
930
|
+
const root = this.writableRoot();
|
|
931
|
+
if (!root)
|
|
932
|
+
throw new ExtensionError('Extensions not configured', 404);
|
|
933
|
+
let raw;
|
|
930
934
|
try {
|
|
931
|
-
|
|
935
|
+
const res = await this.galleryFetch(downloadUrl, 30_000);
|
|
936
|
+
if (!res.ok)
|
|
937
|
+
throw new Error(`download ${String(res.status)}`);
|
|
938
|
+
const capped = await readCapped(res, MAX_ZIP_BYTES);
|
|
939
|
+
if (!capped)
|
|
940
|
+
throw new ExtensionError('Download too large');
|
|
941
|
+
raw = capped;
|
|
932
942
|
}
|
|
933
|
-
catch {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
943
|
+
catch (error) {
|
|
944
|
+
throw new ExtensionError(error instanceof ExtensionError ? error.message : 'Download failed', error instanceof ExtensionError ? error.status : 502);
|
|
945
|
+
}
|
|
946
|
+
if (createHash('sha256').update(raw).digest('hex') !== sha256)
|
|
947
|
+
throw new ExtensionError('SHA-256 mismatch');
|
|
948
|
+
let entries;
|
|
949
|
+
try {
|
|
950
|
+
entries = readZip(raw);
|
|
951
|
+
}
|
|
952
|
+
catch {
|
|
953
|
+
throw new ExtensionError('Invalid zip archive');
|
|
954
|
+
}
|
|
955
|
+
const files = entries.filter((e) => e.name && !e.isDir);
|
|
956
|
+
if (files.reduce((acc, e) => acc + e.size, 0) > MAX_ZIP_BYTES * 10)
|
|
957
|
+
throw new ExtensionError('Archive uncompressed size exceeds limit');
|
|
958
|
+
if (files.length > 1024)
|
|
959
|
+
throw new ExtensionError('Archive contains too many files');
|
|
960
|
+
const prefix = files.every((e) => e.name.startsWith(`${id}/`)) ? `${id}/` : '';
|
|
961
|
+
const stripped = (name) => (prefix && name.startsWith(prefix) ? name.slice(prefix.length) : name);
|
|
962
|
+
const anchor = resolvePathLikePython(root);
|
|
963
|
+
const extDir = resolve(anchor, id);
|
|
938
964
|
for (const e of files) {
|
|
939
|
-
const
|
|
940
|
-
|
|
965
|
+
const decoded = fullyUnquote(stripped(e.name));
|
|
966
|
+
if (!decoded || !isSafeArchiveMember(decoded))
|
|
967
|
+
throw new ExtensionError('Unsafe archive member');
|
|
968
|
+
const target = resolve(extDir, decoded);
|
|
969
|
+
if (!target.startsWith(anchor + sep) || !target.startsWith(extDir + sep))
|
|
970
|
+
throw new ExtensionError('Zip-slip detected');
|
|
971
|
+
}
|
|
972
|
+
let version = 'unknown';
|
|
973
|
+
for (const vfile of ['extension.json', 'manifest.json']) {
|
|
974
|
+
const entry = entries.find((e) => e.name === prefix + vfile);
|
|
975
|
+
if (!entry)
|
|
976
|
+
continue;
|
|
941
977
|
try {
|
|
942
|
-
|
|
978
|
+
const m = JSON.parse(entry.read().toString('utf8'));
|
|
979
|
+
if (isDict(m) && typeof m.version === 'string') {
|
|
980
|
+
version = m.version;
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
943
983
|
}
|
|
944
|
-
catch
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
984
|
+
catch { /* ignore */ }
|
|
985
|
+
}
|
|
986
|
+
try {
|
|
987
|
+
if (lstatSync(extDir).isSymbolicLink())
|
|
988
|
+
throw new ExtensionError('Extension directory is a symlink', 400);
|
|
989
|
+
}
|
|
990
|
+
catch (error) {
|
|
991
|
+
if (error instanceof ExtensionError)
|
|
992
|
+
throw error;
|
|
993
|
+
}
|
|
994
|
+
try {
|
|
995
|
+
makeAnchoredDir(anchor, extDir);
|
|
996
|
+
}
|
|
997
|
+
catch {
|
|
998
|
+
throw new ExtensionError('Extension directory is a symlink', 400);
|
|
999
|
+
}
|
|
1000
|
+
const written = [];
|
|
1001
|
+
const rollback = () => {
|
|
1002
|
+
for (const p of written) {
|
|
1003
|
+
try {
|
|
1004
|
+
unlinkAnchored(anchor, p);
|
|
1005
|
+
}
|
|
1006
|
+
catch { /* gone */ }
|
|
948
1007
|
}
|
|
949
1008
|
try {
|
|
950
|
-
|
|
1009
|
+
rmdirAnchored(anchor, extDir);
|
|
951
1010
|
}
|
|
952
|
-
|
|
953
|
-
|
|
1011
|
+
catch { /* not empty, or gone */ }
|
|
1012
|
+
};
|
|
1013
|
+
try {
|
|
1014
|
+
// Every destination is created or truncated through the anchored walk from the extension root, so a symlink
|
|
1015
|
+
// already inside the extension (or a parent swapped mid-extraction) can never redirect a write outside it.
|
|
1016
|
+
for (const e of files) {
|
|
1017
|
+
const dest = resolve(extDir, fullyUnquote(stripped(e.name)));
|
|
1018
|
+
let fd;
|
|
1019
|
+
try {
|
|
1020
|
+
fd = openAnchoredCreateFd(anchor, dest);
|
|
1021
|
+
}
|
|
1022
|
+
catch (error) {
|
|
1023
|
+
if (!(error instanceof FileExistsError))
|
|
1024
|
+
throw error;
|
|
1025
|
+
fd = openAnchoredWriteFd(anchor, dest);
|
|
1026
|
+
}
|
|
1027
|
+
try {
|
|
1028
|
+
writeFileSync(fd, e.read());
|
|
1029
|
+
}
|
|
1030
|
+
finally {
|
|
1031
|
+
closeSync(fd);
|
|
1032
|
+
}
|
|
1033
|
+
written.push(dest);
|
|
954
1034
|
}
|
|
955
|
-
written.push(dest);
|
|
956
1035
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
throw new ExtensionError('Extraction failed', 500);
|
|
961
|
-
}
|
|
962
|
-
try {
|
|
963
|
-
const manifest = this.loadInstallManifest();
|
|
964
|
-
const record = { version, files: written.map((p) => relative(extDir, p).split(sep).join('/')), installed_at: new Date().toISOString() };
|
|
965
|
-
const next = { version: 1, installed: { ...manifest.installed, [id]: record } };
|
|
966
|
-
if (Buffer.byteLength(JSON.stringify(next, null, 2)) > MAX_INSTALL_MANIFEST_BYTES)
|
|
967
|
-
throw new ExtensionError('Install manifest would exceed size limit');
|
|
968
|
-
this.writeInstallManifest(next);
|
|
969
|
-
}
|
|
970
|
-
catch (error) {
|
|
971
|
-
rollback();
|
|
972
|
-
throw error instanceof ExtensionError ? error : new ExtensionError('Failed to record install', 500);
|
|
973
|
-
}
|
|
974
|
-
return { installed: true, id, version };
|
|
975
|
-
}
|
|
976
|
-
uninstall(idRaw) {
|
|
977
|
-
if (!validId(idRaw))
|
|
978
|
-
throw new ExtensionError('Invalid extension id');
|
|
979
|
-
const id = idRaw.trim();
|
|
980
|
-
const root = this.root();
|
|
981
|
-
if (!root)
|
|
982
|
-
throw new ExtensionError('Extensions not configured', 404);
|
|
983
|
-
const manifest = this.loadInstallManifest();
|
|
984
|
-
const entry = manifest.installed[id];
|
|
985
|
-
if (!entry)
|
|
986
|
-
throw new ExtensionError('Extension not installed', 404);
|
|
987
|
-
const anchor = resolvePathLikePython(root);
|
|
988
|
-
const extDir = resolve(anchor, id);
|
|
989
|
-
for (const rel of entry.files) {
|
|
990
|
-
if (!isSafeArchiveMember(rel))
|
|
991
|
-
continue;
|
|
992
|
-
const target = resolve(extDir, rel);
|
|
993
|
-
if (!target.startsWith(extDir + sep))
|
|
994
|
-
continue;
|
|
995
|
-
try {
|
|
996
|
-
unlinkAnchored(anchor, target);
|
|
1036
|
+
catch {
|
|
1037
|
+
rollback();
|
|
1038
|
+
throw new ExtensionError('Extraction failed', 500);
|
|
997
1039
|
}
|
|
998
|
-
catch { /* gone */ }
|
|
999
|
-
}
|
|
1000
|
-
// Prune now-empty directories, enumerating and removing each through the anchored walk so a directory replaced by
|
|
1001
|
-
// a symlink is skipped rather than followed into its target.
|
|
1002
|
-
const prune = (dir) => {
|
|
1003
|
-
let subdirs;
|
|
1004
1040
|
try {
|
|
1005
|
-
|
|
1041
|
+
const manifest = (await this.loadInstallManifest());
|
|
1042
|
+
const record = { version, files: written.map((p) => relative(extDir, p).split(sep).join('/')), installed_at: new Date().toISOString() };
|
|
1043
|
+
const next = { version: 1, installed: { ...manifest.installed, [id]: record } };
|
|
1044
|
+
if (Buffer.byteLength(JSON.stringify(next, null, 2)) > MAX_INSTALL_MANIFEST_BYTES)
|
|
1045
|
+
throw new ExtensionError('Install manifest would exceed size limit');
|
|
1046
|
+
await this.writeInstallManifest(next);
|
|
1006
1047
|
}
|
|
1007
|
-
catch {
|
|
1008
|
-
|
|
1048
|
+
catch (error) {
|
|
1049
|
+
rollback();
|
|
1050
|
+
throw error instanceof ExtensionError ? error : new ExtensionError('Failed to record install', 500);
|
|
1009
1051
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1052
|
+
return { installed: true, id, version };
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
async uninstall(idRaw) {
|
|
1056
|
+
return await this.mutateState(async () => {
|
|
1057
|
+
if (!validId(idRaw))
|
|
1058
|
+
throw new ExtensionError('Invalid extension id');
|
|
1059
|
+
const id = idRaw.trim();
|
|
1060
|
+
const root = this.root();
|
|
1061
|
+
if (!root)
|
|
1062
|
+
throw new ExtensionError('Extensions not configured', 404);
|
|
1063
|
+
const manifest = (await this.loadInstallManifest());
|
|
1064
|
+
const entry = manifest.installed[id];
|
|
1065
|
+
if (!entry)
|
|
1066
|
+
throw new ExtensionError('Extension not installed', 404);
|
|
1067
|
+
const anchor = resolvePathLikePython(root);
|
|
1068
|
+
const extDir = resolve(anchor, id);
|
|
1069
|
+
for (const rel of entry.files) {
|
|
1070
|
+
if (!isSafeArchiveMember(rel))
|
|
1071
|
+
continue;
|
|
1072
|
+
const target = resolve(extDir, rel);
|
|
1073
|
+
if (!target.startsWith(extDir + sep))
|
|
1074
|
+
continue;
|
|
1075
|
+
try {
|
|
1076
|
+
unlinkAnchored(anchor, target);
|
|
1077
|
+
}
|
|
1078
|
+
catch { /* gone */ }
|
|
1014
1079
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1080
|
+
// Prune now-empty directories, enumerating and removing each through the anchored walk so a directory replaced by
|
|
1081
|
+
// a symlink is skipped rather than followed into its target.
|
|
1082
|
+
const prune = (dir) => {
|
|
1083
|
+
let subdirs;
|
|
1084
|
+
try {
|
|
1085
|
+
subdirs = withAnchoredDir(anchor, dir, (child) => readdirSync(child('.'), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name));
|
|
1086
|
+
}
|
|
1087
|
+
catch {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
for (const name of subdirs)
|
|
1091
|
+
prune(join(dir, name));
|
|
1092
|
+
try {
|
|
1093
|
+
rmdirAnchored(anchor, dir);
|
|
1094
|
+
}
|
|
1095
|
+
catch { /* not empty, or gone */ }
|
|
1096
|
+
};
|
|
1097
|
+
prune(extDir);
|
|
1098
|
+
Reflect.deleteProperty(manifest.installed, id);
|
|
1099
|
+
await this.writeInstallManifest(manifest);
|
|
1100
|
+
return { uninstalled: true, id };
|
|
1101
|
+
});
|
|
1021
1102
|
}
|
|
1022
1103
|
/** Python `serve_extension_static`: bytes, MIME, sandbox CSP for HTML; null means 404. */
|
|
1023
1104
|
staticFile(rel) {
|
|
1024
1105
|
const root = this.root();
|
|
1025
|
-
|
|
1106
|
+
const found = root ? readContainedFile(root, rel) : null;
|
|
1107
|
+
if (!found)
|
|
1026
1108
|
return null;
|
|
1027
|
-
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
if (!file.startsWith(anchor + sep))
|
|
1031
|
-
return null;
|
|
1032
|
-
// Python `(root / rel).resolve()`: a symlinked asset is served when its target stays inside the root, never otherwise.
|
|
1033
|
-
const real = resolvePathLikePython(file);
|
|
1034
|
-
if (real !== file) {
|
|
1035
|
-
if (!real.startsWith(anchor + sep))
|
|
1036
|
-
return null;
|
|
1037
|
-
file = real;
|
|
1038
|
-
}
|
|
1039
|
-
let fd;
|
|
1040
|
-
try {
|
|
1041
|
-
fd = openAnchoredFd(anchor, file, { wantDir: false });
|
|
1042
|
-
}
|
|
1043
|
-
catch {
|
|
1044
|
-
return null;
|
|
1045
|
-
}
|
|
1046
|
-
try {
|
|
1047
|
-
if (!fstatSync(fd).isFile())
|
|
1048
|
-
return null;
|
|
1049
|
-
const ext = file.slice(file.lastIndexOf('.') + 1).toLowerCase();
|
|
1050
|
-
const ct = EXTENSION_MIME[ext] ?? 'text/plain';
|
|
1051
|
-
return { body: readFileSync(fd), contentType: TEXT_MIME.has(ct) ? `${ct}; charset=utf-8` : ct, html: ct === 'text/html' };
|
|
1052
|
-
}
|
|
1053
|
-
catch {
|
|
1054
|
-
return null;
|
|
1055
|
-
}
|
|
1056
|
-
finally {
|
|
1057
|
-
closeSync(fd);
|
|
1058
|
-
}
|
|
1109
|
+
const ext = found.file.slice(found.file.lastIndexOf('.') + 1).toLowerCase();
|
|
1110
|
+
const ct = EXTENSION_MIME[ext] ?? 'text/plain';
|
|
1111
|
+
return { body: found.body, contentType: TEXT_MIME.has(ct) ? `${ct}; charset=utf-8` : ct, html: ct === 'text/html' };
|
|
1059
1112
|
}
|
|
1060
|
-
/** Python `api/extension_manifests.build_manifests`
|
|
1113
|
+
/** Python `api/extension_manifests.build_manifests` extension entries (the manifests route appends dashboard plugins). */
|
|
1061
1114
|
async manifests() {
|
|
1062
|
-
const status = await this.
|
|
1115
|
+
const { status, manifest, assetBase, sidecarsById } = await this.snapshot();
|
|
1116
|
+
// Status rows carry only enable state; panel, nav, capabilities, assets, and metadata come from the first manifest entry per id.
|
|
1117
|
+
const rawById = new Map();
|
|
1118
|
+
for (const e of manifestEntries(manifest)) {
|
|
1119
|
+
const id = entryText(e, 'id');
|
|
1120
|
+
if (!rawById.has(id))
|
|
1121
|
+
rawById.set(id, e);
|
|
1122
|
+
}
|
|
1123
|
+
// Gallery rows are the entries the gallery loader read from their own install folder; `uninstall` accepts exactly the
|
|
1124
|
+
// ids in the install manifest, so a configured entry that shares an installed id never offers Uninstall.
|
|
1125
|
+
const installed = isDict(status.gallery_installed) ? status.gallery_installed : {};
|
|
1063
1126
|
const manifests = [];
|
|
1064
1127
|
const seen = new Set();
|
|
1065
1128
|
const ID = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
@@ -1068,27 +1131,42 @@ export class ExtensionService {
|
|
|
1068
1131
|
if (!ID.test(id) || seen.has(id))
|
|
1069
1132
|
continue;
|
|
1070
1133
|
seen.add(id);
|
|
1071
|
-
const rawManifest =
|
|
1134
|
+
const rawManifest = rawById.get(str(entry.id)) ?? {};
|
|
1072
1135
|
const warnings = [];
|
|
1073
1136
|
let panel = null;
|
|
1074
1137
|
const rawPanel = rawManifest.panel;
|
|
1075
1138
|
if (rawPanel !== null && rawPanel !== undefined) {
|
|
1076
|
-
|
|
1139
|
+
// Like scripts, a panel path is relative to the manifest's folder; gallery entries were rebased onto `<id>/`.
|
|
1140
|
+
const url = typeof rawPanel === 'string' ? `extensions/${assetBase ? `${assetBase}/` : ''}${rawPanel.replace(/^(\.\/)+/, '')}` : '';
|
|
1141
|
+
if (typeof rawPanel !== 'string' || !/^[A-Za-z0-9._/-]{1,300}$/.test(rawPanel) || !isSafeRelativePath(url) || url.length > 400)
|
|
1077
1142
|
warnings.push('panel_path_rejected');
|
|
1078
1143
|
else if (!rawPanel.toLowerCase().endsWith('.html'))
|
|
1079
1144
|
warnings.push('panel_not_html');
|
|
1080
1145
|
else
|
|
1081
|
-
panel =
|
|
1146
|
+
panel = url;
|
|
1082
1147
|
}
|
|
1083
|
-
const scripts = Array.isArray(
|
|
1084
|
-
const stylesheets = Array.isArray(
|
|
1085
|
-
const
|
|
1148
|
+
const scripts = Array.isArray(rawManifest.scripts) ? rawManifest.scripts : [];
|
|
1149
|
+
const stylesheets = Array.isArray(rawManifest.stylesheets) ? rawManifest.stylesheets : [];
|
|
1150
|
+
const theme = themeFromEntry(rawManifest.theme, id);
|
|
1151
|
+
if (!theme && rawManifest.theme != null)
|
|
1152
|
+
warnings.push('theme_rejected');
|
|
1153
|
+
// A skin is the one declaration that works without a panel, so it keeps an entry with leftover assets runnable.
|
|
1154
|
+
const legacy = Boolean((scripts.length || stylesheets.length) && !panel && !theme);
|
|
1086
1155
|
const rawNav = isDict(rawManifest.nav) ? rawManifest.nav : {};
|
|
1087
|
-
const nav = panel ? { label: text(rawNav.label, 40) || text(entry.name
|
|
1088
|
-
const permissions = isDict(
|
|
1089
|
-
const
|
|
1156
|
+
const nav = panel ? { label: text(rawNav.label, 40) || text(entry.name, 40) || id, ...(text(rawNav.icon, 40) ? { icon: text(rawNav.icon, 40) } : {}) } : null;
|
|
1157
|
+
const permissions = isDict(rawManifest.permissions) ? Object.fromEntries(Object.entries(rawManifest.permissions).filter(([k]) => k.length <= 64).map(([k, v]) => [k, Boolean(v)])) : {};
|
|
1158
|
+
const tts = ttsFromEntry(rawManifest.tts);
|
|
1159
|
+
if (!tts && rawManifest.tts != null)
|
|
1160
|
+
warnings.push('tts_rejected');
|
|
1161
|
+
// The sidecar the proxy would serve: same record, availability, and saved consent `proxyTarget` enforces
|
|
1162
|
+
// (which refuses token-v1 sidecars in this release).
|
|
1163
|
+
const record = sidecarsById.get(str(entry.id));
|
|
1164
|
+
const declared = isDict(record?.sidecar) && record.sidecar.proxy_auth !== 'token-v1' ? record.sidecar : null;
|
|
1165
|
+
const proxy = isDict(record?.proxy) ? record.proxy : null;
|
|
1166
|
+
const healthPath = declared ? str(declared.health_path) : '';
|
|
1167
|
+
const sidecar = declared && proxy?.available === true ? { origin: str(declared.origin), ...(healthPath.length <= 200 ? { health_path: healthPath } : {}), consented: proxy.consented === true } : null;
|
|
1090
1168
|
const caps = (Array.isArray(rawManifest.capabilities) ? rawManifest.capabilities : []).filter((c) => typeof c === 'string' && ['settings', 'storage', 'sidecar', 'lifecycle', 'theme', 'tts', 'navigate', 'toast', 'session'].includes(c));
|
|
1091
|
-
manifests.push({ id, name: text(entry.name
|
|
1169
|
+
manifests.push({ id, name: text(entry.name, 80) || id, version: text(rawManifest.version, 40), description: text(rawManifest.description, 300), source: galleryFolders.get(rawManifest) === id && Object.hasOwn(installed, id) ? 'gallery' : 'manifest', enabled: entry.effective_enabled === true && !legacy, can_toggle: entry.can_toggle !== false && !legacy, panel, nav, capabilities: [...new Set(caps)], permissions, settings_schema: Array.isArray(entry.settings_schema) ? entry.settings_schema : [], theme, tts, sidecar, legacy_injection: legacy, warnings });
|
|
1092
1170
|
}
|
|
1093
1171
|
return { protocol_version: 1, manifests };
|
|
1094
1172
|
}
|