synabun 2.0.10 → 2.2.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 +207 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +13 -13
- 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 +30 -17
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +7 -6
- 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 +302 -0
- package/lib/virustotal.mjs +203 -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/budget-judgments.d.ts +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
- package/mcp-server/dist/services/budget-judgments.js +5 -3
- package/mcp-server/dist/services/budget-judgments.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/identity.d.ts +2 -2
- package/mcp-server/dist/services/identity.js +2 -2
- 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/memory-sessions.d.ts +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-sessions.js +28 -2
- package/mcp-server/dist/services/memory-sessions.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +18 -11
- 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 +22 -63
- 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/typesafe-config.js +1 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- 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 +14 -14
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +29 -19
- 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/choice.d.ts +6 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +18 -13
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +2 -2
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- 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/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +9 -6
- package/mcp-server/dist/tools/morelogin.js.map +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 +642 -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/budget-judgments.ts +5 -3
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/identity.ts +2 -2
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/memory-sessions.ts +24 -3
- package/mcp-server/src/services/neural-interface.ts +81 -21
- package/mcp-server/src/services/server-instructions.ts +29 -64
- 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/typesafe-config.ts +1 -1
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +29 -20
- package/mcp-server/src/tools/choice.ts +17 -12
- package/mcp-server/src/tools/git-tools.ts +2 -2
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/morelogin.ts +6 -4
- package/mcp-server/src/tools/skill-tools.ts +592 -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 +3953 -228
- package/neural-interface/i18n/pt-BR.json +3901 -179
- package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
- package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
- package/neural-interface/lib/antigravity/acp-connection.js +299 -0
- package/neural-interface/lib/antigravity/bypass.js +100 -0
- package/neural-interface/lib/antigravity/catalog.js +273 -0
- package/neural-interface/lib/antigravity/home.js +260 -0
- package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
- package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
- package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
- package/neural-interface/lib/antigravity/policy.js +74 -0
- package/neural-interface/lib/antigravity/tool-call.js +178 -0
- package/neural-interface/lib/antigravity/transcripts.js +252 -0
- package/neural-interface/lib/antigravity/usage.js +43 -0
- package/neural-interface/lib/assistant-api.js +128 -28
- package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
- 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-budget-planner.js +7 -3
- package/neural-interface/lib/assistant-budget-split.js +42 -15
- package/neural-interface/lib/assistant-catalog.js +81 -15
- package/neural-interface/lib/assistant-config.js +6 -3
- package/neural-interface/lib/assistant-dispatch.js +1073 -200
- package/neural-interface/lib/assistant-envelope.js +156 -2
- package/neural-interface/lib/assistant-evidence.js +80 -0
- package/neural-interface/lib/assistant-hidden-models.js +7 -5
- package/neural-interface/lib/assistant-persona.js +93 -19
- package/neural-interface/lib/assistant-plan-permissions.js +25 -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 +245 -38
- package/neural-interface/lib/assistant-runtime.js +214 -30
- package/neural-interface/lib/assistant-task-prompt.js +87 -21
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/assistant-usage.js +29 -0
- package/neural-interface/lib/backup-service.js +626 -4
- 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 +84 -0
- package/neural-interface/lib/browser-network-guard.js +476 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +198 -38
- package/neural-interface/lib/claude-agent-bridge.js +11 -2
- package/neural-interface/lib/claude-history-cache.js +210 -0
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-model-catalog.js +5 -1
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install-api.js +1 -1
- package/neural-interface/lib/cli-install.js +163 -154
- package/neural-interface/lib/cli-resolver.js +13 -3
- package/neural-interface/lib/cli-update-planner.js +52 -41
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-rollout-reader.js +52 -0
- package/neural-interface/lib/codex-worker-app-server.js +191 -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/editor-git-api.js +3 -20
- package/neural-interface/lib/exec-loop-command.js +13 -10
- package/neural-interface/lib/file-explorer-api.js +74 -40
- package/neural-interface/lib/file-explorer-watch.js +9 -3
- package/neural-interface/lib/git-bash/install.js +52 -6
- package/neural-interface/lib/git-bash/manager.js +59 -17
- package/neural-interface/lib/git-bash/pins.js +12 -0
- package/neural-interface/lib/git-read.js +2 -2
- package/neural-interface/lib/git-status-runner.js +390 -0
- package/neural-interface/lib/git-status-watchdog.js +40 -0
- package/neural-interface/lib/greeting/budgets.js +1 -0
- package/neural-interface/lib/greeting/engine.js +2 -2
- package/neural-interface/lib/greeting/sources.js +10 -12
- package/neural-interface/lib/greeting/writer.js +1 -1
- 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 +35 -33
- package/neural-interface/lib/mcp-external-servers.js +332 -0
- package/neural-interface/lib/mcp-installer.js +2 -1
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/memory-session-capture.js +17 -3
- package/neural-interface/lib/morelogin.js +177 -25
- package/neural-interface/lib/native-loop-providers.js +965 -66
- package/neural-interface/lib/native-loop-runtime.js +290 -42
- 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 +53 -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/path-confine.js +12 -9
- package/neural-interface/lib/peer-identity.js +793 -0
- package/neural-interface/lib/pty-host/core.js +28 -1
- package/neural-interface/lib/pty-host/lifetime.js +12 -0
- package/neural-interface/lib/pty-host/manager.js +12 -1
- package/neural-interface/lib/remote-policy.js +1 -1
- package/neural-interface/lib/request-gate.js +20 -2
- package/neural-interface/lib/rulesets/api.js +7 -6
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +180 -36
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/rulesets/render.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +81 -31
- 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 +298 -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 +285 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +569 -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 +216 -0
- package/neural-interface/lib/skills/eval-confinement.js +399 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +135 -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 +203 -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 +441 -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 +269 -0
- package/neural-interface/lib/skills/runtime-checks.js +358 -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 +530 -0
- package/neural-interface/lib/skills/telemetry.js +119 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/synabun-version.js +1 -1
- package/neural-interface/lib/system-activity.js +1 -1
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/brain.js +7 -3
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/whatsapp/cards.js +22 -9
- package/neural-interface/lib/whatsapp/config.js +1 -1
- package/neural-interface/lib/worker-confinement.js +1268 -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-question.js +74 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +238 -0
- package/neural-interface/lib/worker-sandbox.js +329 -0
- package/neural-interface/package-lock.json +34 -12
- package/neural-interface/package.json +3 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +3 -3
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +125 -31
- package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
- package/neural-interface/public/shared/agy/agy-client.js +266 -0
- package/neural-interface/public/shared/agy/agy-commands.js +120 -0
- package/neural-interface/public/shared/agy/agy-copy.js +326 -0
- package/neural-interface/public/shared/agy/agy-diff.js +161 -0
- package/neural-interface/public/shared/agy/agy-events.js +469 -0
- package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
- package/neural-interface/public/shared/agy/agy-options.js +232 -0
- package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
- package/neural-interface/public/shared/agy/agy-render.js +392 -0
- package/neural-interface/public/shared/agy/agy-setup.js +149 -0
- package/neural-interface/public/shared/agy/agy-styles.js +604 -0
- package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
- 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 +36 -6
- package/neural-interface/public/shared/assistant/asst-api.js +1 -1
- 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 +105 -44
- package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
- package/neural-interface/public/shared/assistant/asst-control.js +205 -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-hosts.js +16 -7
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +45 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +327 -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 +151 -19
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +445 -82
- package/neural-interface/public/shared/assistant/asst-route.js +304 -53
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
- package/neural-interface/public/shared/assistant/asst-scenes.js +56 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +117 -10
- package/neural-interface/public/shared/assistant/asst-slash.js +17 -9
- package/neural-interface/public/shared/assistant/asst-state.js +87 -24
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +93 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +190 -23
- package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
- 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 +34 -10
- package/neural-interface/public/shared/cdx/cdx-panel.js +59 -151
- package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +39 -11
- package/neural-interface/public/shared/cdx/cdx-render.js +57 -38
- package/neural-interface/public/shared/cdx/cdx-requests.js +14 -4
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +127 -44
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-controller.js +218 -0
- package/neural-interface/public/shared/cli-install-view.js +11 -12
- package/neural-interface/public/shared/cli-status.js +11 -10
- package/neural-interface/public/shared/constants.js +5 -4
- 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 +367 -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/cp/cp-restore.js +162 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-bridge.js +17 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
- package/neural-interface/public/shared/desktop-titlebar.js +13 -4
- package/neural-interface/public/shared/desktop-update-view.js +21 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/explorer/fx-api.js +4 -4
- package/neural-interface/public/shared/explorer/fx-links.js +3 -3
- package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +48 -16
- package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
- package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
- package/neural-interface/public/shared/html-shell.js +30 -15
- 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 +74 -259
- 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-rehydrate.js +13 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +13 -22
- 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 +17 -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 +60 -50
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/provider-icons.js +4 -2
- 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-rules.js +2 -2
- 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/showcase-privacy.js +6 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +222 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +133 -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 +149 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +513 -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 +237 -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 +230 -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-fixes-view.js +359 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -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 +282 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +313 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +748 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +479 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +690 -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 +249 -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 +426 -0
- package/neural-interface/public/shared/state.js +1 -1
- package/neural-interface/public/shared/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1822 -1756
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/text-cache.js +29 -0
- 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/topright-overflow-logic.js +54 -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-antigravity-panel.js +19 -0
- package/neural-interface/public/shared/ui-assistant.js +2 -0
- package/neural-interface/public/shared/ui-automation-studio.js +111 -140
- package/neural-interface/public/shared/ui-claude-panel.js +376 -82
- package/neural-interface/public/shared/ui-cli-banner.js +23 -167
- 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-file-explorer.js +48 -6
- package/neural-interface/public/shared/ui-file-links.js +2 -2
- package/neural-interface/public/shared/ui-games.js +2 -2
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +33 -2
- package/neural-interface/public/shared/ui-link.js +3 -3
- package/neural-interface/public/shared/ui-menubar.js +10 -20
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +11 -1
- package/neural-interface/public/shared/ui-navbar.js +49 -24
- package/neural-interface/public/shared/ui-notifications.js +1 -0
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-resume.js +9 -7
- package/neural-interface/public/shared/ui-schedules-studio.js +546 -431
- package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
- package/neural-interface/public/shared/ui-sessions.js +27 -15
- package/neural-interface/public/shared/ui-settings.js +683 -526
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +4 -4
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
- package/neural-interface/public/shared/ui-sidepanel-windows.js +18 -13
- 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 +270 -190
- package/neural-interface/public/shared/ui-tool-updates.js +6 -4
- package/neural-interface/public/shared/ui-tooltip.js +81 -51
- package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
- 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 +312 -59
- package/neural-interface/public/shared/ui-worker-end.js +5 -1
- 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 +3 -3
- 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-questions.js +34 -0
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +2 -2
- 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 +4553 -2855
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +23 -5
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +34 -12
- package/package.json +17 -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 +6 -6
- 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
- /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
|
@@ -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,10 +7,13 @@
|
|
|
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';
|
|
11
15
|
import { sendToPanel } from './ui-claude-panel.js';
|
|
16
|
+
import { openAntigravityWithPrompt } from './ui-antigravity-panel.js';
|
|
12
17
|
import { getProviderMeta } from './provider-icons.js';
|
|
13
18
|
import {
|
|
14
19
|
getNativeLoopRouterClaimToken,
|
|
@@ -17,14 +22,17 @@ import {
|
|
|
17
22
|
import {
|
|
18
23
|
CLI_PROFILES,
|
|
19
24
|
ensureModelsForProfile,
|
|
20
|
-
|
|
25
|
+
fetchToolCatalog,
|
|
21
26
|
fetchCodexAccounts,
|
|
22
27
|
getCachedCodexAccounts,
|
|
23
|
-
|
|
28
|
+
getCachedToolCatalog,
|
|
24
29
|
getEffortLevelsForProfile,
|
|
25
30
|
getModelsForProfile,
|
|
26
31
|
formatModelOptionLabel,
|
|
27
32
|
isDynamicModelProfile,
|
|
33
|
+
migrateLaunchRecords,
|
|
34
|
+
migrateProviderId,
|
|
35
|
+
migrateProviderModel,
|
|
28
36
|
modelSelectorValue,
|
|
29
37
|
modelMatchesSelector,
|
|
30
38
|
normalizeEffortForProfile,
|
|
@@ -64,6 +72,7 @@ import {
|
|
|
64
72
|
describeCron,
|
|
65
73
|
formatAgo,
|
|
66
74
|
formatCountdown,
|
|
75
|
+
fullDateTime,
|
|
67
76
|
groupIdOf,
|
|
68
77
|
groupSummary,
|
|
69
78
|
inScope,
|
|
@@ -73,8 +82,10 @@ import {
|
|
|
73
82
|
matchesQuery,
|
|
74
83
|
oneLine,
|
|
75
84
|
scopeCounts,
|
|
85
|
+
setSchedulesViewTranslator,
|
|
76
86
|
shortWhen,
|
|
77
87
|
sortSchedules,
|
|
88
|
+
weekdayName,
|
|
78
89
|
whenLabel,
|
|
79
90
|
} from './schedules-view.js';
|
|
80
91
|
import {
|
|
@@ -93,6 +104,7 @@ const LAYOUT_KEY = KEYS.PANEL_PREFIX + 'schedules-window';
|
|
|
93
104
|
const SCOPE_KEY = 'sched-view-scope';
|
|
94
105
|
const SORT_KEY = 'sched-view-sort';
|
|
95
106
|
const COLLAPSED_KEY = 'sched-collapsed-groups';
|
|
107
|
+
const REOPEN_KEY = 'synabun-schedules-reopen'; // sessionStorage, for one reload
|
|
96
108
|
const DEFAULT_GROUP_COLOR = '#4fc3f7';
|
|
97
109
|
|
|
98
110
|
let _panel = null;
|
|
@@ -135,16 +147,16 @@ let _promptBackdrop = null;
|
|
|
135
147
|
let _promptKeyHandler = null;
|
|
136
148
|
const _modelLoadRequests = new Set();
|
|
137
149
|
|
|
138
|
-
let _launchProfile = storage.getItem('as-launch-profile') || 'claude-code';
|
|
150
|
+
let _launchProfile = migrateProviderId(storage.getItem('as-launch-profile') || 'claude-code');
|
|
139
151
|
const _storedLaunchSelection = normalizeAutomationSelection(
|
|
140
|
-
_launchProfile, storage.getItem('as-launch-model'), storage.getItem('as-launch-context-mode'),
|
|
152
|
+
_launchProfile, migrateProviderModel(_launchProfile, storage.getItem('as-launch-model')), storage.getItem('as-launch-context-mode'),
|
|
141
153
|
);
|
|
142
154
|
let _launchModel = _storedLaunchSelection.model;
|
|
143
155
|
let _launchContextMode = _storedLaunchSelection.contextMode || 'default';
|
|
144
156
|
storage.setItem('as-launch-model', _launchModel || '');
|
|
145
157
|
storage.setItem('as-launch-context-mode', _launchContextMode);
|
|
146
158
|
let _launchEffort = storage.getItem('as-launch-effort') || 'off';
|
|
147
|
-
let
|
|
159
|
+
let _launchTools = (() => { try { return JSON.parse(storage.getItem('as-launch-tools') || 'null'); } catch { return null; } })();
|
|
148
160
|
let _launchAccount = storage.getItem('as-launch-account') || 'default'; // Codex ChatGPT account (CODEX_HOME)
|
|
149
161
|
let _codexAccountsRequested = false; // lazy-fetch guard for account selects
|
|
150
162
|
|
|
@@ -183,41 +195,60 @@ const ICONS = {
|
|
|
183
195
|
};
|
|
184
196
|
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
197
|
|
|
198
|
+
// Words: every string the window shows is a `schedules.*` key of i18n/<locale>.json; the ones
|
|
199
|
+
// schedules-view.js decides read through the translator set here. What the user named (schedules,
|
|
200
|
+
// groups, automations) and what a provider named (models, accounts, CLIs) show as they are.
|
|
201
|
+
setSchedulesViewTranslator((key, params) => t(`schedules.view.${key}`, params), getLocale);
|
|
202
|
+
// The same strings, escaped for markup.
|
|
203
|
+
const te = (key, params) => esc(t(key, params));
|
|
204
|
+
const tpe = (key, count, params) => esc(tp(key, count, params));
|
|
205
|
+
|
|
186
206
|
const VIEWS = {
|
|
187
|
-
upcoming: {
|
|
188
|
-
all: {
|
|
189
|
-
attention: {
|
|
190
|
-
paused: {
|
|
191
|
-
timers: {
|
|
192
|
-
activity: {
|
|
207
|
+
upcoming: { icon: ICONS.next },
|
|
208
|
+
all: { icon: ICONS.list },
|
|
209
|
+
attention: { icon: ICONS.alert },
|
|
210
|
+
paused: { icon: ICONS.pause },
|
|
211
|
+
timers: { icon: ICONS.timer },
|
|
212
|
+
activity: { icon: ICONS.activity },
|
|
193
213
|
};
|
|
214
|
+
const viewLabel = (scope) => t(`schedules.views.${scope}`);
|
|
194
215
|
|
|
216
|
+
// A preset is a phrase (`key`) or a time of day, which reads the same in every language.
|
|
195
217
|
const CRON_PRESET_GROUPS = [
|
|
196
|
-
{ group: '
|
|
197
|
-
{
|
|
198
|
-
{
|
|
199
|
-
{
|
|
200
|
-
{
|
|
201
|
-
{
|
|
218
|
+
{ group: 'frequency', presets: [
|
|
219
|
+
{ key: 'every15min', cron: '*/15 * * * *' },
|
|
220
|
+
{ key: 'every30min', cron: '*/30 * * * *' },
|
|
221
|
+
{ key: 'everyHour', cron: '0 * * * *' },
|
|
222
|
+
{ key: 'every2hours', cron: '0 */2 * * *' },
|
|
223
|
+
{ key: 'every6hours', cron: '0 */6 * * *' },
|
|
202
224
|
] },
|
|
203
|
-
{ group: '
|
|
204
|
-
{
|
|
205
|
-
{
|
|
206
|
-
{
|
|
207
|
-
{
|
|
208
|
-
{
|
|
225
|
+
{ group: 'daily', presets: [
|
|
226
|
+
{ time: '09:00', cron: '0 9 * * *' },
|
|
227
|
+
{ time: '12:00', cron: '0 12 * * *' },
|
|
228
|
+
{ time: '18:00', cron: '0 18 * * *' },
|
|
229
|
+
{ key: 'twiceADay', cron: '0 10,18 * * *' },
|
|
230
|
+
{ key: 'threeTimesADay', cron: '0 9,14,19 * * *' },
|
|
209
231
|
] },
|
|
210
|
-
{ group: '
|
|
211
|
-
{
|
|
212
|
-
{
|
|
213
|
-
{
|
|
214
|
-
{
|
|
215
|
-
{
|
|
232
|
+
{ group: 'weekly', presets: [
|
|
233
|
+
{ key: 'weekdays0900', cron: '0 9 * * 1-5' },
|
|
234
|
+
{ key: 'weekdaysTwice', cron: '0 10,18 * * 1-5' },
|
|
235
|
+
{ key: 'weekends1000', cron: '0 10 * * 0,6' },
|
|
236
|
+
{ key: 'monWedFri', cron: '0 9 * * 1,3,5' },
|
|
237
|
+
{ key: 'tueThu', cron: '0 9 * * 2,4' },
|
|
216
238
|
] },
|
|
217
239
|
];
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
240
|
+
const presetLabel = (preset) => (preset.key ? t(`schedules.presets.${preset.key}`) : preset.time);
|
|
241
|
+
|
|
242
|
+
// The words of the launch popups that agent-runtime-options.js and automation-context.js write in
|
|
243
|
+
// English for every window: the ones the Schedules window shows read in the page's language. A
|
|
244
|
+
// word not listed (a level or a description a provider named) shows as it is.
|
|
245
|
+
const LAUNCH_WORDS = {
|
|
246
|
+
Off: 'off', Default: 'default', Low: 'low', Med: 'medium', High: 'high', XHigh: 'xhigh', Max: 'max', Ultra: 'ultra',
|
|
247
|
+
'No thinking': 'noThinking', Minimal: 'minimal', Balanced: 'balanced', Deep: 'deep', 'Very deep': 'veryDeep',
|
|
248
|
+
Maximum: 'maximum', 'Config default': 'configDefault', 'Model default': 'modelDefault', Light: 'light',
|
|
249
|
+
'Maximum, with delegation': 'maximumDelegated',
|
|
250
|
+
};
|
|
251
|
+
const launchWord = (text) => (Object.prototype.hasOwnProperty.call(LAUNCH_WORDS, text) ? t(`schedules.launch.word.${LAUNCH_WORDS[text]}`) : text);
|
|
221
252
|
|
|
222
253
|
function readJson(key, fallback) {
|
|
223
254
|
try { const value = JSON.parse(storage.getItem(key) || 'null'); return value ?? fallback; } catch { return fallback; }
|
|
@@ -238,7 +269,6 @@ const domId = (value) => String(value ?? '').replace(/[^\w-]/g, '_');
|
|
|
238
269
|
// A choice of a popup (cc-dropdown.js). The one shown is the last marked `selected`, else the
|
|
239
270
|
// first, as in a <select>: a saved value a list lacks is added as a choice of its own, marked.
|
|
240
271
|
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
272
|
const cssColor = (value) => (value && CSS.supports('color', String(value)) ? String(value) : 'var(--stg-off)');
|
|
243
273
|
|
|
244
274
|
function showToast(msg) {
|
|
@@ -281,14 +311,14 @@ const listScope = () => LIST_SCOPES.includes(_scope) || _scope === 'ungrouped' |
|
|
|
281
311
|
|
|
282
312
|
function formatTimerCountdown(firesAtISO) {
|
|
283
313
|
const diff = new Date(firesAtISO) - Date.now();
|
|
284
|
-
if (diff <= 0) return 'firing';
|
|
314
|
+
if (diff <= 0) return t('schedules.timers.firing');
|
|
285
315
|
const mins = Math.ceil(diff / 60_000);
|
|
286
316
|
if (mins >= 60) {
|
|
287
317
|
const h = Math.floor(mins / 60);
|
|
288
318
|
const m = mins % 60;
|
|
289
|
-
return m ?
|
|
319
|
+
return m ? t('schedules.timers.countdownHours', { h, m }) : t('schedules.timers.countdownHour', { h });
|
|
290
320
|
}
|
|
291
|
-
return
|
|
321
|
+
return t('schedules.timers.countdownMinutes', { m: mins });
|
|
292
322
|
}
|
|
293
323
|
|
|
294
324
|
// ── Opening and closing ──
|
|
@@ -300,11 +330,28 @@ export function initSchedulesStudio() {
|
|
|
300
330
|
on('schedules:open', (opts) => openPanel(opts || {}));
|
|
301
331
|
on('schedules:toggle', () => (_panel ? closePanel() : openPanel({})));
|
|
302
332
|
setupSync();
|
|
333
|
+
// A language change reloads the app (i18n.js): an open window opens again, in the new language,
|
|
334
|
+
// on the view it showed (Settings does the same for itself).
|
|
335
|
+
window.addEventListener('synabun:locale-change', () => {
|
|
336
|
+
if (!_panel) return;
|
|
337
|
+
try { sessionStorage.setItem(REOPEN_KEY, _scope); } catch { /* private mode: the reload leaves it closed */ }
|
|
338
|
+
});
|
|
339
|
+
const reopen = takeReopen();
|
|
340
|
+
if (reopen) setTimeout(() => openPanel({ scope: reopen }), 0);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** The view to open the window on after a reload a language change asked for; read once. */
|
|
344
|
+
function takeReopen() {
|
|
345
|
+
try {
|
|
346
|
+
const scope = sessionStorage.getItem(REOPEN_KEY);
|
|
347
|
+
if (scope) sessionStorage.removeItem(REOPEN_KEY);
|
|
348
|
+
return scope && (VIEW_SCOPES.includes(scope) || scope === 'ungrouped' || scope.startsWith('group:')) ? scope : null;
|
|
349
|
+
} catch { return null; }
|
|
303
350
|
}
|
|
304
351
|
|
|
305
352
|
async function openPanel(opts = {}) {
|
|
306
353
|
if (isGuest() && !hasPermission('automations')) {
|
|
307
|
-
showGuestToast('
|
|
354
|
+
showGuestToast(t('schedules.window.guestDisabled'));
|
|
308
355
|
return;
|
|
309
356
|
}
|
|
310
357
|
if (_panel) {
|
|
@@ -378,7 +425,6 @@ function applyOpenOptions(opts = {}) {
|
|
|
378
425
|
}
|
|
379
426
|
|
|
380
427
|
function placePanel() {
|
|
381
|
-
const navH = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--navbar-height')) || 48;
|
|
382
428
|
const saved = readJson(LAYOUT_KEY, null);
|
|
383
429
|
if (saved && saved.w > 0 && saved.h > 0) {
|
|
384
430
|
_panel.style.width = saved.w + 'px';
|
|
@@ -386,10 +432,13 @@ function placePanel() {
|
|
|
386
432
|
}
|
|
387
433
|
const width = _panel.offsetWidth;
|
|
388
434
|
const height = _panel.offsetHeight;
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
435
|
+
// Inside the app frame (8px from the window and the bar, as before, and clear of an open explorer)
|
|
436
|
+
const f = frameRect();
|
|
437
|
+
const left = saved && Number.isFinite(saved.x) ? saved.x : f.left + (f.width - width) / 2;
|
|
438
|
+
const top = saved && Number.isFinite(saved.y) ? saved.y : f.top + Math.max(0, (f.height - height) / 2);
|
|
439
|
+
const at = clampBox(left, top, width, height, f);
|
|
440
|
+
_panel.style.left = Math.round(at.x) + 'px';
|
|
441
|
+
_panel.style.top = Math.round(at.y) + 'px';
|
|
393
442
|
}
|
|
394
443
|
|
|
395
444
|
function saveLayout() {
|
|
@@ -443,21 +492,21 @@ function buildPanelHTML() {
|
|
|
443
492
|
<header class="sched-header drag-handle" data-drag="${PANEL_ID}">
|
|
444
493
|
<div class="sched-header-left">
|
|
445
494
|
<span class="sched-header-icon">${ICONS.clock}</span>
|
|
446
|
-
<h3 id="sched-title"
|
|
495
|
+
<h3 id="sched-title">${te('schedules.window.title')}</h3>
|
|
447
496
|
<span class="sched-count" id="sched-total-count"></span>
|
|
448
497
|
</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="
|
|
498
|
+
<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>
|
|
499
|
+
<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
500
|
</header>
|
|
452
501
|
<div class="sched-body">
|
|
453
|
-
<nav class="sched-rail" id="sched-rail" aria-label="
|
|
502
|
+
<nav class="sched-rail" id="sched-rail" aria-label="${te('schedules.window.railLabel')}"></nav>
|
|
454
503
|
<section class="sched-main" aria-labelledby="sched-view-title">
|
|
455
504
|
<!-- A window too narrow for the rail: its views and groups as a popup, and New group. Outside
|
|
456
505
|
the frame below, which each view and editor replaces, so it is there in every one. -->
|
|
457
506
|
<div class="sched-narrow-nav" id="sched-narrow-nav">
|
|
458
|
-
<span class="sched-sr" id="sched-scope-label"
|
|
507
|
+
<span class="sched-sr" id="sched-scope-label">${te('schedules.window.viewLabel')}</span>
|
|
459
508
|
${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
|
|
509
|
+
<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
510
|
</div>
|
|
462
511
|
<div class="sched-frame" id="sched-main"></div>
|
|
463
512
|
</section>
|
|
@@ -476,7 +525,7 @@ function wirePanel() {
|
|
|
476
525
|
if (!btn) return;
|
|
477
526
|
btn.classList.toggle('active', hidden);
|
|
478
527
|
btn.setAttribute('aria-pressed', String(hidden));
|
|
479
|
-
const label = hidden ? '
|
|
528
|
+
const label = t(hidden ? 'schedules.window.showBackdrop' : 'schedules.window.hideBackdrop');
|
|
480
529
|
btn.setAttribute('aria-label', label);
|
|
481
530
|
btn.dataset.tooltip = label;
|
|
482
531
|
}, { signal });
|
|
@@ -492,6 +541,14 @@ function wirePanel() {
|
|
|
492
541
|
_panel.addEventListener('keydown', handlePanelKeydown, { signal });
|
|
493
542
|
// The popups (cc-dropdown.js): one set of listeners for every one the window draws.
|
|
494
543
|
wireDropdowns(_panel, { signal });
|
|
544
|
+
_panel.addEventListener('change', event => {
|
|
545
|
+
if (!['sched-ge-browser', 'sched-ed-browser'].includes(event.target.id)) return;
|
|
546
|
+
const group = event.target.id === 'sched-ge-browser';
|
|
547
|
+
const inherited = group ? false : getGroup(_scheduleEditor?.groupId)?.usesBrowser ?? getTemplate(_scheduleEditor?.templateId)?.usesBrowser;
|
|
548
|
+
const usesBrowser = event.target.value === '' ? !!inherited : event.target.value === 'true';
|
|
549
|
+
const input = $(group ? 'sched-ge-mcp' : 'sched-ed-mcp');
|
|
550
|
+
updateToolPicker(input, readToolPicker(input, { requested: true }), { inheritLabel: toolWord(group ? 'noOverride' : 'templateDefault'), usesBrowser });
|
|
551
|
+
}, { signal });
|
|
495
552
|
// Capture: the menubar's own Escape (it closes an open menu) has run by the time a bubbling
|
|
496
553
|
// listener hears the key, so a menu that was open is seen here first.
|
|
497
554
|
window.addEventListener('keydown', handleEscape, { capture: true, signal });
|
|
@@ -510,7 +567,7 @@ function withDeadline(request) {
|
|
|
510
567
|
let timer;
|
|
511
568
|
const late = new Promise((_, reject) => {
|
|
512
569
|
timer = setTimeout(() => {
|
|
513
|
-
const err = new Error(
|
|
570
|
+
const err = new Error(t('schedules.load.noAnswer', { seconds: LOAD_DEADLINE / 1000 }));
|
|
514
571
|
err.timeout = true;
|
|
515
572
|
reject(err);
|
|
516
573
|
}, LOAD_DEADLINE);
|
|
@@ -518,9 +575,11 @@ function withDeadline(request) {
|
|
|
518
575
|
return Promise.race([request, late]).finally(() => clearTimeout(timer));
|
|
519
576
|
}
|
|
520
577
|
|
|
521
|
-
|
|
578
|
+
// The reason is the server's or the network's own words ("HTTP 500"): it shows as it came.
|
|
579
|
+
const failureReason = (result) => String(result.reason?.message || t('schedules.load.unexpected')).replace(/\.$/, '');
|
|
522
580
|
|
|
523
581
|
async function loadData() {
|
|
582
|
+
_launchTools = await migrateStoredTools(storage, 'as-launch-tools', 'as-launch-mcp-profile');
|
|
524
583
|
const seq = ++_loadSeq;
|
|
525
584
|
loadTimers();
|
|
526
585
|
const [schedules, templates, groups] = await Promise.allSettled([
|
|
@@ -528,21 +587,25 @@ async function loadData() {
|
|
|
528
587
|
]);
|
|
529
588
|
if (seq !== _loadSeq) return; // a newer load is under way: it finishes for both
|
|
530
589
|
const failed = [];
|
|
590
|
+
// A record still saved for the Gemini CLI reads as Antigravity's (migrateLaunchRecords).
|
|
531
591
|
if (schedules.status === 'fulfilled' && Array.isArray(schedules.value)) {
|
|
532
|
-
_schedules = schedules.value;
|
|
592
|
+
_schedules = migrateLaunchRecords(schedules.value);
|
|
533
593
|
_loaded.schedules = true;
|
|
534
594
|
} else failed.push(['schedules', failureReason(schedules)]);
|
|
535
595
|
if (templates.status === 'fulfilled' && Array.isArray(templates.value)) {
|
|
536
|
-
_templates = templates.value;
|
|
596
|
+
_templates = migrateLaunchRecords(templates.value);
|
|
537
597
|
_loaded.templates = true;
|
|
538
598
|
} else failed.push(['automations', failureReason(templates)]);
|
|
539
599
|
if (groups.status === 'fulfilled' && Array.isArray(groups.value)) {
|
|
540
|
-
_groups = groups.value;
|
|
600
|
+
_groups = migrateLaunchRecords(groups.value);
|
|
541
601
|
_loaded.groups = true;
|
|
542
602
|
if (groupIdOf(_scope) && !getGroup(groupIdOf(_scope))) setScope('all', { keepEditors: true });
|
|
543
603
|
} else failed.push(['groups', failureReason(groups)]);
|
|
544
604
|
_loadError = failed.length
|
|
545
|
-
?
|
|
605
|
+
? t('schedules.load.failed', {
|
|
606
|
+
lists: listJoin(failed.map(([what]) => t(`schedules.load.list.${what}`))),
|
|
607
|
+
reasons: [...new Set(failed.map(([, why]) => why))].join('; '),
|
|
608
|
+
})
|
|
546
609
|
: null;
|
|
547
610
|
loadFinished();
|
|
548
611
|
}
|
|
@@ -575,10 +638,10 @@ async function loadTimers() {
|
|
|
575
638
|
const failed = [];
|
|
576
639
|
if (timers.status === 'fulfilled' && timers.value && typeof timers.value === 'object') _scheduleTimerData = timers.value;
|
|
577
640
|
else failed.push(failureReason(timers));
|
|
578
|
-
if (quick.status === 'fulfilled' && Array.isArray(quick.value)) _quickTimers = quick.value;
|
|
641
|
+
if (quick.status === 'fulfilled' && Array.isArray(quick.value)) _quickTimers = migrateLaunchRecords(quick.value);
|
|
579
642
|
else failed.push(failureReason(quick));
|
|
580
643
|
_timersLoaded = true;
|
|
581
|
-
_timersError = failed.length ?
|
|
644
|
+
_timersError = failed.length ? t('schedules.load.timersFailed', { reasons: [...new Set(failed)].join('; ') }) : null;
|
|
582
645
|
if (!_panel) return;
|
|
583
646
|
renderRail();
|
|
584
647
|
renderActiveTimersInto();
|
|
@@ -647,22 +710,27 @@ function renderRail() {
|
|
|
647
710
|
const timers = _quickTimers.length + Object.keys(_scheduleTimerData).length;
|
|
648
711
|
const ids = groupIds();
|
|
649
712
|
const view = (scope, count, { alert = false } = {}) => {
|
|
650
|
-
const {
|
|
713
|
+
const { icon } = VIEWS[scope];
|
|
651
714
|
const current = _scope === scope;
|
|
652
715
|
const countHTML = count == null || !countsKnown() ? '' : `<span class="sched-nav-count${alert && count ? ' sched-nav-count--alert' : ''}">${count}</span>`;
|
|
653
716
|
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">${
|
|
717
|
+
<span class="sched-nav-icon">${icon}</span><span class="sched-nav-label">${esc(viewLabel(scope))}</span>${countHTML}</button>`;
|
|
655
718
|
};
|
|
656
719
|
const groupRows = orderedGroups().map(group => {
|
|
657
720
|
const members = _schedules.filter(s => s.groupId === group.id);
|
|
658
721
|
const summary = groupSummary(members, now);
|
|
659
722
|
const scope = `group:${group.id}`;
|
|
660
|
-
const
|
|
723
|
+
const facts = [
|
|
724
|
+
tp('schedules.count.schedules', summary.total),
|
|
725
|
+
summary.attention ? tp('schedules.count.attention', summary.attention) : '',
|
|
726
|
+
group.mutualExclusion ? t('schedules.rail.oneAtATimeFact') : '',
|
|
727
|
+
].filter(Boolean).join(', ');
|
|
728
|
+
const name = t('schedules.rail.groupLabel', { name: group.name, facts });
|
|
661
729
|
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
730
|
<span class="sched-nav-icon"><span class="sched-dot" style="--dot:${esc(cssColor(group.color))}"></span></span>
|
|
663
731
|
<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="${
|
|
732
|
+
${group.mutualExclusion ? `<span class="sched-nav-flag" data-tooltip="${te('schedules.oneAtATime')}">${ICONS.lock}</span>` : ''}
|
|
733
|
+
${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
734
|
<span class="sched-nav-count">${summary.total}</span>
|
|
667
735
|
</button>`;
|
|
668
736
|
}).join('');
|
|
@@ -682,11 +750,11 @@ function renderRail() {
|
|
|
682
750
|
<div class="sched-rail-sep" role="presentation"></div>
|
|
683
751
|
${view('timers', timers || null)}
|
|
684
752
|
${view('activity', null)}
|
|
685
|
-
<div class="sched-rail-head"><span id="sched-rail-groups"
|
|
753
|
+
<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
754
|
<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"
|
|
755
|
+
${groupRows || (countsKnown() ? `<div class="sched-rail-empty">${te('schedules.rail.noGroups')}</div>` : '')}
|
|
756
|
+
<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"' : ''}>
|
|
757
|
+
<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
758
|
</button>
|
|
691
759
|
</div>
|
|
692
760
|
`;
|
|
@@ -737,24 +805,24 @@ function buildListFrame(frame) {
|
|
|
737
805
|
<div class="sched-toolbar">
|
|
738
806
|
<div class="sched-search">
|
|
739
807
|
${ICONS.search}
|
|
740
|
-
<label class="sched-sr" for="sched-search">${activity ? '
|
|
808
|
+
<label class="sched-sr" for="sched-search">${activity ? te('schedules.list.searchRuns') : te('schedules.list.searchSchedules')}</label>
|
|
741
809
|
<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="
|
|
810
|
+
placeholder="${activity ? te('schedules.list.searchRunsPlaceholder') : te('schedules.list.searchPlaceholder')}" value="${esc(_query)}">
|
|
811
|
+
<button type="button" class="sched-icon-btn" data-action="clear-search" aria-label="${te('schedules.list.clearSearch')}"${_query ? '' : ' hidden'}>${ICONS.close}</button>
|
|
744
812
|
<kbd aria-hidden="true">/</kbd>
|
|
745
813
|
</div>
|
|
746
|
-
${activity ? '' : `<span class="sched-sort" id="sched-sort-wrap"><span class="sched-sr" id="sched-sort-label"
|
|
814
|
+
${activity ? '' : `<span class="sched-sort" id="sched-sort-wrap"><span class="sched-sr" id="sched-sort-label">${te('schedules.list.order')}</span>
|
|
747
815
|
${dropdownHTML('sched-sort', [
|
|
748
|
-
opt('next', '
|
|
749
|
-
opt('name', '
|
|
750
|
-
opt('last', '
|
|
816
|
+
opt('next', t('schedules.list.sortNext'), _sort === 'next'),
|
|
817
|
+
opt('name', t('schedules.list.sortName'), _sort === 'name'),
|
|
818
|
+
opt('last', t('schedules.list.sortLast'), _sort === 'last'),
|
|
751
819
|
], { labelledBy: 'sched-sort-label', inputAttrs: 'data-change="sort"' })}</span>`}
|
|
752
820
|
</div>
|
|
753
821
|
<div class="sched-load-note" id="sched-load-note" role="status"></div>
|
|
754
822
|
<div class="sched-list" id="sched-list" tabindex="-1"></div>
|
|
755
823
|
<footer class="sched-footer">
|
|
756
824
|
<span id="sched-status" role="status" aria-live="polite" aria-atomic="true"></span>
|
|
757
|
-
${activity ? '' : `<span class="sched-footer-hint">${ICONS.move}
|
|
825
|
+
${activity ? '' : `<span class="sched-footer-hint">${ICONS.move}${te('schedules.list.dragHint')}</span>`}
|
|
758
826
|
</footer>
|
|
759
827
|
`;
|
|
760
828
|
if (caret) {
|
|
@@ -768,9 +836,9 @@ function headActions({ group = null } = {}) {
|
|
|
768
836
|
// An editor needs the automations and groups: nothing opens one before they have loaded.
|
|
769
837
|
const wait = editorsWait() ? ' disabled' : '';
|
|
770
838
|
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
|
|
839
|
+
${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>` : ''}
|
|
840
|
+
<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>
|
|
841
|
+
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new"${wait}>${ICONS.plus}<span>${te('schedules.head.newSchedule')}</span></button>
|
|
774
842
|
</div>`;
|
|
775
843
|
}
|
|
776
844
|
|
|
@@ -778,52 +846,61 @@ function groupOverridesText(group) {
|
|
|
778
846
|
const parts = [];
|
|
779
847
|
if (group.profile) parts.push(CLI_PROFILES.find(c => c.id === group.profile)?.label || group.profile);
|
|
780
848
|
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('
|
|
849
|
+
if (group.contextMode && group.contextMode !== 'default') parts.push(t('schedules.overrides.context', { value: group.contextMode }));
|
|
850
|
+
if (group.effort) parts.push(t('schedules.overrides.thinking', { value: group.effort }));
|
|
851
|
+
if (group.tools) parts.push(t('schedules.overrides.mcp', { value: group.tools.join(', ') }));
|
|
852
|
+
if (group.usesBrowser === true) parts.push(t('schedules.overrides.browserOn'));
|
|
853
|
+
else if (group.usesBrowser === false) parts.push(t('schedules.overrides.browserOff'));
|
|
786
854
|
return parts.join(' · ');
|
|
787
855
|
}
|
|
788
856
|
|
|
857
|
+
/** "4 schedules · 3 active · 1 paused · 1 needs attention": the facts of a set of schedules. */
|
|
858
|
+
function countsLine({ total, active, paused, attention }) {
|
|
859
|
+
return [
|
|
860
|
+
tp('schedules.count.schedules', total),
|
|
861
|
+
tp('schedules.count.active', active),
|
|
862
|
+
tp('schedules.count.paused', paused),
|
|
863
|
+
attention ? tp('schedules.count.attention', attention) : '',
|
|
864
|
+
].filter(Boolean).join(' · ');
|
|
865
|
+
}
|
|
866
|
+
|
|
789
867
|
function renderViewHead() {
|
|
790
868
|
const head = $('sched-view-head');
|
|
791
869
|
if (!head) return;
|
|
792
870
|
const now = Date.now();
|
|
793
871
|
const counts = scopeCounts(_schedules, now);
|
|
794
|
-
let title = VIEWS[_scope]
|
|
872
|
+
let title = VIEWS[_scope] ? viewLabel(_scope) : t('schedules.window.title');
|
|
795
873
|
let dot = '';
|
|
796
874
|
let sub = '';
|
|
797
875
|
let meta = '';
|
|
798
876
|
let group = null;
|
|
799
877
|
if (_scope === 'all') {
|
|
800
|
-
sub =
|
|
878
|
+
sub = countsLine({ total: counts.all, active: counts.active, paused: counts.paused, attention: counts.attention });
|
|
801
879
|
} else if (_scope === 'upcoming') {
|
|
802
880
|
// 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.';
|
|
881
|
+
sub = counts.upcoming ? tp('schedules.head.upcoming', counts.upcoming) : t('schedules.head.upcomingNone');
|
|
806
882
|
} else if (_scope === 'attention') {
|
|
807
|
-
sub = '
|
|
883
|
+
sub = t('schedules.head.attention');
|
|
808
884
|
} else if (_scope === 'paused') {
|
|
809
|
-
sub = '
|
|
885
|
+
sub = t('schedules.head.paused');
|
|
810
886
|
} else if (_scope === 'activity') {
|
|
811
|
-
sub = '
|
|
887
|
+
sub = t('schedules.head.activity');
|
|
812
888
|
} else if (_scope === 'ungrouped') {
|
|
813
|
-
title = '
|
|
814
|
-
sub = '
|
|
889
|
+
title = viewLabel('ungrouped');
|
|
890
|
+
sub = t('schedules.head.ungrouped');
|
|
815
891
|
} else if (groupIdOf(_scope)) {
|
|
816
892
|
group = getGroup(groupIdOf(_scope));
|
|
817
893
|
if (group) {
|
|
818
894
|
title = group.name;
|
|
819
895
|
dot = `<span class="sched-dot" style="--dot:${esc(cssColor(group.color))}" aria-hidden="true"></span>`;
|
|
820
896
|
const summary = groupSummary(_schedules.filter(s => s.groupId === group.id), now);
|
|
821
|
-
sub =
|
|
897
|
+
sub = countsLine(summary);
|
|
898
|
+
if (group.mutualExclusion) sub = `${sub} · ${t('schedules.oneAtATime')}`;
|
|
822
899
|
const overrides = groupOverridesText(group);
|
|
823
|
-
if (overrides) meta = `<p class="sched-view-meta sched-group-overrides"
|
|
900
|
+
if (overrides) meta = `<p class="sched-view-meta sched-group-overrides">${te('schedules.head.groupLaunch', { settings: overrides })}</p>`;
|
|
824
901
|
}
|
|
825
902
|
}
|
|
826
|
-
if (_loading) sub = '
|
|
903
|
+
if (_loading) sub = t('schedules.list.loading');
|
|
827
904
|
else if (dataIncomplete()) sub = '';
|
|
828
905
|
// A live update changes counts, not the head's controls: keep them (and the focus on them).
|
|
829
906
|
const key = `${_scope}|${title}|${dot}|${meta}|${editorsWait()}`;
|
|
@@ -849,11 +926,11 @@ function renderNarrowNav() {
|
|
|
849
926
|
if (scope) {
|
|
850
927
|
const counts = scopeCounts(_schedules, Date.now());
|
|
851
928
|
const timers = _quickTimers.length + Object.keys(_scheduleTimerData).length;
|
|
852
|
-
const n = (value) => (!countsKnown() || !value && value !== 0 ? '' :
|
|
929
|
+
const n = (label, value) => (!countsKnown() || !value && value !== 0 ? label : t('schedules.rail.withCount', { label, count: value }));
|
|
853
930
|
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: '
|
|
931
|
+
.map(([id, count]) => opt(id, n(viewLabel(id), count)));
|
|
932
|
+
const groups = orderedGroups().map(g => opt(`group:${g.id}`, n(g.name, _schedules.filter(s => s.groupId === g.id).length)));
|
|
933
|
+
const entries = [{ group: t('schedules.rail.views'), options: views }, { group: t('schedules.rail.groups'), options: [...groups, opt('ungrouped', viewLabel('ungrouped'))] }];
|
|
857
934
|
// Redrawn only when it changes (an open popup stays open, its highlighted choice too).
|
|
858
935
|
const key = JSON.stringify(entries);
|
|
859
936
|
if (scope._key !== key) { setDropdownItems(scope, entries); scope._key = key; }
|
|
@@ -879,7 +956,7 @@ function renderNote() {
|
|
|
879
956
|
if (!note) return;
|
|
880
957
|
const listView = _frame === 'list' || _frame === 'activity';
|
|
881
958
|
const failed = _loading || !_loadError || (listView && dataIncomplete()) ? ''
|
|
882
|
-
: dataIncomplete() ? _loadError :
|
|
959
|
+
: dataIncomplete() ? _loadError : t('schedules.note.showingBefore', { error: _loadError });
|
|
883
960
|
const report = !failed && _frame === 'list' ? cliReportNote() : null;
|
|
884
961
|
const kind = failed ? 'load' : report?.kind || '';
|
|
885
962
|
const text = failed || report?.text || '';
|
|
@@ -889,8 +966,8 @@ function renderNote() {
|
|
|
889
966
|
note.classList.toggle('sched-load-note--ok', kind === 'ok');
|
|
890
967
|
note.classList.toggle('sched-load-note--busy', kind === 'busy');
|
|
891
968
|
const icon = kind === 'ok' ? ICONS.ok : kind === 'busy' ? ICONS.clock : ICONS.alert;
|
|
892
|
-
const action = kind === 'load' ?
|
|
893
|
-
: kind === 'busy' ? '' :
|
|
969
|
+
const action = kind === 'load' ? `<button type="button" class="sched-btn" data-action="retry-load">${te('schedules.note.tryAgain')}</button>`
|
|
970
|
+
: kind === 'busy' ? '' : `<button type="button" class="sched-btn" data-action="dismiss-report">${te('schedules.note.dismiss')}</button>`;
|
|
894
971
|
note.innerHTML = text ? `${icon}<span>${esc(text)}</span>${action}` : '';
|
|
895
972
|
}
|
|
896
973
|
|
|
@@ -898,23 +975,23 @@ function renderNote() {
|
|
|
898
975
|
function cliReportNote() {
|
|
899
976
|
const r = _cliReport;
|
|
900
977
|
if (!r || _scope !== `group:${r.groupId}`) return null;
|
|
901
|
-
if (r.pending) return { kind: 'busy', text:
|
|
978
|
+
if (r.pending) return { kind: 'busy', text: t('schedules.cliReport.clearing') };
|
|
902
979
|
const cli = r.profile ? CLI_PROFILES.find(p => p.id === r.profile)?.label || r.profile : null;
|
|
903
980
|
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(
|
|
981
|
+
// Each sentence is whole in every language; the report is the sentences that apply, in order.
|
|
982
|
+
const parts = [cli ? t('schedules.cliReport.nowRuns', { cli }) : t('schedules.cliReport.noCli')];
|
|
983
|
+
if (r.reset.length) parts.push(t('schedules.cliReport.cleared', { names: listJoin(r.reset) }));
|
|
984
|
+
else if (!kept) parts.push(t('schedules.cliReport.noneLeft'));
|
|
985
|
+
if (r.changed.length) parts.push(tp('schedules.cliReport.changed', r.changed.length, { names: listJoin(r.changed) }));
|
|
986
|
+
if (r.failed.length) parts.push(tp('schedules.cliReport.failed', r.failed.length, { names: listJoin(r.failed) }));
|
|
987
|
+
if (kept) parts.push(tp('schedules.cliReport.open', kept));
|
|
911
988
|
return { kind: kept ? 'warn' : 'ok', text: parts.join(' ') };
|
|
912
989
|
}
|
|
913
990
|
|
|
914
991
|
function loadFailedHTML() {
|
|
915
|
-
const waiting = _pendingAction ?
|
|
916
|
-
return emptyState(ICONS.alert, '
|
|
917
|
-
|
|
992
|
+
const waiting = _pendingAction ? t(_pendingAction === 'new-group' ? 'schedules.loadFailed.waitingGroup' : 'schedules.loadFailed.waitingSchedule') : '';
|
|
993
|
+
return emptyState(ICONS.alert, t('schedules.loadFailed.title'), [_loadError || t('schedules.loadFailed.notEverything'), waiting].filter(Boolean).join(' '),
|
|
994
|
+
`<button type="button" class="sched-btn" data-action="retry-load">${te('schedules.note.tryAgain')}</button>`);
|
|
918
995
|
}
|
|
919
996
|
|
|
920
997
|
/** Keep the place: scroll position and the focused control survive a re-render of the list. */
|
|
@@ -970,29 +1047,30 @@ function renderList() {
|
|
|
970
1047
|
renderNote();
|
|
971
1048
|
|
|
972
1049
|
if (_loading) {
|
|
973
|
-
list.innerHTML = emptyState(ICONS.clock, '
|
|
974
|
-
setStatus('
|
|
1050
|
+
list.innerHTML = emptyState(ICONS.clock, t('schedules.list.loading'), '');
|
|
1051
|
+
setStatus(t('schedules.list.statusLoading'));
|
|
975
1052
|
return;
|
|
976
1053
|
}
|
|
977
1054
|
if (dataIncomplete()) {
|
|
978
1055
|
list.innerHTML = loadFailedHTML();
|
|
979
|
-
setStatus('
|
|
1056
|
+
setStatus(t('schedules.list.statusNotLoaded'));
|
|
980
1057
|
return;
|
|
981
1058
|
}
|
|
982
1059
|
|
|
983
1060
|
const now = Date.now();
|
|
984
1061
|
const shown = visibleSchedules();
|
|
985
1062
|
const scopeTotal = _schedules.filter(s => inScope(s, _scope, { now, groupIds: groupIds() })).length;
|
|
1063
|
+
const query = _query.trim();
|
|
986
1064
|
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 &&
|
|
1065
|
+
list.innerHTML = emptyState(ICONS.clock, t('schedules.empty.none'), t('schedules.empty.noneText'),
|
|
1066
|
+
`<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new">${ICONS.plus}<span>${te('schedules.head.newSchedule')}</span></button>
|
|
1067
|
+
<button type="button" class="sched-btn" data-action="schedule-prompt">${ICONS.sparkle}<span>${te('schedules.head.prompt')}</span></button>`);
|
|
1068
|
+
} else if (!shown.length && query) {
|
|
991
1069
|
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 ?
|
|
1070
|
+
list.innerHTML = emptyState(ICONS.search, t('schedules.empty.noMatch', { query }),
|
|
1071
|
+
elsewhere ? t('schedules.empty.elsewhere') : t('schedules.empty.tryAnother'),
|
|
1072
|
+
`<button type="button" class="sched-btn" data-action="clear-search">${te('schedules.list.clearSearch')}</button>
|
|
1073
|
+
${elsewhere ? `<button type="button" class="sched-btn" data-action="search-all">${te('schedules.empty.searchAll')}</button>` : ''}`);
|
|
996
1074
|
} else if (!shown.length) {
|
|
997
1075
|
list.innerHTML = scopeEmptyState();
|
|
998
1076
|
} else if (_scope === 'all') {
|
|
@@ -1001,9 +1079,9 @@ function renderList() {
|
|
|
1001
1079
|
list.innerHTML = renderUpcomingList(shown, now);
|
|
1002
1080
|
} else {
|
|
1003
1081
|
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]
|
|
1082
|
+
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
1083
|
}
|
|
1006
|
-
setStatus(
|
|
1084
|
+
setStatus(query ? tp('schedules.list.statusMatch', shown.length, { query }) : tp('schedules.list.statusShowing', scopeTotal, { shown: shown.length }));
|
|
1007
1085
|
restoreListState(list, saved);
|
|
1008
1086
|
}
|
|
1009
1087
|
|
|
@@ -1017,12 +1095,12 @@ function emptyState(icon, title, text, actions = '') {
|
|
|
1017
1095
|
}
|
|
1018
1096
|
|
|
1019
1097
|
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
|
|
1098
|
+
if (_scope === 'attention') return emptyState(ICONS.ok, t('schedules.empty.attention'), t('schedules.empty.attentionText'));
|
|
1099
|
+
if (_scope === 'paused') return emptyState(ICONS.pause, t('schedules.empty.paused'), t('schedules.empty.pausedText'));
|
|
1100
|
+
if (_scope === 'upcoming') return emptyState(ICONS.next, t('schedules.empty.upcoming'), t('schedules.empty.upcomingText'));
|
|
1101
|
+
if (_scope === 'ungrouped') return emptyState(ICONS.folder, t('schedules.empty.ungrouped'), '');
|
|
1102
|
+
return emptyState(ICONS.folder, t('schedules.empty.group'), t('schedules.empty.groupText'),
|
|
1103
|
+
`<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-new">${ICONS.plus}<span>${te('schedules.head.newSchedule')}</span></button>`);
|
|
1026
1104
|
}
|
|
1027
1105
|
|
|
1028
1106
|
/** All schedules: one box per group, in the groups' order, then the ungrouped ones. */
|
|
@@ -1037,21 +1115,21 @@ function renderGroupedList(shown, now) {
|
|
|
1037
1115
|
const all = group ? _schedules.filter(s => s.groupId === group.id) : items;
|
|
1038
1116
|
const summary = groupSummary(all, now);
|
|
1039
1117
|
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}
|
|
1118
|
+
summary.attention ? `<span class="is-alert">${ICONS.alert} ${tpe('schedules.count.attention', summary.attention)}</span>` : '',
|
|
1119
|
+
summary.paused ? `<span>${tpe('schedules.count.paused', summary.paused)}</span>` : '',
|
|
1120
|
+
group?.mutualExclusion ? `<span>${ICONS.lock} ${te('schedules.oneAtATime')}</span>` : '',
|
|
1043
1121
|
].filter(Boolean).join('<span aria-hidden="true">·</span>');
|
|
1044
|
-
const name = group ? group.name : '
|
|
1122
|
+
const name = group ? group.name : viewLabel('ungrouped');
|
|
1045
1123
|
return `<section class="sched-box${collapsed ? ' is-collapsed' : ''}" data-group-section="${esc(key)}">
|
|
1046
1124
|
<div class="sched-group-head" data-drop-group="${group ? esc(group.id) : ''}">
|
|
1047
1125
|
<button type="button" class="sched-group-toggle" data-action="group-collapse" data-key="${esc(key)}" aria-expanded="${!collapsed}" aria-controls="${listId}">
|
|
1048
1126
|
<span class="sched-group-chevron">${ICONS.chevron}</span>
|
|
1049
1127
|
${group ? `<span class="sched-dot" style="--dot:${esc(cssColor(group.color))}" aria-hidden="true"></span>` : `<span class="sched-nav-icon">${ICONS.folder}</span>`}
|
|
1050
1128
|
<span class="sched-group-name">${esc(name)}</span>
|
|
1051
|
-
<span class="sched-group-count">${items.length === all.length ? items.length :
|
|
1129
|
+
<span class="sched-group-count">${items.length === all.length ? items.length : te('schedules.list.shownOf', { shown: items.length, total: all.length })}</span>
|
|
1052
1130
|
</button>
|
|
1053
1131
|
${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="
|
|
1132
|
+
${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
1133
|
</div>
|
|
1056
1134
|
<ul class="sched-rows" id="${listId}" aria-label="${esc(name)}"${collapsed ? ' hidden' : ''}>
|
|
1057
1135
|
${sortSchedules(items, _sort).map(s => rowHTML(s, { now, showGroup: false })).join('')}
|
|
@@ -1065,19 +1143,19 @@ function renderUpcomingList(shown, now) {
|
|
|
1065
1143
|
const ordered = sortSchedules(shown, 'next');
|
|
1066
1144
|
const days = new Map();
|
|
1067
1145
|
for (const s of ordered) {
|
|
1068
|
-
const label = dayLabel(s.nextRun, now) || '
|
|
1146
|
+
const label = dayLabel(s.nextRun, now) || t('schedules.list.later');
|
|
1069
1147
|
if (!days.has(label)) days.set(label, []);
|
|
1070
1148
|
days.get(label).push(s);
|
|
1071
1149
|
}
|
|
1072
1150
|
return [...days.entries()].map(([label, items], i) => `
|
|
1073
1151
|
<section class="sched-section" aria-labelledby="sched-day-${i}">
|
|
1074
|
-
<h3 class="sched-section-title" id="sched-day-${i}">${esc(label)}<span>${
|
|
1152
|
+
<h3 class="sched-section-title" id="sched-day-${i}">${esc(label)}<span>${tpe('schedules.count.schedules', items.length)}</span></h3>
|
|
1075
1153
|
<div class="sched-box"><ul class="sched-rows">${items.map(s => rowHTML(s, { now, showGroup: true })).join('')}</ul></div>
|
|
1076
1154
|
</section>`).join('');
|
|
1077
1155
|
}
|
|
1078
1156
|
|
|
1079
1157
|
function nextRunHTML(s, now) {
|
|
1080
|
-
if (!s.enabled) return
|
|
1158
|
+
if (!s.enabled) return `<span class="sched-next" data-state="paused"><span class="sched-next-rel">${te('schedules.row.paused')}</span></span>`;
|
|
1081
1159
|
const countdown = formatCountdown(s.nextRun, now);
|
|
1082
1160
|
const when = s.nextRun ? whenLabel(s.nextRun, now) : '';
|
|
1083
1161
|
return `<span class="sched-next" data-state="${countdown.state}" data-countdown="${esc(s.nextRun || '')}"${when ? ` title="${esc(when)}"` : ''}>
|
|
@@ -1086,7 +1164,7 @@ function nextRunHTML(s, now) {
|
|
|
1086
1164
|
|
|
1087
1165
|
function lastRunHTML(s, now, run = lastRunState(s), { ago: withAgo = true } = {}) {
|
|
1088
1166
|
const ago = withAgo && s.lastRun ? formatAgo(s.lastRun, now) : '';
|
|
1089
|
-
const title = s.lastRun ?
|
|
1167
|
+
const title = s.lastRun ? fullDateTime(s.lastRun) : '';
|
|
1090
1168
|
return `<span class="sched-last" data-kind="${run.kind}"${title ? ` title="${esc(title)}"` : ''}>
|
|
1091
1169
|
<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
1170
|
}
|
|
@@ -1105,7 +1183,7 @@ function rowHTML(s, { now = Date.now(), showGroup = false } = {}) {
|
|
|
1105
1183
|
<div class="sched-row-line" data-action="schedule-expand" data-id="${esc(s.id)}" draggable="true">
|
|
1106
1184
|
<span class="sched-switch-cell">
|
|
1107
1185
|
<button type="button" class="sched-switch" role="switch" aria-checked="${!!s.enabled}" data-action="schedule-toggle" data-id="${esc(s.id)}"
|
|
1108
|
-
aria-label="
|
|
1186
|
+
aria-label="${te('schedules.row.switchLabel', { name: s.name })}" data-tooltip="${s.enabled ? te('schedules.row.pause') : te('schedules.row.resume')}"></button>
|
|
1109
1187
|
</span>
|
|
1110
1188
|
<button type="button" class="sched-row-toggle" data-action="schedule-expand" data-id="${esc(s.id)}" aria-expanded="${open}"${open ? ` aria-controls="${detailId}"` : ''}>
|
|
1111
1189
|
<span class="sched-row-name">${esc(s.name)}</span>
|
|
@@ -1123,55 +1201,53 @@ function rowHTML(s, { now = Date.now(), showGroup = false } = {}) {
|
|
|
1123
1201
|
function detailHTML(s, now, run) {
|
|
1124
1202
|
const template = getTemplate(s.templateId);
|
|
1125
1203
|
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>' : '');
|
|
1204
|
+
const toolCatalog = getCachedToolCatalog();
|
|
1205
|
+
const facts = launchFacts(s, { group, template, profiles: CLI_PROFILES, toolCatalog, translate: t });
|
|
1206
|
+
const sourceNote = (source) => (['group', 'automation', 'default'].includes(source)
|
|
1207
|
+
? ` <span class="sched-source">${te(`schedules.detail.source.${source}`)}</span>` : '');
|
|
1132
1208
|
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' ?
|
|
1209
|
+
? (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))
|
|
1210
|
+
: te('schedules.row.paused');
|
|
1211
|
+
const last = run.kind === 'never' ? esc(run.label)
|
|
1136
1212
|
: `<span class="${run.kind === 'failed' ? 'is-failed' : ''}">${esc(run.label)}</span>${s.lastRun ? ` · ${esc(whenLabel(s.lastRun, now))}` : ''}`;
|
|
1137
1213
|
// 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 =>
|
|
1214
|
+
const themes = [1, 2, 3, 4, 5, 6, 0].filter(d => s.dayThemes?.[String(d)]).map(d => weekdayName(d));
|
|
1139
1215
|
const groupOptions = [
|
|
1140
|
-
opt('', '
|
|
1216
|
+
opt('', viewLabel('ungrouped'), !s.groupId),
|
|
1141
1217
|
...orderedGroups().map(g => opt(g.id, g.name, s.groupId === g.id)),
|
|
1142
1218
|
];
|
|
1143
1219
|
const fact = (label, value, cls = '') => `<div class="sched-fact"><dt>${esc(label)}</dt><dd${cls ? ` class="${cls}"` : ''}>${value}</dd></div>`;
|
|
1144
1220
|
return `
|
|
1145
1221
|
<div class="sched-detail-grid">
|
|
1146
1222
|
<section class="sched-facts-block">
|
|
1147
|
-
<h4 class="sched-facts-title"
|
|
1223
|
+
<h4 class="sched-facts-title">${te('schedules.detail.runs')}</h4>
|
|
1148
1224
|
<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('
|
|
1225
|
+
${fact(t('schedules.detail.automation'), template ? esc(template.name) : `<span class="is-failed">${te('schedules.detail.missingAutomation')}</span>`)}
|
|
1226
|
+
${fact(t('schedules.detail.timetable'), `${esc(describeCron(s.cron))} <span class="sched-source">(${esc(s.cron || '')})</span>`)}
|
|
1227
|
+
${fact(t('schedules.detail.nextRun'), next)}
|
|
1228
|
+
${fact(t('schedules.detail.lastRun'), last)}
|
|
1229
|
+
${run.reason ? fact(t(run.kind === 'failed' ? 'schedules.detail.error' : 'schedules.detail.detail'), esc(oneLine(run.reason))) : ''}
|
|
1230
|
+
${fact(t('schedules.detail.runCount'), esc(String(s.runCount || 0)))}
|
|
1231
|
+
${fact(t('schedules.detail.timeZone'), esc(s.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone))}
|
|
1232
|
+
${themes.length ? fact(t('schedules.detail.dayThemes'), `${esc(themes.join(', '))} <span class="sched-source">${te('schedules.detail.themedDaysOnly')}</span>`) : ''}
|
|
1157
1233
|
</dl>
|
|
1158
1234
|
</section>
|
|
1159
1235
|
<section class="sched-facts-block">
|
|
1160
|
-
<h4 class="sched-facts-title"
|
|
1236
|
+
<h4 class="sched-facts-title">${te('schedules.detail.launchSettings')}</h4>
|
|
1161
1237
|
<dl class="sched-facts">
|
|
1162
1238
|
${facts.map(f => fact(f.label, `${esc(f.value)}${sourceNote(f.source)}`, f.mono ? 'sched-mono' : '')).join('')}
|
|
1163
1239
|
</dl>
|
|
1164
1240
|
</section>
|
|
1165
1241
|
</div>
|
|
1166
1242
|
<div class="sched-detail-bar">
|
|
1167
|
-
<div class="sched-detail-group"><span aria-hidden="true"
|
|
1168
|
-
${dropdownHTML(`sched-move-${domId(s.id)}`, groupOptions, { label:
|
|
1243
|
+
<div class="sched-detail-group"><span aria-hidden="true">${te('schedules.detail.group')}</span>
|
|
1244
|
+
${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
1245
|
</div>
|
|
1170
1246
|
<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
|
|
1247
|
+
<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>
|
|
1248
|
+
<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>
|
|
1249
|
+
<button type="button" class="sched-btn" data-action="schedule-edit" data-id="${esc(s.id)}">${ICONS.edit}<span>${te('schedules.detail.editSchedule')}</span></button>
|
|
1250
|
+
<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
1251
|
</div>`;
|
|
1176
1252
|
}
|
|
1177
1253
|
|
|
@@ -1183,36 +1259,37 @@ function renderActivityList() {
|
|
|
1183
1259
|
if (clear) clear.hidden = !_query;
|
|
1184
1260
|
renderNote();
|
|
1185
1261
|
if (_loading || dataIncomplete()) {
|
|
1186
|
-
list.innerHTML = _loading ? emptyState(ICONS.clock, '
|
|
1262
|
+
list.innerHTML = _loading ? emptyState(ICONS.clock, t('schedules.list.loading'), '') : loadFailedHTML();
|
|
1187
1263
|
return;
|
|
1188
1264
|
}
|
|
1189
1265
|
const now = Date.now();
|
|
1190
1266
|
const ran = _schedules.filter(s => s.lastRun);
|
|
1191
1267
|
const shown = sortSchedules(ran.filter(s => matchesQuery(s, _query, scheduleExtras(s))), 'last');
|
|
1268
|
+
const query = _query.trim();
|
|
1192
1269
|
if (!shown.length) {
|
|
1193
|
-
list.innerHTML =
|
|
1194
|
-
? emptyState(ICONS.search,
|
|
1195
|
-
: emptyState(ICONS.activity, '
|
|
1196
|
-
setStatus(
|
|
1270
|
+
list.innerHTML = query
|
|
1271
|
+
? 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>`)
|
|
1272
|
+
: emptyState(ICONS.activity, t('schedules.empty.noRuns'), t('schedules.empty.noRunsText'));
|
|
1273
|
+
setStatus(query ? t('schedules.list.statusNoMatches') : t('schedules.empty.noRuns'));
|
|
1197
1274
|
restoreListState(list, saved);
|
|
1198
1275
|
return;
|
|
1199
1276
|
}
|
|
1200
|
-
list.innerHTML = `<div class="sched-box"><ul class="sched-rows sched-rows--activity" aria-label="
|
|
1277
|
+
list.innerHTML = `<div class="sched-box"><ul class="sched-rows sched-rows--activity" aria-label="${te('schedules.activity.listLabel')}">${shown.map(s => {
|
|
1201
1278
|
const run = lastRunState(s);
|
|
1202
1279
|
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 : '
|
|
1280
|
+
const sub = run.kind === 'failed' && run.reason ? oneLine(run.reason) : template?.name || t('schedules.detail.missingAutomation');
|
|
1281
|
+
const next = s.enabled ? formatCountdown(s.nextRun, now).text : t('schedules.row.paused');
|
|
1205
1282
|
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="${
|
|
1283
|
+
<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
1284
|
${lastRunHTML(s, now, run, { ago: false })}
|
|
1208
1285
|
<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
1286
|
<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 ? '
|
|
1287
|
+
<span class="sched-activity-when sched-activity-next"><span>${s.enabled ? te('schedules.activity.next') : ''}</span><span>${esc(next)}</span></span>
|
|
1211
1288
|
<span class="sched-row-chevron" aria-hidden="true">${ICONS.chevron}</span>
|
|
1212
1289
|
</button>
|
|
1213
1290
|
</li>`;
|
|
1214
1291
|
}).join('')}</ul></div>`;
|
|
1215
|
-
setStatus(
|
|
1292
|
+
setStatus(query ? tp('schedules.activity.statusMatch', shown.length, { query }) : tp('schedules.count.runs', shown.length));
|
|
1216
1293
|
restoreListState(list, saved);
|
|
1217
1294
|
}
|
|
1218
1295
|
|
|
@@ -1247,20 +1324,19 @@ function renderTimersView() {
|
|
|
1247
1324
|
const customMinutes = $('sched-qt-custom-min')?.value || '';
|
|
1248
1325
|
const selectedSchedule = $('sched-existing-timer-schedule')?.value || '';
|
|
1249
1326
|
const scheduleMinutes = $('sched-existing-timer-min')?.value || '';
|
|
1250
|
-
const templateOptions = [opt('', '
|
|
1251
|
-
const scheduleOptions = [opt('', '
|
|
1327
|
+
const templateOptions = [opt('', t('schedules.form.chooseAutomation')), ..._templates.map(tpl => opt(tpl.id, tpl.name, selectedTemplate === tpl.id))];
|
|
1328
|
+
const scheduleOptions = [opt('', t('schedules.timers.chooseSchedule')), ...sortSchedules(_schedules, 'name').map(s => opt(s.id, s.name, selectedSchedule === s.id))];
|
|
1252
1329
|
const profileOptions = CLI_PROFILES.map(p => opt(p.id, p.label, p.id === _launchProfile));
|
|
1253
1330
|
const modelOptions = renderModelOptions(_launchProfile, _launchModel, { accountId: _launchAccount });
|
|
1254
1331
|
const contextOptions = renderContextOptions(_launchProfile, _launchModel, _launchContextMode, { accountId: _launchAccount });
|
|
1255
1332
|
const effortOptions = renderEffortOptions(_launchProfile, _launchEffort);
|
|
1256
|
-
const mcpOptions = renderMcpOptions(_launchMcpProfile);
|
|
1257
1333
|
const accountOptions = renderAccountOptions(_launchAccount, false);
|
|
1258
1334
|
const showEffort = profileSupportsEffort(_launchProfile);
|
|
1259
|
-
const showMcp = _launchProfile
|
|
1335
|
+
const showMcp = strictToolHost(_launchProfile);
|
|
1260
1336
|
const showAccount = _launchProfile === 'codex';
|
|
1261
1337
|
const usesBrowser = _qtUsesBrowser !== null ? _qtUsesBrowser : !!getTemplate(selectedTemplate)?.usesBrowser;
|
|
1262
1338
|
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 ?
|
|
1339
|
+
.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
1340
|
.join('');
|
|
1265
1341
|
|
|
1266
1342
|
const modelLoadKey = `${_launchProfile}:${_launchAccount}`;
|
|
@@ -1269,52 +1345,52 @@ function renderTimersView() {
|
|
|
1269
1345
|
ensureModelsForProfile(_launchProfile, false, _launchAccount).then(() => { if (_panel && _frame === 'timers') renderTimersView(); });
|
|
1270
1346
|
}
|
|
1271
1347
|
if (showAccount) ensureCodexAccountsLoaded(() => { if (_panel && _frame === 'timers') renderTimersView(); });
|
|
1272
|
-
if (showMcp && !
|
|
1273
|
-
|
|
1348
|
+
if (showMcp && !getCachedToolCatalog()) {
|
|
1349
|
+
fetchToolCatalog().then(catalog => { if (catalog && _panel && _frame === 'timers') renderTimersView(); });
|
|
1274
1350
|
}
|
|
1275
1351
|
|
|
1276
1352
|
main.innerHTML = `
|
|
1277
1353
|
<div class="sched-view-head">
|
|
1278
1354
|
<div class="sched-view-titles">
|
|
1279
|
-
<h2 id="sched-view-title"
|
|
1280
|
-
<p class="sched-view-sub"
|
|
1355
|
+
<h2 id="sched-view-title">${esc(viewLabel('timers'))}</h2>
|
|
1356
|
+
<p class="sched-view-sub">${te('schedules.timers.sub')}</p>
|
|
1281
1357
|
</div>
|
|
1282
1358
|
</div>
|
|
1283
1359
|
<div class="sched-load-note" id="sched-load-note" role="status"></div>
|
|
1284
1360
|
<div class="sched-editor-body">
|
|
1285
1361
|
<div class="sched-form">
|
|
1286
1362
|
<section class="sched-form-section" aria-labelledby="sched-timers-active-title">
|
|
1287
|
-
<h3 class="sched-form-title" id="sched-timers-active-title"
|
|
1363
|
+
<h3 class="sched-form-title" id="sched-timers-active-title">${te('schedules.timers.waiting')}</h3>
|
|
1288
1364
|
<div id="sched-active-timers"></div>
|
|
1289
1365
|
</section>
|
|
1290
1366
|
<section class="sched-form-section" aria-labelledby="sched-qt-title">
|
|
1291
|
-
<h3 class="sched-form-title" id="sched-qt-title"
|
|
1367
|
+
<h3 class="sched-form-title" id="sched-qt-title">${te('schedules.timers.runOnce')}</h3>
|
|
1292
1368
|
<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="
|
|
1369
|
+
${dropdownRow('sched-qt-template', t('schedules.form.automation'), templateOptions)}
|
|
1370
|
+
${dropdownRow('sched-qt-profile', t('schedules.form.cli'), profileOptions)}
|
|
1371
|
+
${dropdownRow('sched-qt-model', t('schedules.form.model'), modelOptions)}
|
|
1372
|
+
${dropdownRow('sched-qt-context', t('schedules.form.context'), contextOptions)}
|
|
1373
|
+
${dropdownRow('sched-qt-effort', t('schedules.form.thinking'), effortOptions, { hidden: !showEffort })}
|
|
1374
|
+
${toolRow('sched-qt-mcp', _launchTools, { provider: _launchProfile, usesBrowser })}
|
|
1375
|
+
${dropdownRow('sched-qt-account', t('schedules.form.account'), accountOptions, { hidden: !showAccount, rowId: 'sched-qt-account-field' })}
|
|
1376
|
+
${formRow('sched-qt-browser', t('schedules.timers.usesBrowser'), toggleHTML('sched-qt-browser', usesBrowser), { toggle: true })}
|
|
1377
|
+
${formRow('sched-qt-custom-min', t('schedules.timers.startIn'), `<span class="sched-chip-list" role="group" aria-label="${te('schedules.timers.delay')}">${qtPresetButtons}</span>
|
|
1378
|
+
<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
1379
|
</div>
|
|
1304
1380
|
<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
|
|
1381
|
+
<button type="button" class="sched-btn" data-action="qt-now">${ICONS.bolt}<span>${te('schedules.detail.runNow')}</span></button>
|
|
1382
|
+
<button type="button" class="sched-btn sched-btn--primary" data-action="qt-go">${ICONS.timer}<span>${te('schedules.timers.start')}</span></button>
|
|
1307
1383
|
</div>
|
|
1308
1384
|
</section>
|
|
1309
1385
|
<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"
|
|
1386
|
+
<h3 class="sched-form-title" id="sched-st-title">${te('schedules.timers.fireSaved')}</h3>
|
|
1387
|
+
<p class="sched-form-intro">${te('schedules.timers.fireSavedIntro')}</p>
|
|
1312
1388
|
<div class="sched-form-group">
|
|
1313
|
-
${dropdownRow('sched-existing-timer-schedule', '
|
|
1314
|
-
${formRow('sched-existing-timer-min', '
|
|
1389
|
+
${dropdownRow('sched-existing-timer-schedule', t('schedules.timers.schedule'), scheduleOptions)}
|
|
1390
|
+
${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
1391
|
</div>
|
|
1316
1392
|
<div class="sched-form-actions">
|
|
1317
|
-
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-timer-create"
|
|
1393
|
+
<button type="button" class="sched-btn sched-btn--primary" data-action="schedule-timer-create">${te('schedules.timers.set')}</button>
|
|
1318
1394
|
</div>
|
|
1319
1395
|
</section>
|
|
1320
1396
|
</div>
|
|
@@ -1353,8 +1429,9 @@ function renderTimersView() {
|
|
|
1353
1429
|
storage.setItem('as-launch-effort', _launchEffort);
|
|
1354
1430
|
});
|
|
1355
1431
|
$('sched-qt-mcp')?.addEventListener('change', (e) => {
|
|
1356
|
-
|
|
1357
|
-
storage.setItem('as-launch-
|
|
1432
|
+
_launchTools = readToolPicker(e.target);
|
|
1433
|
+
storage.setItem('as-launch-tools', JSON.stringify(_launchTools));
|
|
1434
|
+
updateToolPicker(e.target, _launchTools, { inheritLabel: toolWord('templateDefault'), usesBrowser });
|
|
1358
1435
|
});
|
|
1359
1436
|
$('sched-qt-account')?.addEventListener('change', (e) => {
|
|
1360
1437
|
_launchAccount = e.target.value || 'default';
|
|
@@ -1371,13 +1448,19 @@ function renderTimersView() {
|
|
|
1371
1448
|
if (contextPopup) setDropdownItems(contextPopup, renderContextOptions('codex', _launchModel, _launchContextMode, { accountId: _launchAccount }));
|
|
1372
1449
|
});
|
|
1373
1450
|
});
|
|
1374
|
-
$('sched-qt-browser')?.addEventListener('change', (e) => {
|
|
1451
|
+
$('sched-qt-browser')?.addEventListener('change', (e) => {
|
|
1452
|
+
_qtUsesBrowser = e.target.checked;
|
|
1453
|
+
const input = $('sched-qt-mcp');
|
|
1454
|
+
updateToolPicker(input, readToolPicker(input, { requested: true }), { inheritLabel: toolWord('templateDefault'), usesBrowser: _qtUsesBrowser });
|
|
1455
|
+
});
|
|
1375
1456
|
$('sched-qt-template')?.addEventListener('change', (e) => {
|
|
1376
1457
|
const template = getTemplate(e.target.value);
|
|
1377
1458
|
const browser = $('sched-qt-browser');
|
|
1378
1459
|
if (browser && template) {
|
|
1379
1460
|
browser.checked = !!template.usesBrowser;
|
|
1380
1461
|
_qtUsesBrowser = null;
|
|
1462
|
+
const input = $('sched-qt-mcp');
|
|
1463
|
+
updateToolPicker(input, readToolPicker(input, { requested: true }), { inheritLabel: toolWord('templateDefault'), usesBrowser: browser.checked });
|
|
1381
1464
|
}
|
|
1382
1465
|
});
|
|
1383
1466
|
}
|
|
@@ -1389,31 +1472,38 @@ function renderModelOptions(profile, selected, { showService = false, accountId
|
|
|
1389
1472
|
if (!models.length) {
|
|
1390
1473
|
// List not loaded yet — preserve any stored selection so saving the form cannot
|
|
1391
1474
|
// blank it (a blank popup makes save send model:null, which DELETES the override).
|
|
1392
|
-
if (selected) return [opt('', '
|
|
1393
|
-
return [opt('', '
|
|
1475
|
+
if (selected) return [opt('', t('schedules.launch.templateOrDefault')), opt(selected, selected, true)];
|
|
1476
|
+
return [opt('', t('schedules.launch.default'), true)];
|
|
1394
1477
|
}
|
|
1395
1478
|
const known = !!selected && models.some(m => modelMatchesSelector(m, selected));
|
|
1396
1479
|
const current = selected || '';
|
|
1397
1480
|
const opts = [
|
|
1398
|
-
opt('', '
|
|
1481
|
+
opt('', t('schedules.launch.templateOrDefault'), !current),
|
|
1399
1482
|
...models.map(m => opt(modelSelectorValue(m), formatModelOptionLabel(m, {
|
|
1400
1483
|
includeService: showService && profile === 'opencode',
|
|
1401
1484
|
}), modelMatchesSelector(m, current))),
|
|
1402
1485
|
];
|
|
1403
1486
|
// Preserve a stored model that isn't in the (still-loading or curated) list so the
|
|
1404
1487
|
// dropdown never silently blanks it and a save can't wipe the persisted override.
|
|
1405
|
-
if (selected && !known) opts.push(opt(selected,
|
|
1488
|
+
if (selected && !known) opts.push(opt(selected, t('schedules.launch.saved', { value: selected }), true));
|
|
1406
1489
|
return opts;
|
|
1407
1490
|
}
|
|
1408
1491
|
|
|
1492
|
+
/** A context choice of automation-context.js in the page's language (its size stays as written). */
|
|
1493
|
+
function contextChoiceLabel(choice) {
|
|
1494
|
+
if (choice.id === 'default') return choice.size ? t('schedules.launch.contextDefaultSize', { size: choice.size }) : t('schedules.launch.contextProviderDefault');
|
|
1495
|
+
if (choice.id === 'extended' && choice.size) return t('schedules.launch.contextExtendedSize', { size: choice.size });
|
|
1496
|
+
return choice.label;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1409
1499
|
function renderContextOptions(profile, model, selected, { accountId = 'default', inherit = false } = {}) {
|
|
1410
1500
|
const models = getModelsForProfile(profile, accountId);
|
|
1411
1501
|
const choices = automationContextOptions(profile, model, models);
|
|
1412
1502
|
const current = selected || (inherit ? '' : 'default');
|
|
1413
|
-
const options = inherit ? [opt('', '
|
|
1414
|
-
for (const choice of choices) options.push(opt(choice.id, choice
|
|
1503
|
+
const options = inherit ? [opt('', t('schedules.launch.noOverride'), !current)] : [];
|
|
1504
|
+
for (const choice of choices) options.push(opt(choice.id, contextChoiceLabel(choice), current === choice.id));
|
|
1415
1505
|
if (current && !choices.some((choice) => choice.id === current)) {
|
|
1416
|
-
options.push(opt(current,
|
|
1506
|
+
options.push(opt(current, t('schedules.launch.unavailable', { value: current }), true));
|
|
1417
1507
|
}
|
|
1418
1508
|
return options;
|
|
1419
1509
|
}
|
|
@@ -1434,33 +1524,22 @@ function modelChoiceAvailable(profile, model, accountId = 'default') {
|
|
|
1434
1524
|
function renderEffortOptions(profile, selected, { inheritLabel = null } = {}) {
|
|
1435
1525
|
const levels = getEffortLevelsForProfile(profile);
|
|
1436
1526
|
const value = selected || '';
|
|
1437
|
-
if (!inheritLabel && !levels.length) return [opt('off', '
|
|
1527
|
+
if (!inheritLabel && !levels.length) return [opt('off', t('schedules.launch.default'), true)];
|
|
1438
1528
|
const known = !!value && levels.some(e => e.id === value);
|
|
1439
1529
|
const opts = inheritLabel ? [opt('', inheritLabel, !value)] : [];
|
|
1440
|
-
for (const e of levels)
|
|
1530
|
+
for (const e of levels) {
|
|
1531
|
+
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));
|
|
1532
|
+
}
|
|
1441
1533
|
// A saved level this CLI does not list (set for another CLI, or by hand) stays selected,
|
|
1442
1534
|
// so saving the form cannot drop it (the model, MCP and account popups do the same).
|
|
1443
|
-
if (value && !known) opts.push(opt(value,
|
|
1535
|
+
if (value && !known) opts.push(opt(value, t('schedules.launch.saved', { value }), true));
|
|
1444
1536
|
return opts;
|
|
1445
1537
|
}
|
|
1446
1538
|
|
|
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;
|
|
1539
|
+
function toolRow(id, tools, { provider, inheritLabel = toolWord('templateDefault'), usesBrowser } = {}) {
|
|
1540
|
+
return formRow(id, toolWord('label'), strictToolHost(provider)
|
|
1541
|
+
? toolPickerHTML(id, tools, { labelledBy: `${id}-label`, provider, inheritLabel, usesBrowser })
|
|
1542
|
+
: `<span>${esc(toolWord('focus'))}</span>`, { labelId: `${id}-label` });
|
|
1464
1543
|
}
|
|
1465
1544
|
|
|
1466
1545
|
// The choices of the Codex account popup. `allowInherit` adds a
|
|
@@ -1474,12 +1553,14 @@ function renderAccountOptions(selected, allowInherit = false) {
|
|
|
1474
1553
|
const keep = allowInherit && !!selected && !known;
|
|
1475
1554
|
const current = known || keep ? selected : (allowInherit ? '' : 'default');
|
|
1476
1555
|
const opts = accounts.map(a => {
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1556
|
+
// The default account is "Default" in SynaBun's own list (agent-runtime-options.js): that word is
|
|
1557
|
+
// the page's; an account's own label or address shows as it is.
|
|
1558
|
+
const ownDefault = a.isDefault && (a.label === 'Default' || (!a.label && !a.email));
|
|
1559
|
+
const label = ownDefault ? t('schedules.launch.default') : a.label || a.email || a.id;
|
|
1560
|
+
return opt(a.id, a.planType ? t('schedules.launch.accountPlan', { label, plan: a.planType }) : label, a.id === current);
|
|
1480
1561
|
});
|
|
1481
|
-
if (allowInherit) opts.unshift(opt('', '
|
|
1482
|
-
if (keep) opts.push(opt(selected,
|
|
1562
|
+
if (allowInherit) opts.unshift(opt('', t('schedules.launch.templateOrGroupDefault'), !current));
|
|
1563
|
+
if (keep) opts.push(opt(selected, t('schedules.launch.saved', { value: selected }), true));
|
|
1483
1564
|
return opts;
|
|
1484
1565
|
}
|
|
1485
1566
|
|
|
@@ -1495,12 +1576,12 @@ function renderActiveTimersInto() {
|
|
|
1495
1576
|
if (!box) return;
|
|
1496
1577
|
const scheduleTimers = Object.entries(_scheduleTimerData).map(([scheduleId, timer]) => {
|
|
1497
1578
|
const schedule = _schedules.find(s => s.id === scheduleId);
|
|
1498
|
-
const name = schedule?.name || '
|
|
1579
|
+
const name = schedule?.name || t('schedules.timers.schedule');
|
|
1499
1580
|
return `
|
|
1500
1581
|
<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="
|
|
1582
|
+
<span class="sched-timer-name">${esc(name)}<span class="sched-timer-meta">${te('schedules.timers.savedOnce')}</span></span>
|
|
1583
|
+
<strong class="sched-timer-count" data-fires-at="${esc(timer.firesAt)}">${esc(formatTimerCountdown(timer.firesAt))}</strong>
|
|
1584
|
+
<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
1585
|
</div>`;
|
|
1505
1586
|
}).join('');
|
|
1506
1587
|
|
|
@@ -1508,23 +1589,23 @@ function renderActiveTimersInto() {
|
|
|
1508
1589
|
const meta = [
|
|
1509
1590
|
timer.profile ? CLI_PROFILES.find(p => p.id === timer.profile)?.label || timer.profile : '',
|
|
1510
1591
|
timer.model,
|
|
1511
|
-
timer.contextMode && timer.contextMode !== 'default' ?
|
|
1512
|
-
timer.effort ?
|
|
1513
|
-
timer.
|
|
1592
|
+
timer.contextMode && timer.contextMode !== 'default' ? t('schedules.overrides.context', { value: timer.contextMode }) : '',
|
|
1593
|
+
timer.effort ? t('schedules.overrides.thinking', { value: timer.effort }) : '',
|
|
1594
|
+
timer.tools ? t('schedules.overrides.mcp', { value: timer.tools.join(', ') }) : '',
|
|
1514
1595
|
].filter(Boolean).join(' · ');
|
|
1515
1596
|
return `
|
|
1516
1597
|
<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="
|
|
1598
|
+
<span class="sched-timer-name">${esc(timer.templateName)}<span class="sched-timer-meta">${esc(meta || t('schedules.timers.automationOnce'))}</span></span>
|
|
1599
|
+
<strong class="sched-timer-count" data-fires-at="${esc(timer.firesAt)}">${esc(formatTimerCountdown(timer.firesAt))}</strong>
|
|
1600
|
+
<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
1601
|
</div>`;
|
|
1521
1602
|
}).join('');
|
|
1522
1603
|
|
|
1523
1604
|
// The timers load on their own: until they answer (or when they could not), say so here.
|
|
1524
1605
|
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"
|
|
1606
|
+
? `<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
1607
|
: '';
|
|
1527
|
-
const empty = _timersError ? '' : `<p class="sched-form-intro">${_timersLoaded ? '
|
|
1608
|
+
const empty = _timersError ? '' : `<p class="sched-form-intro">${_timersLoaded ? te('schedules.timers.none') : te('schedules.timers.loading')}</p>`;
|
|
1528
1609
|
box.innerHTML = note + (scheduleTimers || quickTimers ? `<div class="sched-form-group">${quickTimers}${scheduleTimers}</div>` : empty);
|
|
1529
1610
|
}
|
|
1530
1611
|
|
|
@@ -1584,7 +1665,7 @@ function redrawEditor(main, html) {
|
|
|
1584
1665
|
|
|
1585
1666
|
function editorHead({ back, title, sub, titleId }) {
|
|
1586
1667
|
return `<header class="sched-editor-head">
|
|
1587
|
-
<button type="button" class="sched-icon-btn" data-action="${back}" aria-label="
|
|
1668
|
+
<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
1669
|
<div class="sched-editor-titles"><h2 id="${titleId}">${esc(title)}</h2>${sub ? `<p>${esc(sub)}</p>` : ''}</div>
|
|
1589
1670
|
</header>`;
|
|
1590
1671
|
}
|
|
@@ -1598,8 +1679,8 @@ function openGroupEditor(id) {
|
|
|
1598
1679
|
// `_original` is the group as it was when the editor opened: a save sends only what the user
|
|
1599
1680
|
// changed from it, so a change made elsewhere meanwhile (a sync update) is never undone.
|
|
1600
1681
|
_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,
|
|
1682
|
+
? { 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)) }
|
|
1683
|
+
: { name: '', color: DEFAULT_GROUP_COLOR, mutualExclusion: false, profile: null, model: null, contextMode: null, effort: null, tools: null, codexAccountId: null, usesBrowser: undefined };
|
|
1603
1684
|
ensureModelsForProfile(_groupEditorDraft.profile || 'claude-code', false, _groupEditorDraft.codexAccountId || 'default').then(() => {
|
|
1604
1685
|
if (_panel && _groupEditorDraft) {
|
|
1605
1686
|
collectGroupEditorDraft();
|
|
@@ -1619,7 +1700,7 @@ function collectGroupEditorDraft() {
|
|
|
1619
1700
|
model: $('sched-ge-model')?.value || null,
|
|
1620
1701
|
contextMode: $('sched-ge-context')?.value || null,
|
|
1621
1702
|
effort: $('sched-ge-effort')?.value || null,
|
|
1622
|
-
|
|
1703
|
+
tools: readEditorTools('sched-ge-mcp', _groupEditorDraft?.tools),
|
|
1623
1704
|
codexAccountId: $('sched-ge-account')?.value || null,
|
|
1624
1705
|
usesBrowser: browserRaw === '' ? undefined : browserRaw === 'true',
|
|
1625
1706
|
});
|
|
@@ -1635,20 +1716,19 @@ function renderGroupEditor(group) {
|
|
|
1635
1716
|
const profileForModels = d.profile || 'claude-code';
|
|
1636
1717
|
const accountId = d.codexAccountId || 'default';
|
|
1637
1718
|
const profileOptions = [
|
|
1638
|
-
opt('', '
|
|
1719
|
+
opt('', t('schedules.launch.noOverride'), !d.profile),
|
|
1639
1720
|
...CLI_PROFILES.map(p => opt(p.id, p.label, d.profile === p.id)),
|
|
1640
1721
|
];
|
|
1641
1722
|
const modelOptions = renderModelOptions(profileForModels, d.model || '', { showService: true, accountId });
|
|
1642
1723
|
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 || '');
|
|
1724
|
+
const effortOptions = renderEffortOptions(profileForModels, d.effort || '', { inheritLabel: t('schedules.launch.noOverride') });
|
|
1645
1725
|
const accountOptions = renderAccountOptions(d.codexAccountId || '', true);
|
|
1646
1726
|
const showEffort = profileSupportsEffort(profileForModels);
|
|
1647
|
-
const showMcp = profileForModels
|
|
1727
|
+
const showMcp = strictToolHost(profileForModels);
|
|
1648
1728
|
const showAccount = profileForModels === 'codex';
|
|
1649
1729
|
const rerender = () => { if (_panel && _groupEditorDraft) { collectGroupEditorDraft(); renderGroupEditor(_groupEditorId ? getGroup(_groupEditorId) : null); } };
|
|
1650
1730
|
if (showAccount) ensureCodexAccountsLoaded(rerender);
|
|
1651
|
-
if (showMcp && !
|
|
1731
|
+
if (showMcp && !getCachedToolCatalog()) fetchToolCatalog().then(catalog => { if (catalog) rerender(); });
|
|
1652
1732
|
const browserValue = d.usesBrowser === true ? 'true' : d.usesBrowser === false ? 'false' : '';
|
|
1653
1733
|
const ordered = orderedGroups();
|
|
1654
1734
|
const position = group ? ordered.findIndex(g => g.id === group.id) : -1;
|
|
@@ -1656,55 +1736,55 @@ function renderGroupEditor(group) {
|
|
|
1656
1736
|
|
|
1657
1737
|
redrawEditor(main, `
|
|
1658
1738
|
<div class="sched-editor">
|
|
1659
|
-
${editorHead({ back: 'group-editor-cancel', title: creating ? '
|
|
1739
|
+
${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
1740
|
<div class="sched-editor-body">
|
|
1661
1741
|
<div class="sched-form">
|
|
1662
1742
|
<section class="sched-form-section" aria-labelledby="sched-ge-sec-group">
|
|
1663
|
-
<h3 class="sched-form-title" id="sched-ge-sec-group"
|
|
1743
|
+
<h3 class="sched-form-title" id="sched-ge-sec-group">${te('schedules.form.group')}</h3>
|
|
1664
1744
|
<div class="sched-form-group">
|
|
1665
|
-
${formRow('sched-ge-name', '
|
|
1666
|
-
${formRow('sched-ge-color', '
|
|
1667
|
-
${formRow('sched-ge-mutex', '
|
|
1668
|
-
{ help: '
|
|
1745
|
+
${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">`)}
|
|
1746
|
+
${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)}">`)}
|
|
1747
|
+
${formRow('sched-ge-mutex', t('schedules.oneAtATime'), toggleHTML('sched-ge-mutex', !!d.mutualExclusion, 'sched-ge-mutex-help'),
|
|
1748
|
+
{ help: te('schedules.groupEditor.oneAtATimeHelp'), toggle: true })}
|
|
1669
1749
|
</div>
|
|
1670
1750
|
</section>
|
|
1671
1751
|
<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"
|
|
1752
|
+
<h3 class="sched-form-title" id="sched-ge-sec-launch">${te('schedules.form.launchOverrides')}</h3>
|
|
1753
|
+
<p class="sched-form-intro">${te('schedules.groupEditor.launchIntro')}</p>
|
|
1674
1754
|
<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', '
|
|
1755
|
+
${dropdownRow('sched-ge-profile', t('schedules.form.cli'), profileOptions)}
|
|
1756
|
+
${dropdownRow('sched-ge-model', t('schedules.form.model'), modelOptions)}
|
|
1757
|
+
${dropdownRow('sched-ge-context', t('schedules.form.context'), contextOptions)}
|
|
1758
|
+
${dropdownRow('sched-ge-effort', t('schedules.form.thinking'), effortOptions, { hidden: !showEffort })}
|
|
1759
|
+
${toolRow('sched-ge-mcp', d.tools, { provider: profileForModels, inheritLabel: toolWord('noOverride'), usesBrowser: d.usesBrowser })}
|
|
1760
|
+
${dropdownRow('sched-ge-account', t('schedules.form.account'), accountOptions, { hidden: !showAccount, rowId: 'sched-ge-account-field' })}
|
|
1761
|
+
${dropdownRow('sched-ge-browser', t('schedules.form.browser'), [
|
|
1762
|
+
opt('', t('schedules.launch.noOverride'), browserValue === ''),
|
|
1763
|
+
opt('true', t('schedules.launch.alwaysOn'), browserValue === 'true'),
|
|
1764
|
+
opt('false', t('schedules.launch.alwaysOff'), browserValue === 'false'),
|
|
1685
1765
|
])}
|
|
1686
1766
|
</div>
|
|
1687
1767
|
</section>
|
|
1688
1768
|
${!group ? '' : `
|
|
1689
1769
|
<section class="sched-form-section" aria-labelledby="sched-ge-sec-order">
|
|
1690
|
-
<h3 class="sched-form-title" id="sched-ge-sec-order"
|
|
1770
|
+
<h3 class="sched-form-title" id="sched-ge-sec-order">${te('schedules.groupEditor.order')}</h3>
|
|
1691
1771
|
<div class="sched-form-group">
|
|
1692
1772
|
<div class="sched-form-row">
|
|
1693
|
-
<span class="sched-form-label"
|
|
1773
|
+
<span class="sched-form-label">${te('schedules.groupEditor.position', { position: position + 1, count: ordered.length })}</span>
|
|
1694
1774
|
<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
|
|
1775
|
+
<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>
|
|
1776
|
+
<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
1777
|
</div>
|
|
1698
1778
|
</div>
|
|
1699
1779
|
</div>
|
|
1700
1780
|
</section>
|
|
1701
1781
|
<section class="sched-form-section" aria-labelledby="sched-ge-sec-delete">
|
|
1702
|
-
<h3 class="sched-form-title" id="sched-ge-sec-delete"
|
|
1782
|
+
<h3 class="sched-form-title" id="sched-ge-sec-delete">${te('schedules.groupEditor.delete')}</h3>
|
|
1703
1783
|
<div class="sched-form-group">
|
|
1704
1784
|
<div class="sched-form-row">
|
|
1705
|
-
<span class="sched-form-label">${members ?
|
|
1785
|
+
<span class="sched-form-label">${members ? tpe('schedules.groupEditor.membersStay', members) : te('schedules.groupEditor.empty')}</span>
|
|
1706
1786
|
<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
|
|
1787
|
+
<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
1788
|
</div>
|
|
1709
1789
|
</div>
|
|
1710
1790
|
</div>
|
|
@@ -1712,8 +1792,8 @@ function renderGroupEditor(group) {
|
|
|
1712
1792
|
</div>
|
|
1713
1793
|
</div>
|
|
1714
1794
|
<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 ? '
|
|
1795
|
+
<button type="button" class="sched-btn" data-action="group-editor-cancel">${te('schedules.editor.cancel')}</button>
|
|
1796
|
+
<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
1797
|
</footer>
|
|
1718
1798
|
</div>`);
|
|
1719
1799
|
wireGroupEditor();
|
|
@@ -1727,7 +1807,7 @@ function wireGroupEditor() {
|
|
|
1727
1807
|
_groupEditorDraft.model = null;
|
|
1728
1808
|
_groupEditorDraft.contextMode = _groupEditorDraft.profile ? 'default' : null;
|
|
1729
1809
|
_groupEditorDraft.effort = null;
|
|
1730
|
-
_groupEditorDraft.
|
|
1810
|
+
_groupEditorDraft.tools = null;
|
|
1731
1811
|
_groupEditorDraft.codexAccountId = null;
|
|
1732
1812
|
const rerender = () => { if (_panel && _groupEditorDraft) renderGroupEditor(_groupEditorId ? getGroup(_groupEditorId) : null); };
|
|
1733
1813
|
ensureModelsForProfile(_groupEditorDraft.profile || 'claude-code', false, 'default').then(() => { collectGroupEditorDraft(); rerender(); });
|
|
@@ -1765,18 +1845,18 @@ function renderScheduleEditor() {
|
|
|
1765
1845
|
// A saved automation or group the lists do not show (deleted elsewhere, or not in what loaded)
|
|
1766
1846
|
// stays selected: a popup without it would show the first choice, and a save would move it.
|
|
1767
1847
|
const templateOptions = [
|
|
1768
|
-
opt('', '
|
|
1769
|
-
..._templates.map(
|
|
1770
|
-
...(s.templateId && !getTemplate(s.templateId) ? [opt(s.templateId,
|
|
1848
|
+
opt('', t('schedules.form.chooseAutomation')),
|
|
1849
|
+
..._templates.map(tpl => opt(tpl.id, t('schedules.scheduleEditor.automationOption', { name: tpl.name, category: tpl.category || t('schedules.scheduleEditor.customCategory') }), s.templateId === tpl.id)),
|
|
1850
|
+
...(s.templateId && !getTemplate(s.templateId) ? [opt(s.templateId, t('schedules.launch.saved', { value: s.templateId }), true)] : []),
|
|
1771
1851
|
];
|
|
1772
1852
|
const groupOptions = [
|
|
1773
|
-
opt('', '
|
|
1853
|
+
opt('', viewLabel('ungrouped'), !s.groupId),
|
|
1774
1854
|
...orderedGroups().map(g => opt(g.id, g.name, s.groupId === g.id)),
|
|
1775
|
-
...(s.groupId && !getGroup(s.groupId) ? [opt(s.groupId,
|
|
1855
|
+
...(s.groupId && !getGroup(s.groupId) ? [opt(s.groupId, t('schedules.launch.saved', { value: s.groupId }), true)] : []),
|
|
1776
1856
|
];
|
|
1777
1857
|
const tz = s.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1778
1858
|
const profileOptions = [
|
|
1779
|
-
opt('', '
|
|
1859
|
+
opt('', t('schedules.launch.templateDefault'), !s.profile),
|
|
1780
1860
|
...CLI_PROFILES.map(p => opt(p.id, p.label, s.profile === p.id)),
|
|
1781
1861
|
];
|
|
1782
1862
|
const template = getTemplate(s.templateId);
|
|
@@ -1786,20 +1866,22 @@ function renderScheduleEditor() {
|
|
|
1786
1866
|
const effectiveModel = s.model || group?.model || template?.model || null;
|
|
1787
1867
|
const modelOptions = renderModelOptions(profileForModels, s.model || '', { showService: true, accountId });
|
|
1788
1868
|
const contextOptions = renderContextOptions(profileForModels, effectiveModel, s.contextMode, { accountId, inherit: true });
|
|
1789
|
-
const effortOptions = renderEffortOptions(profileForModels, s.effort || '', { inheritLabel: '
|
|
1790
|
-
const mcpOptions = renderMcpOptions(s.mcpProfile || '');
|
|
1869
|
+
const effortOptions = renderEffortOptions(profileForModels, s.effort || '', { inheritLabel: t('schedules.launch.templateOrDefault') });
|
|
1791
1870
|
const accountOptions = renderAccountOptions(s.codexAccountId || '', true);
|
|
1792
1871
|
const showEffort = profileSupportsEffort(profileForModels);
|
|
1793
|
-
const showMcp = profileForModels
|
|
1872
|
+
const showMcp = strictToolHost(profileForModels);
|
|
1794
1873
|
const showAccount = profileForModels === 'codex';
|
|
1795
1874
|
if (showAccount) ensureCodexAccountsLoaded(() => { if (_panel && _scheduleEditor) { collectScheduleEditorDraft(); renderScheduleEditor(); } });
|
|
1796
1875
|
const browserValue = s.usesBrowser === true ? 'true' : s.usesBrowser === false ? 'false' : '';
|
|
1797
1876
|
const cron = s.cron || '0 9 * * *';
|
|
1798
|
-
const presetsHTML = CRON_PRESET_GROUPS.map(group =>
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1877
|
+
const presetsHTML = CRON_PRESET_GROUPS.map(group => {
|
|
1878
|
+
const name = t(`schedules.presets.group.${group.group}`);
|
|
1879
|
+
return `
|
|
1880
|
+
<div class="sched-preset-group" role="group" aria-label="${te('schedules.scheduleEditor.presetGroup', { group: name })}">
|
|
1881
|
+
<span>${esc(name)}</span>
|
|
1882
|
+
<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>
|
|
1883
|
+
</div>`;
|
|
1884
|
+
}).join('');
|
|
1803
1885
|
const dayOrder = [1, 2, 3, 4, 5, 6, 0];
|
|
1804
1886
|
const hasThemes = dayOrder.some(idx => s.dayThemes?.[String(idx)]);
|
|
1805
1887
|
// A textarea, not an input: a theme can span lines, and an input would drop its line breaks.
|
|
@@ -1807,13 +1889,14 @@ function renderScheduleEditor() {
|
|
|
1807
1889
|
// whole entry away.
|
|
1808
1890
|
const dayThemes = dayOrder.map(idx => {
|
|
1809
1891
|
const entry = s.dayThemes?.[String(idx)];
|
|
1810
|
-
const
|
|
1892
|
+
const day = weekdayName(idx, { long: true });
|
|
1893
|
+
const help = entry?.label ? te('schedules.scheduleEditor.themeLabel', { label: entry.label }) : '';
|
|
1811
1894
|
// A day without an entry keeps the button's room, so the fields line up.
|
|
1812
1895
|
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="
|
|
1896
|
+
? `<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
1897
|
: '<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="
|
|
1898
|
+
return formRow(`sched-ed-day-${idx}`, day,
|
|
1899
|
+
`<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
1900
|
{ help });
|
|
1818
1901
|
}).join('');
|
|
1819
1902
|
|
|
@@ -1824,72 +1907,72 @@ function renderScheduleEditor() {
|
|
|
1824
1907
|
if (_panel && _scheduleEditor) { collectScheduleEditorDraft(); renderScheduleEditor(); }
|
|
1825
1908
|
});
|
|
1826
1909
|
}
|
|
1827
|
-
if (showMcp && !
|
|
1828
|
-
|
|
1910
|
+
if (showMcp && !getCachedToolCatalog()) {
|
|
1911
|
+
fetchToolCatalog().then(catalog => { if (catalog && _panel && _scheduleEditor) { collectScheduleEditorDraft(); renderScheduleEditor(); } });
|
|
1829
1912
|
}
|
|
1830
1913
|
|
|
1831
1914
|
redrawEditor(main, `
|
|
1832
1915
|
<div class="sched-editor">
|
|
1833
|
-
${editorHead({ back: 'schedule-editor-close', title: isEdit ? '
|
|
1916
|
+
${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
1917
|
<div class="sched-editor-body">
|
|
1835
1918
|
<div class="sched-form">
|
|
1836
1919
|
<section class="sched-form-section" aria-labelledby="sched-ed-sec-basics">
|
|
1837
|
-
<h3 class="sched-form-title" id="sched-ed-sec-basics"
|
|
1920
|
+
<h3 class="sched-form-title" id="sched-ed-sec-basics">${te('schedules.timers.schedule')}</h3>
|
|
1838
1921
|
<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: '
|
|
1922
|
+
${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">`)}
|
|
1923
|
+
${dropdownRow('sched-ed-template', t('schedules.form.automation'), templateOptions)}
|
|
1924
|
+
${dropdownRow('sched-ed-group', t('schedules.form.group'), groupOptions)}
|
|
1925
|
+
${formRow('sched-ed-enabled', t('schedules.scheduleEditor.active'), toggleHTML('sched-ed-enabled', s.enabled !== false, 'sched-ed-enabled-help'),
|
|
1926
|
+
{ help: te('schedules.scheduleEditor.activeHelp'), toggle: true })}
|
|
1844
1927
|
</div>
|
|
1845
1928
|
</section>
|
|
1846
1929
|
|
|
1847
1930
|
<section class="sched-form-section" aria-labelledby="sched-ed-sec-when">
|
|
1848
|
-
<h3 class="sched-form-title" id="sched-ed-sec-when"
|
|
1931
|
+
<h3 class="sched-form-title" id="sched-ed-sec-when">${te('schedules.scheduleEditor.when')}</h3>
|
|
1849
1932
|
<div class="sched-form-group">
|
|
1850
1933
|
<div class="sched-form-row sched-form-row--top">
|
|
1851
|
-
<label class="sched-form-label" for="sched-ed-cron"
|
|
1934
|
+
<label class="sched-form-label" for="sched-ed-cron">${te('schedules.scheduleEditor.cron')}</label>
|
|
1852
1935
|
<div class="sched-form-stack">
|
|
1853
1936
|
<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
1937
|
<p class="sched-help" id="sched-ed-cron-desc" aria-live="polite">${esc(describeCron(cron))}</p>
|
|
1855
1938
|
</div>
|
|
1856
1939
|
</div>
|
|
1857
1940
|
<div class="sched-form-row sched-form-row--top">
|
|
1858
|
-
<span class="sched-form-label"
|
|
1941
|
+
<span class="sched-form-label">${te('schedules.scheduleEditor.presets')}</span>
|
|
1859
1942
|
<div class="sched-presets">${presetsHTML}</div>
|
|
1860
1943
|
</div>
|
|
1861
|
-
${formRow('sched-ed-tz', '
|
|
1944
|
+
${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
1945
|
</div>
|
|
1863
1946
|
</section>
|
|
1864
1947
|
|
|
1865
1948
|
<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"
|
|
1949
|
+
<h3 class="sched-form-title" id="sched-ed-sec-launch">${te('schedules.form.launchOverrides')}</h3>
|
|
1950
|
+
<p class="sched-form-intro">${te('schedules.scheduleEditor.launchIntro')}</p>
|
|
1868
1951
|
<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', '
|
|
1952
|
+
${dropdownRow('sched-ed-profile', t('schedules.form.cli'), profileOptions)}
|
|
1953
|
+
${dropdownRow('sched-ed-model', t('schedules.form.model'), modelOptions)}
|
|
1954
|
+
${dropdownRow('sched-ed-context', t('schedules.form.context'), contextOptions)}
|
|
1955
|
+
${dropdownRow('sched-ed-effort', t('schedules.form.thinking'), effortOptions, { hidden: !showEffort })}
|
|
1956
|
+
${toolRow('sched-ed-mcp', s.tools, { provider: profileForModels, usesBrowser: s.usesBrowser ?? group?.usesBrowser ?? template?.usesBrowser })}
|
|
1957
|
+
${dropdownRow('sched-ed-account', t('schedules.form.account'), accountOptions, { hidden: !showAccount, rowId: 'sched-ed-account-field' })}
|
|
1958
|
+
${dropdownRow('sched-ed-browser', t('schedules.form.browser'), [
|
|
1959
|
+
opt('', t('schedules.launch.templateDefault'), browserValue === ''),
|
|
1960
|
+
opt('true', t('schedules.launch.alwaysOn'), browserValue === 'true'),
|
|
1961
|
+
opt('false', t('schedules.launch.alwaysOff'), browserValue === 'false'),
|
|
1879
1962
|
])}
|
|
1880
1963
|
</div>
|
|
1881
1964
|
</section>
|
|
1882
1965
|
|
|
1883
1966
|
<details class="sched-form-section"${hasThemes ? ' open' : ''}>
|
|
1884
|
-
<summary class="sched-form-title"
|
|
1885
|
-
<p class="sched-form-intro"
|
|
1967
|
+
<summary class="sched-form-title">${te('schedules.detail.dayThemes')} <small>${te('schedules.scheduleEditor.optional')}</small></summary>
|
|
1968
|
+
<p class="sched-form-intro">${te('schedules.scheduleEditor.dayThemesIntro')}</p>
|
|
1886
1969
|
<div class="sched-form-group" id="sched-ed-daythemes">${dayThemes}</div>
|
|
1887
1970
|
</details>
|
|
1888
1971
|
</div>
|
|
1889
1972
|
</div>
|
|
1890
1973
|
<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 ? '
|
|
1974
|
+
<button type="button" class="sched-btn" data-action="schedule-editor-close">${te('schedules.editor.cancel')}</button>
|
|
1975
|
+
<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
1976
|
</footer>
|
|
1894
1977
|
</div>
|
|
1895
1978
|
`);
|
|
@@ -1901,7 +1984,7 @@ function renderScheduleEditor() {
|
|
|
1901
1984
|
_scheduleEditor.model = null;
|
|
1902
1985
|
_scheduleEditor.contextMode = null;
|
|
1903
1986
|
_scheduleEditor.effort = null;
|
|
1904
|
-
_scheduleEditor.
|
|
1987
|
+
_scheduleEditor.tools = null;
|
|
1905
1988
|
_scheduleEditor.codexAccountId = null;
|
|
1906
1989
|
renderScheduleEditor();
|
|
1907
1990
|
});
|
|
@@ -1916,7 +1999,7 @@ function renderScheduleEditor() {
|
|
|
1916
1999
|
_scheduleEditor.model = null;
|
|
1917
2000
|
_scheduleEditor.contextMode = _scheduleEditor.profile ? 'default' : null;
|
|
1918
2001
|
_scheduleEditor.effort = null;
|
|
1919
|
-
_scheduleEditor.
|
|
2002
|
+
_scheduleEditor.tools = null;
|
|
1920
2003
|
_scheduleEditor.codexAccountId = null;
|
|
1921
2004
|
renderScheduleEditor();
|
|
1922
2005
|
});
|
|
@@ -1949,7 +2032,7 @@ function updateCronDesc() {
|
|
|
1949
2032
|
const valid = value.split(/\s+/).length === 5;
|
|
1950
2033
|
if (!valid && value) input.setAttribute('aria-invalid', 'true');
|
|
1951
2034
|
else input.removeAttribute('aria-invalid');
|
|
1952
|
-
if (desc) desc.textContent = valid ? describeCron(value) : '
|
|
2035
|
+
if (desc) desc.textContent = valid ? describeCron(value) : t('schedules.scheduleEditor.cronHelp');
|
|
1953
2036
|
_panel?.querySelectorAll('[data-action="cron-preset"]').forEach(btn => btn.setAttribute('aria-pressed', String(btn.dataset.cron === value)));
|
|
1954
2037
|
}
|
|
1955
2038
|
|
|
@@ -1958,12 +2041,18 @@ function updateCronDesc() {
|
|
|
1958
2041
|
// group pushes every changed launch field onto its schedules). So an edit sends a field only when
|
|
1959
2042
|
// the form's value differs from the saved one: a popup that cannot show a saved value yet, or a
|
|
1960
2043
|
// save of an untouched form, never clears anything.
|
|
1961
|
-
const LAUNCH_FIELDS = ['profile', 'model', 'contextMode', 'effort', '
|
|
2044
|
+
const LAUNCH_FIELDS = ['profile', 'model', 'contextMode', 'effort', 'tools', 'codexAccountId', 'usesBrowser'];
|
|
1962
2045
|
// What a CLI's choice decides: a model, context, thinking level, tool list or account of one CLI
|
|
1963
2046
|
// 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', '
|
|
2047
|
+
const CLI_DEPENDENT = ['model', 'contextMode', 'effort', 'tools', 'codexAccountId'];
|
|
2048
|
+
// A missing picker is not a request to clear tools (focus hosts and catalog loading).
|
|
2049
|
+
function readEditorTools(id, saved) {
|
|
2050
|
+
const input = $(id);
|
|
2051
|
+
return input ? readToolPicker(input) : saved ?? null;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
1965
2054
|
const blankToNull = (value) => (value === undefined || value === null || value === '' ? null : value);
|
|
1966
|
-
const sameValue = (a, b) => blankToNull(a) === blankToNull(b);
|
|
2055
|
+
const sameValue = (a, b) => stableJson(blankToNull(a)) === stableJson(blankToNull(b));
|
|
1967
2056
|
|
|
1968
2057
|
function stableJson(value) {
|
|
1969
2058
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
|
|
@@ -2006,7 +2095,7 @@ function collectScheduleEditorDraft() {
|
|
|
2006
2095
|
model: $('sched-ed-model')?.value || null,
|
|
2007
2096
|
contextMode: $('sched-ed-context')?.value || null,
|
|
2008
2097
|
effort: $('sched-ed-effort')?.value || null,
|
|
2009
|
-
|
|
2098
|
+
tools: readEditorTools('sched-ed-mcp', _scheduleEditor?.tools),
|
|
2010
2099
|
codexAccountId: $('sched-ed-account')?.value || null,
|
|
2011
2100
|
usesBrowser: browserRaw === '' ? undefined : browserRaw === 'true',
|
|
2012
2101
|
enabled: $('sched-ed-enabled')?.checked ?? _scheduleEditor.enabled,
|
|
@@ -2022,7 +2111,7 @@ function markInvalid(id, message) {
|
|
|
2022
2111
|
}
|
|
2023
2112
|
|
|
2024
2113
|
async function saveScheduleFromEditor() {
|
|
2025
|
-
if (editorsWait()) { showToast('
|
|
2114
|
+
if (editorsWait()) { showToast(t('schedules.toast.notLoaded')); return; }
|
|
2026
2115
|
const original = _scheduleEditor?._original || null;
|
|
2027
2116
|
const name = $('sched-ed-name')?.value?.trim();
|
|
2028
2117
|
const templateId = $('sched-ed-template')?.value;
|
|
@@ -2037,15 +2126,15 @@ async function saveScheduleFromEditor() {
|
|
|
2037
2126
|
contextMode: $('sched-ed-context')?.value || null,
|
|
2038
2127
|
// 'off' is a choice of its own (no thinking), never the same as "no override".
|
|
2039
2128
|
effort: $('sched-ed-effort')?.value || null,
|
|
2040
|
-
|
|
2129
|
+
tools: readEditorTools('sched-ed-mcp', _scheduleEditor?.tools),
|
|
2041
2130
|
// Read whatever the CLI: a saved account is kept while its row is hidden, never cleared by it.
|
|
2042
2131
|
codexAccountId: $('sched-ed-account')?.value || null,
|
|
2043
2132
|
usesBrowser: browserRaw === '' ? null : browserRaw === 'true',
|
|
2044
2133
|
};
|
|
2045
2134
|
|
|
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', '
|
|
2135
|
+
if (!name) { markInvalid('sched-ed-name', t('schedules.toast.nameRequired')); return; }
|
|
2136
|
+
if (!templateId) { markInvalid('sched-ed-template', t('schedules.toast.automationRequired')); return; }
|
|
2137
|
+
if (!cron || cron.split(/\s+/).length !== 5) { markInvalid('sched-ed-cron', t('schedules.toast.cronRequired')); return; }
|
|
2049
2138
|
|
|
2050
2139
|
const template = getTemplate(templateId);
|
|
2051
2140
|
const group = groupId ? getGroup(groupId) : null;
|
|
@@ -2057,10 +2146,10 @@ async function saveScheduleFromEditor() {
|
|
|
2057
2146
|
const effectiveModel = launch.model || group?.model || template?.model || null;
|
|
2058
2147
|
const effectiveAccount = launch.codexAccountId || group?.codexAccountId || template?.codexAccountId || 'default';
|
|
2059
2148
|
if (!modelChoiceAvailable(effectiveProfile, effectiveModel, effectiveAccount)) {
|
|
2060
|
-
showToast('
|
|
2149
|
+
showToast(t('schedules.toast.modelUnavailable')); return;
|
|
2061
2150
|
}
|
|
2062
2151
|
if (!contextChoiceAvailable(effectiveProfile, effectiveModel, launch.contextMode, effectiveAccount)) {
|
|
2063
|
-
showToast('
|
|
2152
|
+
showToast(t('schedules.toast.contextUnavailable')); return;
|
|
2064
2153
|
}
|
|
2065
2154
|
}
|
|
2066
2155
|
|
|
@@ -2081,7 +2170,7 @@ async function saveScheduleFromEditor() {
|
|
|
2081
2170
|
_scheduleEditor = null;
|
|
2082
2171
|
render({ frame: true });
|
|
2083
2172
|
focusRow(original.id);
|
|
2084
|
-
showToast('
|
|
2173
|
+
showToast(t('schedules.toast.noChanges'));
|
|
2085
2174
|
return;
|
|
2086
2175
|
}
|
|
2087
2176
|
} else {
|
|
@@ -2099,11 +2188,11 @@ async function saveScheduleFromEditor() {
|
|
|
2099
2188
|
saved = await updateSchedule(draft.id, payload);
|
|
2100
2189
|
const idx = _schedules.findIndex(s => s.id === saved.id);
|
|
2101
2190
|
if (idx >= 0) _schedules[idx] = saved;
|
|
2102
|
-
showToast('
|
|
2191
|
+
showToast(t('schedules.toast.updated'));
|
|
2103
2192
|
} else {
|
|
2104
2193
|
saved = await createSchedule(payload);
|
|
2105
2194
|
if (!_schedules.some(s => s.id === saved.id)) _schedules.push(saved);
|
|
2106
|
-
showToast('
|
|
2195
|
+
showToast(t('schedules.toast.created'));
|
|
2107
2196
|
}
|
|
2108
2197
|
if (_panel !== panel || _scheduleEditor !== draft) { refresh(); return; }
|
|
2109
2198
|
_scheduleEditor = null;
|
|
@@ -2115,7 +2204,7 @@ async function saveScheduleFromEditor() {
|
|
|
2115
2204
|
row?.querySelector('.sched-row-toggle')?.focus({ preventScroll: true });
|
|
2116
2205
|
}
|
|
2117
2206
|
} catch (err) {
|
|
2118
|
-
showToast('
|
|
2207
|
+
showToast(t('schedules.toast.saveFailed', { error: err.message }));
|
|
2119
2208
|
}
|
|
2120
2209
|
}
|
|
2121
2210
|
|
|
@@ -2193,7 +2282,7 @@ async function handleClick(e) {
|
|
|
2193
2282
|
}
|
|
2194
2283
|
case 'schedule-edit-prompt': {
|
|
2195
2284
|
const templateId = el.dataset.templateId;
|
|
2196
|
-
if (!templateId) { showToast('
|
|
2285
|
+
if (!templateId) { showToast(t('schedules.toast.noAutomation')); break; }
|
|
2197
2286
|
closePanel();
|
|
2198
2287
|
emit('automations:open', { templateId });
|
|
2199
2288
|
break;
|
|
@@ -2263,7 +2352,7 @@ async function handleClick(e) {
|
|
|
2263
2352
|
_scheduleEditor.dayThemes = themes;
|
|
2264
2353
|
renderScheduleEditor();
|
|
2265
2354
|
$(`sched-ed-day-${day}`)?.focus();
|
|
2266
|
-
showToast(
|
|
2355
|
+
showToast(t('schedules.toast.themeRemoved', { day: weekdayName(day, { long: true }) }));
|
|
2267
2356
|
break;
|
|
2268
2357
|
}
|
|
2269
2358
|
case 'group-editor-save':
|
|
@@ -2486,10 +2575,10 @@ async function moveScheduleToGroup(id, groupId) {
|
|
|
2486
2575
|
if (!schedule || (schedule.groupId || null) === (groupId || null)) return;
|
|
2487
2576
|
try {
|
|
2488
2577
|
replaceSchedule(await updateSchedule(id, { groupId }));
|
|
2489
|
-
showToast(groupId ?
|
|
2578
|
+
showToast(t('schedules.toast.movedTo', { group: groupId ? getGroup(groupId)?.name || t('schedules.toast.aGroup') : viewLabel('ungrouped') }));
|
|
2490
2579
|
refresh();
|
|
2491
2580
|
} catch (err) {
|
|
2492
|
-
showToast('
|
|
2581
|
+
showToast(t('schedules.toast.moveFailed', { error: err.message }));
|
|
2493
2582
|
refresh();
|
|
2494
2583
|
}
|
|
2495
2584
|
}
|
|
@@ -2501,11 +2590,11 @@ async function toggleSchedule(id, el = null) {
|
|
|
2501
2590
|
try {
|
|
2502
2591
|
replaceSchedule(await updateSchedule(id, { enabled: !schedule.enabled }));
|
|
2503
2592
|
const updated = _schedules.find(s => s.id === id);
|
|
2504
|
-
showToast(updated?.enabled ?
|
|
2593
|
+
showToast(t(updated?.enabled ? 'schedules.toast.resumed' : 'schedules.toast.paused', { name: schedule.name }));
|
|
2505
2594
|
refresh();
|
|
2506
2595
|
} catch (err) {
|
|
2507
2596
|
el?.removeAttribute('aria-busy');
|
|
2508
|
-
showToast('
|
|
2597
|
+
showToast(t('schedules.toast.toggleFailed', { error: err.message }));
|
|
2509
2598
|
}
|
|
2510
2599
|
}
|
|
2511
2600
|
|
|
@@ -2516,9 +2605,9 @@ async function fireSchedule(id, el = null) {
|
|
|
2516
2605
|
sidepanelWindowId: getNativeLoopRouterWindowId(),
|
|
2517
2606
|
sidepanelClaimToken: getNativeLoopRouterClaimToken(),
|
|
2518
2607
|
});
|
|
2519
|
-
showToast('
|
|
2608
|
+
showToast(t('schedules.toast.launching'));
|
|
2520
2609
|
} catch (err) {
|
|
2521
|
-
showToast('
|
|
2610
|
+
showToast(t('schedules.toast.runFailed', { error: err.message }));
|
|
2522
2611
|
} finally {
|
|
2523
2612
|
if (el?.isConnected) el.disabled = false;
|
|
2524
2613
|
}
|
|
@@ -2533,18 +2622,18 @@ function openConfirm({ kind, id, opener }) {
|
|
|
2533
2622
|
if (kind === 'schedule') {
|
|
2534
2623
|
const schedule = _schedules.find(s => s.id === id);
|
|
2535
2624
|
if (!schedule) return;
|
|
2536
|
-
title = '
|
|
2625
|
+
title = t('schedules.confirm.scheduleTitle');
|
|
2537
2626
|
name = schedule.name;
|
|
2538
|
-
text = '
|
|
2539
|
-
accept = '
|
|
2627
|
+
text = t('schedules.confirm.scheduleText');
|
|
2628
|
+
accept = t('schedules.confirm.scheduleAccept');
|
|
2540
2629
|
} else {
|
|
2541
2630
|
const group = getGroup(id);
|
|
2542
2631
|
if (!group) return;
|
|
2543
2632
|
const count = _schedules.filter(s => s.groupId === id).length;
|
|
2544
|
-
title = '
|
|
2633
|
+
title = t('schedules.confirm.groupTitle');
|
|
2545
2634
|
name = group.name;
|
|
2546
|
-
text = count ?
|
|
2547
|
-
accept = '
|
|
2635
|
+
text = count ? tp('schedules.groupEditor.membersStay', count) : t('schedules.groupEditor.empty');
|
|
2636
|
+
accept = t('schedules.confirm.groupAccept');
|
|
2548
2637
|
}
|
|
2549
2638
|
_confirm = { kind, id, opener };
|
|
2550
2639
|
layer.innerHTML = `<div class="sched-dialog" role="alertdialog" aria-modal="true" aria-labelledby="sched-dialog-title" aria-describedby="sched-dialog-text">
|
|
@@ -2554,7 +2643,7 @@ function openConfirm({ kind, id, opener }) {
|
|
|
2554
2643
|
<p class="sched-help" id="sched-dialog-text">${esc(text)}</p>
|
|
2555
2644
|
</div>
|
|
2556
2645
|
<div class="sched-dialog-actions">
|
|
2557
|
-
<button type="button" class="sched-btn" data-action="confirm-cancel"
|
|
2646
|
+
<button type="button" class="sched-btn" data-action="confirm-cancel">${te('schedules.editor.cancel')}</button>
|
|
2558
2647
|
<button type="button" class="sched-btn sched-btn--danger" data-action="confirm-accept">${ICONS.trash}<span>${esc(accept)}</span></button>
|
|
2559
2648
|
</div>
|
|
2560
2649
|
</div>`;
|
|
@@ -2581,14 +2670,14 @@ async function acceptConfirm(button) {
|
|
|
2581
2670
|
await deleteSchedule(id);
|
|
2582
2671
|
_schedules = _schedules.filter(s => s.id !== id);
|
|
2583
2672
|
_expanded.delete(id);
|
|
2584
|
-
showToast('
|
|
2673
|
+
showToast(t('schedules.toast.deleted'));
|
|
2585
2674
|
} else {
|
|
2586
2675
|
await deleteScheduleGroup(id);
|
|
2587
2676
|
_groups = _groups.filter(g => g.id !== id);
|
|
2588
2677
|
for (const schedule of _schedules) if (schedule.groupId === id) schedule.groupId = null;
|
|
2589
2678
|
if (_scope === `group:${id}`) setScope('ungrouped', { keepEditors: true });
|
|
2590
2679
|
if (_groupEditorId === id) { _groupEditorId = null; _groupEditorDraft = null; }
|
|
2591
|
-
showToast('
|
|
2680
|
+
showToast(t('schedules.toast.groupDeleted'));
|
|
2592
2681
|
}
|
|
2593
2682
|
_confirm.opener = null;
|
|
2594
2683
|
closeConfirm();
|
|
@@ -2596,17 +2685,17 @@ async function acceptConfirm(button) {
|
|
|
2596
2685
|
$('sched-search')?.focus();
|
|
2597
2686
|
} catch (err) {
|
|
2598
2687
|
button.disabled = false;
|
|
2599
|
-
showToast(
|
|
2688
|
+
showToast(t(kind === 'schedule' ? 'schedules.toast.deleteFailed' : 'schedules.toast.groupDeleteFailed', { error: err.message }));
|
|
2600
2689
|
}
|
|
2601
2690
|
}
|
|
2602
2691
|
|
|
2603
2692
|
// ── Groups ──
|
|
2604
2693
|
|
|
2605
2694
|
async function saveGroupFromEditor(id) {
|
|
2606
|
-
if (editorsWait()) { showToast('
|
|
2695
|
+
if (editorsWait()) { showToast(t('schedules.toast.notLoaded')); return; }
|
|
2607
2696
|
collectGroupEditorDraft();
|
|
2608
2697
|
const name = $('sched-ge-name')?.value?.trim();
|
|
2609
|
-
if (!name) { markInvalid('sched-ge-name', '
|
|
2698
|
+
if (!name) { markInvalid('sched-ge-name', t('schedules.toast.groupNameRequired')); return; }
|
|
2610
2699
|
const color = $('sched-ge-color')?.value || null;
|
|
2611
2700
|
const mutualExclusion = !!$('sched-ge-mutex')?.checked;
|
|
2612
2701
|
const browserRaw = $('sched-ge-browser')?.value || '';
|
|
@@ -2615,7 +2704,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2615
2704
|
model: $('sched-ge-model')?.value || null,
|
|
2616
2705
|
contextMode: $('sched-ge-context')?.value || null,
|
|
2617
2706
|
effort: $('sched-ge-effort')?.value || null, // 'off' stays explicit
|
|
2618
|
-
|
|
2707
|
+
tools: readEditorTools('sched-ge-mcp', _groupEditorDraft?.tools),
|
|
2619
2708
|
codexAccountId: $('sched-ge-account')?.value || null, // read whatever the CLI: hidden is not cleared
|
|
2620
2709
|
usesBrowser: browserRaw === '' ? null : browserRaw === 'true',
|
|
2621
2710
|
};
|
|
@@ -2624,10 +2713,10 @@ async function saveGroupFromEditor(id) {
|
|
|
2624
2713
|
if (!existing || ['profile', 'model', 'contextMode', 'codexAccountId'].some(k => !sameValue(launch[k], existing[k]))) {
|
|
2625
2714
|
const effectiveProfile = launch.profile || 'claude-code';
|
|
2626
2715
|
if (!modelChoiceAvailable(effectiveProfile, launch.model, launch.codexAccountId || 'default')) {
|
|
2627
|
-
showToast('
|
|
2716
|
+
showToast(t('schedules.toast.modelUnavailable')); return;
|
|
2628
2717
|
}
|
|
2629
2718
|
if (!contextChoiceAvailable(effectiveProfile, launch.model, launch.contextMode, launch.codexAccountId || 'default')) {
|
|
2630
|
-
showToast('
|
|
2719
|
+
showToast(t('schedules.toast.contextUnavailable')); return;
|
|
2631
2720
|
}
|
|
2632
2721
|
}
|
|
2633
2722
|
|
|
@@ -2643,7 +2732,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2643
2732
|
for (const key of LAUNCH_FIELDS) if (launch[key] !== null) payload[key] = launch[key];
|
|
2644
2733
|
const group = await createScheduleGroup(payload);
|
|
2645
2734
|
if (!_groups.some(g => g.id === group.id)) _groups.push(group);
|
|
2646
|
-
showToast('
|
|
2735
|
+
showToast(t('schedules.toast.groupCreated'));
|
|
2647
2736
|
if (!stillHere()) { refresh(); return; }
|
|
2648
2737
|
_groupEditorId = null;
|
|
2649
2738
|
_groupEditorDraft = null;
|
|
@@ -2652,7 +2741,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2652
2741
|
render({ frame: true });
|
|
2653
2742
|
_panel?.querySelector(`.sched-nav-item[data-scope="group:${CSS.escape(group.id)}"]`)?.focus();
|
|
2654
2743
|
} catch (err) {
|
|
2655
|
-
showToast('
|
|
2744
|
+
showToast(t('schedules.toast.groupFailed', { error: err.message }));
|
|
2656
2745
|
}
|
|
2657
2746
|
return;
|
|
2658
2747
|
}
|
|
@@ -2674,7 +2763,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2674
2763
|
_groupEditorDraft = null;
|
|
2675
2764
|
render({ frame: true });
|
|
2676
2765
|
_panel?.querySelector(`.sched-nav-item[data-scope="group:${CSS.escape(id)}"]`)?.focus();
|
|
2677
|
-
showToast('
|
|
2766
|
+
showToast(t('schedules.toast.noChanges'));
|
|
2678
2767
|
return;
|
|
2679
2768
|
}
|
|
2680
2769
|
// What each schedule of the group holds as the user saves: a reset afterwards writes only over
|
|
@@ -2692,7 +2781,7 @@ async function saveGroupFromEditor(id) {
|
|
|
2692
2781
|
_panel?.querySelector(`.sched-nav-item[data-scope="group:${CSS.escape(id)}"]`)?.focus();
|
|
2693
2782
|
}
|
|
2694
2783
|
if (!cliChanged) {
|
|
2695
|
-
showToast('
|
|
2784
|
+
showToast(t('schedules.toast.groupUpdated'));
|
|
2696
2785
|
} else {
|
|
2697
2786
|
_cliReport = { groupId: id, profile: launch.profile, pending: true };
|
|
2698
2787
|
renderNote();
|
|
@@ -2700,15 +2789,13 @@ async function saveGroupFromEditor(id) {
|
|
|
2700
2789
|
_cliReport = { groupId: id, profile: launch.profile, ...result };
|
|
2701
2790
|
renderNote();
|
|
2702
2791
|
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');
|
|
2792
|
+
showToast(kept ? tp('schedules.toast.groupUpdatedKept', kept) : t('schedules.toast.groupUpdated'));
|
|
2706
2793
|
}
|
|
2707
2794
|
// The schedules show what the save pushed onto them. From here on nothing is navigated:
|
|
2708
2795
|
// the user may have opened something else meanwhile, and it stays as it is.
|
|
2709
2796
|
await loadData();
|
|
2710
2797
|
} catch (err) {
|
|
2711
|
-
showToast('
|
|
2798
|
+
showToast(t('schedules.toast.groupUpdateFailed', { error: err.message }));
|
|
2712
2799
|
}
|
|
2713
2800
|
}
|
|
2714
2801
|
|
|
@@ -2718,7 +2805,7 @@ const RESET_KEYS = ['profile', ...CLI_DEPENDENT];
|
|
|
2718
2805
|
function captureMembers(groupId, launch) {
|
|
2719
2806
|
return _schedules.filter(s => s.groupId === groupId)
|
|
2720
2807
|
.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]
|
|
2808
|
+
.filter(m => RESET_KEYS.some(k => m.values[k] !== null && !sameValue(m.values[k], launch[k])));
|
|
2722
2809
|
}
|
|
2723
2810
|
|
|
2724
2811
|
// server.js pushes a group field onto the group's schedules only when the group's own value
|
|
@@ -2746,11 +2833,11 @@ async function resetSchedulesForCli(groupId, launch, members) {
|
|
|
2746
2833
|
const moved = !fresh || fresh.groupId !== groupId;
|
|
2747
2834
|
const changed = moved || RESET_KEYS.some(key => {
|
|
2748
2835
|
const now = blankToNull(fresh[key]);
|
|
2749
|
-
return now
|
|
2836
|
+
return !sameValue(now, member.values[key]) && !sameValue(now, target(key));
|
|
2750
2837
|
});
|
|
2751
2838
|
if (changed) { report.changed.push(name); continue; }
|
|
2752
2839
|
const reset = {};
|
|
2753
|
-
for (const key of RESET_KEYS) if (blankToNull(fresh[key]) !== null &&
|
|
2840
|
+
for (const key of RESET_KEYS) if (blankToNull(fresh[key]) !== null && !sameValue(fresh[key], target(key))) reset[key] = launch[key];
|
|
2754
2841
|
if (!Object.keys(reset).length) continue; // the save's own push already did it
|
|
2755
2842
|
try {
|
|
2756
2843
|
const saved = await updateSchedule(member.id, reset);
|
|
@@ -2783,7 +2870,7 @@ async function moveGroup(id, direction) {
|
|
|
2783
2870
|
const pressed = _panel?.querySelector(`[data-action="${direction < 0 ? 'group-up' : 'group-down'}"]`);
|
|
2784
2871
|
(pressed && !pressed.disabled ? pressed : _panel?.querySelector(`[data-action="${direction < 0 ? 'group-down' : 'group-up'}"]`))?.focus();
|
|
2785
2872
|
} catch (err) {
|
|
2786
|
-
showToast('
|
|
2873
|
+
showToast(t('schedules.toast.reorderFailed', { error: err.message }));
|
|
2787
2874
|
}
|
|
2788
2875
|
}
|
|
2789
2876
|
|
|
@@ -2798,6 +2885,7 @@ function openPromptScheduleModal() {
|
|
|
2798
2885
|
document.body.appendChild(_promptBackdrop);
|
|
2799
2886
|
|
|
2800
2887
|
const claudeMeta = getProviderMeta('claude-code');
|
|
2888
|
+
const antigravityMeta = getProviderMeta('antigravity');
|
|
2801
2889
|
const opencodeMeta = getProviderMeta('opencode');
|
|
2802
2890
|
const codexMeta = getProviderMeta('codex');
|
|
2803
2891
|
_promptModal = document.createElement('div');
|
|
@@ -2810,11 +2898,11 @@ function openPromptScheduleModal() {
|
|
|
2810
2898
|
<div class="sched-prompt-head-text">
|
|
2811
2899
|
<span class="sched-prompt-icon">${ICONS.sparkle}</span>
|
|
2812
2900
|
<div>
|
|
2813
|
-
<h3 id="sched-prompt-title"
|
|
2814
|
-
<p
|
|
2901
|
+
<h3 id="sched-prompt-title">${te('schedules.prompt.title')}</h3>
|
|
2902
|
+
<p>${te('schedules.prompt.sub')}</p>
|
|
2815
2903
|
</div>
|
|
2816
2904
|
</div>
|
|
2817
|
-
<button class="sched-prompt-close" data-action="prompt-modal-close" aria-label="
|
|
2905
|
+
<button class="sched-prompt-close" data-action="prompt-modal-close" aria-label="${te('schedules.prompt.close')}">${ICONS.close}</button>
|
|
2818
2906
|
</header>
|
|
2819
2907
|
<div class="sched-prompt-tiles">
|
|
2820
2908
|
<button class="sched-prompt-tile" style="--provider-color:${claudeMeta.color};" data-action="prompt-modal-pick" data-runtime="claude-code">
|
|
@@ -2823,15 +2911,23 @@ function openPromptScheduleModal() {
|
|
|
2823
2911
|
<span class="sched-prompt-tile-title">${claudeMeta.label}</span>
|
|
2824
2912
|
<span class="sched-prompt-tile-sub">Anthropic</span>
|
|
2825
2913
|
</span>
|
|
2826
|
-
<span class="sched-prompt-pill sched-prompt-pill--active"
|
|
2914
|
+
<span class="sched-prompt-pill sched-prompt-pill--active">${te('schedules.prompt.ready')}</span>
|
|
2915
|
+
</button>
|
|
2916
|
+
<button class="sched-prompt-tile" style="--provider-color:${antigravityMeta.color};" data-action="prompt-modal-pick" data-runtime="antigravity">
|
|
2917
|
+
<span class="sched-prompt-tile-logo" aria-hidden="true">${antigravityMeta.icon}</span>
|
|
2918
|
+
<span class="sched-prompt-tile-body">
|
|
2919
|
+
<span class="sched-prompt-tile-title">${antigravityMeta.label}</span>
|
|
2920
|
+
<span class="sched-prompt-tile-sub">Google</span>
|
|
2921
|
+
</span>
|
|
2922
|
+
<span class="sched-prompt-pill sched-prompt-pill--active">${te('schedules.prompt.ready')}</span>
|
|
2827
2923
|
</button>
|
|
2828
2924
|
<button class="sched-prompt-tile disabled" style="--provider-color:${opencodeMeta.color};" disabled aria-disabled="true">
|
|
2829
2925
|
<span class="sched-prompt-tile-logo" aria-hidden="true">${opencodeMeta.icon}</span>
|
|
2830
2926
|
<span class="sched-prompt-tile-body">
|
|
2831
2927
|
<span class="sched-prompt-tile-title">${opencodeMeta.label}</span>
|
|
2832
|
-
<span class="sched-prompt-tile-sub"
|
|
2928
|
+
<span class="sched-prompt-tile-sub">${te('schedules.prompt.multiProvider')}</span>
|
|
2833
2929
|
</span>
|
|
2834
|
-
<span class="sched-prompt-pill"
|
|
2930
|
+
<span class="sched-prompt-pill">${te('schedules.prompt.planned')}</span>
|
|
2835
2931
|
</button>
|
|
2836
2932
|
<button class="sched-prompt-tile disabled" style="--provider-color:${codexMeta.color};" disabled aria-disabled="true">
|
|
2837
2933
|
<span class="sched-prompt-tile-logo" aria-hidden="true">${codexMeta.icon}</span>
|
|
@@ -2839,7 +2935,7 @@ function openPromptScheduleModal() {
|
|
|
2839
2935
|
<span class="sched-prompt-tile-title">Codex CLI</span>
|
|
2840
2936
|
<span class="sched-prompt-tile-sub">OpenAI</span>
|
|
2841
2937
|
</span>
|
|
2842
|
-
<span class="sched-prompt-pill"
|
|
2938
|
+
<span class="sched-prompt-pill">${te('schedules.prompt.planned')}</span>
|
|
2843
2939
|
</button>
|
|
2844
2940
|
</div>
|
|
2845
2941
|
`;
|
|
@@ -2881,8 +2977,8 @@ function closePromptScheduleModal() {
|
|
|
2881
2977
|
}
|
|
2882
2978
|
|
|
2883
2979
|
async function handlePromptPick(runtime, triggerEl = null) {
|
|
2884
|
-
if (runtime !== 'claude-code') {
|
|
2885
|
-
showToast('
|
|
2980
|
+
if (runtime !== 'claude-code' && runtime !== 'antigravity') {
|
|
2981
|
+
showToast(t('schedules.toast.comingSoon'));
|
|
2886
2982
|
return;
|
|
2887
2983
|
}
|
|
2888
2984
|
if (triggerEl) {
|
|
@@ -2890,58 +2986,75 @@ async function handlePromptPick(runtime, triggerEl = null) {
|
|
|
2890
2986
|
triggerEl.setAttribute('aria-busy', 'true');
|
|
2891
2987
|
}
|
|
2892
2988
|
try {
|
|
2893
|
-
|
|
2989
|
+
if (runtime === 'antigravity') {
|
|
2990
|
+
// The Antigravity panel reads `/synabun …` as the bundled skill only on a server whose panel bridge says so
|
|
2991
|
+
// (lib/antigravity/panel-bridge.js 'synabun-command'); an older one would send the words as they are.
|
|
2992
|
+
if (!(await antigravityTakesSynabunCommands())) { showToast(t('schedules.toast.restartForAntigravity')); return; }
|
|
2993
|
+
await openAntigravityWithPrompt('/synabun schedule', { newTab: true, send: true });
|
|
2994
|
+
} else {
|
|
2995
|
+
await sendToPanel('/synabun schedule', { newTab: true, tabLabel: t('schedules.prompt.tabLabel'), autoSubmit: true });
|
|
2996
|
+
}
|
|
2894
2997
|
} catch (err) {
|
|
2895
|
-
showToast('
|
|
2998
|
+
showToast(t(runtime === 'antigravity' ? 'schedules.toast.antigravityPanelFailed' : 'schedules.toast.panelFailed', { error: err.message }));
|
|
2896
2999
|
} finally {
|
|
2897
3000
|
closePromptScheduleModal();
|
|
2898
3001
|
}
|
|
2899
3002
|
}
|
|
2900
3003
|
|
|
3004
|
+
/** Whether this server's Antigravity panel runs `/synabun …` (GET /api/antigravity/panel lists 'synabun-command'). */
|
|
3005
|
+
async function antigravityTakesSynabunCommands() {
|
|
3006
|
+
try {
|
|
3007
|
+
const res = await fetch('/api/antigravity/panel');
|
|
3008
|
+
if (!res.ok) return false;
|
|
3009
|
+
const body = await res.json();
|
|
3010
|
+
return Array.isArray(body?.capabilities) && body.capabilities.includes('synabun-command');
|
|
3011
|
+
} catch { return false; }
|
|
3012
|
+
}
|
|
3013
|
+
|
|
2901
3014
|
// ── Timers: writes ──
|
|
2902
3015
|
|
|
2903
3016
|
async function createQuickTimerFromForm(runNow) {
|
|
2904
3017
|
const templateId = $('sched-qt-template')?.value;
|
|
2905
|
-
if (!templateId) { markInvalid('sched-qt-template', '
|
|
3018
|
+
if (!templateId) { markInvalid('sched-qt-template', t('schedules.toast.chooseAutomation')); return; }
|
|
2906
3019
|
const customVal = Number($('sched-qt-custom-min')?.value);
|
|
2907
3020
|
const minutes = customVal >= 1 ? customVal : _selectedQtMinutes;
|
|
2908
|
-
if (!runNow && !minutes) { showToast('
|
|
3021
|
+
if (!runNow && !minutes) { showToast(t('schedules.toast.chooseDelay')); return; }
|
|
2909
3022
|
const template = getTemplate(templateId);
|
|
2910
3023
|
const profile = $('sched-qt-profile')?.value || 'claude-code';
|
|
2911
3024
|
const model = $('sched-qt-model')?.value || null;
|
|
2912
3025
|
const contextMode = $('sched-qt-context')?.value || 'default';
|
|
2913
3026
|
if (!modelChoiceAvailable(profile, model, _launchAccount)) {
|
|
2914
|
-
showToast('
|
|
3027
|
+
showToast(t('schedules.toast.modelUnavailable')); return;
|
|
2915
3028
|
}
|
|
2916
3029
|
if (!contextChoiceAvailable(profile, model, contextMode, _launchAccount)) {
|
|
2917
|
-
showToast('
|
|
3030
|
+
showToast(t('schedules.toast.contextUnavailable')); return;
|
|
2918
3031
|
}
|
|
2919
3032
|
const effortRaw = $('sched-qt-effort')?.value || null;
|
|
2920
3033
|
const effort = normalizeEffortForProfile(profile, effortRaw);
|
|
2921
|
-
const
|
|
3034
|
+
const tools = strictToolHost(profile) ? readToolPicker($('sched-qt-mcp')) : null;
|
|
2922
3035
|
const codexAccountId = profile === 'codex' ? ($('sched-qt-account')?.value || _launchAccount || 'default') : undefined;
|
|
2923
3036
|
const usesBrowser = _qtUsesBrowser !== null ? _qtUsesBrowser : !!template?.usesBrowser;
|
|
2924
3037
|
try {
|
|
2925
3038
|
if (runNow) {
|
|
2926
3039
|
const result = await triggerQuickTimerNow(templateId, {
|
|
2927
|
-
profile, model, contextMode, effort,
|
|
3040
|
+
profile, model, contextMode, effort, tools, usesBrowser, codexAccountId,
|
|
2928
3041
|
sidepanelWindowId: getNativeLoopRouterWindowId(),
|
|
2929
3042
|
sidepanelClaimToken: getNativeLoopRouterClaimToken(),
|
|
2930
3043
|
});
|
|
2931
|
-
showToast(
|
|
3044
|
+
showToast(t('schedules.toast.runningNow', { name: result.templateName }));
|
|
2932
3045
|
} else {
|
|
2933
|
-
const result = await createQuickTimer(templateId, minutes, { profile, model, contextMode, effort,
|
|
3046
|
+
const result = await createQuickTimer(templateId, minutes, { profile, model, contextMode, effort, tools, usesBrowser, codexAccountId });
|
|
2934
3047
|
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,
|
|
3048
|
+
_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
3049
|
}
|
|
2937
3050
|
_selectedQtMinutes = null;
|
|
2938
3051
|
_panel?.querySelectorAll('[data-action="qt-select"]').forEach(chip => chip.setAttribute('aria-pressed', 'false'));
|
|
2939
|
-
showToast(
|
|
3052
|
+
showToast(t('schedules.toast.timerSet', { name: result.templateName }));
|
|
2940
3053
|
}
|
|
2941
3054
|
renderActiveTimersInto();
|
|
2942
3055
|
renderRail();
|
|
2943
3056
|
} catch (err) {
|
|
2944
|
-
showToast((runNow ? '
|
|
3057
|
+
showToast(t(runNow ? 'schedules.toast.runNowFailed' : 'schedules.toast.timerFailed', { error: err.message }));
|
|
2945
3058
|
}
|
|
2946
3059
|
}
|
|
2947
3060
|
|
|
@@ -2951,25 +3064,25 @@ async function cancelQuickTimerById(id) {
|
|
|
2951
3064
|
_quickTimers = _quickTimers.filter(t => t.id !== id);
|
|
2952
3065
|
renderActiveTimersInto();
|
|
2953
3066
|
renderRail();
|
|
2954
|
-
showToast('
|
|
3067
|
+
showToast(t('schedules.toast.timerCancelled'));
|
|
2955
3068
|
} catch (err) {
|
|
2956
|
-
showToast('
|
|
3069
|
+
showToast(t('schedules.toast.cancelFailed', { error: err.message }));
|
|
2957
3070
|
}
|
|
2958
3071
|
}
|
|
2959
3072
|
|
|
2960
3073
|
async function createExistingScheduleTimer() {
|
|
2961
3074
|
const scheduleId = $('sched-existing-timer-schedule')?.value;
|
|
2962
3075
|
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', '
|
|
3076
|
+
if (!scheduleId) { markInvalid('sched-existing-timer-schedule', t('schedules.toast.chooseSchedule')); return; }
|
|
3077
|
+
if (!minutes || minutes < 1) { markInvalid('sched-existing-timer-min', t('schedules.toast.minutesRequired')); return; }
|
|
2965
3078
|
try {
|
|
2966
3079
|
const result = await startScheduleTimer(scheduleId, minutes);
|
|
2967
3080
|
_scheduleTimerData[scheduleId] = { firesAt: result.firesAt, minutes: result.minutes };
|
|
2968
3081
|
renderActiveTimersInto();
|
|
2969
3082
|
renderRail();
|
|
2970
|
-
showToast('
|
|
3083
|
+
showToast(t('schedules.toast.scheduleTimerSet'));
|
|
2971
3084
|
} catch (err) {
|
|
2972
|
-
showToast('
|
|
3085
|
+
showToast(t('schedules.toast.timerFailed', { error: err.message }));
|
|
2973
3086
|
}
|
|
2974
3087
|
}
|
|
2975
3088
|
|
|
@@ -2979,9 +3092,9 @@ async function cancelExistingScheduleTimer(id) {
|
|
|
2979
3092
|
delete _scheduleTimerData[id];
|
|
2980
3093
|
renderActiveTimersInto();
|
|
2981
3094
|
renderRail();
|
|
2982
|
-
showToast('
|
|
3095
|
+
showToast(t('schedules.toast.timerCancelled'));
|
|
2983
3096
|
} catch (err) {
|
|
2984
|
-
showToast('
|
|
3097
|
+
showToast(t('schedules.toast.cancelFailed', { error: err.message }));
|
|
2985
3098
|
}
|
|
2986
3099
|
}
|
|
2987
3100
|
|
|
@@ -3033,7 +3146,7 @@ function setupSync() {
|
|
|
3033
3146
|
refresh();
|
|
3034
3147
|
});
|
|
3035
3148
|
on('sync:schedule:fired', (data) => {
|
|
3036
|
-
if (data?.scheduleName) showToast(
|
|
3149
|
+
if (data?.scheduleName) showToast(t('schedules.toast.fired', { name: data.scheduleName }));
|
|
3037
3150
|
if (_panel) scheduleRefresh();
|
|
3038
3151
|
});
|
|
3039
3152
|
on('sync:schedule:completed', (data) => {
|
|
@@ -3047,7 +3160,7 @@ function setupSync() {
|
|
|
3047
3160
|
}
|
|
3048
3161
|
});
|
|
3049
3162
|
on('sync:schedule:failed', (data) => {
|
|
3050
|
-
if (data?.reason) showToast(
|
|
3163
|
+
if (data?.reason) showToast(t('schedules.toast.scheduleFailed', { reason: data.reason }));
|
|
3051
3164
|
if (_panel) scheduleRefresh();
|
|
3052
3165
|
});
|
|
3053
3166
|
on('sync:schedule:timer-set', (data) => {
|
|
@@ -3057,7 +3170,7 @@ function setupSync() {
|
|
|
3057
3170
|
}
|
|
3058
3171
|
});
|
|
3059
3172
|
on('sync:schedule:timer-fired', (data) => {
|
|
3060
|
-
if (data?.scheduleName) showToast(
|
|
3173
|
+
if (data?.scheduleName) showToast(t('schedules.toast.timerFired', { name: data.scheduleName }));
|
|
3061
3174
|
if (data?.scheduleId) delete _scheduleTimerData[data.scheduleId];
|
|
3062
3175
|
if (_panel) { renderRail(); renderActiveTimersInto(); }
|
|
3063
3176
|
});
|
|
@@ -3084,29 +3197,31 @@ function setupSync() {
|
|
|
3084
3197
|
refresh();
|
|
3085
3198
|
});
|
|
3086
3199
|
on('sync:schedule-group:reordered', (data) => {
|
|
3087
|
-
if (Array.isArray(data?.groups)) _groups = data.groups;
|
|
3200
|
+
if (Array.isArray(data?.groups)) _groups = migrateLaunchRecords(data.groups);
|
|
3088
3201
|
refresh();
|
|
3089
3202
|
});
|
|
3090
3203
|
on('sync:quick-timer:set', (data) => {
|
|
3091
3204
|
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,
|
|
3205
|
+
_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
3206
|
if (_panel) { renderRail(); renderActiveTimersInto(); }
|
|
3094
3207
|
}
|
|
3095
3208
|
});
|
|
3096
3209
|
on('sync:quick-timer:fired', (data) => {
|
|
3097
|
-
if (data?.templateName) showToast(
|
|
3210
|
+
if (data?.templateName) showToast(t('schedules.toast.timerFired', { name: data.templateName }));
|
|
3098
3211
|
if (data?.timerId) _quickTimers = _quickTimers.filter(t => t.id !== data.timerId);
|
|
3099
3212
|
if (_panel) { renderRail(); renderActiveTimersInto(); }
|
|
3100
3213
|
});
|
|
3101
3214
|
on('sync:quick-timer:fired-now', (data) => {
|
|
3102
|
-
if (data?.templateName) showToast(
|
|
3215
|
+
if (data?.templateName) showToast(t('schedules.toast.runningNow', { name: data.templateName }));
|
|
3103
3216
|
});
|
|
3104
3217
|
on('sync:quick-timer:cancelled', (data) => {
|
|
3105
3218
|
if (data?.timerId) _quickTimers = _quickTimers.filter(t => t.id !== data.timerId);
|
|
3106
3219
|
if (_panel) { renderRail(); renderActiveTimersInto(); }
|
|
3107
3220
|
});
|
|
3108
3221
|
on('sync:quick-timer:failed', (data) => {
|
|
3109
|
-
if (data?.reason) showToast(
|
|
3222
|
+
if (data?.reason) showToast(t('schedules.toast.timerFailed', { error: data.reason }));
|
|
3110
3223
|
});
|
|
3111
3224
|
}
|
|
3112
3225
|
|
|
3226
|
+
|
|
3227
|
+
configureToolPickerTranslation(t);
|