synabun 2.0.10 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +37 -4
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +228 -0
- package/lib/virustotal.mjs +192 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +16 -10
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +20 -62
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +639 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3463 -200
- package/neural-interface/i18n/pt-BR.json +3411 -151
- package/neural-interface/lib/assistant-api.js +127 -28
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +120 -54
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
- package/neural-interface/lib/assistant-catalog.js +6 -7
- package/neural-interface/lib/assistant-dispatch.js +871 -156
- package/neural-interface/lib/assistant-envelope.js +3 -1
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +13 -8
- package/neural-interface/lib/assistant-runtime.js +56 -18
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-worker-app-server.js +172 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +68 -68
- package/neural-interface/lib/exec-loop-command.js +4 -8
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +547 -47
- package/neural-interface/lib/native-loop-runtime.js +268 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +17 -34
- package/neural-interface/lib/opencode-history.js +48 -218
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +44 -0
- package/neural-interface/lib/opencode-protocol.js +249 -112
- package/neural-interface/lib/opencode-runtime-config.js +32 -22
- package/neural-interface/lib/opencode-serve-records.js +39 -21
- package/neural-interface/lib/opencode-v2-client.js +48 -153
- package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +73 -37
- package/neural-interface/lib/opencode2-proxy.js +23 -63
- package/neural-interface/lib/opencode2-translate.js +51 -2
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/peer-identity.js +763 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +17 -30
- package/neural-interface/lib/session-title-generator.js +58 -36
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +294 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +200 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +18 -12
- package/neural-interface/package.json +2 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +35 -19
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
- package/neural-interface/public/shared/assistant/asst-control.js +157 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +401 -77
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +11 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +89 -19
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +366 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-update-view.js +20 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/html-shell.js +9 -11
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +195 -61
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +3 -1
- package/neural-interface/public/shared/settings/settings-ia.js +6 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1525 -1779
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-automation-studio.js +91 -128
- package/neural-interface/public/shared/ui-claude-panel.js +77 -5
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +15 -3
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-menubar.js +8 -18
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
- package/neural-interface/public/shared/ui-sessions.js +5 -3
- package/neural-interface/public/shared/ui-settings.js +594 -462
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +33 -2830
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +15 -5
- package/neural-interface/public/shared/ui-terminal.js +124 -180
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- package/neural-interface/public/shared/ui-trash.js +6 -4
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -509
- package/neural-interface/public/shared/ui-update.js +38 -2
- package/neural-interface/public/shared/ui-whiteboard.js +311 -58
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +32 -1
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +69 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +32 -0
- package/neural-interface/server.js +3934 -2454
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +4 -4
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { migrateStoredTools } from './tool-list-storage.js';
|
|
2
|
+
import { configureToolPickerTranslation, toolPickerHTML, readToolPicker, updateToolPicker, taskToolHost as strictToolHost, toolWord } from './tool-list-picker.js';
|
|
1
3
|
// SynaBun Neural Interface — the Schedules window.
|
|
2
4
|
// A rail of views (Up next, All, Needs attention, Paused, Timers, Activity) and of groups, one
|
|
3
5
|
// list of schedules with their details on demand, and the editors in place of the list. What a
|
|
@@ -5,6 +7,8 @@
|
|
|
5
7
|
|
|
6
8
|
import { emit, on } from './state.js';
|
|
7
9
|
import { storage } from './storage.js';
|
|
10
|
+
import { frameRect, clampBox } from './app-frame.js';
|
|
11
|
+
import { t, tp, getLocale } from './i18n.js';
|
|
8
12
|
import { KEYS } from './constants.js';
|
|
9
13
|
import { isGuest, hasPermission, showGuestToast } from './ui-sync.js';
|
|
10
14
|
import { registerLaunchPrefetch } from './launch-prefetch.js';
|
|
@@ -17,10 +21,10 @@ import {
|
|
|
17
21
|
import {
|
|
18
22
|
CLI_PROFILES,
|
|
19
23
|
ensureModelsForProfile,
|
|
20
|
-
|
|
24
|
+
fetchToolCatalog,
|
|
21
25
|
fetchCodexAccounts,
|
|
22
26
|
getCachedCodexAccounts,
|
|
23
|
-
|
|
27
|
+
getCachedToolCatalog,
|
|
24
28
|
getEffortLevelsForProfile,
|
|
25
29
|
getModelsForProfile,
|
|
26
30
|
formatModelOptionLabel,
|
|
@@ -64,6 +68,7 @@ import {
|
|
|
64
68
|
describeCron,
|
|
65
69
|
formatAgo,
|
|
66
70
|
formatCountdown,
|
|
71
|
+
fullDateTime,
|
|
67
72
|
groupIdOf,
|
|
68
73
|
groupSummary,
|
|
69
74
|
inScope,
|
|
@@ -73,8 +78,10 @@ import {
|
|
|
73
78
|
matchesQuery,
|
|
74
79
|
oneLine,
|
|
75
80
|
scopeCounts,
|
|
81
|
+
setSchedulesViewTranslator,
|
|
76
82
|
shortWhen,
|
|
77
83
|
sortSchedules,
|
|
84
|
+
weekdayName,
|
|
78
85
|
whenLabel,
|
|
79
86
|
} from './schedules-view.js';
|
|
80
87
|
import {
|
|
@@ -93,6 +100,7 @@ const LAYOUT_KEY = KEYS.PANEL_PREFIX + 'schedules-window';
|
|
|
93
100
|
const SCOPE_KEY = 'sched-view-scope';
|
|
94
101
|
const SORT_KEY = 'sched-view-sort';
|
|
95
102
|
const COLLAPSED_KEY = 'sched-collapsed-groups';
|
|
103
|
+
const REOPEN_KEY = 'synabun-schedules-reopen'; // sessionStorage, for one reload
|
|
96
104
|
const DEFAULT_GROUP_COLOR = '#4fc3f7';
|
|
97
105
|
|
|
98
106
|
let _panel = null;
|
|
@@ -144,7 +152,7 @@ let _launchContextMode = _storedLaunchSelection.contextMode || 'default';
|
|
|
144
152
|
storage.setItem('as-launch-model', _launchModel || '');
|
|
145
153
|
storage.setItem('as-launch-context-mode', _launchContextMode);
|
|
146
154
|
let _launchEffort = storage.getItem('as-launch-effort') || 'off';
|
|
147
|
-
let
|
|
155
|
+
let _launchTools = (() => { try { return JSON.parse(storage.getItem('as-launch-tools') || 'null'); } catch { return null; } })();
|
|
148
156
|
let _launchAccount = storage.getItem('as-launch-account') || 'default'; // Codex ChatGPT account (CODEX_HOME)
|
|
149
157
|
let _codexAccountsRequested = false; // lazy-fetch guard for account selects
|
|
150
158
|
|
|
@@ -183,41 +191,60 @@ const ICONS = {
|
|
|
183
191
|
};
|
|
184
192
|
const RESULT_ICON = { ok: ICONS.ok, failed: ICONS.failed, started: ICONS.started, stopped: ICONS.stopped, interrupted: ICONS.alert, never: ICONS.never, other: ICONS.never };
|
|
185
193
|
|
|
194
|
+
// Words: every string the window shows is a `schedules.*` key of i18n/<locale>.json; the ones
|
|
195
|
+
// schedules-view.js decides read through the translator set here. What the user named (schedules,
|
|
196
|
+
// groups, automations) and what a provider named (models, accounts, CLIs) show as they are.
|
|
197
|
+
setSchedulesViewTranslator((key, params) => t(`schedules.view.${key}`, params), getLocale);
|
|
198
|
+
// The same strings, escaped for markup.
|
|
199
|
+
const te = (key, params) => esc(t(key, params));
|
|
200
|
+
const tpe = (key, count, params) => esc(tp(key, count, params));
|
|
201
|
+
|
|
186
202
|
const VIEWS = {
|
|
187
|
-
upcoming: {
|
|
188
|
-
all: {
|
|
189
|
-
attention: {
|
|
190
|
-
paused: {
|
|
191
|
-
timers: {
|
|
192
|
-
activity: {
|
|
203
|
+
upcoming: { icon: ICONS.next },
|
|
204
|
+
all: { icon: ICONS.list },
|
|
205
|
+
attention: { icon: ICONS.alert },
|
|
206
|
+
paused: { icon: ICONS.pause },
|
|
207
|
+
timers: { icon: ICONS.timer },
|
|
208
|
+
activity: { icon: ICONS.activity },
|
|
193
209
|
};
|
|
210
|
+
const viewLabel = (scope) => t(`schedules.views.${scope}`);
|
|
194
211
|
|
|
212
|
+
// A preset is a phrase (`key`) or a time of day, which reads the same in every language.
|
|
195
213
|
const CRON_PRESET_GROUPS = [
|
|
196
|
-
{ group: '
|
|
197
|
-
{
|
|
198
|
-
{
|
|
199
|
-
{
|
|
200
|
-
{
|
|
201
|
-
{
|
|
214
|
+
{ group: 'frequency', presets: [
|
|
215
|
+
{ key: 'every15min', cron: '*/15 * * * *' },
|
|
216
|
+
{ key: 'every30min', cron: '*/30 * * * *' },
|
|
217
|
+
{ key: 'everyHour', cron: '0 * * * *' },
|
|
218
|
+
{ key: 'every2hours', cron: '0 */2 * * *' },
|
|
219
|
+
{ key: 'every6hours', cron: '0 */6 * * *' },
|
|
202
220
|
] },
|
|
203
|
-
{ group: '
|
|
204
|
-
{
|
|
205
|
-
{
|
|
206
|
-
{
|
|
207
|
-
{
|
|
208
|
-
{
|
|
221
|
+
{ group: 'daily', presets: [
|
|
222
|
+
{ time: '09:00', cron: '0 9 * * *' },
|
|
223
|
+
{ time: '12:00', cron: '0 12 * * *' },
|
|
224
|
+
{ time: '18:00', cron: '0 18 * * *' },
|
|
225
|
+
{ key: 'twiceADay', cron: '0 10,18 * * *' },
|
|
226
|
+
{ key: 'threeTimesADay', cron: '0 9,14,19 * * *' },
|
|
209
227
|
] },
|
|
210
|
-
{ group: '
|
|
211
|
-
{
|
|
212
|
-
{
|
|
213
|
-
{
|
|
214
|
-
{
|
|
215
|
-
{
|
|
228
|
+
{ group: 'weekly', presets: [
|
|
229
|
+
{ key: 'weekdays0900', cron: '0 9 * * 1-5' },
|
|
230
|
+
{ key: 'weekdaysTwice', cron: '0 10,18 * * 1-5' },
|
|
231
|
+
{ key: 'weekends1000', cron: '0 10 * * 0,6' },
|
|
232
|
+
{ key: 'monWedFri', cron: '0 9 * * 1,3,5' },
|
|
233
|
+
{ key: 'tueThu', cron: '0 9 * * 2,4' },
|
|
216
234
|
] },
|
|
217
235
|
];
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
236
|
+
const presetLabel = (preset) => (preset.key ? t(`schedules.presets.${preset.key}`) : preset.time);
|
|
237
|
+
|
|
238
|
+
// The words of the launch popups that agent-runtime-options.js and automation-context.js write in
|
|
239
|
+
// English for every window: the ones the Schedules window shows read in the page's language. A
|
|
240
|
+
// word not listed (a level or a description a provider named) shows as it is.
|
|
241
|
+
const LAUNCH_WORDS = {
|
|
242
|
+
Off: 'off', Default: 'default', Low: 'low', Med: 'medium', High: 'high', XHigh: 'xhigh', Max: 'max', Ultra: 'ultra',
|
|
243
|
+
'No thinking': 'noThinking', Minimal: 'minimal', Balanced: 'balanced', Deep: 'deep', 'Very deep': 'veryDeep',
|
|
244
|
+
Maximum: 'maximum', 'Config default': 'configDefault', 'Model default': 'modelDefault', Light: 'light',
|
|
245
|
+
'Maximum, with delegation': 'maximumDelegated',
|
|
246
|
+
};
|
|
247
|
+
const launchWord = (text) => (Object.prototype.hasOwnProperty.call(LAUNCH_WORDS, text) ? t(`schedules.launch.word.${LAUNCH_WORDS[text]}`) : text);
|
|
221
248
|
|
|
222
249
|
function readJson(key, fallback) {
|
|
223
250
|
try { const value = JSON.parse(storage.getItem(key) || 'null'); return value ?? fallback; } catch { return fallback; }
|
|
@@ -238,7 +265,6 @@ const domId = (value) => String(value ?? '').replace(/[^\w-]/g, '_');
|
|
|
238
265
|
// A choice of a popup (cc-dropdown.js). The one shown is the last marked `selected`, else the
|
|
239
266
|
// first, as in a <select>: a saved value a list lacks is added as a choice of its own, marked.
|
|
240
267
|
const opt = (value, label, selected = false) => ({ value: value ?? '', label: String(label ?? ''), selected: !!selected });
|
|
241
|
-
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
242
268
|
const cssColor = (value) => (value && CSS.supports('color', String(value)) ? String(value) : 'var(--stg-off)');
|
|
243
269
|
|
|
244
270
|
function showToast(msg) {
|
|
@@ -281,14 +307,14 @@ const listScope = () => LIST_SCOPES.includes(_scope) || _scope === 'ungrouped' |
|
|
|
281
307
|
|
|
282
308
|
function formatTimerCountdown(firesAtISO) {
|
|
283
309
|
const diff = new Date(firesAtISO) - Date.now();
|
|
284
|
-
if (diff <= 0) return 'firing';
|
|
310
|
+
if (diff <= 0) return t('schedules.timers.firing');
|
|
285
311
|
const mins = Math.ceil(diff / 60_000);
|
|
286
312
|
if (mins >= 60) {
|
|
287
313
|
const h = Math.floor(mins / 60);
|
|
288
314
|
const m = mins % 60;
|
|
289
|
-
return m ?
|
|
315
|
+
return m ? t('schedules.timers.countdownHours', { h, m }) : t('schedules.timers.countdownHour', { h });
|
|
290
316
|
}
|
|
291
|
-
return
|
|
317
|
+
return t('schedules.timers.countdownMinutes', { m: mins });
|
|
292
318
|
}
|
|
293
319
|
|
|
294
320
|
// ── Opening and closing ──
|
|
@@ -300,11 +326,28 @@ export function initSchedulesStudio() {
|
|
|
300
326
|
on('schedules:open', (opts) => openPanel(opts || {}));
|
|
301
327
|
on('schedules:toggle', () => (_panel ? closePanel() : openPanel({})));
|
|
302
328
|
setupSync();
|
|
329
|
+
// A language change reloads the app (i18n.js): an open window opens again, in the new language,
|
|
330
|
+
// on the view it showed (Settings does the same for itself).
|
|
331
|
+
window.addEventListener('synabun:locale-change', () => {
|
|
332
|
+
if (!_panel) return;
|
|
333
|
+
try { sessionStorage.setItem(REOPEN_KEY, _scope); } catch { /* private mode: the reload leaves it closed */ }
|
|
334
|
+
});
|
|
335
|
+
const reopen = takeReopen();
|
|
336
|
+
if (reopen) setTimeout(() => openPanel({ scope: reopen }), 0);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** The view to open the window on after a reload a language change asked for; read once. */
|
|
340
|
+
function takeReopen() {
|
|
341
|
+
try {
|
|
342
|
+
const scope = sessionStorage.getItem(REOPEN_KEY);
|
|
343
|
+
if (scope) sessionStorage.removeItem(REOPEN_KEY);
|
|
344
|
+
return scope && (VIEW_SCOPES.includes(scope) || scope === 'ungrouped' || scope.startsWith('group:')) ? scope : null;
|
|
345
|
+
} catch { return null; }
|
|
303
346
|
}
|
|
304
347
|
|
|
305
348
|
async function openPanel(opts = {}) {
|
|
306
349
|
if (isGuest() && !hasPermission('automations')) {
|
|
307
|
-
showGuestToast('
|
|
350
|
+
showGuestToast(t('schedules.window.guestDisabled'));
|
|
308
351
|
return;
|
|
309
352
|
}
|
|
310
353
|
if (_panel) {
|
|
@@ -378,7 +421,6 @@ function applyOpenOptions(opts = {}) {
|
|
|
378
421
|
}
|
|
379
422
|
|
|
380
423
|
function placePanel() {
|
|
381
|
-
const navH = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--navbar-height')) || 48;
|
|
382
424
|
const saved = readJson(LAYOUT_KEY, null);
|
|
383
425
|
if (saved && saved.w > 0 && saved.h > 0) {
|
|
384
426
|
_panel.style.width = saved.w + 'px';
|
|
@@ -386,10 +428,13 @@ function placePanel() {
|
|
|
386
428
|
}
|
|
387
429
|
const width = _panel.offsetWidth;
|
|
388
430
|
const height = _panel.offsetHeight;
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
431
|
+
// Inside the app frame (8px from the window and the bar, as before, and clear of an open explorer)
|
|
432
|
+
const f = frameRect();
|
|
433
|
+
const left = saved && Number.isFinite(saved.x) ? saved.x : f.left + (f.width - width) / 2;
|
|
434
|
+
const top = saved && Number.isFinite(saved.y) ? saved.y : f.top + Math.max(0, (f.height - height) / 2);
|
|
435
|
+
const at = clampBox(left, top, width, height, f);
|
|
436
|
+
_panel.style.left = Math.round(at.x) + 'px';
|
|
437
|
+
_panel.style.top = Math.round(at.y) + 'px';
|
|
393
438
|
}
|
|
394
439
|
|
|
395
440
|
function saveLayout() {
|
|
@@ -443,21 +488,21 @@ function buildPanelHTML() {
|
|
|
443
488
|
<header class="sched-header drag-handle" data-drag="${PANEL_ID}">
|
|
444
489
|
<div class="sched-header-left">
|
|
445
490
|
<span class="sched-header-icon">${ICONS.clock}</span>
|
|
446
|
-
<h3 id="sched-title"
|
|
491
|
+
<h3 id="sched-title">${te('schedules.window.title')}</h3>
|
|
447
492
|
<span class="sched-count" id="sched-total-count"></span>
|
|
448
493
|
</div>
|
|
449
|
-
<button type="button" class="backdrop-toggle-btn" id="sched-backdrop-toggle" aria-pressed="false" aria-label="
|
|
450
|
-
<button type="button" class="sched-close" id="sched-close" aria-label="
|
|
494
|
+
<button type="button" class="backdrop-toggle-btn" id="sched-backdrop-toggle" aria-pressed="false" aria-label="${te('schedules.window.hideBackdrop')}" data-tooltip="${te('schedules.window.hideBackdrop')}">${ICONS.eye}</button>
|
|
495
|
+
<button type="button" class="sched-close" id="sched-close" aria-label="${te('schedules.window.close')}" data-tooltip="${te('schedules.window.closeTip')}">${ICONS.close}</button>
|
|
451
496
|
</header>
|
|
452
497
|
<div class="sched-body">
|
|
453
|
-
<nav class="sched-rail" id="sched-rail" aria-label="
|
|
498
|
+
<nav class="sched-rail" id="sched-rail" aria-label="${te('schedules.window.railLabel')}"></nav>
|
|
454
499
|
<section class="sched-main" aria-labelledby="sched-view-title">
|
|
455
500
|
<!-- A window too narrow for the rail: its views and groups as a popup, and New group. Outside
|
|
456
501
|
the frame below, which each view and editor replaces, so it is there in every one. -->
|
|
457
502
|
<div class="sched-narrow-nav" id="sched-narrow-nav">
|
|
458
|
-
<span class="sched-sr" id="sched-scope-label"
|
|
503
|
+
<span class="sched-sr" id="sched-scope-label">${te('schedules.window.viewLabel')}</span>
|
|
459
504
|
${dropdownHTML('sched-scope-select', [], { classes: 'sched-scope-select', labelledBy: 'sched-scope-label', inputAttrs: 'data-change="scope"' })}
|
|
460
|
-
<button type="button" class="sched-btn" data-action="group-new" id="sched-narrow-new-group">${ICONS.plus}<span
|
|
505
|
+
<button type="button" class="sched-btn" data-action="group-new" id="sched-narrow-new-group">${ICONS.plus}<span>${te('schedules.rail.newGroup')}</span></button>
|
|
461
506
|
</div>
|
|
462
507
|
<div class="sched-frame" id="sched-main"></div>
|
|
463
508
|
</section>
|
|
@@ -476,7 +521,7 @@ function wirePanel() {
|
|
|
476
521
|
if (!btn) return;
|
|
477
522
|
btn.classList.toggle('active', hidden);
|
|
478
523
|
btn.setAttribute('aria-pressed', String(hidden));
|
|
479
|
-
const label = hidden ? '
|
|
524
|
+
const label = t(hidden ? 'schedules.window.showBackdrop' : 'schedules.window.hideBackdrop');
|
|
480
525
|
btn.setAttribute('aria-label', label);
|
|
481
526
|
btn.dataset.tooltip = label;
|
|
482
527
|
}, { signal });
|
|
@@ -492,6 +537,14 @@ function wirePanel() {
|
|
|
492
537
|
_panel.addEventListener('keydown', handlePanelKeydown, { signal });
|
|
493
538
|
// The popups (cc-dropdown.js): one set of listeners for every one the window draws.
|
|
494
539
|
wireDropdowns(_panel, { signal });
|
|
540
|
+
_panel.addEventListener('change', event => {
|
|
541
|
+
if (!['sched-ge-browser', 'sched-ed-browser'].includes(event.target.id)) return;
|
|
542
|
+
const group = event.target.id === 'sched-ge-browser';
|
|
543
|
+
const inherited = group ? false : getGroup(_scheduleEditor?.groupId)?.usesBrowser ?? getTemplate(_scheduleEditor?.templateId)?.usesBrowser;
|
|
544
|
+
const usesBrowser = event.target.value === '' ? !!inherited : event.target.value === 'true';
|
|
545
|
+
const input = $(group ? 'sched-ge-mcp' : 'sched-ed-mcp');
|
|
546
|
+
updateToolPicker(input, readToolPicker(input, { requested: true }), { inheritLabel: toolWord(group ? 'noOverride' : 'templateDefault'), usesBrowser });
|
|
547
|
+
}, { signal });
|
|
495
548
|
// Capture: the menubar's own Escape (it closes an open menu) has run by the time a bubbling
|
|
496
549
|
// listener hears the key, so a menu that was open is seen here first.
|
|
497
550
|
window.addEventListener('keydown', handleEscape, { capture: true, signal });
|
|
@@ -510,7 +563,7 @@ function withDeadline(request) {
|
|
|
510
563
|
let timer;
|
|
511
564
|
const late = new Promise((_, reject) => {
|
|
512
565
|
timer = setTimeout(() => {
|
|
513
|
-
const err = new Error(
|
|
566
|
+
const err = new Error(t('schedules.load.noAnswer', { seconds: LOAD_DEADLINE / 1000 }));
|
|
514
567
|
err.timeout = true;
|
|
515
568
|
reject(err);
|
|
516
569
|
}, LOAD_DEADLINE);
|
|
@@ -518,9 +571,11 @@ function withDeadline(request) {
|
|
|
518
571
|
return Promise.race([request, late]).finally(() => clearTimeout(timer));
|
|
519
572
|
}
|
|
520
573
|
|
|
521
|
-
|
|
574
|
+
// The reason is the server's or the network's own words ("HTTP 500"): it shows as it came.
|
|
575
|
+
const failureReason = (result) => String(result.reason?.message || t('schedules.load.unexpected')).replace(/\.$/, '');
|
|
522
576
|
|
|
523
577
|
async function loadData() {
|
|
578
|
+
_launchTools = await migrateStoredTools(storage, 'as-launch-tools', 'as-launch-mcp-profile');
|
|
524
579
|
const seq = ++_loadSeq;
|
|
525
580
|
loadTimers();
|
|
526
581
|
const [schedules, templates, groups] = await Promise.allSettled([
|
|
@@ -542,7 +597,10 @@ async function loadData() {
|
|
|
542
597
|
if (groupIdOf(_scope) && !getGroup(groupIdOf(_scope))) setScope('all', { keepEditors: true });
|
|
543
598
|
} else failed.push(['groups', failureReason(groups)]);
|
|
544
599
|
_loadError = failed.length
|
|
545
|
-
?
|
|
600
|
+
? t('schedules.load.failed', {
|
|
601
|
+
lists: listJoin(failed.map(([what]) => t(`schedules.load.list.${what}`))),
|
|
602
|
+
reasons: [...new Set(failed.map(([, why]) => why))].join('; '),
|
|
603
|
+
})
|
|
546
604
|
: null;
|
|
547
605
|
loadFinished();
|
|
548
606
|
}
|
|
@@ -578,7 +636,7 @@ async function loadTimers() {
|
|
|
578
636
|
if (quick.status === 'fulfilled' && Array.isArray(quick.value)) _quickTimers = quick.value;
|
|
579
637
|
else failed.push(failureReason(quick));
|
|
580
638
|
_timersLoaded = true;
|
|
581
|
-
_timersError = failed.length ?
|
|
639
|
+
_timersError = failed.length ? t('schedules.load.timersFailed', { reasons: [...new Set(failed)].join('; ') }) : null;
|
|
582
640
|
if (!_panel) return;
|
|
583
641
|
renderRail();
|
|
584
642
|
renderActiveTimersInto();
|
|
@@ -647,22 +705,27 @@ function renderRail() {
|
|
|
647
705
|
const timers = _quickTimers.length + Object.keys(_scheduleTimerData).length;
|
|
648
706
|
const ids = groupIds();
|
|
649
707
|
const view = (scope, count, { alert = false } = {}) => {
|
|
650
|
-
const {
|
|
708
|
+
const { icon } = VIEWS[scope];
|
|
651
709
|
const current = _scope === scope;
|
|
652
710
|
const countHTML = count == null || !countsKnown() ? '' : `<span class="sched-nav-count${alert && count ? ' sched-nav-count--alert' : ''}">${count}</span>`;
|
|
653
711
|
return `<button type="button" class="sched-nav-item" data-action="set-scope" data-scope="${scope}"${current ? ' aria-current="page"' : ''}>
|
|
654
|
-
<span class="sched-nav-icon">${icon}</span><span class="sched-nav-label">${
|
|
712
|
+
<span class="sched-nav-icon">${icon}</span><span class="sched-nav-label">${esc(viewLabel(scope))}</span>${countHTML}</button>`;
|
|
655
713
|
};
|
|
656
714
|
const groupRows = orderedGroups().map(group => {
|
|
657
715
|
const members = _schedules.filter(s => s.groupId === group.id);
|
|
658
716
|
const summary = groupSummary(members, now);
|
|
659
717
|
const scope = `group:${group.id}`;
|
|
660
|
-
const
|
|
718
|
+
const facts = [
|
|
719
|
+
tp('schedules.count.schedules', summary.total),
|
|
720
|
+
summary.attention ? tp('schedules.count.attention', summary.attention) : '',
|
|
721
|
+
group.mutualExclusion ? t('schedules.rail.oneAtATimeFact') : '',
|
|
722
|
+
].filter(Boolean).join(', ');
|
|
723
|
+
const name = t('schedules.rail.groupLabel', { name: group.name, facts });
|
|
661
724
|
return `<button type="button" class="sched-nav-item" data-action="set-scope" data-scope="${esc(scope)}" data-drop-group="${esc(group.id)}" aria-label="${esc(name)}"${_scope === scope ? ' aria-current="page"' : ''}>
|
|
662
725
|
<span class="sched-nav-icon"><span class="sched-dot" style="--dot:${esc(cssColor(group.color))}"></span></span>
|
|
663
726
|
<span class="sched-nav-label">${esc(group.name)}</span>
|
|
664
|
-
${group.mutualExclusion ? `<span class="sched-nav-flag" data-tooltip="
|
|
665
|
-
${summary.attention ? `<span class="sched-nav-flag sched-nav-flag--alert" data-tooltip="${
|
|
727
|
+
${group.mutualExclusion ? `<span class="sched-nav-flag" data-tooltip="${te('schedules.oneAtATime')}">${ICONS.lock}</span>` : ''}
|
|
728
|
+
${summary.attention ? `<span class="sched-nav-flag sched-nav-flag--alert" data-tooltip="${tpe('schedules.rail.attentionTip', summary.attention)}">${ICONS.alert}${summary.attention}</span>` : ''}
|
|
666
729
|
<span class="sched-nav-count">${summary.total}</span>
|
|
667
730
|
</button>`;
|
|
668
731
|
}).join('');
|
|
@@ -682,11 +745,11 @@ function renderRail() {
|
|
|
682
745
|
<div class="sched-rail-sep" role="presentation"></div>
|
|
683
746
|
${view('timers', timers || null)}
|
|
684
747
|
${view('activity', null)}
|
|
685
|
-
<div class="sched-rail-head"><span id="sched-rail-groups"
|
|
748
|
+
<div class="sched-rail-head"><span id="sched-rail-groups">${te('schedules.rail.groups')}</span><button type="button" class="sched-icon-btn" data-action="group-new" aria-label="${te('schedules.rail.newGroup')}" data-tooltip="${te('schedules.rail.newGroup')}"${editorsWait() ? ' disabled' : ''}>${ICONS.plus}</button></div>
|
|
686
749
|
<div role="group" aria-labelledby="sched-rail-groups">
|
|
687
|
-
${groupRows || (countsKnown() ?
|
|
688
|
-
<button type="button" class="sched-nav-item" data-action="set-scope" data-scope="ungrouped" data-drop-group="" aria-label="
|
|
689
|
-
<span class="sched-nav-icon">${ICONS.folder}</span><span class="sched-nav-label"
|
|
750
|
+
${groupRows || (countsKnown() ? `<div class="sched-rail-empty">${te('schedules.rail.noGroups')}</div>` : '')}
|
|
751
|
+
<button type="button" class="sched-nav-item" data-action="set-scope" data-scope="ungrouped" data-drop-group="" aria-label="${countsKnown() ? te('schedules.rail.groupLabel', { name: viewLabel('ungrouped'), facts: tp('schedules.count.schedules', ungrouped) }) : esc(viewLabel('ungrouped'))}"${_scope === 'ungrouped' ? ' aria-current="page"' : ''}>
|
|
752
|
+
<span class="sched-nav-icon">${ICONS.folder}</span><span class="sched-nav-label">${esc(viewLabel('ungrouped'))}</span><span class="sched-nav-count">${countsKnown() ? ungrouped : ''}</span>
|
|
690
753
|
</button>
|
|
691
754
|
</div>
|
|
692
755
|
`;
|
|
@@ -737,24 +800,24 @@ function buildListFrame(frame) {
|
|
|
737
800
|
<div class="sched-toolbar">
|
|
738
801
|
<div class="sched-search">
|
|
739
802
|
${ICONS.search}
|
|
740
|
-
<label class="sched-sr" for="sched-search">${activity ? '
|
|
803
|
+
<label class="sched-sr" for="sched-search">${activity ? te('schedules.list.searchRuns') : te('schedules.list.searchSchedules')}</label>
|
|
741
804
|
<input id="sched-search" type="search" autocomplete="off" spellcheck="false"
|
|
742
|
-
placeholder="${activity ? '
|
|
743
|
-
<button type="button" class="sched-icon-btn" data-action="clear-search" aria-label="
|
|
805
|
+
placeholder="${activity ? te('schedules.list.searchRunsPlaceholder') : te('schedules.list.searchPlaceholder')}" value="${esc(_query)}">
|
|
806
|
+
<button type="button" class="sched-icon-btn" data-action="clear-search" aria-label="${te('schedules.list.clearSearch')}"${_query ? '' : ' hidden'}>${ICONS.close}</button>
|
|
744
807
|
<kbd aria-hidden="true">/</kbd>
|
|
745
808
|
</div>
|
|
746
|
-
${activity ? '' : `<span class="sched-sort" id="sched-sort-wrap"><span class="sched-sr" id="sched-sort-label"
|
|
809
|
+
${activity ? '' : `<span class="sched-sort" id="sched-sort-wrap"><span class="sched-sr" id="sched-sort-label">${te('schedules.list.order')}</span>
|
|
747
810
|
${dropdownHTML('sched-sort', [
|
|
748
|
-
opt('next', '
|
|
749
|
-
opt('name', '
|
|
750
|
-
opt('last', '
|
|
811
|
+
opt('next', t('schedules.list.sortNext'), _sort === 'next'),
|
|
812
|
+
opt('name', t('schedules.list.sortName'), _sort === 'name'),
|
|
813
|
+
opt('last', t('schedules.list.sortLast'), _sort === 'last'),
|
|
751
814
|
], { labelledBy: 'sched-sort-label', inputAttrs: 'data-change="sort"' })}</span>`}
|
|
752
815
|
</div>
|
|
753
816
|
<div class="sched-load-note" id="sched-load-note" role="status"></div>
|
|
754
817
|
<div class="sched-list" id="sched-list" tabindex="-1"></div>
|
|
755
818
|
<footer class="sched-footer">
|
|
756
819
|
<span id="sched-status" role="status" aria-live="polite" aria-atomic="true"></span>
|
|
757
|
-
${activity ? '' : `<span class="sched-footer-hint">${ICONS.move}
|
|
820
|
+
${activity ? '' : `<span class="sched-footer-hint">${ICONS.move}${te('schedules.list.dragHint')}</span>`}
|
|
758
821
|
</footer>
|
|
759
822
|
`;
|
|
760
823
|
if (caret) {
|
|
@@ -768,9 +831,9 @@ function headActions({ group = null } = {}) {
|
|
|
768
831
|
// An editor needs the automations and groups: nothing opens one before they have loaded.
|
|
769
832
|
const wait = editorsWait() ? ' disabled' : '';
|
|
770
833
|
return `<div class="sched-view-actions">
|
|
771
|
-
${group ? `<button type="button" class="sched-btn" data-action="group-edit" data-id="${esc(group.id)}"${wait}>${ICONS.edit}<span
|
|
772
|
-
<button type="button" class="sched-btn sched-btn--text" data-action="schedule-prompt" data-tooltip="
|
|
773
|
-
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new"${wait}>${ICONS.plus}<span
|
|
834
|
+
${group ? `<button type="button" class="sched-btn" data-action="group-edit" data-id="${esc(group.id)}"${wait}>${ICONS.edit}<span>${te('schedules.head.editGroup')}</span></button>` : ''}
|
|
835
|
+
<button type="button" class="sched-btn sched-btn--text" data-action="schedule-prompt" data-tooltip="${te('schedules.head.promptTip')}">${ICONS.sparkle}<span>${te('schedules.head.prompt')}</span></button>
|
|
836
|
+
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new"${wait}>${ICONS.plus}<span>${te('schedules.head.newSchedule')}</span></button>
|
|
774
837
|
</div>`;
|
|
775
838
|
}
|
|
776
839
|
|
|
@@ -778,52 +841,61 @@ function groupOverridesText(group) {
|
|
|
778
841
|
const parts = [];
|
|
779
842
|
if (group.profile) parts.push(CLI_PROFILES.find(c => c.id === group.profile)?.label || group.profile);
|
|
780
843
|
if (group.model) parts.push(group.model);
|
|
781
|
-
if (group.contextMode && group.contextMode !== 'default') parts.push(
|
|
782
|
-
if (group.effort) parts.push(
|
|
783
|
-
if (group.
|
|
784
|
-
if (group.usesBrowser === true) parts.push('
|
|
785
|
-
else if (group.usesBrowser === false) parts.push('
|
|
844
|
+
if (group.contextMode && group.contextMode !== 'default') parts.push(t('schedules.overrides.context', { value: group.contextMode }));
|
|
845
|
+
if (group.effort) parts.push(t('schedules.overrides.thinking', { value: group.effort }));
|
|
846
|
+
if (group.tools) parts.push(t('schedules.overrides.mcp', { value: group.tools.join(', ') }));
|
|
847
|
+
if (group.usesBrowser === true) parts.push(t('schedules.overrides.browserOn'));
|
|
848
|
+
else if (group.usesBrowser === false) parts.push(t('schedules.overrides.browserOff'));
|
|
786
849
|
return parts.join(' · ');
|
|
787
850
|
}
|
|
788
851
|
|
|
852
|
+
/** "4 schedules · 3 active · 1 paused · 1 needs attention": the facts of a set of schedules. */
|
|
853
|
+
function countsLine({ total, active, paused, attention }) {
|
|
854
|
+
return [
|
|
855
|
+
tp('schedules.count.schedules', total),
|
|
856
|
+
tp('schedules.count.active', active),
|
|
857
|
+
tp('schedules.count.paused', paused),
|
|
858
|
+
attention ? tp('schedules.count.attention', attention) : '',
|
|
859
|
+
].filter(Boolean).join(' · ');
|
|
860
|
+
}
|
|
861
|
+
|
|
789
862
|
function renderViewHead() {
|
|
790
863
|
const head = $('sched-view-head');
|
|
791
864
|
if (!head) return;
|
|
792
865
|
const now = Date.now();
|
|
793
866
|
const counts = scopeCounts(_schedules, now);
|
|
794
|
-
let title = VIEWS[_scope]
|
|
867
|
+
let title = VIEWS[_scope] ? viewLabel(_scope) : t('schedules.window.title');
|
|
795
868
|
let dot = '';
|
|
796
869
|
let sub = '';
|
|
797
870
|
let meta = '';
|
|
798
871
|
let group = null;
|
|
799
872
|
if (_scope === 'all') {
|
|
800
|
-
sub =
|
|
873
|
+
sub = countsLine({ total: counts.all, active: counts.active, paused: counts.paused, attention: counts.attention });
|
|
801
874
|
} else if (_scope === 'upcoming') {
|
|
802
875
|
// One row per schedule, at its next run (a schedule that runs three times today is listed once).
|
|
803
|
-
sub = counts.upcoming
|
|
804
|
-
? `${counts.upcoming === 1 ? '1 schedule runs' : `${counts.upcoming} schedules run`} in the next 24 hours, each at its next run, soonest first.`
|
|
805
|
-
: 'Nothing runs in the next 24 hours.';
|
|
876
|
+
sub = counts.upcoming ? tp('schedules.head.upcoming', counts.upcoming) : t('schedules.head.upcomingNone');
|
|
806
877
|
} else if (_scope === 'attention') {
|
|
807
|
-
sub = '
|
|
878
|
+
sub = t('schedules.head.attention');
|
|
808
879
|
} else if (_scope === 'paused') {
|
|
809
|
-
sub = '
|
|
880
|
+
sub = t('schedules.head.paused');
|
|
810
881
|
} else if (_scope === 'activity') {
|
|
811
|
-
sub = '
|
|
882
|
+
sub = t('schedules.head.activity');
|
|
812
883
|
} else if (_scope === 'ungrouped') {
|
|
813
|
-
title = '
|
|
814
|
-
sub = '
|
|
884
|
+
title = viewLabel('ungrouped');
|
|
885
|
+
sub = t('schedules.head.ungrouped');
|
|
815
886
|
} else if (groupIdOf(_scope)) {
|
|
816
887
|
group = getGroup(groupIdOf(_scope));
|
|
817
888
|
if (group) {
|
|
818
889
|
title = group.name;
|
|
819
890
|
dot = `<span class="sched-dot" style="--dot:${esc(cssColor(group.color))}" aria-hidden="true"></span>`;
|
|
820
891
|
const summary = groupSummary(_schedules.filter(s => s.groupId === group.id), now);
|
|
821
|
-
sub =
|
|
892
|
+
sub = countsLine(summary);
|
|
893
|
+
if (group.mutualExclusion) sub = `${sub} · ${t('schedules.oneAtATime')}`;
|
|
822
894
|
const overrides = groupOverridesText(group);
|
|
823
|
-
if (overrides) meta = `<p class="sched-view-meta sched-group-overrides"
|
|
895
|
+
if (overrides) meta = `<p class="sched-view-meta sched-group-overrides">${te('schedules.head.groupLaunch', { settings: overrides })}</p>`;
|
|
824
896
|
}
|
|
825
897
|
}
|
|
826
|
-
if (_loading) sub = '
|
|
898
|
+
if (_loading) sub = t('schedules.list.loading');
|
|
827
899
|
else if (dataIncomplete()) sub = '';
|
|
828
900
|
// A live update changes counts, not the head's controls: keep them (and the focus on them).
|
|
829
901
|
const key = `${_scope}|${title}|${dot}|${meta}|${editorsWait()}`;
|
|
@@ -849,11 +921,11 @@ function renderNarrowNav() {
|
|
|
849
921
|
if (scope) {
|
|
850
922
|
const counts = scopeCounts(_schedules, Date.now());
|
|
851
923
|
const timers = _quickTimers.length + Object.keys(_scheduleTimerData).length;
|
|
852
|
-
const n = (value) => (!countsKnown() || !value && value !== 0 ? '' :
|
|
924
|
+
const n = (label, value) => (!countsKnown() || !value && value !== 0 ? label : t('schedules.rail.withCount', { label, count: value }));
|
|
853
925
|
const views = [['upcoming', counts.upcoming], ['all', counts.all], ['attention', counts.attention], ['paused', counts.paused], ['timers', timers || null], ['activity', null]]
|
|
854
|
-
.map(([id, count]) => opt(id,
|
|
855
|
-
const groups = orderedGroups().map(g => opt(`group:${g.id}`,
|
|
856
|
-
const entries = [{ group: '
|
|
926
|
+
.map(([id, count]) => opt(id, n(viewLabel(id), count)));
|
|
927
|
+
const groups = orderedGroups().map(g => opt(`group:${g.id}`, n(g.name, _schedules.filter(s => s.groupId === g.id).length)));
|
|
928
|
+
const entries = [{ group: t('schedules.rail.views'), options: views }, { group: t('schedules.rail.groups'), options: [...groups, opt('ungrouped', viewLabel('ungrouped'))] }];
|
|
857
929
|
// Redrawn only when it changes (an open popup stays open, its highlighted choice too).
|
|
858
930
|
const key = JSON.stringify(entries);
|
|
859
931
|
if (scope._key !== key) { setDropdownItems(scope, entries); scope._key = key; }
|
|
@@ -879,7 +951,7 @@ function renderNote() {
|
|
|
879
951
|
if (!note) return;
|
|
880
952
|
const listView = _frame === 'list' || _frame === 'activity';
|
|
881
953
|
const failed = _loading || !_loadError || (listView && dataIncomplete()) ? ''
|
|
882
|
-
: dataIncomplete() ? _loadError :
|
|
954
|
+
: dataIncomplete() ? _loadError : t('schedules.note.showingBefore', { error: _loadError });
|
|
883
955
|
const report = !failed && _frame === 'list' ? cliReportNote() : null;
|
|
884
956
|
const kind = failed ? 'load' : report?.kind || '';
|
|
885
957
|
const text = failed || report?.text || '';
|
|
@@ -889,8 +961,8 @@ function renderNote() {
|
|
|
889
961
|
note.classList.toggle('sched-load-note--ok', kind === 'ok');
|
|
890
962
|
note.classList.toggle('sched-load-note--busy', kind === 'busy');
|
|
891
963
|
const icon = kind === 'ok' ? ICONS.ok : kind === 'busy' ? ICONS.clock : ICONS.alert;
|
|
892
|
-
const action = kind === 'load' ?
|
|
893
|
-
: kind === 'busy' ? '' :
|
|
964
|
+
const action = kind === 'load' ? `<button type="button" class="sched-btn" data-action="retry-load">${te('schedules.note.tryAgain')}</button>`
|
|
965
|
+
: kind === 'busy' ? '' : `<button type="button" class="sched-btn" data-action="dismiss-report">${te('schedules.note.dismiss')}</button>`;
|
|
894
966
|
note.innerHTML = text ? `${icon}<span>${esc(text)}</span>${action}` : '';
|
|
895
967
|
}
|
|
896
968
|
|
|
@@ -898,23 +970,23 @@ function renderNote() {
|
|
|
898
970
|
function cliReportNote() {
|
|
899
971
|
const r = _cliReport;
|
|
900
972
|
if (!r || _scope !== `group:${r.groupId}`) return null;
|
|
901
|
-
if (r.pending) return { kind: 'busy', text:
|
|
973
|
+
if (r.pending) return { kind: 'busy', text: t('schedules.cliReport.clearing') };
|
|
902
974
|
const cli = r.profile ? CLI_PROFILES.find(p => p.id === r.profile)?.label || r.profile : null;
|
|
903
975
|
const kept = r.changed.length + r.failed.length;
|
|
904
|
-
|
|
905
|
-
const parts = [cli ?
|
|
906
|
-
if (r.reset.length) parts.push(
|
|
907
|
-
else if (!kept) parts.push('
|
|
908
|
-
if (r.changed.length) parts.push(
|
|
909
|
-
if (r.failed.length) parts.push(
|
|
910
|
-
if (kept) parts.push(
|
|
976
|
+
// Each sentence is whole in every language; the report is the sentences that apply, in order.
|
|
977
|
+
const parts = [cli ? t('schedules.cliReport.nowRuns', { cli }) : t('schedules.cliReport.noCli')];
|
|
978
|
+
if (r.reset.length) parts.push(t('schedules.cliReport.cleared', { names: listJoin(r.reset) }));
|
|
979
|
+
else if (!kept) parts.push(t('schedules.cliReport.noneLeft'));
|
|
980
|
+
if (r.changed.length) parts.push(tp('schedules.cliReport.changed', r.changed.length, { names: listJoin(r.changed) }));
|
|
981
|
+
if (r.failed.length) parts.push(tp('schedules.cliReport.failed', r.failed.length, { names: listJoin(r.failed) }));
|
|
982
|
+
if (kept) parts.push(tp('schedules.cliReport.open', kept));
|
|
911
983
|
return { kind: kept ? 'warn' : 'ok', text: parts.join(' ') };
|
|
912
984
|
}
|
|
913
985
|
|
|
914
986
|
function loadFailedHTML() {
|
|
915
|
-
const waiting = _pendingAction ?
|
|
916
|
-
return emptyState(ICONS.alert, '
|
|
917
|
-
|
|
987
|
+
const waiting = _pendingAction ? t(_pendingAction === 'new-group' ? 'schedules.loadFailed.waitingGroup' : 'schedules.loadFailed.waitingSchedule') : '';
|
|
988
|
+
return emptyState(ICONS.alert, t('schedules.loadFailed.title'), [_loadError || t('schedules.loadFailed.notEverything'), waiting].filter(Boolean).join(' '),
|
|
989
|
+
`<button type="button" class="sched-btn" data-action="retry-load">${te('schedules.note.tryAgain')}</button>`);
|
|
918
990
|
}
|
|
919
991
|
|
|
920
992
|
/** Keep the place: scroll position and the focused control survive a re-render of the list. */
|
|
@@ -970,29 +1042,30 @@ function renderList() {
|
|
|
970
1042
|
renderNote();
|
|
971
1043
|
|
|
972
1044
|
if (_loading) {
|
|
973
|
-
list.innerHTML = emptyState(ICONS.clock, '
|
|
974
|
-
setStatus('
|
|
1045
|
+
list.innerHTML = emptyState(ICONS.clock, t('schedules.list.loading'), '');
|
|
1046
|
+
setStatus(t('schedules.list.statusLoading'));
|
|
975
1047
|
return;
|
|
976
1048
|
}
|
|
977
1049
|
if (dataIncomplete()) {
|
|
978
1050
|
list.innerHTML = loadFailedHTML();
|
|
979
|
-
setStatus('
|
|
1051
|
+
setStatus(t('schedules.list.statusNotLoaded'));
|
|
980
1052
|
return;
|
|
981
1053
|
}
|
|
982
1054
|
|
|
983
1055
|
const now = Date.now();
|
|
984
1056
|
const shown = visibleSchedules();
|
|
985
1057
|
const scopeTotal = _schedules.filter(s => inScope(s, _scope, { now, groupIds: groupIds() })).length;
|
|
1058
|
+
const query = _query.trim();
|
|
986
1059
|
if (!_schedules.length) {
|
|
987
|
-
list.innerHTML = emptyState(ICONS.clock, '
|
|
988
|
-
`<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new">${ICONS.plus}<span
|
|
989
|
-
<button type="button" class="sched-btn" data-action="schedule-prompt">${ICONS.sparkle}<span
|
|
990
|
-
} else if (!shown.length &&
|
|
1060
|
+
list.innerHTML = emptyState(ICONS.clock, t('schedules.empty.none'), t('schedules.empty.noneText'),
|
|
1061
|
+
`<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new">${ICONS.plus}<span>${te('schedules.head.newSchedule')}</span></button>
|
|
1062
|
+
<button type="button" class="sched-btn" data-action="schedule-prompt">${ICONS.sparkle}<span>${te('schedules.head.prompt')}</span></button>`);
|
|
1063
|
+
} else if (!shown.length && query) {
|
|
991
1064
|
const elsewhere = _scope !== 'all' && _schedules.some(s => matchesQuery(s, _query, scheduleExtras(s)));
|
|
992
|
-
list.innerHTML = emptyState(ICONS.search,
|
|
993
|
-
elsewhere ? '
|
|
994
|
-
`<button type="button" class="sched-btn" data-action="clear-search"
|
|
995
|
-
${elsewhere ?
|
|
1065
|
+
list.innerHTML = emptyState(ICONS.search, t('schedules.empty.noMatch', { query }),
|
|
1066
|
+
elsewhere ? t('schedules.empty.elsewhere') : t('schedules.empty.tryAnother'),
|
|
1067
|
+
`<button type="button" class="sched-btn" data-action="clear-search">${te('schedules.list.clearSearch')}</button>
|
|
1068
|
+
${elsewhere ? `<button type="button" class="sched-btn" data-action="search-all">${te('schedules.empty.searchAll')}</button>` : ''}`);
|
|
996
1069
|
} else if (!shown.length) {
|
|
997
1070
|
list.innerHTML = scopeEmptyState();
|
|
998
1071
|
} else if (_scope === 'all') {
|
|
@@ -1001,9 +1074,9 @@ function renderList() {
|
|
|
1001
1074
|
list.innerHTML = renderUpcomingList(shown, now);
|
|
1002
1075
|
} else {
|
|
1003
1076
|
const rows = sortSchedules(shown, _sort).map(s => rowHTML(s, { now, showGroup: !groupIdOf(_scope) })).join('');
|
|
1004
|
-
list.innerHTML = `<div class="sched-box"><ul class="sched-rows" aria-label="${esc(VIEWS[_scope]
|
|
1077
|
+
list.innerHTML = `<div class="sched-box"><ul class="sched-rows" aria-label="${esc(VIEWS[_scope] ? viewLabel(_scope) : t('schedules.window.title'))}">${rows}</ul></div>`;
|
|
1005
1078
|
}
|
|
1006
|
-
setStatus(
|
|
1079
|
+
setStatus(query ? tp('schedules.list.statusMatch', shown.length, { query }) : tp('schedules.list.statusShowing', scopeTotal, { shown: shown.length }));
|
|
1007
1080
|
restoreListState(list, saved);
|
|
1008
1081
|
}
|
|
1009
1082
|
|
|
@@ -1017,12 +1090,12 @@ function emptyState(icon, title, text, actions = '') {
|
|
|
1017
1090
|
}
|
|
1018
1091
|
|
|
1019
1092
|
function scopeEmptyState() {
|
|
1020
|
-
if (_scope === 'attention') return emptyState(ICONS.ok, '
|
|
1021
|
-
if (_scope === 'paused') return emptyState(ICONS.pause, '
|
|
1022
|
-
if (_scope === 'upcoming') return emptyState(ICONS.next, '
|
|
1023
|
-
if (_scope === 'ungrouped') return emptyState(ICONS.folder, '
|
|
1024
|
-
return emptyState(ICONS.folder, '
|
|
1025
|
-
`<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new">${ICONS.plus}<span
|
|
1093
|
+
if (_scope === 'attention') return emptyState(ICONS.ok, t('schedules.empty.attention'), t('schedules.empty.attentionText'));
|
|
1094
|
+
if (_scope === 'paused') return emptyState(ICONS.pause, t('schedules.empty.paused'), t('schedules.empty.pausedText'));
|
|
1095
|
+
if (_scope === 'upcoming') return emptyState(ICONS.next, t('schedules.empty.upcoming'), t('schedules.empty.upcomingText'));
|
|
1096
|
+
if (_scope === 'ungrouped') return emptyState(ICONS.folder, t('schedules.empty.ungrouped'), '');
|
|
1097
|
+
return emptyState(ICONS.folder, t('schedules.empty.group'), t('schedules.empty.groupText'),
|
|
1098
|
+
`<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new">${ICONS.plus}<span>${te('schedules.head.newSchedule')}</span></button>`);
|
|
1026
1099
|
}
|
|
1027
1100
|
|
|
1028
1101
|
/** All schedules: one box per group, in the groups' order, then the ungrouped ones. */
|
|
@@ -1037,21 +1110,21 @@ function renderGroupedList(shown, now) {
|
|
|
1037
1110
|
const all = group ? _schedules.filter(s => s.groupId === group.id) : items;
|
|
1038
1111
|
const summary = groupSummary(all, now);
|
|
1039
1112
|
const meta = [
|
|
1040
|
-
summary.attention ? `<span class="is-alert">${ICONS.alert} ${summary.attention}
|
|
1041
|
-
summary.paused ? `<span>${summary.paused}
|
|
1042
|
-
group?.mutualExclusion ? `<span>${ICONS.lock}
|
|
1113
|
+
summary.attention ? `<span class="is-alert">${ICONS.alert} ${tpe('schedules.count.attention', summary.attention)}</span>` : '',
|
|
1114
|
+
summary.paused ? `<span>${tpe('schedules.count.paused', summary.paused)}</span>` : '',
|
|
1115
|
+
group?.mutualExclusion ? `<span>${ICONS.lock} ${te('schedules.oneAtATime')}</span>` : '',
|
|
1043
1116
|
].filter(Boolean).join('<span aria-hidden="true">·</span>');
|
|
1044
|
-
const name = group ? group.name : '
|
|
1117
|
+
const name = group ? group.name : viewLabel('ungrouped');
|
|
1045
1118
|
return `<section class="sched-box${collapsed ? ' is-collapsed' : ''}" data-group-section="${esc(key)}">
|
|
1046
1119
|
<div class="sched-group-head" data-drop-group="${group ? esc(group.id) : ''}">
|
|
1047
1120
|
<button type="button" class="sched-group-toggle" data-action="group-collapse" data-key="${esc(key)}" aria-expanded="${!collapsed}" aria-controls="${listId}">
|
|
1048
1121
|
<span class="sched-group-chevron">${ICONS.chevron}</span>
|
|
1049
1122
|
${group ? `<span class="sched-dot" style="--dot:${esc(cssColor(group.color))}" aria-hidden="true"></span>` : `<span class="sched-nav-icon">${ICONS.folder}</span>`}
|
|
1050
1123
|
<span class="sched-group-name">${esc(name)}</span>
|
|
1051
|
-
<span class="sched-group-count">${items.length === all.length ? items.length :
|
|
1124
|
+
<span class="sched-group-count">${items.length === all.length ? items.length : te('schedules.list.shownOf', { shown: items.length, total: all.length })}</span>
|
|
1052
1125
|
</button>
|
|
1053
1126
|
${meta ? `<span class="sched-group-meta">${meta}</span>` : ''}
|
|
1054
|
-
${group ? `<button type="button" class="sched-icon-btn" data-action="group-edit" data-id="${esc(group.id)}" aria-label="
|
|
1127
|
+
${group ? `<button type="button" class="sched-icon-btn" data-action="group-edit" data-id="${esc(group.id)}" aria-label="${te('schedules.list.editGroupNamed', { name })}" data-tooltip="${te('schedules.head.editGroup')}">${ICONS.edit}</button>` : ''}
|
|
1055
1128
|
</div>
|
|
1056
1129
|
<ul class="sched-rows" id="${listId}" aria-label="${esc(name)}"${collapsed ? ' hidden' : ''}>
|
|
1057
1130
|
${sortSchedules(items, _sort).map(s => rowHTML(s, { now, showGroup: false })).join('')}
|
|
@@ -1065,19 +1138,19 @@ function renderUpcomingList(shown, now) {
|
|
|
1065
1138
|
const ordered = sortSchedules(shown, 'next');
|
|
1066
1139
|
const days = new Map();
|
|
1067
1140
|
for (const s of ordered) {
|
|
1068
|
-
const label = dayLabel(s.nextRun, now) || '
|
|
1141
|
+
const label = dayLabel(s.nextRun, now) || t('schedules.list.later');
|
|
1069
1142
|
if (!days.has(label)) days.set(label, []);
|
|
1070
1143
|
days.get(label).push(s);
|
|
1071
1144
|
}
|
|
1072
1145
|
return [...days.entries()].map(([label, items], i) => `
|
|
1073
1146
|
<section class="sched-section" aria-labelledby="sched-day-${i}">
|
|
1074
|
-
<h3 class="sched-section-title" id="sched-day-${i}">${esc(label)}<span>${
|
|
1147
|
+
<h3 class="sched-section-title" id="sched-day-${i}">${esc(label)}<span>${tpe('schedules.count.schedules', items.length)}</span></h3>
|
|
1075
1148
|
<div class="sched-box"><ul class="sched-rows">${items.map(s => rowHTML(s, { now, showGroup: true })).join('')}</ul></div>
|
|
1076
1149
|
</section>`).join('');
|
|
1077
1150
|
}
|
|
1078
1151
|
|
|
1079
1152
|
function nextRunHTML(s, now) {
|
|
1080
|
-
if (!s.enabled) return
|
|
1153
|
+
if (!s.enabled) return `<span class="sched-next" data-state="paused"><span class="sched-next-rel">${te('schedules.row.paused')}</span></span>`;
|
|
1081
1154
|
const countdown = formatCountdown(s.nextRun, now);
|
|
1082
1155
|
const when = s.nextRun ? whenLabel(s.nextRun, now) : '';
|
|
1083
1156
|
return `<span class="sched-next" data-state="${countdown.state}" data-countdown="${esc(s.nextRun || '')}"${when ? ` title="${esc(when)}"` : ''}>
|
|
@@ -1086,7 +1159,7 @@ function nextRunHTML(s, now) {
|
|
|
1086
1159
|
|
|
1087
1160
|
function lastRunHTML(s, now, run = lastRunState(s), { ago: withAgo = true } = {}) {
|
|
1088
1161
|
const ago = withAgo && s.lastRun ? formatAgo(s.lastRun, now) : '';
|
|
1089
|
-
const title = s.lastRun ?
|
|
1162
|
+
const title = s.lastRun ? fullDateTime(s.lastRun) : '';
|
|
1090
1163
|
return `<span class="sched-last" data-kind="${run.kind}"${title ? ` title="${esc(title)}"` : ''}>
|
|
1091
1164
|
<span class="sched-last-word">${RESULT_ICON[run.kind] || ICONS.never}<span>${esc(run.label)}</span></span>${ago ? `<span class="sched-last-ago">${esc(ago)}</span>` : ''}</span>`;
|
|
1092
1165
|
}
|
|
@@ -1105,7 +1178,7 @@ function rowHTML(s, { now = Date.now(), showGroup = false } = {}) {
|
|
|
1105
1178
|
<div class="sched-row-line" data-action="schedule-expand" data-id="${esc(s.id)}" draggable="true">
|
|
1106
1179
|
<span class="sched-switch-cell">
|
|
1107
1180
|
<button type="button" class="sched-switch" role="switch" aria-checked="${!!s.enabled}" data-action="schedule-toggle" data-id="${esc(s.id)}"
|
|
1108
|
-
aria-label="
|
|
1181
|
+
aria-label="${te('schedules.row.switchLabel', { name: s.name })}" data-tooltip="${s.enabled ? te('schedules.row.pause') : te('schedules.row.resume')}"></button>
|
|
1109
1182
|
</span>
|
|
1110
1183
|
<button type="button" class="sched-row-toggle" data-action="schedule-expand" data-id="${esc(s.id)}" aria-expanded="${open}"${open ? ` aria-controls="${detailId}"` : ''}>
|
|
1111
1184
|
<span class="sched-row-name">${esc(s.name)}</span>
|
|
@@ -1123,55 +1196,53 @@ function rowHTML(s, { now = Date.now(), showGroup = false } = {}) {
|
|
|
1123
1196
|
function detailHTML(s, now, run) {
|
|
1124
1197
|
const template = getTemplate(s.templateId);
|
|
1125
1198
|
const group = getGroup(s.groupId);
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
: source === 'automation' ? ' <span class="sched-source">(automation)</span>'
|
|
1131
|
-
: source === 'default' ? ' <span class="sched-source">(default)</span>' : '');
|
|
1199
|
+
const toolCatalog = getCachedToolCatalog();
|
|
1200
|
+
const facts = launchFacts(s, { group, template, profiles: CLI_PROFILES, toolCatalog, translate: t });
|
|
1201
|
+
const sourceNote = (source) => (['group', 'automation', 'default'].includes(source)
|
|
1202
|
+
? ` <span class="sched-source">${te(`schedules.detail.source.${source}`)}</span>` : '');
|
|
1132
1203
|
const next = s.enabled
|
|
1133
|
-
? (s.nextRun ? `${esc(whenLabel(s.nextRun, now))} · <span data-countdown="${esc(s.nextRun)}">${esc(formatCountdown(s.nextRun, now).text)}</span>` :
|
|
1134
|
-
: '
|
|
1135
|
-
const last = run.kind === 'never' ?
|
|
1204
|
+
? (s.nextRun ? `${esc(whenLabel(s.nextRun, now))} · <span data-countdown="${esc(s.nextRun)}">${esc(formatCountdown(s.nextRun, now).text)}</span>` : esc(formatCountdown(null, now).text))
|
|
1205
|
+
: te('schedules.row.paused');
|
|
1206
|
+
const last = run.kind === 'never' ? esc(run.label)
|
|
1136
1207
|
: `<span class="${run.kind === 'failed' ? 'is-failed' : ''}">${esc(run.label)}</span>${s.lastRun ? ` · ${esc(whenLabel(s.lastRun, now))}` : ''}`;
|
|
1137
1208
|
// Any entry counts: with day themes, the server runs a schedule only on the days that have one.
|
|
1138
|
-
const themes = [1, 2, 3, 4, 5, 6, 0].filter(d => s.dayThemes?.[String(d)]).map(d =>
|
|
1209
|
+
const themes = [1, 2, 3, 4, 5, 6, 0].filter(d => s.dayThemes?.[String(d)]).map(d => weekdayName(d));
|
|
1139
1210
|
const groupOptions = [
|
|
1140
|
-
opt('', '
|
|
1211
|
+
opt('', viewLabel('ungrouped'), !s.groupId),
|
|
1141
1212
|
...orderedGroups().map(g => opt(g.id, g.name, s.groupId === g.id)),
|
|
1142
1213
|
];
|
|
1143
1214
|
const fact = (label, value, cls = '') => `<div class="sched-fact"><dt>${esc(label)}</dt><dd${cls ? ` class="${cls}"` : ''}>${value}</dd></div>`;
|
|
1144
1215
|
return `
|
|
1145
1216
|
<div class="sched-detail-grid">
|
|
1146
1217
|
<section class="sched-facts-block">
|
|
1147
|
-
<h4 class="sched-facts-title"
|
|
1218
|
+
<h4 class="sched-facts-title">${te('schedules.detail.runs')}</h4>
|
|
1148
1219
|
<dl class="sched-facts">
|
|
1149
|
-
${fact('
|
|
1150
|
-
${fact('
|
|
1151
|
-
${fact('
|
|
1152
|
-
${fact('
|
|
1153
|
-
${run.reason ? fact(run.kind === 'failed' ? '
|
|
1154
|
-
${fact('
|
|
1155
|
-
${fact('
|
|
1156
|
-
${themes.length ? fact('
|
|
1220
|
+
${fact(t('schedules.detail.automation'), template ? esc(template.name) : `<span class="is-failed">${te('schedules.detail.missingAutomation')}</span>`)}
|
|
1221
|
+
${fact(t('schedules.detail.timetable'), `${esc(describeCron(s.cron))} <span class="sched-source">(${esc(s.cron || '')})</span>`)}
|
|
1222
|
+
${fact(t('schedules.detail.nextRun'), next)}
|
|
1223
|
+
${fact(t('schedules.detail.lastRun'), last)}
|
|
1224
|
+
${run.reason ? fact(t(run.kind === 'failed' ? 'schedules.detail.error' : 'schedules.detail.detail'), esc(oneLine(run.reason))) : ''}
|
|
1225
|
+
${fact(t('schedules.detail.runCount'), esc(String(s.runCount || 0)))}
|
|
1226
|
+
${fact(t('schedules.detail.timeZone'), esc(s.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone))}
|
|
1227
|
+
${themes.length ? fact(t('schedules.detail.dayThemes'), `${esc(themes.join(', '))} <span class="sched-source">${te('schedules.detail.themedDaysOnly')}</span>`) : ''}
|
|
1157
1228
|
</dl>
|
|
1158
1229
|
</section>
|
|
1159
1230
|
<section class="sched-facts-block">
|
|
1160
|
-
<h4 class="sched-facts-title"
|
|
1231
|
+
<h4 class="sched-facts-title">${te('schedules.detail.launchSettings')}</h4>
|
|
1161
1232
|
<dl class="sched-facts">
|
|
1162
1233
|
${facts.map(f => fact(f.label, `${esc(f.value)}${sourceNote(f.source)}`, f.mono ? 'sched-mono' : '')).join('')}
|
|
1163
1234
|
</dl>
|
|
1164
1235
|
</section>
|
|
1165
1236
|
</div>
|
|
1166
1237
|
<div class="sched-detail-bar">
|
|
1167
|
-
<div class="sched-detail-group"><span aria-hidden="true"
|
|
1168
|
-
${dropdownHTML(`sched-move-${domId(s.id)}`, groupOptions, { label:
|
|
1238
|
+
<div class="sched-detail-group"><span aria-hidden="true">${te('schedules.detail.group')}</span>
|
|
1239
|
+
${dropdownHTML(`sched-move-${domId(s.id)}`, groupOptions, { label: t('schedules.detail.groupOf', { name: s.name }), inputAttrs: `data-change="move-group" data-id="${esc(s.id)}"` })}
|
|
1169
1240
|
</div>
|
|
1170
1241
|
<span class="sched-detail-spacer"></span>
|
|
1171
|
-
<button type="button" class="sched-btn" data-action="schedule-test" data-id="${esc(s.id)}" data-tooltip="
|
|
1172
|
-
<button type="button" class="sched-btn" data-action="schedule-edit-prompt" data-id="${esc(s.id)}" data-template-id="${esc(s.templateId || '')}" data-tooltip="
|
|
1173
|
-
<button type="button" class="sched-btn" data-action="schedule-edit" data-id="${esc(s.id)}">${ICONS.edit}<span
|
|
1174
|
-
<button type="button" class="sched-btn sched-btn--danger" data-action="schedule-delete" data-id="${esc(s.id)}">${ICONS.trash}<span
|
|
1242
|
+
<button type="button" class="sched-btn" data-action="schedule-test" data-id="${esc(s.id)}" data-tooltip="${te('schedules.detail.runNowTip')}">${ICONS.bolt}<span>${te('schedules.detail.runNow')}</span></button>
|
|
1243
|
+
<button type="button" class="sched-btn" data-action="schedule-edit-prompt" data-id="${esc(s.id)}" data-template-id="${esc(s.templateId || '')}" data-tooltip="${te('schedules.detail.editPromptTip')}">${ICONS.script}<span>${te('schedules.detail.editPrompt')}</span></button>
|
|
1244
|
+
<button type="button" class="sched-btn" data-action="schedule-edit" data-id="${esc(s.id)}">${ICONS.edit}<span>${te('schedules.detail.editSchedule')}</span></button>
|
|
1245
|
+
<button type="button" class="sched-btn sched-btn--danger" data-action="schedule-delete" data-id="${esc(s.id)}">${ICONS.trash}<span>${te('schedules.detail.delete')}</span></button>
|
|
1175
1246
|
</div>`;
|
|
1176
1247
|
}
|
|
1177
1248
|
|
|
@@ -1183,36 +1254,37 @@ function renderActivityList() {
|
|
|
1183
1254
|
if (clear) clear.hidden = !_query;
|
|
1184
1255
|
renderNote();
|
|
1185
1256
|
if (_loading || dataIncomplete()) {
|
|
1186
|
-
list.innerHTML = _loading ? emptyState(ICONS.clock, '
|
|
1257
|
+
list.innerHTML = _loading ? emptyState(ICONS.clock, t('schedules.list.loading'), '') : loadFailedHTML();
|
|
1187
1258
|
return;
|
|
1188
1259
|
}
|
|
1189
1260
|
const now = Date.now();
|
|
1190
1261
|
const ran = _schedules.filter(s => s.lastRun);
|
|
1191
1262
|
const shown = sortSchedules(ran.filter(s => matchesQuery(s, _query, scheduleExtras(s))), 'last');
|
|
1263
|
+
const query = _query.trim();
|
|
1192
1264
|
if (!shown.length) {
|
|
1193
|
-
list.innerHTML =
|
|
1194
|
-
? emptyState(ICONS.search,
|
|
1195
|
-
: emptyState(ICONS.activity, '
|
|
1196
|
-
setStatus(
|
|
1265
|
+
list.innerHTML = query
|
|
1266
|
+
? emptyState(ICONS.search, t('schedules.empty.noRunMatch', { query }), t('schedules.empty.tryAnother'), `<button type="button" class="sched-btn" data-action="clear-search">${te('schedules.list.clearSearch')}</button>`)
|
|
1267
|
+
: emptyState(ICONS.activity, t('schedules.empty.noRuns'), t('schedules.empty.noRunsText'));
|
|
1268
|
+
setStatus(query ? t('schedules.list.statusNoMatches') : t('schedules.empty.noRuns'));
|
|
1197
1269
|
restoreListState(list, saved);
|
|
1198
1270
|
return;
|
|
1199
1271
|
}
|
|
1200
|
-
list.innerHTML = `<div class="sched-box"><ul class="sched-rows sched-rows--activity" aria-label="
|
|
1272
|
+
list.innerHTML = `<div class="sched-box"><ul class="sched-rows sched-rows--activity" aria-label="${te('schedules.activity.listLabel')}">${shown.map(s => {
|
|
1201
1273
|
const run = lastRunState(s);
|
|
1202
1274
|
const template = getTemplate(s.templateId);
|
|
1203
|
-
const sub = run.kind === 'failed' && run.reason ? oneLine(run.reason) : template?.name || '
|
|
1204
|
-
const next = s.enabled ? formatCountdown(s.nextRun, now).text : '
|
|
1275
|
+
const sub = run.kind === 'failed' && run.reason ? oneLine(run.reason) : template?.name || t('schedules.detail.missingAutomation');
|
|
1276
|
+
const next = s.enabled ? formatCountdown(s.nextRun, now).text : t('schedules.row.paused');
|
|
1205
1277
|
return `<li class="sched-row" data-schedule-id="${esc(s.id)}">
|
|
1206
|
-
<button type="button" class="sched-activity-btn" data-action="reveal-schedule" data-id="${esc(s.id)}" aria-label="${
|
|
1278
|
+
<button type="button" class="sched-activity-btn" data-action="reveal-schedule" data-id="${esc(s.id)}" aria-label="${te('schedules.activity.rowLabel', { name: s.name, result: run.label, ago: formatAgo(s.lastRun, now) })}">
|
|
1207
1279
|
${lastRunHTML(s, now, run, { ago: false })}
|
|
1208
1280
|
<span class="sched-activity-main"><span class="sched-row-name">${esc(s.name)}</span><span class="sched-row-sub"><span>${esc(sub)}</span></span></span>
|
|
1209
1281
|
<span class="sched-activity-when"><span>${esc(formatAgo(s.lastRun, now))}</span><span>${esc(whenLabel(s.lastRun, now))}</span></span>
|
|
1210
|
-
<span class="sched-activity-when sched-activity-next"><span>${s.enabled ? '
|
|
1282
|
+
<span class="sched-activity-when sched-activity-next"><span>${s.enabled ? te('schedules.activity.next') : ''}</span><span>${esc(next)}</span></span>
|
|
1211
1283
|
<span class="sched-row-chevron" aria-hidden="true">${ICONS.chevron}</span>
|
|
1212
1284
|
</button>
|
|
1213
1285
|
</li>`;
|
|
1214
1286
|
}).join('')}</ul></div>`;
|
|
1215
|
-
setStatus(
|
|
1287
|
+
setStatus(query ? tp('schedules.activity.statusMatch', shown.length, { query }) : tp('schedules.count.runs', shown.length));
|
|
1216
1288
|
restoreListState(list, saved);
|
|
1217
1289
|
}
|
|
1218
1290
|
|
|
@@ -1247,20 +1319,19 @@ function renderTimersView() {
|
|
|
1247
1319
|
const customMinutes = $('sched-qt-custom-min')?.value || '';
|
|
1248
1320
|
const selectedSchedule = $('sched-existing-timer-schedule')?.value || '';
|
|
1249
1321
|
const scheduleMinutes = $('sched-existing-timer-min')?.value || '';
|
|
1250
|
-
const templateOptions = [opt('', '
|
|
1251
|
-
const scheduleOptions = [opt('', '
|
|
1322
|
+
const templateOptions = [opt('', t('schedules.form.chooseAutomation')), ..._templates.map(tpl => opt(tpl.id, tpl.name, selectedTemplate === tpl.id))];
|
|
1323
|
+
const scheduleOptions = [opt('', t('schedules.timers.chooseSchedule')), ...sortSchedules(_schedules, 'name').map(s => opt(s.id, s.name, selectedSchedule === s.id))];
|
|
1252
1324
|
const profileOptions = CLI_PROFILES.map(p => opt(p.id, p.label, p.id === _launchProfile));
|
|
1253
1325
|
const modelOptions = renderModelOptions(_launchProfile, _launchModel, { accountId: _launchAccount });
|
|
1254
1326
|
const contextOptions = renderContextOptions(_launchProfile, _launchModel, _launchContextMode, { accountId: _launchAccount });
|
|
1255
1327
|
const effortOptions = renderEffortOptions(_launchProfile, _launchEffort);
|
|
1256
|
-
const mcpOptions = renderMcpOptions(_launchMcpProfile);
|
|
1257
1328
|
const accountOptions = renderAccountOptions(_launchAccount, false);
|
|
1258
1329
|
const showEffort = profileSupportsEffort(_launchProfile);
|
|
1259
|
-
const showMcp = _launchProfile
|
|
1330
|
+
const showMcp = strictToolHost(_launchProfile);
|
|
1260
1331
|
const showAccount = _launchProfile === 'codex';
|
|
1261
1332
|
const usesBrowser = _qtUsesBrowser !== null ? _qtUsesBrowser : !!getTemplate(selectedTemplate)?.usesBrowser;
|
|
1262
1333
|
const qtPresetButtons = [5, 15, 30, 60, 120, 240]
|
|
1263
|
-
.map(min => `<button type="button" class="sched-chip" data-action="qt-select" data-minutes="${min}" aria-pressed="${_selectedQtMinutes === min}">${min >= 60 ?
|
|
1334
|
+
.map(min => `<button type="button" class="sched-chip" data-action="qt-select" data-minutes="${min}" aria-pressed="${_selectedQtMinutes === min}">${min >= 60 ? te('schedules.timers.presetHours', { n: min / 60 }) : te('schedules.timers.presetMinutes', { n: min })}</button>`)
|
|
1264
1335
|
.join('');
|
|
1265
1336
|
|
|
1266
1337
|
const modelLoadKey = `${_launchProfile}:${_launchAccount}`;
|
|
@@ -1269,52 +1340,52 @@ function renderTimersView() {
|
|
|
1269
1340
|
ensureModelsForProfile(_launchProfile, false, _launchAccount).then(() => { if (_panel && _frame === 'timers') renderTimersView(); });
|
|
1270
1341
|
}
|
|
1271
1342
|
if (showAccount) ensureCodexAccountsLoaded(() => { if (_panel && _frame === 'timers') renderTimersView(); });
|
|
1272
|
-
if (showMcp && !
|
|
1273
|
-
|
|
1343
|
+
if (showMcp && !getCachedToolCatalog()) {
|
|
1344
|
+
fetchToolCatalog().then(catalog => { if (catalog && _panel && _frame === 'timers') renderTimersView(); });
|
|
1274
1345
|
}
|
|
1275
1346
|
|
|
1276
1347
|
main.innerHTML = `
|
|
1277
1348
|
<div class="sched-view-head">
|
|
1278
1349
|
<div class="sched-view-titles">
|
|
1279
|
-
<h2 id="sched-view-title"
|
|
1280
|
-
<p class="sched-view-sub"
|
|
1350
|
+
<h2 id="sched-view-title">${esc(viewLabel('timers'))}</h2>
|
|
1351
|
+
<p class="sched-view-sub">${te('schedules.timers.sub')}</p>
|
|
1281
1352
|
</div>
|
|
1282
1353
|
</div>
|
|
1283
1354
|
<div class="sched-load-note" id="sched-load-note" role="status"></div>
|
|
1284
1355
|
<div class="sched-editor-body">
|
|
1285
1356
|
<div class="sched-form">
|
|
1286
1357
|
<section class="sched-form-section" aria-labelledby="sched-timers-active-title">
|
|
1287
|
-
<h3 class="sched-form-title" id="sched-timers-active-title"
|
|
1358
|
+
<h3 class="sched-form-title" id="sched-timers-active-title">${te('schedules.timers.waiting')}</h3>
|
|
1288
1359
|
<div id="sched-active-timers"></div>
|
|
1289
1360
|
</section>
|
|
1290
1361
|
<section class="sched-form-section" aria-labelledby="sched-qt-title">
|
|
1291
|
-
<h3 class="sched-form-title" id="sched-qt-title"
|
|
1362
|
+
<h3 class="sched-form-title" id="sched-qt-title">${te('schedules.timers.runOnce')}</h3>
|
|
1292
1363
|
<div class="sched-form-group">
|
|
1293
|
-
${dropdownRow('sched-qt-template', '
|
|
1294
|
-
${dropdownRow('sched-qt-profile', '
|
|
1295
|
-
${dropdownRow('sched-qt-model', '
|
|
1296
|
-
${dropdownRow('sched-qt-context', '
|
|
1297
|
-
${dropdownRow('sched-qt-effort', '
|
|
1298
|
-
${
|
|
1299
|
-
${dropdownRow('sched-qt-account', '
|
|
1300
|
-
${formRow('sched-qt-browser', '
|
|
1301
|
-
${formRow('sched-qt-custom-min', '
|
|
1302
|
-
<input class="sched-input" id="sched-qt-custom-min" type="number" min="1" max="1440" value="${esc(customMinutes)}" placeholder="
|
|
1364
|
+
${dropdownRow('sched-qt-template', t('schedules.form.automation'), templateOptions)}
|
|
1365
|
+
${dropdownRow('sched-qt-profile', t('schedules.form.cli'), profileOptions)}
|
|
1366
|
+
${dropdownRow('sched-qt-model', t('schedules.form.model'), modelOptions)}
|
|
1367
|
+
${dropdownRow('sched-qt-context', t('schedules.form.context'), contextOptions)}
|
|
1368
|
+
${dropdownRow('sched-qt-effort', t('schedules.form.thinking'), effortOptions, { hidden: !showEffort })}
|
|
1369
|
+
${toolRow('sched-qt-mcp', _launchTools, { provider: _launchProfile, usesBrowser })}
|
|
1370
|
+
${dropdownRow('sched-qt-account', t('schedules.form.account'), accountOptions, { hidden: !showAccount, rowId: 'sched-qt-account-field' })}
|
|
1371
|
+
${formRow('sched-qt-browser', t('schedules.timers.usesBrowser'), toggleHTML('sched-qt-browser', usesBrowser), { toggle: true })}
|
|
1372
|
+
${formRow('sched-qt-custom-min', t('schedules.timers.startIn'), `<span class="sched-chip-list" role="group" aria-label="${te('schedules.timers.delay')}">${qtPresetButtons}</span>
|
|
1373
|
+
<input class="sched-input" id="sched-qt-custom-min" type="number" min="1" max="1440" value="${esc(customMinutes)}" placeholder="${te('schedules.timers.minutesPlaceholder')}" aria-label="${te('schedules.timers.minutesFromNow')}">`, { top: true })}
|
|
1303
1374
|
</div>
|
|
1304
1375
|
<div class="sched-form-actions">
|
|
1305
|
-
<button type="button" class="sched-btn" data-action="qt-now">${ICONS.bolt}<span
|
|
1306
|
-
<button type="button" class="sched-btn sched-btn--primary" data-action="qt-go">${ICONS.timer}<span
|
|
1376
|
+
<button type="button" class="sched-btn" data-action="qt-now">${ICONS.bolt}<span>${te('schedules.detail.runNow')}</span></button>
|
|
1377
|
+
<button type="button" class="sched-btn sched-btn--primary" data-action="qt-go">${ICONS.timer}<span>${te('schedules.timers.start')}</span></button>
|
|
1307
1378
|
</div>
|
|
1308
1379
|
</section>
|
|
1309
1380
|
<section class="sched-form-section" aria-labelledby="sched-st-title">
|
|
1310
|
-
<h3 class="sched-form-title" id="sched-st-title"
|
|
1311
|
-
<p class="sched-form-intro"
|
|
1381
|
+
<h3 class="sched-form-title" id="sched-st-title">${te('schedules.timers.fireSaved')}</h3>
|
|
1382
|
+
<p class="sched-form-intro">${te('schedules.timers.fireSavedIntro')}</p>
|
|
1312
1383
|
<div class="sched-form-group">
|
|
1313
|
-
${dropdownRow('sched-existing-timer-schedule', '
|
|
1314
|
-
${formRow('sched-existing-timer-min', '
|
|
1384
|
+
${dropdownRow('sched-existing-timer-schedule', t('schedules.timers.schedule'), scheduleOptions)}
|
|
1385
|
+
${formRow('sched-existing-timer-min', t('schedules.timers.minutesFromNow'), `<input class="sched-input" id="sched-existing-timer-min" type="number" min="1" max="1440" value="${esc(scheduleMinutes)}" placeholder="15">`)}
|
|
1315
1386
|
</div>
|
|
1316
1387
|
<div class="sched-form-actions">
|
|
1317
|
-
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-timer-create"
|
|
1388
|
+
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-timer-create">${te('schedules.timers.set')}</button>
|
|
1318
1389
|
</div>
|
|
1319
1390
|
</section>
|
|
1320
1391
|
</div>
|
|
@@ -1353,8 +1424,9 @@ function renderTimersView() {
|
|
|
1353
1424
|
storage.setItem('as-launch-effort', _launchEffort);
|
|
1354
1425
|
});
|
|
1355
1426
|
$('sched-qt-mcp')?.addEventListener('change', (e) => {
|
|
1356
|
-
|
|
1357
|
-
storage.setItem('as-launch-
|
|
1427
|
+
_launchTools = readToolPicker(e.target);
|
|
1428
|
+
storage.setItem('as-launch-tools', JSON.stringify(_launchTools));
|
|
1429
|
+
updateToolPicker(e.target, _launchTools, { inheritLabel: toolWord('templateDefault'), usesBrowser });
|
|
1358
1430
|
});
|
|
1359
1431
|
$('sched-qt-account')?.addEventListener('change', (e) => {
|
|
1360
1432
|
_launchAccount = e.target.value || 'default';
|
|
@@ -1371,13 +1443,19 @@ function renderTimersView() {
|
|
|
1371
1443
|
if (contextPopup) setDropdownItems(contextPopup, renderContextOptions('codex', _launchModel, _launchContextMode, { accountId: _launchAccount }));
|
|
1372
1444
|
});
|
|
1373
1445
|
});
|
|
1374
|
-
$('sched-qt-browser')?.addEventListener('change', (e) => {
|
|
1446
|
+
$('sched-qt-browser')?.addEventListener('change', (e) => {
|
|
1447
|
+
_qtUsesBrowser = e.target.checked;
|
|
1448
|
+
const input = $('sched-qt-mcp');
|
|
1449
|
+
updateToolPicker(input, readToolPicker(input, { requested: true }), { inheritLabel: toolWord('templateDefault'), usesBrowser: _qtUsesBrowser });
|
|
1450
|
+
});
|
|
1375
1451
|
$('sched-qt-template')?.addEventListener('change', (e) => {
|
|
1376
1452
|
const template = getTemplate(e.target.value);
|
|
1377
1453
|
const browser = $('sched-qt-browser');
|
|
1378
1454
|
if (browser && template) {
|
|
1379
1455
|
browser.checked = !!template.usesBrowser;
|
|
1380
1456
|
_qtUsesBrowser = null;
|
|
1457
|
+
const input = $('sched-qt-mcp');
|
|
1458
|
+
updateToolPicker(input, readToolPicker(input, { requested: true }), { inheritLabel: toolWord('templateDefault'), usesBrowser: browser.checked });
|
|
1381
1459
|
}
|
|
1382
1460
|
});
|
|
1383
1461
|
}
|
|
@@ -1389,31 +1467,38 @@ function renderModelOptions(profile, selected, { showService = false, accountId
|
|
|
1389
1467
|
if (!models.length) {
|
|
1390
1468
|
// List not loaded yet — preserve any stored selection so saving the form cannot
|
|
1391
1469
|
// blank it (a blank popup makes save send model:null, which DELETES the override).
|
|
1392
|
-
if (selected) return [opt('', '
|
|
1393
|
-
return [opt('', '
|
|
1470
|
+
if (selected) return [opt('', t('schedules.launch.templateOrDefault')), opt(selected, selected, true)];
|
|
1471
|
+
return [opt('', t('schedules.launch.default'), true)];
|
|
1394
1472
|
}
|
|
1395
1473
|
const known = !!selected && models.some(m => modelMatchesSelector(m, selected));
|
|
1396
1474
|
const current = selected || '';
|
|
1397
1475
|
const opts = [
|
|
1398
|
-
opt('', '
|
|
1476
|
+
opt('', t('schedules.launch.templateOrDefault'), !current),
|
|
1399
1477
|
...models.map(m => opt(modelSelectorValue(m), formatModelOptionLabel(m, {
|
|
1400
1478
|
includeService: showService && profile === 'opencode',
|
|
1401
1479
|
}), modelMatchesSelector(m, current))),
|
|
1402
1480
|
];
|
|
1403
1481
|
// Preserve a stored model that isn't in the (still-loading or curated) list so the
|
|
1404
1482
|
// dropdown never silently blanks it and a save can't wipe the persisted override.
|
|
1405
|
-
if (selected && !known) opts.push(opt(selected,
|
|
1483
|
+
if (selected && !known) opts.push(opt(selected, t('schedules.launch.saved', { value: selected }), true));
|
|
1406
1484
|
return opts;
|
|
1407
1485
|
}
|
|
1408
1486
|
|
|
1487
|
+
/** A context choice of automation-context.js in the page's language (its size stays as written). */
|
|
1488
|
+
function contextChoiceLabel(choice) {
|
|
1489
|
+
if (choice.id === 'default') return choice.size ? t('schedules.launch.contextDefaultSize', { size: choice.size }) : t('schedules.launch.contextProviderDefault');
|
|
1490
|
+
if (choice.id === 'extended' && choice.size) return t('schedules.launch.contextExtendedSize', { size: choice.size });
|
|
1491
|
+
return choice.label;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1409
1494
|
function renderContextOptions(profile, model, selected, { accountId = 'default', inherit = false } = {}) {
|
|
1410
1495
|
const models = getModelsForProfile(profile, accountId);
|
|
1411
1496
|
const choices = automationContextOptions(profile, model, models);
|
|
1412
1497
|
const current = selected || (inherit ? '' : 'default');
|
|
1413
|
-
const options = inherit ? [opt('', '
|
|
1414
|
-
for (const choice of choices) options.push(opt(choice.id, choice
|
|
1498
|
+
const options = inherit ? [opt('', t('schedules.launch.noOverride'), !current)] : [];
|
|
1499
|
+
for (const choice of choices) options.push(opt(choice.id, contextChoiceLabel(choice), current === choice.id));
|
|
1415
1500
|
if (current && !choices.some((choice) => choice.id === current)) {
|
|
1416
|
-
options.push(opt(current,
|
|
1501
|
+
options.push(opt(current, t('schedules.launch.unavailable', { value: current }), true));
|
|
1417
1502
|
}
|
|
1418
1503
|
return options;
|
|
1419
1504
|
}
|
|
@@ -1434,33 +1519,22 @@ function modelChoiceAvailable(profile, model, accountId = 'default') {
|
|
|
1434
1519
|
function renderEffortOptions(profile, selected, { inheritLabel = null } = {}) {
|
|
1435
1520
|
const levels = getEffortLevelsForProfile(profile);
|
|
1436
1521
|
const value = selected || '';
|
|
1437
|
-
if (!inheritLabel && !levels.length) return [opt('off', '
|
|
1522
|
+
if (!inheritLabel && !levels.length) return [opt('off', t('schedules.launch.default'), true)];
|
|
1438
1523
|
const known = !!value && levels.some(e => e.id === value);
|
|
1439
1524
|
const opts = inheritLabel ? [opt('', inheritLabel, !value)] : [];
|
|
1440
|
-
for (const e of levels)
|
|
1525
|
+
for (const e of levels) {
|
|
1526
|
+
opts.push(opt(e.id, e.desc ? t('schedules.launch.effortOption', { level: launchWord(e.label), desc: launchWord(e.desc) }) : launchWord(e.label), e.id === value));
|
|
1527
|
+
}
|
|
1441
1528
|
// A saved level this CLI does not list (set for another CLI, or by hand) stays selected,
|
|
1442
1529
|
// so saving the form cannot drop it (the model, MCP and account popups do the same).
|
|
1443
|
-
if (value && !known) opts.push(opt(value,
|
|
1530
|
+
if (value && !known) opts.push(opt(value, t('schedules.launch.saved', { value }), true));
|
|
1444
1531
|
return opts;
|
|
1445
1532
|
}
|
|
1446
1533
|
|
|
1447
|
-
function
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
if (!selected) return [opt('', 'Loading...')];
|
|
1452
|
-
return [opt(selected, `${selected} (loading...)`, true)];
|
|
1453
|
-
}
|
|
1454
|
-
const known = !!selected && !!presets[selected];
|
|
1455
|
-
const options = entries.map(([id, p]) => {
|
|
1456
|
-
const label = p.label || id;
|
|
1457
|
-
const tools = p.tools != null ? ` - ${p.tools} tools` : '';
|
|
1458
|
-
return opt(id, `${label}${tools}`, known && id === selected);
|
|
1459
|
-
});
|
|
1460
|
-
// A saved tool list that is not a preset ("browser,browser_twitter") stays selected, so saving
|
|
1461
|
-
// the form cannot quietly replace it with another preset (the model popup does the same).
|
|
1462
|
-
if (selected && !known) options.push(opt(selected, `${selected} (saved)`, true));
|
|
1463
|
-
return options;
|
|
1534
|
+
function toolRow(id, tools, { provider, inheritLabel = toolWord('templateDefault'), usesBrowser } = {}) {
|
|
1535
|
+
return formRow(id, toolWord('label'), strictToolHost(provider)
|
|
1536
|
+
? toolPickerHTML(id, tools, { labelledBy: `${id}-label`, provider, inheritLabel, usesBrowser })
|
|
1537
|
+
: `<span>${esc(toolWord('focus'))}</span>`, { labelId: `${id}-label` });
|
|
1464
1538
|
}
|
|
1465
1539
|
|
|
1466
1540
|
// The choices of the Codex account popup. `allowInherit` adds a
|
|
@@ -1474,12 +1548,14 @@ function renderAccountOptions(selected, allowInherit = false) {
|
|
|
1474
1548
|
const keep = allowInherit && !!selected && !known;
|
|
1475
1549
|
const current = known || keep ? selected : (allowInherit ? '' : 'default');
|
|
1476
1550
|
const opts = accounts.map(a => {
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1551
|
+
// The default account is "Default" in SynaBun's own list (agent-runtime-options.js): that word is
|
|
1552
|
+
// the page's; an account's own label or address shows as it is.
|
|
1553
|
+
const ownDefault = a.isDefault && (a.label === 'Default' || (!a.label && !a.email));
|
|
1554
|
+
const label = ownDefault ? t('schedules.launch.default') : a.label || a.email || a.id;
|
|
1555
|
+
return opt(a.id, a.planType ? t('schedules.launch.accountPlan', { label, plan: a.planType }) : label, a.id === current);
|
|
1480
1556
|
});
|
|
1481
|
-
if (allowInherit) opts.unshift(opt('', '
|
|
1482
|
-
if (keep) opts.push(opt(selected,
|
|
1557
|
+
if (allowInherit) opts.unshift(opt('', t('schedules.launch.templateOrGroupDefault'), !current));
|
|
1558
|
+
if (keep) opts.push(opt(selected, t('schedules.launch.saved', { value: selected }), true));
|
|
1483
1559
|
return opts;
|
|
1484
1560
|
}
|
|
1485
1561
|
|
|
@@ -1495,12 +1571,12 @@ function renderActiveTimersInto() {
|
|
|
1495
1571
|
if (!box) return;
|
|
1496
1572
|
const scheduleTimers = Object.entries(_scheduleTimerData).map(([scheduleId, timer]) => {
|
|
1497
1573
|
const schedule = _schedules.find(s => s.id === scheduleId);
|
|
1498
|
-
const name = schedule?.name || '
|
|
1574
|
+
const name = schedule?.name || t('schedules.timers.schedule');
|
|
1499
1575
|
return `
|
|
1500
1576
|
<div class="sched-timer-row" data-timer-id="${esc(scheduleId)}" data-timer-kind="schedule">
|
|
1501
|
-
<span class="sched-timer-name">${esc(name)}<span class="sched-timer-meta"
|
|
1502
|
-
<strong class="sched-timer-count" data-fires-at="${esc(timer.firesAt)}">${formatTimerCountdown(timer.firesAt)}</strong>
|
|
1503
|
-
<button type="button" class="sched-btn" data-action="schedule-timer-cancel" data-id="${esc(scheduleId)}" aria-label="
|
|
1577
|
+
<span class="sched-timer-name">${esc(name)}<span class="sched-timer-meta">${te('schedules.timers.savedOnce')}</span></span>
|
|
1578
|
+
<strong class="sched-timer-count" data-fires-at="${esc(timer.firesAt)}">${esc(formatTimerCountdown(timer.firesAt))}</strong>
|
|
1579
|
+
<button type="button" class="sched-btn" data-action="schedule-timer-cancel" data-id="${esc(scheduleId)}" aria-label="${te('schedules.timers.cancelFor', { name })}">${te('schedules.timers.cancel')}</button>
|
|
1504
1580
|
</div>`;
|
|
1505
1581
|
}).join('');
|
|
1506
1582
|
|
|
@@ -1508,23 +1584,23 @@ function renderActiveTimersInto() {
|
|
|
1508
1584
|
const meta = [
|
|
1509
1585
|
timer.profile ? CLI_PROFILES.find(p => p.id === timer.profile)?.label || timer.profile : '',
|
|
1510
1586
|
timer.model,
|
|
1511
|
-
timer.contextMode && timer.contextMode !== 'default' ?
|
|
1512
|
-
timer.effort ?
|
|
1513
|
-
timer.
|
|
1587
|
+
timer.contextMode && timer.contextMode !== 'default' ? t('schedules.overrides.context', { value: timer.contextMode }) : '',
|
|
1588
|
+
timer.effort ? t('schedules.overrides.thinking', { value: timer.effort }) : '',
|
|
1589
|
+
timer.tools ? t('schedules.overrides.mcp', { value: timer.tools.join(', ') }) : '',
|
|
1514
1590
|
].filter(Boolean).join(' · ');
|
|
1515
1591
|
return `
|
|
1516
1592
|
<div class="sched-timer-row" data-timer-id="${esc(timer.id)}" data-timer-kind="quick">
|
|
1517
|
-
<span class="sched-timer-name">${esc(timer.templateName)}<span class="sched-timer-meta">${esc(meta || '
|
|
1518
|
-
<strong class="sched-timer-count" data-fires-at="${esc(timer.firesAt)}">${formatTimerCountdown(timer.firesAt)}</strong>
|
|
1519
|
-
<button type="button" class="sched-btn" data-action="qt-cancel" data-id="${esc(timer.id)}" aria-label="
|
|
1593
|
+
<span class="sched-timer-name">${esc(timer.templateName)}<span class="sched-timer-meta">${esc(meta || t('schedules.timers.automationOnce'))}</span></span>
|
|
1594
|
+
<strong class="sched-timer-count" data-fires-at="${esc(timer.firesAt)}">${esc(formatTimerCountdown(timer.firesAt))}</strong>
|
|
1595
|
+
<button type="button" class="sched-btn" data-action="qt-cancel" data-id="${esc(timer.id)}" aria-label="${te('schedules.timers.cancelFor', { name: timer.templateName })}">${te('schedules.timers.cancel')}</button>
|
|
1520
1596
|
</div>`;
|
|
1521
1597
|
}).join('');
|
|
1522
1598
|
|
|
1523
1599
|
// The timers load on their own: until they answer (or when they could not), say so here.
|
|
1524
1600
|
const note = _timersError
|
|
1525
|
-
? `<div class="sched-load-note">${ICONS.alert}<span>${esc(_timersError)}</span><button type="button" class="sched-btn" data-action="retry-timers"
|
|
1601
|
+
? `<div class="sched-load-note">${ICONS.alert}<span>${esc(_timersError)}</span><button type="button" class="sched-btn" data-action="retry-timers">${te('schedules.note.tryAgain')}</button></div>`
|
|
1526
1602
|
: '';
|
|
1527
|
-
const empty = _timersError ? '' : `<p class="sched-form-intro">${_timersLoaded ? '
|
|
1603
|
+
const empty = _timersError ? '' : `<p class="sched-form-intro">${_timersLoaded ? te('schedules.timers.none') : te('schedules.timers.loading')}</p>`;
|
|
1528
1604
|
box.innerHTML = note + (scheduleTimers || quickTimers ? `<div class="sched-form-group">${quickTimers}${scheduleTimers}</div>` : empty);
|
|
1529
1605
|
}
|
|
1530
1606
|
|
|
@@ -1584,7 +1660,7 @@ function redrawEditor(main, html) {
|
|
|
1584
1660
|
|
|
1585
1661
|
function editorHead({ back, title, sub, titleId }) {
|
|
1586
1662
|
return `<header class="sched-editor-head">
|
|
1587
|
-
<button type="button" class="sched-icon-btn" data-action="${back}" aria-label="
|
|
1663
|
+
<button type="button" class="sched-icon-btn" data-action="${back}" aria-label="${te('schedules.editor.back')}" data-tooltip="${te('schedules.editor.backTip')}">${ICONS.back}</button>
|
|
1588
1664
|
<div class="sched-editor-titles"><h2 id="${titleId}">${esc(title)}</h2>${sub ? `<p>${esc(sub)}</p>` : ''}</div>
|
|
1589
1665
|
</header>`;
|
|
1590
1666
|
}
|
|
@@ -1598,8 +1674,8 @@ function openGroupEditor(id) {
|
|
|
1598
1674
|
// `_original` is the group as it was when the editor opened: a save sends only what the user
|
|
1599
1675
|
// changed from it, so a change made elsewhere meanwhile (a sync update) is never undone.
|
|
1600
1676
|
_groupEditorDraft = group
|
|
1601
|
-
? { name: group.name, color: group.color, mutualExclusion: !!group.mutualExclusion, profile: group.profile || null, model: group.model || null, contextMode: group.contextMode || null, effort: group.effort || null,
|
|
1602
|
-
: { name: '', color: DEFAULT_GROUP_COLOR, mutualExclusion: false, profile: null, model: null, contextMode: null, effort: null,
|
|
1677
|
+
? { name: group.name, color: group.color, mutualExclusion: !!group.mutualExclusion, profile: group.profile || null, model: group.model || null, contextMode: group.contextMode || null, effort: group.effort || null, tools: group.tools || null, codexAccountId: group.codexAccountId || null, usesBrowser: group.usesBrowser, _original: JSON.parse(JSON.stringify(group)) }
|
|
1678
|
+
: { name: '', color: DEFAULT_GROUP_COLOR, mutualExclusion: false, profile: null, model: null, contextMode: null, effort: null, tools: null, codexAccountId: null, usesBrowser: undefined };
|
|
1603
1679
|
ensureModelsForProfile(_groupEditorDraft.profile || 'claude-code', false, _groupEditorDraft.codexAccountId || 'default').then(() => {
|
|
1604
1680
|
if (_panel && _groupEditorDraft) {
|
|
1605
1681
|
collectGroupEditorDraft();
|
|
@@ -1619,7 +1695,7 @@ function collectGroupEditorDraft() {
|
|
|
1619
1695
|
model: $('sched-ge-model')?.value || null,
|
|
1620
1696
|
contextMode: $('sched-ge-context')?.value || null,
|
|
1621
1697
|
effort: $('sched-ge-effort')?.value || null,
|
|
1622
|
-
|
|
1698
|
+
tools: readEditorTools('sched-ge-mcp', _groupEditorDraft?.tools),
|
|
1623
1699
|
codexAccountId: $('sched-ge-account')?.value || null,
|
|
1624
1700
|
usesBrowser: browserRaw === '' ? undefined : browserRaw === 'true',
|
|
1625
1701
|
});
|
|
@@ -1635,20 +1711,19 @@ function renderGroupEditor(group) {
|
|
|
1635
1711
|
const profileForModels = d.profile || 'claude-code';
|
|
1636
1712
|
const accountId = d.codexAccountId || 'default';
|
|
1637
1713
|
const profileOptions = [
|
|
1638
|
-
opt('', '
|
|
1714
|
+
opt('', t('schedules.launch.noOverride'), !d.profile),
|
|
1639
1715
|
...CLI_PROFILES.map(p => opt(p.id, p.label, d.profile === p.id)),
|
|
1640
1716
|
];
|
|
1641
1717
|
const modelOptions = renderModelOptions(profileForModels, d.model || '', { showService: true, accountId });
|
|
1642
1718
|
const contextOptions = renderContextOptions(profileForModels, d.model, d.contextMode, { accountId, inherit: true });
|
|
1643
|
-
const effortOptions = renderEffortOptions(profileForModels, d.effort || '', { inheritLabel: '
|
|
1644
|
-
const mcpOptions = renderMcpOptions(d.mcpProfile || '');
|
|
1719
|
+
const effortOptions = renderEffortOptions(profileForModels, d.effort || '', { inheritLabel: t('schedules.launch.noOverride') });
|
|
1645
1720
|
const accountOptions = renderAccountOptions(d.codexAccountId || '', true);
|
|
1646
1721
|
const showEffort = profileSupportsEffort(profileForModels);
|
|
1647
|
-
const showMcp = profileForModels
|
|
1722
|
+
const showMcp = strictToolHost(profileForModels);
|
|
1648
1723
|
const showAccount = profileForModels === 'codex';
|
|
1649
1724
|
const rerender = () => { if (_panel && _groupEditorDraft) { collectGroupEditorDraft(); renderGroupEditor(_groupEditorId ? getGroup(_groupEditorId) : null); } };
|
|
1650
1725
|
if (showAccount) ensureCodexAccountsLoaded(rerender);
|
|
1651
|
-
if (showMcp && !
|
|
1726
|
+
if (showMcp && !getCachedToolCatalog()) fetchToolCatalog().then(catalog => { if (catalog) rerender(); });
|
|
1652
1727
|
const browserValue = d.usesBrowser === true ? 'true' : d.usesBrowser === false ? 'false' : '';
|
|
1653
1728
|
const ordered = orderedGroups();
|
|
1654
1729
|
const position = group ? ordered.findIndex(g => g.id === group.id) : -1;
|
|
@@ -1656,55 +1731,55 @@ function renderGroupEditor(group) {
|
|
|
1656
1731
|
|
|
1657
1732
|
redrawEditor(main, `
|
|
1658
1733
|
<div class="sched-editor">
|
|
1659
|
-
${editorHead({ back: 'group-editor-cancel', title: creating ? '
|
|
1734
|
+
${editorHead({ back: 'group-editor-cancel', title: t(creating ? 'schedules.groupEditor.newTitle' : 'schedules.head.editGroup'), sub: creating ? t('schedules.groupEditor.newSub') : (group || d._original)?.name, titleId: 'sched-view-title' })}
|
|
1660
1735
|
<div class="sched-editor-body">
|
|
1661
1736
|
<div class="sched-form">
|
|
1662
1737
|
<section class="sched-form-section" aria-labelledby="sched-ge-sec-group">
|
|
1663
|
-
<h3 class="sched-form-title" id="sched-ge-sec-group"
|
|
1738
|
+
<h3 class="sched-form-title" id="sched-ge-sec-group">${te('schedules.form.group')}</h3>
|
|
1664
1739
|
<div class="sched-form-group">
|
|
1665
|
-
${formRow('sched-ge-name', '
|
|
1666
|
-
${formRow('sched-ge-color', '
|
|
1667
|
-
${formRow('sched-ge-mutex', '
|
|
1668
|
-
{ help: '
|
|
1740
|
+
${formRow('sched-ge-name', t('schedules.form.name'), `<input class="sched-input" id="sched-ge-name" type="text" value="${esc(d.name || '')}" placeholder="${te('schedules.groupEditor.namePlaceholder')}" autocomplete="off">`)}
|
|
1741
|
+
${formRow('sched-ge-color', t('schedules.groupEditor.colour'), `<input class="sched-input" id="sched-ge-color" type="color" value="${esc(/^#[0-9a-f]{6}$/i.test(d.color || '') ? d.color : DEFAULT_GROUP_COLOR)}">`)}
|
|
1742
|
+
${formRow('sched-ge-mutex', t('schedules.oneAtATime'), toggleHTML('sched-ge-mutex', !!d.mutualExclusion, 'sched-ge-mutex-help'),
|
|
1743
|
+
{ help: te('schedules.groupEditor.oneAtATimeHelp'), toggle: true })}
|
|
1669
1744
|
</div>
|
|
1670
1745
|
</section>
|
|
1671
1746
|
<section class="sched-form-section" aria-labelledby="sched-ge-sec-launch">
|
|
1672
|
-
<h3 class="sched-form-title" id="sched-ge-sec-launch"
|
|
1673
|
-
<p class="sched-form-intro"
|
|
1747
|
+
<h3 class="sched-form-title" id="sched-ge-sec-launch">${te('schedules.form.launchOverrides')}</h3>
|
|
1748
|
+
<p class="sched-form-intro">${te('schedules.groupEditor.launchIntro')}</p>
|
|
1674
1749
|
<div class="sched-form-group">
|
|
1675
|
-
${dropdownRow('sched-ge-profile', '
|
|
1676
|
-
${dropdownRow('sched-ge-model', '
|
|
1677
|
-
${dropdownRow('sched-ge-context', '
|
|
1678
|
-
${dropdownRow('sched-ge-effort', '
|
|
1679
|
-
${
|
|
1680
|
-
${dropdownRow('sched-ge-account', '
|
|
1681
|
-
${dropdownRow('sched-ge-browser', '
|
|
1682
|
-
opt('', '
|
|
1683
|
-
opt('true', '
|
|
1684
|
-
opt('false', '
|
|
1750
|
+
${dropdownRow('sched-ge-profile', t('schedules.form.cli'), profileOptions)}
|
|
1751
|
+
${dropdownRow('sched-ge-model', t('schedules.form.model'), modelOptions)}
|
|
1752
|
+
${dropdownRow('sched-ge-context', t('schedules.form.context'), contextOptions)}
|
|
1753
|
+
${dropdownRow('sched-ge-effort', t('schedules.form.thinking'), effortOptions, { hidden: !showEffort })}
|
|
1754
|
+
${toolRow('sched-ge-mcp', d.tools, { provider: profileForModels, inheritLabel: toolWord('noOverride'), usesBrowser: d.usesBrowser })}
|
|
1755
|
+
${dropdownRow('sched-ge-account', t('schedules.form.account'), accountOptions, { hidden: !showAccount, rowId: 'sched-ge-account-field' })}
|
|
1756
|
+
${dropdownRow('sched-ge-browser', t('schedules.form.browser'), [
|
|
1757
|
+
opt('', t('schedules.launch.noOverride'), browserValue === ''),
|
|
1758
|
+
opt('true', t('schedules.launch.alwaysOn'), browserValue === 'true'),
|
|
1759
|
+
opt('false', t('schedules.launch.alwaysOff'), browserValue === 'false'),
|
|
1685
1760
|
])}
|
|
1686
1761
|
</div>
|
|
1687
1762
|
</section>
|
|
1688
1763
|
${!group ? '' : `
|
|
1689
1764
|
<section class="sched-form-section" aria-labelledby="sched-ge-sec-order">
|
|
1690
|
-
<h3 class="sched-form-title" id="sched-ge-sec-order"
|
|
1765
|
+
<h3 class="sched-form-title" id="sched-ge-sec-order">${te('schedules.groupEditor.order')}</h3>
|
|
1691
1766
|
<div class="sched-form-group">
|
|
1692
1767
|
<div class="sched-form-row">
|
|
1693
|
-
<span class="sched-form-label"
|
|
1768
|
+
<span class="sched-form-label">${te('schedules.groupEditor.position', { position: position + 1, count: ordered.length })}</span>
|
|
1694
1769
|
<div class="sched-form-control">
|
|
1695
|
-
<button type="button" class="sched-btn" data-action="group-up" data-id="${esc(group.id)}"${position <= 0 ? ' disabled' : ''}>${ICONS.up}<span
|
|
1696
|
-
<button type="button" class="sched-btn" data-action="group-down" data-id="${esc(group.id)}"${position >= ordered.length - 1 ? ' disabled' : ''}>${ICONS.down}<span
|
|
1770
|
+
<button type="button" class="sched-btn" data-action="group-up" data-id="${esc(group.id)}"${position <= 0 ? ' disabled' : ''}>${ICONS.up}<span>${te('schedules.groupEditor.moveUp')}</span></button>
|
|
1771
|
+
<button type="button" class="sched-btn" data-action="group-down" data-id="${esc(group.id)}"${position >= ordered.length - 1 ? ' disabled' : ''}>${ICONS.down}<span>${te('schedules.groupEditor.moveDown')}</span></button>
|
|
1697
1772
|
</div>
|
|
1698
1773
|
</div>
|
|
1699
1774
|
</div>
|
|
1700
1775
|
</section>
|
|
1701
1776
|
<section class="sched-form-section" aria-labelledby="sched-ge-sec-delete">
|
|
1702
|
-
<h3 class="sched-form-title" id="sched-ge-sec-delete"
|
|
1777
|
+
<h3 class="sched-form-title" id="sched-ge-sec-delete">${te('schedules.groupEditor.delete')}</h3>
|
|
1703
1778
|
<div class="sched-form-group">
|
|
1704
1779
|
<div class="sched-form-row">
|
|
1705
|
-
<span class="sched-form-label">${members ?
|
|
1780
|
+
<span class="sched-form-label">${members ? tpe('schedules.groupEditor.membersStay', members) : te('schedules.groupEditor.empty')}</span>
|
|
1706
1781
|
<div class="sched-form-control">
|
|
1707
|
-
<button type="button" class="sched-btn sched-btn--danger" data-action="group-delete" data-id="${esc(group.id)}">${ICONS.trash}<span
|
|
1782
|
+
<button type="button" class="sched-btn sched-btn--danger" data-action="group-delete" data-id="${esc(group.id)}">${ICONS.trash}<span>${te('schedules.groupEditor.deleteGroup')}</span></button>
|
|
1708
1783
|
</div>
|
|
1709
1784
|
</div>
|
|
1710
1785
|
</div>
|
|
@@ -1712,8 +1787,8 @@ function renderGroupEditor(group) {
|
|
|
1712
1787
|
</div>
|
|
1713
1788
|
</div>
|
|
1714
1789
|
<footer class="sched-editor-actions">
|
|
1715
|
-
<button type="button" class="sched-btn" data-action="group-editor-cancel"
|
|
1716
|
-
<button type="button" class="sched-btn sched-btn--primary" data-action="group-editor-save" data-id="${esc(_groupEditorId || '')}">${creating ? '
|
|
1790
|
+
<button type="button" class="sched-btn" data-action="group-editor-cancel">${te('schedules.editor.cancel')}</button>
|
|
1791
|
+
<button type="button" class="sched-btn sched-btn--primary" data-action="group-editor-save" data-id="${esc(_groupEditorId || '')}">${creating ? te('schedules.groupEditor.create') : te('schedules.editor.save')}</button>
|
|
1717
1792
|
</footer>
|
|
1718
1793
|
</div>`);
|
|
1719
1794
|
wireGroupEditor();
|
|
@@ -1727,7 +1802,7 @@ function wireGroupEditor() {
|
|
|
1727
1802
|
_groupEditorDraft.model = null;
|
|
1728
1803
|
_groupEditorDraft.contextMode = _groupEditorDraft.profile ? 'default' : null;
|
|
1729
1804
|
_groupEditorDraft.effort = null;
|
|
1730
|
-
_groupEditorDraft.
|
|
1805
|
+
_groupEditorDraft.tools = null;
|
|
1731
1806
|
_groupEditorDraft.codexAccountId = null;
|
|
1732
1807
|
const rerender = () => { if (_panel && _groupEditorDraft) renderGroupEditor(_groupEditorId ? getGroup(_groupEditorId) : null); };
|
|
1733
1808
|
ensureModelsForProfile(_groupEditorDraft.profile || 'claude-code', false, 'default').then(() => { collectGroupEditorDraft(); rerender(); });
|
|
@@ -1765,18 +1840,18 @@ function renderScheduleEditor() {
|
|
|
1765
1840
|
// A saved automation or group the lists do not show (deleted elsewhere, or not in what loaded)
|
|
1766
1841
|
// stays selected: a popup without it would show the first choice, and a save would move it.
|
|
1767
1842
|
const templateOptions = [
|
|
1768
|
-
opt('', '
|
|
1769
|
-
..._templates.map(
|
|
1770
|
-
...(s.templateId && !getTemplate(s.templateId) ? [opt(s.templateId,
|
|
1843
|
+
opt('', t('schedules.form.chooseAutomation')),
|
|
1844
|
+
..._templates.map(tpl => opt(tpl.id, t('schedules.scheduleEditor.automationOption', { name: tpl.name, category: tpl.category || t('schedules.scheduleEditor.customCategory') }), s.templateId === tpl.id)),
|
|
1845
|
+
...(s.templateId && !getTemplate(s.templateId) ? [opt(s.templateId, t('schedules.launch.saved', { value: s.templateId }), true)] : []),
|
|
1771
1846
|
];
|
|
1772
1847
|
const groupOptions = [
|
|
1773
|
-
opt('', '
|
|
1848
|
+
opt('', viewLabel('ungrouped'), !s.groupId),
|
|
1774
1849
|
...orderedGroups().map(g => opt(g.id, g.name, s.groupId === g.id)),
|
|
1775
|
-
...(s.groupId && !getGroup(s.groupId) ? [opt(s.groupId,
|
|
1850
|
+
...(s.groupId && !getGroup(s.groupId) ? [opt(s.groupId, t('schedules.launch.saved', { value: s.groupId }), true)] : []),
|
|
1776
1851
|
];
|
|
1777
1852
|
const tz = s.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1778
1853
|
const profileOptions = [
|
|
1779
|
-
opt('', '
|
|
1854
|
+
opt('', t('schedules.launch.templateDefault'), !s.profile),
|
|
1780
1855
|
...CLI_PROFILES.map(p => opt(p.id, p.label, s.profile === p.id)),
|
|
1781
1856
|
];
|
|
1782
1857
|
const template = getTemplate(s.templateId);
|
|
@@ -1786,20 +1861,22 @@ function renderScheduleEditor() {
|
|
|
1786
1861
|
const effectiveModel = s.model || group?.model || template?.model || null;
|
|
1787
1862
|
const modelOptions = renderModelOptions(profileForModels, s.model || '', { showService: true, accountId });
|
|
1788
1863
|
const contextOptions = renderContextOptions(profileForModels, effectiveModel, s.contextMode, { accountId, inherit: true });
|
|
1789
|
-
const effortOptions = renderEffortOptions(profileForModels, s.effort || '', { inheritLabel: '
|
|
1790
|
-
const mcpOptions = renderMcpOptions(s.mcpProfile || '');
|
|
1864
|
+
const effortOptions = renderEffortOptions(profileForModels, s.effort || '', { inheritLabel: t('schedules.launch.templateOrDefault') });
|
|
1791
1865
|
const accountOptions = renderAccountOptions(s.codexAccountId || '', true);
|
|
1792
1866
|
const showEffort = profileSupportsEffort(profileForModels);
|
|
1793
|
-
const showMcp = profileForModels
|
|
1867
|
+
const showMcp = strictToolHost(profileForModels);
|
|
1794
1868
|
const showAccount = profileForModels === 'codex';
|
|
1795
1869
|
if (showAccount) ensureCodexAccountsLoaded(() => { if (_panel && _scheduleEditor) { collectScheduleEditorDraft(); renderScheduleEditor(); } });
|
|
1796
1870
|
const browserValue = s.usesBrowser === true ? 'true' : s.usesBrowser === false ? 'false' : '';
|
|
1797
1871
|
const cron = s.cron || '0 9 * * *';
|
|
1798
|
-
const presetsHTML = CRON_PRESET_GROUPS.map(group =>
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1872
|
+
const presetsHTML = CRON_PRESET_GROUPS.map(group => {
|
|
1873
|
+
const name = t(`schedules.presets.group.${group.group}`);
|
|
1874
|
+
return `
|
|
1875
|
+
<div class="sched-preset-group" role="group" aria-label="${te('schedules.scheduleEditor.presetGroup', { group: name })}">
|
|
1876
|
+
<span>${esc(name)}</span>
|
|
1877
|
+
<div class="sched-chip-list">${group.presets.map(p => `<button type="button" class="sched-chip" data-action="cron-preset" data-cron="${esc(p.cron)}" aria-pressed="${p.cron === cron}" data-tooltip="${esc(describeCron(p.cron))}">${esc(presetLabel(p))}</button>`).join('')}</div>
|
|
1878
|
+
</div>`;
|
|
1879
|
+
}).join('');
|
|
1803
1880
|
const dayOrder = [1, 2, 3, 4, 5, 6, 0];
|
|
1804
1881
|
const hasThemes = dayOrder.some(idx => s.dayThemes?.[String(idx)]);
|
|
1805
1882
|
// A textarea, not an input: a theme can span lines, and an input would drop its line breaks.
|
|
@@ -1807,13 +1884,14 @@ function renderScheduleEditor() {
|
|
|
1807
1884
|
// whole entry away.
|
|
1808
1885
|
const dayThemes = dayOrder.map(idx => {
|
|
1809
1886
|
const entry = s.dayThemes?.[String(idx)];
|
|
1810
|
-
const
|
|
1887
|
+
const day = weekdayName(idx, { long: true });
|
|
1888
|
+
const help = entry?.label ? te('schedules.scheduleEditor.themeLabel', { label: entry.label }) : '';
|
|
1811
1889
|
// A day without an entry keeps the button's room, so the fields line up.
|
|
1812
1890
|
const remove = entry
|
|
1813
|
-
? `<button type="button" class="sched-icon-btn sched-icon-btn--danger sched-day-remove" data-action="day-theme-remove" data-day="${idx}" aria-label="
|
|
1891
|
+
? `<button type="button" class="sched-icon-btn sched-icon-btn--danger sched-day-remove" data-action="day-theme-remove" data-day="${idx}" aria-label="${te('schedules.scheduleEditor.removeTheme', { day })}" data-tooltip="${te('schedules.scheduleEditor.removeThemeTip')}">${ICONS.trash}</button>`
|
|
1814
1892
|
: '<span class="sched-icon-btn sched-day-remove sched-day-remove--room" aria-hidden="true"></span>';
|
|
1815
|
-
return formRow(`sched-ed-day-${idx}`,
|
|
1816
|
-
`<textarea class="sched-input sched-textarea" id="sched-ed-day-${idx}" data-day="${idx}" rows="2" spellcheck="false" placeholder="
|
|
1893
|
+
return formRow(`sched-ed-day-${idx}`, day,
|
|
1894
|
+
`<textarea class="sched-input sched-textarea" id="sched-ed-day-${idx}" data-day="${idx}" rows="2" spellcheck="false" placeholder="${te('schedules.scheduleEditor.themePlaceholder', { day })}"${help ? ` aria-describedby="sched-ed-day-${idx}-help"` : ''}>${esc(entry?.contextOverride || '')}</textarea>${remove}`,
|
|
1817
1895
|
{ help });
|
|
1818
1896
|
}).join('');
|
|
1819
1897
|
|
|
@@ -1824,72 +1902,72 @@ function renderScheduleEditor() {
|
|
|
1824
1902
|
if (_panel && _scheduleEditor) { collectScheduleEditorDraft(); renderScheduleEditor(); }
|
|
1825
1903
|
});
|
|
1826
1904
|
}
|
|
1827
|
-
if (showMcp && !
|
|
1828
|
-
|
|
1905
|
+
if (showMcp && !getCachedToolCatalog()) {
|
|
1906
|
+
fetchToolCatalog().then(catalog => { if (catalog && _panel && _scheduleEditor) { collectScheduleEditorDraft(); renderScheduleEditor(); } });
|
|
1829
1907
|
}
|
|
1830
1908
|
|
|
1831
1909
|
redrawEditor(main, `
|
|
1832
1910
|
<div class="sched-editor">
|
|
1833
|
-
${editorHead({ back: 'schedule-editor-close', title: isEdit ? '
|
|
1911
|
+
${editorHead({ back: 'schedule-editor-close', title: t(isEdit ? 'schedules.detail.editSchedule' : 'schedules.head.newSchedule'), sub: isEdit ? s.name : t('schedules.scheduleEditor.newSub'), titleId: 'sched-view-title' })}
|
|
1834
1912
|
<div class="sched-editor-body">
|
|
1835
1913
|
<div class="sched-form">
|
|
1836
1914
|
<section class="sched-form-section" aria-labelledby="sched-ed-sec-basics">
|
|
1837
|
-
<h3 class="sched-form-title" id="sched-ed-sec-basics"
|
|
1915
|
+
<h3 class="sched-form-title" id="sched-ed-sec-basics">${te('schedules.timers.schedule')}</h3>
|
|
1838
1916
|
<div class="sched-form-group">
|
|
1839
|
-
${formRow('sched-ed-name', '
|
|
1840
|
-
${dropdownRow('sched-ed-template', '
|
|
1841
|
-
${dropdownRow('sched-ed-group', '
|
|
1842
|
-
${formRow('sched-ed-enabled', '
|
|
1843
|
-
{ help: '
|
|
1917
|
+
${formRow('sched-ed-name', t('schedules.form.name'), `<input class="sched-input" id="sched-ed-name" type="text" value="${esc(s.name || '')}" placeholder="${te('schedules.scheduleEditor.namePlaceholder')}" autocomplete="off">`)}
|
|
1918
|
+
${dropdownRow('sched-ed-template', t('schedules.form.automation'), templateOptions)}
|
|
1919
|
+
${dropdownRow('sched-ed-group', t('schedules.form.group'), groupOptions)}
|
|
1920
|
+
${formRow('sched-ed-enabled', t('schedules.scheduleEditor.active'), toggleHTML('sched-ed-enabled', s.enabled !== false, 'sched-ed-enabled-help'),
|
|
1921
|
+
{ help: te('schedules.scheduleEditor.activeHelp'), toggle: true })}
|
|
1844
1922
|
</div>
|
|
1845
1923
|
</section>
|
|
1846
1924
|
|
|
1847
1925
|
<section class="sched-form-section" aria-labelledby="sched-ed-sec-when">
|
|
1848
|
-
<h3 class="sched-form-title" id="sched-ed-sec-when"
|
|
1926
|
+
<h3 class="sched-form-title" id="sched-ed-sec-when">${te('schedules.scheduleEditor.when')}</h3>
|
|
1849
1927
|
<div class="sched-form-group">
|
|
1850
1928
|
<div class="sched-form-row sched-form-row--top">
|
|
1851
|
-
<label class="sched-form-label" for="sched-ed-cron"
|
|
1929
|
+
<label class="sched-form-label" for="sched-ed-cron">${te('schedules.scheduleEditor.cron')}</label>
|
|
1852
1930
|
<div class="sched-form-stack">
|
|
1853
1931
|
<div class="sched-form-control"><input class="sched-input sched-mono" id="sched-ed-cron" type="text" value="${esc(cron)}" placeholder="0 9 * * *" spellcheck="false" autocomplete="off" aria-describedby="sched-ed-cron-desc"></div>
|
|
1854
1932
|
<p class="sched-help" id="sched-ed-cron-desc" aria-live="polite">${esc(describeCron(cron))}</p>
|
|
1855
1933
|
</div>
|
|
1856
1934
|
</div>
|
|
1857
1935
|
<div class="sched-form-row sched-form-row--top">
|
|
1858
|
-
<span class="sched-form-label"
|
|
1936
|
+
<span class="sched-form-label">${te('schedules.scheduleEditor.presets')}</span>
|
|
1859
1937
|
<div class="sched-presets">${presetsHTML}</div>
|
|
1860
1938
|
</div>
|
|
1861
|
-
${formRow('sched-ed-tz', '
|
|
1939
|
+
${formRow('sched-ed-tz', t('schedules.detail.timeZone'), `<input class="sched-input" id="sched-ed-tz" type="text" value="${esc(tz)}" spellcheck="false" autocomplete="off">`)}
|
|
1862
1940
|
</div>
|
|
1863
1941
|
</section>
|
|
1864
1942
|
|
|
1865
1943
|
<section class="sched-form-section" aria-labelledby="sched-ed-sec-launch">
|
|
1866
|
-
<h3 class="sched-form-title" id="sched-ed-sec-launch"
|
|
1867
|
-
<p class="sched-form-intro"
|
|
1944
|
+
<h3 class="sched-form-title" id="sched-ed-sec-launch">${te('schedules.form.launchOverrides')}</h3>
|
|
1945
|
+
<p class="sched-form-intro">${te('schedules.scheduleEditor.launchIntro')}</p>
|
|
1868
1946
|
<div class="sched-form-group">
|
|
1869
|
-
${dropdownRow('sched-ed-profile', '
|
|
1870
|
-
${dropdownRow('sched-ed-model', '
|
|
1871
|
-
${dropdownRow('sched-ed-context', '
|
|
1872
|
-
${dropdownRow('sched-ed-effort', '
|
|
1873
|
-
${
|
|
1874
|
-
${dropdownRow('sched-ed-account', '
|
|
1875
|
-
${dropdownRow('sched-ed-browser', '
|
|
1876
|
-
opt('', '
|
|
1877
|
-
opt('true', '
|
|
1878
|
-
opt('false', '
|
|
1947
|
+
${dropdownRow('sched-ed-profile', t('schedules.form.cli'), profileOptions)}
|
|
1948
|
+
${dropdownRow('sched-ed-model', t('schedules.form.model'), modelOptions)}
|
|
1949
|
+
${dropdownRow('sched-ed-context', t('schedules.form.context'), contextOptions)}
|
|
1950
|
+
${dropdownRow('sched-ed-effort', t('schedules.form.thinking'), effortOptions, { hidden: !showEffort })}
|
|
1951
|
+
${toolRow('sched-ed-mcp', s.tools, { provider: profileForModels, usesBrowser: s.usesBrowser ?? group?.usesBrowser ?? template?.usesBrowser })}
|
|
1952
|
+
${dropdownRow('sched-ed-account', t('schedules.form.account'), accountOptions, { hidden: !showAccount, rowId: 'sched-ed-account-field' })}
|
|
1953
|
+
${dropdownRow('sched-ed-browser', t('schedules.form.browser'), [
|
|
1954
|
+
opt('', t('schedules.launch.templateDefault'), browserValue === ''),
|
|
1955
|
+
opt('true', t('schedules.launch.alwaysOn'), browserValue === 'true'),
|
|
1956
|
+
opt('false', t('schedules.launch.alwaysOff'), browserValue === 'false'),
|
|
1879
1957
|
])}
|
|
1880
1958
|
</div>
|
|
1881
1959
|
</section>
|
|
1882
1960
|
|
|
1883
1961
|
<details class="sched-form-section"${hasThemes ? ' open' : ''}>
|
|
1884
|
-
<summary class="sched-form-title"
|
|
1885
|
-
<p class="sched-form-intro"
|
|
1962
|
+
<summary class="sched-form-title">${te('schedules.detail.dayThemes')} <small>${te('schedules.scheduleEditor.optional')}</small></summary>
|
|
1963
|
+
<p class="sched-form-intro">${te('schedules.scheduleEditor.dayThemesIntro')}</p>
|
|
1886
1964
|
<div class="sched-form-group" id="sched-ed-daythemes">${dayThemes}</div>
|
|
1887
1965
|
</details>
|
|
1888
1966
|
</div>
|
|
1889
1967
|
</div>
|
|
1890
1968
|
<footer class="sched-editor-actions">
|
|
1891
|
-
<button type="button" class="sched-btn" data-action="schedule-editor-close"
|
|
1892
|
-
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-editor-save">${isEdit ? '
|
|
1969
|
+
<button type="button" class="sched-btn" data-action="schedule-editor-close">${te('schedules.editor.cancel')}</button>
|
|
1970
|
+
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-editor-save">${isEdit ? te('schedules.editor.save') : te('schedules.scheduleEditor.create')}</button>
|
|
1893
1971
|
</footer>
|
|
1894
1972
|
</div>
|
|
1895
1973
|
`);
|
|
@@ -1901,7 +1979,7 @@ function renderScheduleEditor() {
|
|
|
1901
1979
|
_scheduleEditor.model = null;
|
|
1902
1980
|
_scheduleEditor.contextMode = null;
|
|
1903
1981
|
_scheduleEditor.effort = null;
|
|
1904
|
-
_scheduleEditor.
|
|
1982
|
+
_scheduleEditor.tools = null;
|
|
1905
1983
|
_scheduleEditor.codexAccountId = null;
|
|
1906
1984
|
renderScheduleEditor();
|
|
1907
1985
|
});
|
|
@@ -1916,7 +1994,7 @@ function renderScheduleEditor() {
|
|
|
1916
1994
|
_scheduleEditor.model = null;
|
|
1917
1995
|
_scheduleEditor.contextMode = _scheduleEditor.profile ? 'default' : null;
|
|
1918
1996
|
_scheduleEditor.effort = null;
|
|
1919
|
-
_scheduleEditor.
|
|
1997
|
+
_scheduleEditor.tools = null;
|
|
1920
1998
|
_scheduleEditor.codexAccountId = null;
|
|
1921
1999
|
renderScheduleEditor();
|
|
1922
2000
|
});
|
|
@@ -1949,7 +2027,7 @@ function updateCronDesc() {
|
|
|
1949
2027
|
const valid = value.split(/\s+/).length === 5;
|
|
1950
2028
|
if (!valid && value) input.setAttribute('aria-invalid', 'true');
|
|
1951
2029
|
else input.removeAttribute('aria-invalid');
|
|
1952
|
-
if (desc) desc.textContent = valid ? describeCron(value) : '
|
|
2030
|
+
if (desc) desc.textContent = valid ? describeCron(value) : t('schedules.scheduleEditor.cronHelp');
|
|
1953
2031
|
_panel?.querySelectorAll('[data-action="cron-preset"]').forEach(btn => btn.setAttribute('aria-pressed', String(btn.dataset.cron === value)));
|
|
1954
2032
|
}
|
|
1955
2033
|
|
|
@@ -1958,12 +2036,18 @@ function updateCronDesc() {
|
|
|
1958
2036
|
// group pushes every changed launch field onto its schedules). So an edit sends a field only when
|
|
1959
2037
|
// the form's value differs from the saved one: a popup that cannot show a saved value yet, or a
|
|
1960
2038
|
// save of an untouched form, never clears anything.
|
|
1961
|
-
const LAUNCH_FIELDS = ['profile', 'model', 'contextMode', 'effort', '
|
|
2039
|
+
const LAUNCH_FIELDS = ['profile', 'model', 'contextMode', 'effort', 'tools', 'codexAccountId', 'usesBrowser'];
|
|
1962
2040
|
// What a CLI's choice decides: a model, context, thinking level, tool list or account of one CLI
|
|
1963
2041
|
// means nothing to another, so a change of CLI resets them (the editors' CLI popups do the same).
|
|
1964
|
-
const CLI_DEPENDENT = ['model', 'contextMode', 'effort', '
|
|
2042
|
+
const CLI_DEPENDENT = ['model', 'contextMode', 'effort', 'tools', 'codexAccountId'];
|
|
2043
|
+
// A missing picker is not a request to clear tools (focus hosts and catalog loading).
|
|
2044
|
+
function readEditorTools(id, saved) {
|
|
2045
|
+
const input = $(id);
|
|
2046
|
+
return input ? readToolPicker(input) : saved ?? null;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
1965
2049
|
const blankToNull = (value) => (value === undefined || value === null || value === '' ? null : value);
|
|
1966
|
-
const sameValue = (a, b) => blankToNull(a) === blankToNull(b);
|
|
2050
|
+
const sameValue = (a, b) => stableJson(blankToNull(a)) === stableJson(blankToNull(b));
|
|
1967
2051
|
|
|
1968
2052
|
function stableJson(value) {
|
|
1969
2053
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
|
|
@@ -2006,7 +2090,7 @@ function collectScheduleEditorDraft() {
|
|
|
2006
2090
|
model: $('sched-ed-model')?.value || null,
|
|
2007
2091
|
contextMode: $('sched-ed-context')?.value || null,
|
|
2008
2092
|
effort: $('sched-ed-effort')?.value || null,
|
|
2009
|
-
|
|
2093
|
+
tools: readEditorTools('sched-ed-mcp', _scheduleEditor?.tools),
|
|
2010
2094
|
codexAccountId: $('sched-ed-account')?.value || null,
|
|
2011
2095
|
usesBrowser: browserRaw === '' ? undefined : browserRaw === 'true',
|
|
2012
2096
|
enabled: $('sched-ed-enabled')?.checked ?? _scheduleEditor.enabled,
|
|
@@ -2022,7 +2106,7 @@ function markInvalid(id, message) {
|
|
|
2022
2106
|
}
|
|
2023
2107
|
|
|
2024
2108
|
async function saveScheduleFromEditor() {
|
|
2025
|
-
if (editorsWait()) { showToast('
|
|
2109
|
+
if (editorsWait()) { showToast(t('schedules.toast.notLoaded')); return; }
|
|
2026
2110
|
const original = _scheduleEditor?._original || null;
|
|
2027
2111
|
const name = $('sched-ed-name')?.value?.trim();
|
|
2028
2112
|
const templateId = $('sched-ed-template')?.value;
|
|
@@ -2037,15 +2121,15 @@ async function saveScheduleFromEditor() {
|
|
|
2037
2121
|
contextMode: $('sched-ed-context')?.value || null,
|
|
2038
2122
|
// 'off' is a choice of its own (no thinking), never the same as "no override".
|
|
2039
2123
|
effort: $('sched-ed-effort')?.value || null,
|
|
2040
|
-
|
|
2124
|
+
tools: readEditorTools('sched-ed-mcp', _scheduleEditor?.tools),
|
|
2041
2125
|
// Read whatever the CLI: a saved account is kept while its row is hidden, never cleared by it.
|
|
2042
2126
|
codexAccountId: $('sched-ed-account')?.value || null,
|
|
2043
2127
|
usesBrowser: browserRaw === '' ? null : browserRaw === 'true',
|
|
2044
2128
|
};
|
|
2045
2129
|
|
|
2046
|
-
if (!name) { markInvalid('sched-ed-name', '
|
|
2047
|
-
if (!templateId) { markInvalid('sched-ed-template', '
|
|
2048
|
-
if (!cron || cron.split(/\s+/).length !== 5) { markInvalid('sched-ed-cron', '
|
|
2130
|
+
if (!name) { markInvalid('sched-ed-name', t('schedules.toast.nameRequired')); return; }
|
|
2131
|
+
if (!templateId) { markInvalid('sched-ed-template', t('schedules.toast.automationRequired')); return; }
|
|
2132
|
+
if (!cron || cron.split(/\s+/).length !== 5) { markInvalid('sched-ed-cron', t('schedules.toast.cronRequired')); return; }
|
|
2049
2133
|
|
|
2050
2134
|
const template = getTemplate(templateId);
|
|
2051
2135
|
const group = groupId ? getGroup(groupId) : null;
|
|
@@ -2057,10 +2141,10 @@ async function saveScheduleFromEditor() {
|
|
|
2057
2141
|
const effectiveModel = launch.model || group?.model || template?.model || null;
|
|
2058
2142
|
const effectiveAccount = launch.codexAccountId || group?.codexAccountId || template?.codexAccountId || 'default';
|
|
2059
2143
|
if (!modelChoiceAvailable(effectiveProfile, effectiveModel, effectiveAccount)) {
|
|
2060
|
-
showToast('
|
|
2144
|
+
showToast(t('schedules.toast.modelUnavailable')); return;
|
|
2061
2145
|
}
|
|
2062
2146
|
if (!contextChoiceAvailable(effectiveProfile, effectiveModel, launch.contextMode, effectiveAccount)) {
|
|
2063
|
-
showToast('
|
|
2147
|
+
showToast(t('schedules.toast.contextUnavailable')); return;
|
|
2064
2148
|
}
|
|
2065
2149
|
}
|
|
2066
2150
|
|
|
@@ -2081,7 +2165,7 @@ async function saveScheduleFromEditor() {
|
|
|
2081
2165
|
_scheduleEditor = null;
|
|
2082
2166
|
render({ frame: true });
|
|
2083
2167
|
focusRow(original.id);
|
|
2084
|
-
showToast('
|
|
2168
|
+
showToast(t('schedules.toast.noChanges'));
|
|
2085
2169
|
return;
|
|
2086
2170
|
}
|
|
2087
2171
|
} else {
|
|
@@ -2099,11 +2183,11 @@ async function saveScheduleFromEditor() {
|
|
|
2099
2183
|
saved = await updateSchedule(draft.id, payload);
|
|
2100
2184
|
const idx = _schedules.findIndex(s => s.id === saved.id);
|
|
2101
2185
|
if (idx >= 0) _schedules[idx] = saved;
|
|
2102
|
-
showToast('
|
|
2186
|
+
showToast(t('schedules.toast.updated'));
|
|
2103
2187
|
} else {
|
|
2104
2188
|
saved = await createSchedule(payload);
|
|
2105
2189
|
if (!_schedules.some(s => s.id === saved.id)) _schedules.push(saved);
|
|
2106
|
-
showToast('
|
|
2190
|
+
showToast(t('schedules.toast.created'));
|
|
2107
2191
|
}
|
|
2108
2192
|
if (_panel !== panel || _scheduleEditor !== draft) { refresh(); return; }
|
|
2109
2193
|
_scheduleEditor = null;
|
|
@@ -2115,7 +2199,7 @@ async function saveScheduleFromEditor() {
|
|
|
2115
2199
|
row?.querySelector('.sched-row-toggle')?.focus({ preventScroll: true });
|
|
2116
2200
|
}
|
|
2117
2201
|
} catch (err) {
|
|
2118
|
-
showToast('
|
|
2202
|
+
showToast(t('schedules.toast.saveFailed', { error: err.message }));
|
|
2119
2203
|
}
|
|
2120
2204
|
}
|
|
2121
2205
|
|
|
@@ -2193,7 +2277,7 @@ async function handleClick(e) {
|
|
|
2193
2277
|
}
|
|
2194
2278
|
case 'schedule-edit-prompt': {
|
|
2195
2279
|
const templateId = el.dataset.templateId;
|
|
2196
|
-
if (!templateId) { showToast('
|
|
2280
|
+
if (!templateId) { showToast(t('schedules.toast.noAutomation')); break; }
|
|
2197
2281
|
closePanel();
|
|
2198
2282
|
emit('automations:open', { templateId });
|
|
2199
2283
|
break;
|
|
@@ -2263,7 +2347,7 @@ async function handleClick(e) {
|
|
|
2263
2347
|
_scheduleEditor.dayThemes = themes;
|
|
2264
2348
|
renderScheduleEditor();
|
|
2265
2349
|
$(`sched-ed-day-${day}`)?.focus();
|
|
2266
|
-
showToast(
|
|
2350
|
+
showToast(t('schedules.toast.themeRemoved', { day: weekdayName(day, { long: true }) }));
|
|
2267
2351
|
break;
|
|
2268
2352
|
}
|
|
2269
2353
|
case 'group-editor-save':
|
|
@@ -2486,10 +2570,10 @@ async function moveScheduleToGroup(id, groupId) {
|
|
|
2486
2570
|
if (!schedule || (schedule.groupId || null) === (groupId || null)) return;
|
|
2487
2571
|
try {
|
|
2488
2572
|
replaceSchedule(await updateSchedule(id, { groupId }));
|
|
2489
|
-
showToast(groupId ?
|
|
2573
|
+
showToast(t('schedules.toast.movedTo', { group: groupId ? getGroup(groupId)?.name || t('schedules.toast.aGroup') : viewLabel('ungrouped') }));
|
|
2490
2574
|
refresh();
|
|
2491
2575
|
} catch (err) {
|
|
2492
|
-
showToast('
|
|
2576
|
+
showToast(t('schedules.toast.moveFailed', { error: err.message }));
|
|
2493
2577
|
refresh();
|
|
2494
2578
|
}
|
|
2495
2579
|
}
|
|
@@ -2501,11 +2585,11 @@ async function toggleSchedule(id, el = null) {
|
|
|
2501
2585
|
try {
|
|
2502
2586
|
replaceSchedule(await updateSchedule(id, { enabled: !schedule.enabled }));
|
|
2503
2587
|
const updated = _schedules.find(s => s.id === id);
|
|
2504
|
-
showToast(updated?.enabled ?
|
|
2588
|
+
showToast(t(updated?.enabled ? 'schedules.toast.resumed' : 'schedules.toast.paused', { name: schedule.name }));
|
|
2505
2589
|
refresh();
|
|
2506
2590
|
} catch (err) {
|
|
2507
2591
|
el?.removeAttribute('aria-busy');
|
|
2508
|
-
showToast('
|
|
2592
|
+
showToast(t('schedules.toast.toggleFailed', { error: err.message }));
|
|
2509
2593
|
}
|
|
2510
2594
|
}
|
|
2511
2595
|
|
|
@@ -2516,9 +2600,9 @@ async function fireSchedule(id, el = null) {
|
|
|
2516
2600
|
sidepanelWindowId: getNativeLoopRouterWindowId(),
|
|
2517
2601
|
sidepanelClaimToken: getNativeLoopRouterClaimToken(),
|
|
2518
2602
|
});
|
|
2519
|
-
showToast('
|
|
2603
|
+
showToast(t('schedules.toast.launching'));
|
|
2520
2604
|
} catch (err) {
|
|
2521
|
-
showToast('
|
|
2605
|
+
showToast(t('schedules.toast.runFailed', { error: err.message }));
|
|
2522
2606
|
} finally {
|
|
2523
2607
|
if (el?.isConnected) el.disabled = false;
|
|
2524
2608
|
}
|
|
@@ -2533,18 +2617,18 @@ function openConfirm({ kind, id, opener }) {
|
|
|
2533
2617
|
if (kind === 'schedule') {
|
|
2534
2618
|
const schedule = _schedules.find(s => s.id === id);
|
|
2535
2619
|
if (!schedule) return;
|
|
2536
|
-
title = '
|
|
2620
|
+
title = t('schedules.confirm.scheduleTitle');
|
|
2537
2621
|
name = schedule.name;
|
|
2538
|
-
text = '
|
|
2539
|
-
accept = '
|
|
2622
|
+
text = t('schedules.confirm.scheduleText');
|
|
2623
|
+
accept = t('schedules.confirm.scheduleAccept');
|
|
2540
2624
|
} else {
|
|
2541
2625
|
const group = getGroup(id);
|
|
2542
2626
|
if (!group) return;
|
|
2543
2627
|
const count = _schedules.filter(s => s.groupId === id).length;
|
|
2544
|
-
title = '
|
|
2628
|
+
title = t('schedules.confirm.groupTitle');
|
|
2545
2629
|
name = group.name;
|
|
2546
|
-
text = count ?
|
|
2547
|
-
accept = '
|
|
2630
|
+
text = count ? tp('schedules.groupEditor.membersStay', count) : t('schedules.groupEditor.empty');
|
|
2631
|
+
accept = t('schedules.confirm.groupAccept');
|
|
2548
2632
|
}
|
|
2549
2633
|
_confirm = { kind, id, opener };
|
|
2550
2634
|
layer.innerHTML = `<div class="sched-dialog" role="alertdialog" aria-modal="true" aria-labelledby="sched-dialog-title" aria-describedby="sched-dialog-text">
|
|
@@ -2554,7 +2638,7 @@ function openConfirm({ kind, id, opener }) {
|
|
|
2554
2638
|
<p class="sched-help" id="sched-dialog-text">${esc(text)}</p>
|
|
2555
2639
|
</div>
|
|
2556
2640
|
<div class="sched-dialog-actions">
|
|
2557
|
-
<button type="button" class="sched-btn" data-action="confirm-cancel"
|
|
2641
|
+
<button type="button" class="sched-btn" data-action="confirm-cancel">${te('schedules.editor.cancel')}</button>
|
|
2558
2642
|
<button type="button" class="sched-btn sched-btn--danger" data-action="confirm-accept">${ICONS.trash}<span>${esc(accept)}</span></button>
|
|
2559
2643
|
</div>
|
|
2560
2644
|
</div>`;
|
|
@@ -2581,14 +2665,14 @@ async function acceptConfirm(button) {
|
|
|
2581
2665
|
await deleteSchedule(id);
|
|
2582
2666
|
_schedules = _schedules.filter(s => s.id !== id);
|
|
2583
2667
|
_expanded.delete(id);
|
|
2584
|
-
showToast('
|
|
2668
|
+
showToast(t('schedules.toast.deleted'));
|
|
2585
2669
|
} else {
|
|
2586
2670
|
await deleteScheduleGroup(id);
|
|
2587
2671
|
_groups = _groups.filter(g => g.id !== id);
|
|
2588
2672
|
for (const schedule of _schedules) if (schedule.groupId === id) schedule.groupId = null;
|
|
2589
2673
|
if (_scope === `group:${id}`) setScope('ungrouped', { keepEditors: true });
|
|
2590
2674
|
if (_groupEditorId === id) { _groupEditorId = null; _groupEditorDraft = null; }
|
|
2591
|
-
showToast('
|
|
2675
|
+
showToast(t('schedules.toast.groupDeleted'));
|
|
2592
2676
|
}
|
|
2593
2677
|
_confirm.opener = null;
|
|
2594
2678
|
closeConfirm();
|
|
@@ -2596,17 +2680,17 @@ async function acceptConfirm(button) {
|
|
|
2596
2680
|
$('sched-search')?.focus();
|
|
2597
2681
|
} catch (err) {
|
|
2598
2682
|
button.disabled = false;
|
|
2599
|
-
showToast(
|
|
2683
|
+
showToast(t(kind === 'schedule' ? 'schedules.toast.deleteFailed' : 'schedules.toast.groupDeleteFailed', { error: err.message }));
|
|
2600
2684
|
}
|
|
2601
2685
|
}
|
|
2602
2686
|
|
|
2603
2687
|
// ── Groups ──
|
|
2604
2688
|
|
|
2605
2689
|
async function saveGroupFromEditor(id) {
|
|
2606
|
-
if (editorsWait()) { showToast('
|
|
2690
|
+
if (editorsWait()) { showToast(t('schedules.toast.notLoaded')); return; }
|
|
2607
2691
|
collectGroupEditorDraft();
|
|
2608
2692
|
const name = $('sched-ge-name')?.value?.trim();
|
|
2609
|
-
if (!name) { markInvalid('sched-ge-name', '
|
|
2693
|
+
if (!name) { markInvalid('sched-ge-name', t('schedules.toast.groupNameRequired')); return; }
|
|
2610
2694
|
const color = $('sched-ge-color')?.value || null;
|
|
2611
2695
|
const mutualExclusion = !!$('sched-ge-mutex')?.checked;
|
|
2612
2696
|
const browserRaw = $('sched-ge-browser')?.value || '';
|
|
@@ -2615,7 +2699,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2615
2699
|
model: $('sched-ge-model')?.value || null,
|
|
2616
2700
|
contextMode: $('sched-ge-context')?.value || null,
|
|
2617
2701
|
effort: $('sched-ge-effort')?.value || null, // 'off' stays explicit
|
|
2618
|
-
|
|
2702
|
+
tools: readEditorTools('sched-ge-mcp', _groupEditorDraft?.tools),
|
|
2619
2703
|
codexAccountId: $('sched-ge-account')?.value || null, // read whatever the CLI: hidden is not cleared
|
|
2620
2704
|
usesBrowser: browserRaw === '' ? null : browserRaw === 'true',
|
|
2621
2705
|
};
|
|
@@ -2624,10 +2708,10 @@ async function saveGroupFromEditor(id) {
|
|
|
2624
2708
|
if (!existing || ['profile', 'model', 'contextMode', 'codexAccountId'].some(k => !sameValue(launch[k], existing[k]))) {
|
|
2625
2709
|
const effectiveProfile = launch.profile || 'claude-code';
|
|
2626
2710
|
if (!modelChoiceAvailable(effectiveProfile, launch.model, launch.codexAccountId || 'default')) {
|
|
2627
|
-
showToast('
|
|
2711
|
+
showToast(t('schedules.toast.modelUnavailable')); return;
|
|
2628
2712
|
}
|
|
2629
2713
|
if (!contextChoiceAvailable(effectiveProfile, launch.model, launch.contextMode, launch.codexAccountId || 'default')) {
|
|
2630
|
-
showToast('
|
|
2714
|
+
showToast(t('schedules.toast.contextUnavailable')); return;
|
|
2631
2715
|
}
|
|
2632
2716
|
}
|
|
2633
2717
|
|
|
@@ -2643,7 +2727,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2643
2727
|
for (const key of LAUNCH_FIELDS) if (launch[key] !== null) payload[key] = launch[key];
|
|
2644
2728
|
const group = await createScheduleGroup(payload);
|
|
2645
2729
|
if (!_groups.some(g => g.id === group.id)) _groups.push(group);
|
|
2646
|
-
showToast('
|
|
2730
|
+
showToast(t('schedules.toast.groupCreated'));
|
|
2647
2731
|
if (!stillHere()) { refresh(); return; }
|
|
2648
2732
|
_groupEditorId = null;
|
|
2649
2733
|
_groupEditorDraft = null;
|
|
@@ -2652,7 +2736,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2652
2736
|
render({ frame: true });
|
|
2653
2737
|
_panel?.querySelector(`.sched-nav-item[data-scope="group:${CSS.escape(group.id)}"]`)?.focus();
|
|
2654
2738
|
} catch (err) {
|
|
2655
|
-
showToast('
|
|
2739
|
+
showToast(t('schedules.toast.groupFailed', { error: err.message }));
|
|
2656
2740
|
}
|
|
2657
2741
|
return;
|
|
2658
2742
|
}
|
|
@@ -2674,7 +2758,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2674
2758
|
_groupEditorDraft = null;
|
|
2675
2759
|
render({ frame: true });
|
|
2676
2760
|
_panel?.querySelector(`.sched-nav-item[data-scope="group:${CSS.escape(id)}"]`)?.focus();
|
|
2677
|
-
showToast('
|
|
2761
|
+
showToast(t('schedules.toast.noChanges'));
|
|
2678
2762
|
return;
|
|
2679
2763
|
}
|
|
2680
2764
|
// What each schedule of the group holds as the user saves: a reset afterwards writes only over
|
|
@@ -2692,7 +2776,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2692
2776
|
_panel?.querySelector(`.sched-nav-item[data-scope="group:${CSS.escape(id)}"]`)?.focus();
|
|
2693
2777
|
}
|
|
2694
2778
|
if (!cliChanged) {
|
|
2695
|
-
showToast('
|
|
2779
|
+
showToast(t('schedules.toast.groupUpdated'));
|
|
2696
2780
|
} else {
|
|
2697
2781
|
_cliReport = { groupId: id, profile: launch.profile, pending: true };
|
|
2698
2782
|
renderNote();
|
|
@@ -2700,15 +2784,13 @@ async function saveGroupFromEditor(id) {
|
|
|
2700
2784
|
_cliReport = { groupId: id, profile: launch.profile, ...result };
|
|
2701
2785
|
renderNote();
|
|
2702
2786
|
const kept = result.changed.length + result.failed.length;
|
|
2703
|
-
showToast(kept
|
|
2704
|
-
? `Group updated. ${plural(kept, 'schedule')} kept the old CLI's settings: the group's page names ${kept === 1 ? 'it' : 'them'}`
|
|
2705
|
-
: 'Group updated');
|
|
2787
|
+
showToast(kept ? tp('schedules.toast.groupUpdatedKept', kept) : t('schedules.toast.groupUpdated'));
|
|
2706
2788
|
}
|
|
2707
2789
|
// The schedules show what the save pushed onto them. From here on nothing is navigated:
|
|
2708
2790
|
// the user may have opened something else meanwhile, and it stays as it is.
|
|
2709
2791
|
await loadData();
|
|
2710
2792
|
} catch (err) {
|
|
2711
|
-
showToast('
|
|
2793
|
+
showToast(t('schedules.toast.groupUpdateFailed', { error: err.message }));
|
|
2712
2794
|
}
|
|
2713
2795
|
}
|
|
2714
2796
|
|
|
@@ -2718,7 +2800,7 @@ const RESET_KEYS = ['profile', ...CLI_DEPENDENT];
|
|
|
2718
2800
|
function captureMembers(groupId, launch) {
|
|
2719
2801
|
return _schedules.filter(s => s.groupId === groupId)
|
|
2720
2802
|
.map(s => ({ id: s.id, name: s.name || s.id, values: Object.fromEntries(RESET_KEYS.map(k => [k, blankToNull(s[k])])) }))
|
|
2721
|
-
.filter(m => RESET_KEYS.some(k => m.values[k] !== null && m.values[k]
|
|
2803
|
+
.filter(m => RESET_KEYS.some(k => m.values[k] !== null && !sameValue(m.values[k], launch[k])));
|
|
2722
2804
|
}
|
|
2723
2805
|
|
|
2724
2806
|
// server.js pushes a group field onto the group's schedules only when the group's own value
|
|
@@ -2746,11 +2828,11 @@ async function resetSchedulesForCli(groupId, launch, members) {
|
|
|
2746
2828
|
const moved = !fresh || fresh.groupId !== groupId;
|
|
2747
2829
|
const changed = moved || RESET_KEYS.some(key => {
|
|
2748
2830
|
const now = blankToNull(fresh[key]);
|
|
2749
|
-
return now
|
|
2831
|
+
return !sameValue(now, member.values[key]) && !sameValue(now, target(key));
|
|
2750
2832
|
});
|
|
2751
2833
|
if (changed) { report.changed.push(name); continue; }
|
|
2752
2834
|
const reset = {};
|
|
2753
|
-
for (const key of RESET_KEYS) if (blankToNull(fresh[key]) !== null &&
|
|
2835
|
+
for (const key of RESET_KEYS) if (blankToNull(fresh[key]) !== null && !sameValue(fresh[key], target(key))) reset[key] = launch[key];
|
|
2754
2836
|
if (!Object.keys(reset).length) continue; // the save's own push already did it
|
|
2755
2837
|
try {
|
|
2756
2838
|
const saved = await updateSchedule(member.id, reset);
|
|
@@ -2783,7 +2865,7 @@ async function moveGroup(id, direction) {
|
|
|
2783
2865
|
const pressed = _panel?.querySelector(`[data-action="${direction < 0 ? 'group-up' : 'group-down'}"]`);
|
|
2784
2866
|
(pressed && !pressed.disabled ? pressed : _panel?.querySelector(`[data-action="${direction < 0 ? 'group-down' : 'group-up'}"]`))?.focus();
|
|
2785
2867
|
} catch (err) {
|
|
2786
|
-
showToast('
|
|
2868
|
+
showToast(t('schedules.toast.reorderFailed', { error: err.message }));
|
|
2787
2869
|
}
|
|
2788
2870
|
}
|
|
2789
2871
|
|
|
@@ -2810,11 +2892,11 @@ function openPromptScheduleModal() {
|
|
|
2810
2892
|
<div class="sched-prompt-head-text">
|
|
2811
2893
|
<span class="sched-prompt-icon">${ICONS.sparkle}</span>
|
|
2812
2894
|
<div>
|
|
2813
|
-
<h3 id="sched-prompt-title"
|
|
2814
|
-
<p
|
|
2895
|
+
<h3 id="sched-prompt-title">${te('schedules.prompt.title')}</h3>
|
|
2896
|
+
<p>${te('schedules.prompt.sub')}</p>
|
|
2815
2897
|
</div>
|
|
2816
2898
|
</div>
|
|
2817
|
-
<button class="sched-prompt-close" data-action="prompt-modal-close" aria-label="
|
|
2899
|
+
<button class="sched-prompt-close" data-action="prompt-modal-close" aria-label="${te('schedules.prompt.close')}">${ICONS.close}</button>
|
|
2818
2900
|
</header>
|
|
2819
2901
|
<div class="sched-prompt-tiles">
|
|
2820
2902
|
<button class="sched-prompt-tile" style="--provider-color:${claudeMeta.color};" data-action="prompt-modal-pick" data-runtime="claude-code">
|
|
@@ -2823,15 +2905,15 @@ function openPromptScheduleModal() {
|
|
|
2823
2905
|
<span class="sched-prompt-tile-title">${claudeMeta.label}</span>
|
|
2824
2906
|
<span class="sched-prompt-tile-sub">Anthropic</span>
|
|
2825
2907
|
</span>
|
|
2826
|
-
<span class="sched-prompt-pill sched-prompt-pill--active"
|
|
2908
|
+
<span class="sched-prompt-pill sched-prompt-pill--active">${te('schedules.prompt.ready')}</span>
|
|
2827
2909
|
</button>
|
|
2828
2910
|
<button class="sched-prompt-tile disabled" style="--provider-color:${opencodeMeta.color};" disabled aria-disabled="true">
|
|
2829
2911
|
<span class="sched-prompt-tile-logo" aria-hidden="true">${opencodeMeta.icon}</span>
|
|
2830
2912
|
<span class="sched-prompt-tile-body">
|
|
2831
2913
|
<span class="sched-prompt-tile-title">${opencodeMeta.label}</span>
|
|
2832
|
-
<span class="sched-prompt-tile-sub"
|
|
2914
|
+
<span class="sched-prompt-tile-sub">${te('schedules.prompt.multiProvider')}</span>
|
|
2833
2915
|
</span>
|
|
2834
|
-
<span class="sched-prompt-pill"
|
|
2916
|
+
<span class="sched-prompt-pill">${te('schedules.prompt.planned')}</span>
|
|
2835
2917
|
</button>
|
|
2836
2918
|
<button class="sched-prompt-tile disabled" style="--provider-color:${codexMeta.color};" disabled aria-disabled="true">
|
|
2837
2919
|
<span class="sched-prompt-tile-logo" aria-hidden="true">${codexMeta.icon}</span>
|
|
@@ -2839,7 +2921,7 @@ function openPromptScheduleModal() {
|
|
|
2839
2921
|
<span class="sched-prompt-tile-title">Codex CLI</span>
|
|
2840
2922
|
<span class="sched-prompt-tile-sub">OpenAI</span>
|
|
2841
2923
|
</span>
|
|
2842
|
-
<span class="sched-prompt-pill"
|
|
2924
|
+
<span class="sched-prompt-pill">${te('schedules.prompt.planned')}</span>
|
|
2843
2925
|
</button>
|
|
2844
2926
|
</div>
|
|
2845
2927
|
`;
|
|
@@ -2882,7 +2964,7 @@ function closePromptScheduleModal() {
|
|
|
2882
2964
|
|
|
2883
2965
|
async function handlePromptPick(runtime, triggerEl = null) {
|
|
2884
2966
|
if (runtime !== 'claude-code') {
|
|
2885
|
-
showToast('
|
|
2967
|
+
showToast(t('schedules.toast.comingSoon'));
|
|
2886
2968
|
return;
|
|
2887
2969
|
}
|
|
2888
2970
|
if (triggerEl) {
|
|
@@ -2890,9 +2972,9 @@ async function handlePromptPick(runtime, triggerEl = null) {
|
|
|
2890
2972
|
triggerEl.setAttribute('aria-busy', 'true');
|
|
2891
2973
|
}
|
|
2892
2974
|
try {
|
|
2893
|
-
await sendToPanel('/synabun schedule', { newTab: true, tabLabel: '
|
|
2975
|
+
await sendToPanel('/synabun schedule', { newTab: true, tabLabel: t('schedules.prompt.tabLabel'), autoSubmit: true });
|
|
2894
2976
|
} catch (err) {
|
|
2895
|
-
showToast('
|
|
2977
|
+
showToast(t('schedules.toast.panelFailed', { error: err.message }));
|
|
2896
2978
|
} finally {
|
|
2897
2979
|
closePromptScheduleModal();
|
|
2898
2980
|
}
|
|
@@ -2902,46 +2984,46 @@ async function handlePromptPick(runtime, triggerEl = null) {
|
|
|
2902
2984
|
|
|
2903
2985
|
async function createQuickTimerFromForm(runNow) {
|
|
2904
2986
|
const templateId = $('sched-qt-template')?.value;
|
|
2905
|
-
if (!templateId) { markInvalid('sched-qt-template', '
|
|
2987
|
+
if (!templateId) { markInvalid('sched-qt-template', t('schedules.toast.chooseAutomation')); return; }
|
|
2906
2988
|
const customVal = Number($('sched-qt-custom-min')?.value);
|
|
2907
2989
|
const minutes = customVal >= 1 ? customVal : _selectedQtMinutes;
|
|
2908
|
-
if (!runNow && !minutes) { showToast('
|
|
2990
|
+
if (!runNow && !minutes) { showToast(t('schedules.toast.chooseDelay')); return; }
|
|
2909
2991
|
const template = getTemplate(templateId);
|
|
2910
2992
|
const profile = $('sched-qt-profile')?.value || 'claude-code';
|
|
2911
2993
|
const model = $('sched-qt-model')?.value || null;
|
|
2912
2994
|
const contextMode = $('sched-qt-context')?.value || 'default';
|
|
2913
2995
|
if (!modelChoiceAvailable(profile, model, _launchAccount)) {
|
|
2914
|
-
showToast('
|
|
2996
|
+
showToast(t('schedules.toast.modelUnavailable')); return;
|
|
2915
2997
|
}
|
|
2916
2998
|
if (!contextChoiceAvailable(profile, model, contextMode, _launchAccount)) {
|
|
2917
|
-
showToast('
|
|
2999
|
+
showToast(t('schedules.toast.contextUnavailable')); return;
|
|
2918
3000
|
}
|
|
2919
3001
|
const effortRaw = $('sched-qt-effort')?.value || null;
|
|
2920
3002
|
const effort = normalizeEffortForProfile(profile, effortRaw);
|
|
2921
|
-
const
|
|
3003
|
+
const tools = strictToolHost(profile) ? readToolPicker($('sched-qt-mcp')) : null;
|
|
2922
3004
|
const codexAccountId = profile === 'codex' ? ($('sched-qt-account')?.value || _launchAccount || 'default') : undefined;
|
|
2923
3005
|
const usesBrowser = _qtUsesBrowser !== null ? _qtUsesBrowser : !!template?.usesBrowser;
|
|
2924
3006
|
try {
|
|
2925
3007
|
if (runNow) {
|
|
2926
3008
|
const result = await triggerQuickTimerNow(templateId, {
|
|
2927
|
-
profile, model, contextMode, effort,
|
|
3009
|
+
profile, model, contextMode, effort, tools, usesBrowser, codexAccountId,
|
|
2928
3010
|
sidepanelWindowId: getNativeLoopRouterWindowId(),
|
|
2929
3011
|
sidepanelClaimToken: getNativeLoopRouterClaimToken(),
|
|
2930
3012
|
});
|
|
2931
|
-
showToast(
|
|
3013
|
+
showToast(t('schedules.toast.runningNow', { name: result.templateName }));
|
|
2932
3014
|
} else {
|
|
2933
|
-
const result = await createQuickTimer(templateId, minutes, { profile, model, contextMode, effort,
|
|
3015
|
+
const result = await createQuickTimer(templateId, minutes, { profile, model, contextMode, effort, tools, usesBrowser, codexAccountId });
|
|
2934
3016
|
if (!_quickTimers.some(t => t.id === result.timerId)) {
|
|
2935
|
-
_quickTimers.push({ id: result.timerId, templateId, templateName: result.templateName, firesAt: result.firesAt, minutes: result.minutes, profile: result.profile, model: result.model, contextMode: result.contextMode, effort: result.effort,
|
|
3017
|
+
_quickTimers.push({ id: result.timerId, templateId, templateName: result.templateName, firesAt: result.firesAt, minutes: result.minutes, profile: result.profile, model: result.model, contextMode: result.contextMode, effort: result.effort, tools: result.tools, usesBrowser: result.usesBrowser });
|
|
2936
3018
|
}
|
|
2937
3019
|
_selectedQtMinutes = null;
|
|
2938
3020
|
_panel?.querySelectorAll('[data-action="qt-select"]').forEach(chip => chip.setAttribute('aria-pressed', 'false'));
|
|
2939
|
-
showToast(
|
|
3021
|
+
showToast(t('schedules.toast.timerSet', { name: result.templateName }));
|
|
2940
3022
|
}
|
|
2941
3023
|
renderActiveTimersInto();
|
|
2942
3024
|
renderRail();
|
|
2943
3025
|
} catch (err) {
|
|
2944
|
-
showToast((runNow ? '
|
|
3026
|
+
showToast(t(runNow ? 'schedules.toast.runNowFailed' : 'schedules.toast.timerFailed', { error: err.message }));
|
|
2945
3027
|
}
|
|
2946
3028
|
}
|
|
2947
3029
|
|
|
@@ -2951,25 +3033,25 @@ async function cancelQuickTimerById(id) {
|
|
|
2951
3033
|
_quickTimers = _quickTimers.filter(t => t.id !== id);
|
|
2952
3034
|
renderActiveTimersInto();
|
|
2953
3035
|
renderRail();
|
|
2954
|
-
showToast('
|
|
3036
|
+
showToast(t('schedules.toast.timerCancelled'));
|
|
2955
3037
|
} catch (err) {
|
|
2956
|
-
showToast('
|
|
3038
|
+
showToast(t('schedules.toast.cancelFailed', { error: err.message }));
|
|
2957
3039
|
}
|
|
2958
3040
|
}
|
|
2959
3041
|
|
|
2960
3042
|
async function createExistingScheduleTimer() {
|
|
2961
3043
|
const scheduleId = $('sched-existing-timer-schedule')?.value;
|
|
2962
3044
|
const minutes = Number($('sched-existing-timer-min')?.value);
|
|
2963
|
-
if (!scheduleId) { markInvalid('sched-existing-timer-schedule', '
|
|
2964
|
-
if (!minutes || minutes < 1) { markInvalid('sched-existing-timer-min', '
|
|
3045
|
+
if (!scheduleId) { markInvalid('sched-existing-timer-schedule', t('schedules.toast.chooseSchedule')); return; }
|
|
3046
|
+
if (!minutes || minutes < 1) { markInvalid('sched-existing-timer-min', t('schedules.toast.minutesRequired')); return; }
|
|
2965
3047
|
try {
|
|
2966
3048
|
const result = await startScheduleTimer(scheduleId, minutes);
|
|
2967
3049
|
_scheduleTimerData[scheduleId] = { firesAt: result.firesAt, minutes: result.minutes };
|
|
2968
3050
|
renderActiveTimersInto();
|
|
2969
3051
|
renderRail();
|
|
2970
|
-
showToast('
|
|
3052
|
+
showToast(t('schedules.toast.scheduleTimerSet'));
|
|
2971
3053
|
} catch (err) {
|
|
2972
|
-
showToast('
|
|
3054
|
+
showToast(t('schedules.toast.timerFailed', { error: err.message }));
|
|
2973
3055
|
}
|
|
2974
3056
|
}
|
|
2975
3057
|
|
|
@@ -2979,9 +3061,9 @@ async function cancelExistingScheduleTimer(id) {
|
|
|
2979
3061
|
delete _scheduleTimerData[id];
|
|
2980
3062
|
renderActiveTimersInto();
|
|
2981
3063
|
renderRail();
|
|
2982
|
-
showToast('
|
|
3064
|
+
showToast(t('schedules.toast.timerCancelled'));
|
|
2983
3065
|
} catch (err) {
|
|
2984
|
-
showToast('
|
|
3066
|
+
showToast(t('schedules.toast.cancelFailed', { error: err.message }));
|
|
2985
3067
|
}
|
|
2986
3068
|
}
|
|
2987
3069
|
|
|
@@ -3033,7 +3115,7 @@ function setupSync() {
|
|
|
3033
3115
|
refresh();
|
|
3034
3116
|
});
|
|
3035
3117
|
on('sync:schedule:fired', (data) => {
|
|
3036
|
-
if (data?.scheduleName) showToast(
|
|
3118
|
+
if (data?.scheduleName) showToast(t('schedules.toast.fired', { name: data.scheduleName }));
|
|
3037
3119
|
if (_panel) scheduleRefresh();
|
|
3038
3120
|
});
|
|
3039
3121
|
on('sync:schedule:completed', (data) => {
|
|
@@ -3047,7 +3129,7 @@ function setupSync() {
|
|
|
3047
3129
|
}
|
|
3048
3130
|
});
|
|
3049
3131
|
on('sync:schedule:failed', (data) => {
|
|
3050
|
-
if (data?.reason) showToast(
|
|
3132
|
+
if (data?.reason) showToast(t('schedules.toast.scheduleFailed', { reason: data.reason }));
|
|
3051
3133
|
if (_panel) scheduleRefresh();
|
|
3052
3134
|
});
|
|
3053
3135
|
on('sync:schedule:timer-set', (data) => {
|
|
@@ -3057,7 +3139,7 @@ function setupSync() {
|
|
|
3057
3139
|
}
|
|
3058
3140
|
});
|
|
3059
3141
|
on('sync:schedule:timer-fired', (data) => {
|
|
3060
|
-
if (data?.scheduleName) showToast(
|
|
3142
|
+
if (data?.scheduleName) showToast(t('schedules.toast.timerFired', { name: data.scheduleName }));
|
|
3061
3143
|
if (data?.scheduleId) delete _scheduleTimerData[data.scheduleId];
|
|
3062
3144
|
if (_panel) { renderRail(); renderActiveTimersInto(); }
|
|
3063
3145
|
});
|
|
@@ -3089,24 +3171,26 @@ function setupSync() {
|
|
|
3089
3171
|
});
|
|
3090
3172
|
on('sync:quick-timer:set', (data) => {
|
|
3091
3173
|
if (data?.timerId && !_quickTimers.some(t => t.id === data.timerId)) {
|
|
3092
|
-
_quickTimers.push({ id: data.timerId, templateName: data.templateName, firesAt: data.firesAt, minutes: data.minutes, profile: data.profile, model: data.model, contextMode: data.contextMode, effort: data.effort,
|
|
3174
|
+
_quickTimers.push({ id: data.timerId, templateName: data.templateName, firesAt: data.firesAt, minutes: data.minutes, profile: data.profile, model: data.model, contextMode: data.contextMode, effort: data.effort, tools: data.tools, usesBrowser: data.usesBrowser });
|
|
3093
3175
|
if (_panel) { renderRail(); renderActiveTimersInto(); }
|
|
3094
3176
|
}
|
|
3095
3177
|
});
|
|
3096
3178
|
on('sync:quick-timer:fired', (data) => {
|
|
3097
|
-
if (data?.templateName) showToast(
|
|
3179
|
+
if (data?.templateName) showToast(t('schedules.toast.timerFired', { name: data.templateName }));
|
|
3098
3180
|
if (data?.timerId) _quickTimers = _quickTimers.filter(t => t.id !== data.timerId);
|
|
3099
3181
|
if (_panel) { renderRail(); renderActiveTimersInto(); }
|
|
3100
3182
|
});
|
|
3101
3183
|
on('sync:quick-timer:fired-now', (data) => {
|
|
3102
|
-
if (data?.templateName) showToast(
|
|
3184
|
+
if (data?.templateName) showToast(t('schedules.toast.runningNow', { name: data.templateName }));
|
|
3103
3185
|
});
|
|
3104
3186
|
on('sync:quick-timer:cancelled', (data) => {
|
|
3105
3187
|
if (data?.timerId) _quickTimers = _quickTimers.filter(t => t.id !== data.timerId);
|
|
3106
3188
|
if (_panel) { renderRail(); renderActiveTimersInto(); }
|
|
3107
3189
|
});
|
|
3108
3190
|
on('sync:quick-timer:failed', (data) => {
|
|
3109
|
-
if (data?.reason) showToast(
|
|
3191
|
+
if (data?.reason) showToast(t('schedules.toast.timerFailed', { error: data.reason }));
|
|
3110
3192
|
});
|
|
3111
3193
|
}
|
|
3112
3194
|
|
|
3195
|
+
|
|
3196
|
+
configureToolPickerTranslation(t);
|