@jsonstudio/rcc 0.89.1803 → 0.89.1959
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/configsamples/config.json +19 -0
- package/configsamples/provider/deepseek/config.v1.json +59 -0
- package/dist/build-info.js +2 -2
- package/dist/cli/commands/claude.d.ts +4 -0
- package/dist/cli/commands/claude.js +56 -0
- package/dist/cli/commands/claude.js.map +1 -0
- package/dist/cli/commands/clock-admin.d.ts +20 -0
- package/dist/cli/commands/clock-admin.js +234 -0
- package/dist/cli/commands/clock-admin.js.map +1 -0
- package/dist/cli/commands/code.d.ts +0 -42
- package/dist/cli/commands/code.js +4 -414
- package/dist/cli/commands/code.js.map +1 -1
- package/dist/cli/commands/codex.d.ts +4 -0
- package/dist/cli/commands/codex.js +43 -0
- package/dist/cli/commands/codex.js.map +1 -0
- package/dist/cli/commands/examples.js +13 -16
- package/dist/cli/commands/examples.js.map +1 -1
- package/dist/cli/commands/init/basic.d.ts +40 -0
- package/dist/cli/commands/init/basic.js +482 -0
- package/dist/cli/commands/init/basic.js.map +1 -0
- package/dist/cli/commands/init/camoufox.d.ts +7 -0
- package/dist/cli/commands/init/camoufox.js +59 -0
- package/dist/cli/commands/init/camoufox.js.map +1 -0
- package/dist/cli/commands/init/interactive.d.ts +18 -0
- package/dist/cli/commands/init/interactive.js +223 -0
- package/dist/cli/commands/init/interactive.js.map +1 -0
- package/dist/cli/commands/init/shared.d.ts +66 -0
- package/dist/cli/commands/init/shared.js +9 -0
- package/dist/cli/commands/init/shared.js.map +1 -0
- package/dist/cli/commands/init/workflows.d.ts +29 -0
- package/dist/cli/commands/init/workflows.js +341 -0
- package/dist/cli/commands/init/workflows.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -26
- package/dist/cli/commands/init.js +220 -53
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/launcher-kernel.d.ts +78 -0
- package/dist/cli/commands/launcher-kernel.js +1194 -0
- package/dist/cli/commands/launcher-kernel.js.map +1 -0
- package/dist/cli/commands/start.js +27 -1
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +24 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/stop.d.ts +1 -0
- package/dist/cli/commands/stop.js +201 -4
- package/dist/cli/commands/stop.js.map +1 -1
- package/dist/cli/commands/tmux-inject.d.ts +20 -0
- package/dist/cli/commands/tmux-inject.js +212 -0
- package/dist/cli/commands/tmux-inject.js.map +1 -0
- package/dist/cli/config/init-provider-catalog.js +34 -0
- package/dist/cli/config/init-provider-catalog.js.map +1 -1
- package/dist/cli/register/claude-command.d.ts +3 -0
- package/dist/cli/register/claude-command.js +5 -0
- package/dist/cli/register/claude-command.js.map +1 -0
- package/dist/cli/register/clock-admin-command.d.ts +3 -0
- package/dist/cli/register/clock-admin-command.js +5 -0
- package/dist/cli/register/clock-admin-command.js.map +1 -0
- package/dist/cli/register/codex-command.d.ts +3 -0
- package/dist/cli/register/codex-command.js +5 -0
- package/dist/cli/register/codex-command.js.map +1 -0
- package/dist/cli/register/status-config-commands.d.ts +2 -0
- package/dist/cli/register/status-config-commands.js.map +1 -1
- package/dist/cli/register/tmux-inject-command.d.ts +3 -0
- package/dist/cli/register/tmux-inject-command.js +5 -0
- package/dist/cli/register/tmux-inject-command.js.map +1 -0
- package/dist/cli/server/port-utils.d.ts +3 -2
- package/dist/cli/server/port-utils.js +171 -32
- package/dist/cli/server/port-utils.js.map +1 -1
- package/dist/cli.js +45 -6
- package/dist/cli.js.map +1 -1
- package/dist/client/gemini/gemini-protocol-client.js +56 -5
- package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
- package/dist/commands/token-daemon.js +59 -7
- package/dist/commands/token-daemon.js.map +1 -1
- package/dist/commands/validate.js +87 -15
- package/dist/commands/validate.js.map +1 -1
- package/dist/config/routecodex-config-loader.js +31 -2
- package/dist/config/routecodex-config-loader.js.map +1 -1
- package/dist/docs/daemon-admin-ui.html +948 -74
- package/dist/index.d.ts +1 -0
- package/dist/index.js +325 -37
- package/dist/index.js.map +1 -1
- package/dist/manager/quota/provider-quota-center.js +8 -14
- package/dist/manager/quota/provider-quota-center.js.map +1 -1
- package/dist/modules/llmswitch/bridge.d.ts +39 -0
- package/dist/modules/llmswitch/bridge.js +169 -0
- package/dist/modules/llmswitch/bridge.js.map +1 -1
- package/dist/modules/pipeline/utils/colored-logger.js +1 -1
- package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
- package/dist/providers/auth/deepseek-account-auth.d.ts +39 -0
- package/dist/providers/auth/deepseek-account-auth.js +329 -0
- package/dist/providers/auth/deepseek-account-auth.js.map +1 -0
- package/dist/providers/auth/deepseek-account-token-acquirer.d.ts +15 -0
- package/dist/providers/auth/deepseek-account-token-acquirer.js +644 -0
- package/dist/providers/auth/deepseek-account-token-acquirer.js.map +1 -0
- package/dist/providers/auth/oauth-lifecycle.js +26 -4
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/auth/oauth-repair-cooldown.d.ts +5 -0
- package/dist/providers/auth/oauth-repair-cooldown.js +39 -0
- package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
- package/dist/providers/auth/token-scanner/index.d.ts +6 -0
- package/dist/providers/auth/token-scanner/index.js +53 -0
- package/dist/providers/auth/token-scanner/index.js.map +1 -1
- package/dist/providers/core/api/provider-config.d.ts +17 -2
- package/dist/providers/core/api/provider-types.d.ts +6 -0
- package/dist/providers/core/api/provider-types.js.map +1 -1
- package/dist/providers/core/config/camoufox-launcher.d.ts +7 -0
- package/dist/providers/core/config/camoufox-launcher.js +68 -21
- package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +19 -0
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/contracts/deepseek-provider-contract.d.ts +34 -0
- package/dist/providers/core/contracts/deepseek-provider-contract.js +100 -0
- package/dist/providers/core/contracts/deepseek-provider-contract.js.map +1 -0
- package/dist/providers/core/runtime/anthropic-http-provider.d.ts +0 -5
- package/dist/providers/core/runtime/anthropic-http-provider.js +0 -26
- package/dist/providers/core/runtime/anthropic-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/deepseek-http-provider.d.ts +35 -0
- package/dist/providers/core/runtime/deepseek-http-provider.js +373 -0
- package/dist/providers/core/runtime/deepseek-http-provider.js.map +1 -0
- package/dist/providers/core/runtime/deepseek-session-pow.d.ts +55 -0
- package/dist/providers/core/runtime/deepseek-session-pow.js +422 -0
- package/dist/providers/core/runtime/deepseek-session-pow.js.map +1 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +0 -3
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +0 -72
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-http-provider.d.ts +1 -7
- package/dist/providers/core/runtime/gemini-http-provider.js +3 -110
- package/dist/providers/core/runtime/gemini-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
- package/dist/providers/core/runtime/http-request-executor.js +4 -0
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +10 -4
- package/dist/providers/core/runtime/http-transport-provider.js +308 -82
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/iflow-http-provider.d.ts +0 -4
- package/dist/providers/core/runtime/iflow-http-provider.js +0 -28
- package/dist/providers/core/runtime/iflow-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-factory.d.ts +5 -0
- package/dist/providers/core/runtime/provider-factory.js +59 -6
- package/dist/providers/core/runtime/provider-factory.js.map +1 -1
- package/dist/providers/core/runtime/responses-provider.d.ts +0 -2
- package/dist/providers/core/runtime/responses-provider.js +0 -11
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/strategies/oauth-device-flow.js +16 -1
- package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
- package/dist/providers/core/utils/provider-type-utils.js +2 -1
- package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
- package/dist/providers/profile/families/anthropic-profile.d.ts +2 -0
- package/dist/providers/profile/families/anthropic-profile.js +32 -0
- package/dist/providers/profile/families/anthropic-profile.js.map +1 -0
- package/dist/providers/profile/families/antigravity-profile.d.ts +2 -0
- package/dist/providers/profile/families/antigravity-profile.js +109 -0
- package/dist/providers/profile/families/antigravity-profile.js.map +1 -0
- package/dist/providers/profile/families/glm-profile.d.ts +2 -0
- package/dist/providers/profile/families/glm-profile.js +48 -0
- package/dist/providers/profile/families/glm-profile.js.map +1 -0
- package/dist/providers/profile/families/iflow-profile.d.ts +2 -0
- package/dist/providers/profile/families/iflow-profile.js +232 -0
- package/dist/providers/profile/families/iflow-profile.js.map +1 -0
- package/dist/providers/profile/families/qwen-profile.d.ts +2 -0
- package/dist/providers/profile/families/qwen-profile.js +14 -0
- package/dist/providers/profile/families/qwen-profile.js.map +1 -0
- package/dist/providers/profile/families/responses-profile.d.ts +2 -0
- package/dist/providers/profile/families/responses-profile.js +28 -0
- package/dist/providers/profile/families/responses-profile.js.map +1 -0
- package/dist/providers/profile/profile-contracts.d.ts +74 -0
- package/dist/providers/profile/profile-contracts.js +2 -0
- package/dist/providers/profile/profile-contracts.js.map +1 -0
- package/dist/providers/profile/profile-registry.d.ts +3 -0
- package/dist/providers/profile/profile-registry.js +40 -0
- package/dist/providers/profile/profile-registry.js.map +1 -0
- package/dist/providers/profile/provider-directory.d.ts +2 -0
- package/dist/providers/profile/provider-directory.js +55 -0
- package/dist/providers/profile/provider-directory.js.map +1 -0
- package/dist/providers/profile/provider-profile-loader.js +43 -3
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +8 -0
- package/dist/scripts/deepseek/pow-solver.mjs +146 -0
- package/dist/scripts/deepseek/sha3_wasm_bg.7b9ca65ddd.wasm +0 -0
- package/dist/server/handlers/config-admin-handler.js +27 -0
- package/dist/server/handlers/config-admin-handler.js.map +1 -1
- package/dist/server/runtime/http-server/clock-client-registry.d.ts +113 -0
- package/dist/server/runtime/http-server/clock-client-registry.js +592 -0
- package/dist/server/runtime/http-server/clock-client-registry.js.map +1 -0
- package/dist/server/runtime/http-server/clock-client-routes.d.ts +2 -0
- package/dist/server/runtime/http-server/clock-client-routes.js +481 -0
- package/dist/server/runtime/http-server/clock-client-routes.js.map +1 -0
- package/dist/server/runtime/http-server/clock-daemon-inject-config.d.ts +1 -0
- package/dist/server/runtime/http-server/clock-daemon-inject-config.js +11 -0
- package/dist/server/runtime/http-server/clock-daemon-inject-config.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +3 -3
- package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/auth-session.d.ts +1 -0
- package/dist/server/runtime/http-server/daemon-admin/auth-session.js +18 -2
- package/dist/server/runtime/http-server/daemon-admin/auth-session.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/control-handler.js +2 -15
- package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +65 -7
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
- package/dist/server/runtime/http-server/executor-metadata.js +37 -1
- package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
- package/dist/server/runtime/http-server/executor-provider.js +55 -0
- package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
- package/dist/server/runtime/http-server/executor-response.js +49 -1
- package/dist/server/runtime/http-server/executor-response.js.map +1 -1
- package/dist/server/runtime/http-server/index.d.ts +10 -0
- package/dist/server/runtime/http-server/index.js +534 -9
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/managed-process-probe.d.ts +6 -0
- package/dist/server/runtime/http-server/managed-process-probe.js +294 -0
- package/dist/server/runtime/http-server/managed-process-probe.js.map +1 -0
- package/dist/server/runtime/http-server/middleware.js +16 -1
- package/dist/server/runtime/http-server/middleware.js.map +1 -1
- package/dist/server/runtime/http-server/provider-utils.js +6 -2
- package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.d.ts +1 -0
- package/dist/server/runtime/http-server/request-executor.js +360 -35
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +95 -3
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/stats-manager.d.ts +10 -0
- package/dist/server/runtime/http-server/stats-manager.js +119 -16
- package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
- package/dist/server/runtime/http-server/tmux-session-probe.d.ts +3 -0
- package/dist/server/runtime/http-server/tmux-session-probe.js +101 -0
- package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -0
- package/dist/server/utils/stage-logger.js +21 -5
- package/dist/server/utils/stage-logger.js.map +1 -1
- package/dist/token-daemon/index.js +59 -10
- package/dist/token-daemon/index.js.map +1 -1
- package/dist/token-daemon/server-utils.d.ts +1 -0
- package/dist/token-daemon/server-utils.js +4 -1
- package/dist/token-daemon/server-utils.js.map +1 -1
- package/dist/token-daemon/token-daemon.js +38 -4
- package/dist/token-daemon/token-daemon.js.map +1 -1
- package/dist/token-daemon/token-types.d.ts +1 -1
- package/dist/token-daemon/token-types.js +2 -1
- package/dist/token-daemon/token-types.js.map +1 -1
- package/dist/token-daemon/token-utils.js +5 -2
- package/dist/token-daemon/token-utils.js.map +1 -1
- package/dist/utils/clock-client-token.d.ts +3 -0
- package/dist/utils/clock-client-token.js +54 -0
- package/dist/utils/clock-client-token.js.map +1 -0
- package/dist/utils/managed-server-pids.d.ts +25 -0
- package/dist/utils/managed-server-pids.js +176 -0
- package/dist/utils/managed-server-pids.js.map +1 -0
- package/dist/utils/process-lifecycle-logger.d.ts +8 -0
- package/dist/utils/process-lifecycle-logger.js +151 -0
- package/dist/utils/process-lifecycle-logger.js.map +1 -0
- package/dist/utils/runtime-exit-forensics.d.ts +30 -0
- package/dist/utils/runtime-exit-forensics.js +101 -0
- package/dist/utils/runtime-exit-forensics.js.map +1 -0
- package/dist/utils/shutdown-caller-context.d.ts +22 -0
- package/dist/utils/shutdown-caller-context.js +25 -0
- package/dist/utils/shutdown-caller-context.js.map +1 -0
- package/docs/PROVIDERS_BUILTIN.md +8 -0
- package/docs/PROVIDER_TYPES.md +3 -1
- package/docs/SERVERTOOL_PRE_COMMAND_HOOKS.md +85 -0
- package/docs/clock-client-daemon-design.md +343 -0
- package/docs/daemon-admin-ui.html +948 -74
- package/docs/providers/deepseek-web-provider-design.md +192 -0
- package/docs/routing-instructions.md +4 -1
- package/docs/stop-message-auto.md +4 -3
- package/docs/v2-architecture/PROVIDER-V2-CHANGESET-RELEASE-CHECKLIST.md +80 -0
- package/docs/v2-architecture/PROVIDER-V2-LAYERING-ADR-DRAFT.md +225 -0
- package/docs/v2-architecture/PROVIDER-V2-MIGRATION-MATRIX-DRAFT.md +88 -0
- package/docs/v2-architecture/PROVIDER-V2-PHASED-MIGRATION-ROLLBACK-DRAFT.md +164 -0
- package/docs/v2-architecture/PROVIDER-V2-PROFILE-API-REGISTRY-DRAFT.md +201 -0
- package/docs/v2-architecture/PROVIDER-V2-PROFILE-GEMINI-DRAFT.md +56 -0
- package/docs/v2-architecture/PROVIDER-V2-REFACTOR-OVERVIEW-DRAFT.md +102 -0
- package/docs/v2-architecture/PROVIDER-V2-VERIFICATION-MATRIX-DRAFT.md +163 -0
- package/package.json +10 -9
- package/scripts/copy-compat-assets.mjs +18 -0
- package/scripts/copy-modules-config.mjs +1 -0
- package/scripts/deepseek/pow-solver.mjs +146 -0
- package/scripts/deepseek/sha3_wasm_bg.7b9ca65ddd.wasm +0 -0
- package/scripts/ensure-cli-executable.mjs +64 -0
- package/scripts/install-global.sh +5 -2
- package/scripts/install.sh +1 -1
- package/scripts/monitor/daemon-kill-watch.mjs +184 -0
- package/scripts/monitor/port-kill-watch.sh +74 -0
- package/scripts/quick-install.sh +1 -1
|
@@ -376,6 +376,10 @@
|
|
|
376
376
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
.grid.grid-clock {
|
|
380
|
+
grid-template-columns: minmax(0, 2.2fr) minmax(0, 0.8fr);
|
|
381
|
+
}
|
|
382
|
+
|
|
379
383
|
.grid.grid-one {
|
|
380
384
|
grid-template-columns: minmax(0, 1fr);
|
|
381
385
|
}
|
|
@@ -465,6 +469,71 @@
|
|
|
465
469
|
font-weight: 650;
|
|
466
470
|
}
|
|
467
471
|
|
|
472
|
+
.table tr.clock-detail-row td {
|
|
473
|
+
background: rgba(255, 255, 255, 0.01);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.clock-group-toggle {
|
|
477
|
+
all: unset;
|
|
478
|
+
cursor: pointer;
|
|
479
|
+
display: block;
|
|
480
|
+
width: 100%;
|
|
481
|
+
white-space: normal;
|
|
482
|
+
line-height: 1.35;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.clock-group-summary {
|
|
486
|
+
margin-top: 4px;
|
|
487
|
+
font-size: 11px;
|
|
488
|
+
color: var(--muted);
|
|
489
|
+
white-space: normal;
|
|
490
|
+
overflow-wrap: anywhere;
|
|
491
|
+
line-height: 1.3;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.clock-detail-list {
|
|
495
|
+
display: flex;
|
|
496
|
+
flex-direction: column;
|
|
497
|
+
gap: 6px;
|
|
498
|
+
padding: 4px 0;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.clock-detail-item {
|
|
502
|
+
display: flex;
|
|
503
|
+
align-items: center;
|
|
504
|
+
gap: 8px;
|
|
505
|
+
min-width: 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.clock-detail-item .clock-detail-content {
|
|
509
|
+
min-width: 0;
|
|
510
|
+
flex: 1;
|
|
511
|
+
white-space: normal;
|
|
512
|
+
overflow-wrap: anywhere;
|
|
513
|
+
line-height: 1.3;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.table.table-clock-merged th:nth-child(1),
|
|
517
|
+
.table.table-clock-merged td:nth-child(1) {
|
|
518
|
+
width: 10%;
|
|
519
|
+
}
|
|
520
|
+
.table.table-clock-merged th:nth-child(2),
|
|
521
|
+
.table.table-clock-merged td:nth-child(2) {
|
|
522
|
+
width: 50%;
|
|
523
|
+
}
|
|
524
|
+
.table.table-clock-merged th:nth-child(3),
|
|
525
|
+
.table.table-clock-merged td:nth-child(3) {
|
|
526
|
+
width: 14%;
|
|
527
|
+
}
|
|
528
|
+
.table.table-clock-merged th:nth-child(4),
|
|
529
|
+
.table.table-clock-merged td:nth-child(4) {
|
|
530
|
+
width: 14%;
|
|
531
|
+
}
|
|
532
|
+
.table.table-clock-merged th:nth-child(5),
|
|
533
|
+
.table.table-clock-merged td:nth-child(5) {
|
|
534
|
+
width: 12%;
|
|
535
|
+
}
|
|
536
|
+
|
|
468
537
|
.table tr.provider-row:hover td {
|
|
469
538
|
background: rgba(78, 161, 255, 0.06);
|
|
470
539
|
}
|
|
@@ -502,6 +571,19 @@
|
|
|
502
571
|
justify-content: flex-end;
|
|
503
572
|
}
|
|
504
573
|
|
|
574
|
+
.auth-actions {
|
|
575
|
+
display: flex;
|
|
576
|
+
flex-wrap: wrap;
|
|
577
|
+
gap: 6px;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.auth-action-btn {
|
|
581
|
+
padding: 3px 8px;
|
|
582
|
+
font-size: 11px;
|
|
583
|
+
border-radius: 8px;
|
|
584
|
+
white-space: nowrap;
|
|
585
|
+
}
|
|
586
|
+
|
|
505
587
|
.truncate {
|
|
506
588
|
white-space: nowrap;
|
|
507
589
|
overflow: hidden;
|
|
@@ -585,12 +667,19 @@
|
|
|
585
667
|
|
|
586
668
|
.auth-mode-tabs {
|
|
587
669
|
margin: 10px 0;
|
|
670
|
+
position: relative;
|
|
671
|
+
z-index: 2;
|
|
588
672
|
}
|
|
589
673
|
|
|
590
674
|
.auth-mode-tabs .tab {
|
|
591
675
|
min-width: 120px;
|
|
592
676
|
}
|
|
593
677
|
|
|
678
|
+
.auth-mode-panels {
|
|
679
|
+
position: relative;
|
|
680
|
+
z-index: 2;
|
|
681
|
+
}
|
|
682
|
+
|
|
594
683
|
.log {
|
|
595
684
|
white-space: pre-wrap;
|
|
596
685
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
|
@@ -639,6 +728,7 @@
|
|
|
639
728
|
<div class="pill"><span id="statusDot" class="dot"></span><span id="statusText">connecting…</span></div>
|
|
640
729
|
<div class="pill"><span class="mono" id="serverId">serverId: —</span></div>
|
|
641
730
|
<div class="pill"><span class="mono" id="serverVersion">version: —</span></div>
|
|
731
|
+
<div class="pill"><span class="mono" id="clockOverviewText">clock: loading…</span></div>
|
|
642
732
|
<button id="restartRuntimeBtn" class="primary">Restart runtime</button>
|
|
643
733
|
</div>
|
|
644
734
|
</header>
|
|
@@ -702,6 +792,7 @@
|
|
|
702
792
|
<button class="tab" data-tab="credentials">Auth Provider Pool</button>
|
|
703
793
|
<button class="tab" data-tab="quota">Quota Pool</button>
|
|
704
794
|
<button class="tab" data-tab="routing">Runtime Routing Pool</button>
|
|
795
|
+
<button class="tab" data-tab="clock">Clock</button>
|
|
705
796
|
</div>
|
|
706
797
|
|
|
707
798
|
<section id="panelControl" data-panel="control" style="display:none;">
|
|
@@ -794,6 +885,59 @@
|
|
|
794
885
|
</div>
|
|
795
886
|
</section>
|
|
796
887
|
|
|
888
|
+
<section id="panelClock" data-panel="clock" style="display:none;">
|
|
889
|
+
<div class="grid grid-clock">
|
|
890
|
+
<div class="card" style="box-shadow: none;">
|
|
891
|
+
<p class="section-title">Clock tasks (merged)</p>
|
|
892
|
+
<p class="section-sub">Merged by content + recurrence + tool to reduce repeated rows. Expand a row to inspect per-time details.</p>
|
|
893
|
+
<div class="row" style="margin-bottom: 10px;">
|
|
894
|
+
<label for="clockSessionFilterInput">session filter</label>
|
|
895
|
+
<input id="clockSessionFilterInput" type="text" placeholder="conversation session id (optional)" style="min-width: 320px; flex: 1;" />
|
|
896
|
+
<button id="refreshClockBtn" class="primary">Refresh</button>
|
|
897
|
+
<span id="clockHint" class="muted" style="margin-left:auto; font-size:12px;"></span>
|
|
898
|
+
</div>
|
|
899
|
+
<div id="clockSummaryPills" class="row" style="margin-bottom: 10px;"></div>
|
|
900
|
+
|
|
901
|
+
<p class="section-title" style="margin-top: 8px;">Merged list</p>
|
|
902
|
+
<div class="table-wrap">
|
|
903
|
+
<table class="table table-clock-merged">
|
|
904
|
+
<thead>
|
|
905
|
+
<tr>
|
|
906
|
+
<th>status</th>
|
|
907
|
+
<th>task (foldable)</th>
|
|
908
|
+
<th>next due</th>
|
|
909
|
+
<th>time plan</th>
|
|
910
|
+
<th>scope</th>
|
|
911
|
+
</tr>
|
|
912
|
+
</thead>
|
|
913
|
+
<tbody id="clockMergedTbody"></tbody>
|
|
914
|
+
</table>
|
|
915
|
+
</div>
|
|
916
|
+
</div>
|
|
917
|
+
|
|
918
|
+
<div class="card" style="box-shadow: none;">
|
|
919
|
+
<p class="section-title">Clock daemon bindings</p>
|
|
920
|
+
<p class="section-sub">tmux daemon registry and heartbeat state used by clock injection.</p>
|
|
921
|
+
<div class="table-wrap">
|
|
922
|
+
<table class="table">
|
|
923
|
+
<thead>
|
|
924
|
+
<tr>
|
|
925
|
+
<th>daemonId</th>
|
|
926
|
+
<th>tmuxSession</th>
|
|
927
|
+
<th>boundConversations</th>
|
|
928
|
+
<th>heartbeat</th>
|
|
929
|
+
<th>lastInject</th>
|
|
930
|
+
<th>status</th>
|
|
931
|
+
<th>lastError</th>
|
|
932
|
+
</tr>
|
|
933
|
+
</thead>
|
|
934
|
+
<tbody id="clockDaemonsTbody"></tbody>
|
|
935
|
+
</table>
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
</div>
|
|
939
|
+
</section>
|
|
940
|
+
|
|
797
941
|
<section id="panelProviders" data-panel="providers">
|
|
798
942
|
<div class="grid grid-wide-left">
|
|
799
943
|
<div class="card" style="box-shadow: none;">
|
|
@@ -1105,6 +1249,7 @@
|
|
|
1105
1249
|
<th>status</th>
|
|
1106
1250
|
<th>expires</th>
|
|
1107
1251
|
<th>secretRef</th>
|
|
1252
|
+
<th>action</th>
|
|
1108
1253
|
</tr>
|
|
1109
1254
|
</thead>
|
|
1110
1255
|
<tbody id="credentialsTbody"></tbody>
|
|
@@ -1202,7 +1347,7 @@
|
|
|
1202
1347
|
<label for="quotaFilterInput">filter</label>
|
|
1203
1348
|
<input id="quotaFilterInput" type="text" placeholder="providerKey contains…" style="width: 320px;" />
|
|
1204
1349
|
<label><input id="quotaHideOkToggle" type="checkbox" /> hide ok</label>
|
|
1205
|
-
<label><input id="quotaOnlyRoutedTargetsToggle" type="checkbox" checked />
|
|
1350
|
+
<label><input id="quotaOnlyRoutedTargetsToggle" type="checkbox" checked disabled /> routed models only</label>
|
|
1206
1351
|
<button id="quotaSelectVisibleBtn">Select visible</button>
|
|
1207
1352
|
<button id="quotaClearSelectionBtn">Clear selection</button>
|
|
1208
1353
|
<label><input id="quotaAutoRefreshToggle" type="checkbox" checked /> auto refresh</label>
|
|
@@ -1362,6 +1507,9 @@
|
|
|
1362
1507
|
adminAuth: null,
|
|
1363
1508
|
controlSnapshot: null,
|
|
1364
1509
|
controlSnapshotUpdatedAt: 0,
|
|
1510
|
+
clockSnapshot: null,
|
|
1511
|
+
clockSnapshotUpdatedAt: 0,
|
|
1512
|
+
clockExpandedGroups: {},
|
|
1365
1513
|
quotaProviders: [],
|
|
1366
1514
|
quotaProvidersUpdatedAt: 0,
|
|
1367
1515
|
quotaProviderMap: null,
|
|
@@ -1416,10 +1564,19 @@
|
|
|
1416
1564
|
el.style.display = value ? "block" : "none";
|
|
1417
1565
|
const raw = value || "";
|
|
1418
1566
|
const max = 12000;
|
|
1419
|
-
const out = raw.length > max ? raw.slice(0, max) + "\n
|
|
1567
|
+
const out = raw.length > max ? raw.slice(0, max) + "\n...(truncated)" : raw;
|
|
1420
1568
|
el.textContent = out;
|
|
1421
1569
|
}
|
|
1422
1570
|
|
|
1571
|
+
function appendLog(id, line) {
|
|
1572
|
+
const el = $(id);
|
|
1573
|
+
if (!el) return;
|
|
1574
|
+
const prev = textOf(el.textContent || "").trim();
|
|
1575
|
+
const nextLine = textOf(line || "").trim();
|
|
1576
|
+
if (!nextLine) return;
|
|
1577
|
+
setLog(id, prev ? (prev + "\n" + nextLine) : nextLine);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1423
1580
|
function getApiKey() {
|
|
1424
1581
|
try {
|
|
1425
1582
|
return sessionStorage.getItem("routecodex:apikey") || "";
|
|
@@ -1460,7 +1617,7 @@
|
|
|
1460
1617
|
}
|
|
1461
1618
|
|
|
1462
1619
|
function selectTab(name) {
|
|
1463
|
-
document.querySelectorAll(".tab").forEach((btn) => {
|
|
1620
|
+
document.querySelectorAll(".tab[data-tab]").forEach((btn) => {
|
|
1464
1621
|
btn.classList.toggle("active", btn.getAttribute("data-tab") === name);
|
|
1465
1622
|
});
|
|
1466
1623
|
const panels = [
|
|
@@ -1470,7 +1627,8 @@
|
|
|
1470
1627
|
{ name: "tokens", el: $("panelTokens") },
|
|
1471
1628
|
{ name: "credentials", el: $("panelCredentials") },
|
|
1472
1629
|
{ name: "quota", el: $("panelQuota") },
|
|
1473
|
-
{ name: "routing", el: $("panelRouting") }
|
|
1630
|
+
{ name: "routing", el: $("panelRouting") },
|
|
1631
|
+
{ name: "clock", el: $("panelClock") }
|
|
1474
1632
|
];
|
|
1475
1633
|
for (const p of panels) p.el.style.display = p.name === name ? "block" : "none";
|
|
1476
1634
|
|
|
@@ -1480,7 +1638,7 @@
|
|
|
1480
1638
|
}
|
|
1481
1639
|
|
|
1482
1640
|
function getActiveTab() {
|
|
1483
|
-
const active = document.querySelector(".tab.active");
|
|
1641
|
+
const active = document.querySelector(".tab[data-tab].active");
|
|
1484
1642
|
const name = active ? active.getAttribute("data-tab") : null;
|
|
1485
1643
|
return name || "providers";
|
|
1486
1644
|
}
|
|
@@ -1492,7 +1650,8 @@
|
|
|
1492
1650
|
tokens: 0,
|
|
1493
1651
|
credentials: 0,
|
|
1494
1652
|
quota: 0,
|
|
1495
|
-
routing: 0
|
|
1653
|
+
routing: 0,
|
|
1654
|
+
clock: 0
|
|
1496
1655
|
};
|
|
1497
1656
|
|
|
1498
1657
|
async function maybeRefreshTab(name) {
|
|
@@ -1516,6 +1675,9 @@
|
|
|
1516
1675
|
await refreshRuntimes();
|
|
1517
1676
|
await refreshRoutingSources();
|
|
1518
1677
|
}
|
|
1678
|
+
else if (key === "clock") {
|
|
1679
|
+
await refreshClockTab();
|
|
1680
|
+
}
|
|
1519
1681
|
} catch {
|
|
1520
1682
|
// ignore refresh failures on tab switch
|
|
1521
1683
|
}
|
|
@@ -2100,6 +2262,486 @@
|
|
|
2100
2262
|
};
|
|
2101
2263
|
}
|
|
2102
2264
|
|
|
2265
|
+
async function refreshClockOverview() {
|
|
2266
|
+
const overviewEl = $("clockOverviewText");
|
|
2267
|
+
if (!overviewEl) return;
|
|
2268
|
+
try {
|
|
2269
|
+
const res = await fetch("/daemon/clock/tasks");
|
|
2270
|
+
if (!res || !res.ok) throw new Error(res ? String(res.status) : "request_failed");
|
|
2271
|
+
const data = await res.json().catch(() => ({}));
|
|
2272
|
+
const sessions = data && Array.isArray(data.sessions) ? data.sessions : [];
|
|
2273
|
+
const records = data && Array.isArray(data.records) ? data.records : [];
|
|
2274
|
+
const now = Date.now();
|
|
2275
|
+
let taskCount = 0;
|
|
2276
|
+
let dueCount = 0;
|
|
2277
|
+
for (const session of sessions) {
|
|
2278
|
+
const tasks = session && Array.isArray(session.tasks) ? session.tasks : [];
|
|
2279
|
+
taskCount += tasks.length;
|
|
2280
|
+
for (const task of tasks) {
|
|
2281
|
+
let dueAtMs = Number(task && task.dueAtMs);
|
|
2282
|
+
if (!Number.isFinite(dueAtMs) && task && typeof task.dueAt === "string") {
|
|
2283
|
+
const parsed = Date.parse(task.dueAt);
|
|
2284
|
+
dueAtMs = Number.isFinite(parsed) ? parsed : NaN;
|
|
2285
|
+
}
|
|
2286
|
+
if (Number.isFinite(dueAtMs) && dueAtMs <= now) {
|
|
2287
|
+
dueCount += 1;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
const onlineCount = records.filter((record) => {
|
|
2292
|
+
const hb = Number(record && record.lastHeartbeatAtMs);
|
|
2293
|
+
return Number.isFinite(hb) && now - hb <= 45_000;
|
|
2294
|
+
}).length;
|
|
2295
|
+
overviewEl.textContent = "clock: tasks " + taskCount + " · due " + dueCount + " · daemon " + onlineCount + "/" + records.length;
|
|
2296
|
+
} catch {
|
|
2297
|
+
overviewEl.textContent = "clock: unavailable";
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
function clockParseDueAtMs(task) {
|
|
2302
|
+
const rawMs = Number(task && task.dueAtMs);
|
|
2303
|
+
if (Number.isFinite(rawMs)) return Math.floor(rawMs);
|
|
2304
|
+
const rawIso = task && typeof task.dueAt === "string" ? task.dueAt.trim() : "";
|
|
2305
|
+
if (!rawIso) return NaN;
|
|
2306
|
+
const parsed = Date.parse(rawIso);
|
|
2307
|
+
return Number.isFinite(parsed) ? Math.floor(parsed) : NaN;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
function clockTaskStatusKind(task, now) {
|
|
2311
|
+
const dueAtMs = clockParseDueAtMs(task);
|
|
2312
|
+
if (!Number.isFinite(dueAtMs)) return "warn";
|
|
2313
|
+
const delta = dueAtMs - now;
|
|
2314
|
+
if (delta < -60_000) return "bad";
|
|
2315
|
+
if (delta <= 0) return "warn";
|
|
2316
|
+
return "ok";
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
function clockTaskStatusText(task, now) {
|
|
2320
|
+
const dueAtMs = clockParseDueAtMs(task);
|
|
2321
|
+
if (!Number.isFinite(dueAtMs)) return "invalid";
|
|
2322
|
+
const delta = dueAtMs - now;
|
|
2323
|
+
if (delta < -60_000) return "overdue";
|
|
2324
|
+
if (delta <= 0) return "due";
|
|
2325
|
+
if (delta <= 60_000) return "soon";
|
|
2326
|
+
return "scheduled";
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
function clockRecurrenceText(task) {
|
|
2330
|
+
const rec = task && task.recurrence && typeof task.recurrence === "object" ? task.recurrence : null;
|
|
2331
|
+
if (!rec) return "one-shot";
|
|
2332
|
+
const kind = textOf(rec.kind || "").trim() || "recurring";
|
|
2333
|
+
const maxRuns = Number(rec.maxRuns);
|
|
2334
|
+
const count = Number(task && task.deliveryCount);
|
|
2335
|
+
let base = kind;
|
|
2336
|
+
if (kind === "interval") {
|
|
2337
|
+
const everyMinutes = Number(rec.everyMinutes);
|
|
2338
|
+
if (Number.isFinite(everyMinutes) && everyMinutes > 0) {
|
|
2339
|
+
base += "/" + Math.floor(everyMinutes) + "m";
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
if (Number.isFinite(maxRuns) && maxRuns > 0) {
|
|
2343
|
+
const safeCount = Number.isFinite(count) && count > 0 ? Math.floor(count) : 0;
|
|
2344
|
+
base += " " + safeCount + "/" + Math.floor(maxRuns);
|
|
2345
|
+
}
|
|
2346
|
+
return base;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
function clockNormalizeContent(value) {
|
|
2350
|
+
return textOf(value).trim().replace(/\s+/g, " ");
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
function clockShortLabel(value, head = 8, tail = 4) {
|
|
2354
|
+
const raw = textOf(value).trim();
|
|
2355
|
+
if (!raw) return "—";
|
|
2356
|
+
const maxLen = Math.max(6, head + tail + 1);
|
|
2357
|
+
if (raw.length <= maxLen) return raw;
|
|
2358
|
+
return raw.slice(0, head) + "…" + raw.slice(-tail);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
function clockSessionPreview(sessionIds, limit = 3) {
|
|
2362
|
+
const ids = Array.isArray(sessionIds) ? sessionIds.filter(Boolean) : [];
|
|
2363
|
+
if (!ids.length) return "—";
|
|
2364
|
+
const shown = ids.slice(0, limit).map((id) => clockShortLabel(id, 6, 3));
|
|
2365
|
+
if (ids.length > limit) {
|
|
2366
|
+
shown.push("+" + String(ids.length - limit));
|
|
2367
|
+
}
|
|
2368
|
+
return shown.join(", ");
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function clockFormatCompactTs(ms) {
|
|
2372
|
+
if (!Number.isFinite(ms) || ms <= 0) return "—";
|
|
2373
|
+
const d = new Date(ms);
|
|
2374
|
+
if (Number.isNaN(d.getTime())) return "—";
|
|
2375
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
2376
|
+
return pad(d.getMonth() + 1) + "-" + pad(d.getDate()) + " " + pad(d.getHours()) + ":" + pad(d.getMinutes());
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function clockReadableTmuxSessionId(value) {
|
|
2380
|
+
const raw = textOf(value).trim();
|
|
2381
|
+
if (!raw) return "—";
|
|
2382
|
+
const managed = /^rcc_([a-zA-Z0-9_-]+)_(\d{13})_([a-f0-9]{4,})$/i.exec(raw);
|
|
2383
|
+
if (!managed) return clockShortLabel(raw, 10, 6);
|
|
2384
|
+
const role = textOf(managed[1]).replace(/_/g, "-");
|
|
2385
|
+
const startedAt = Number(managed[2]);
|
|
2386
|
+
const startedText = clockFormatCompactTs(startedAt);
|
|
2387
|
+
const suffix = textOf(managed[3]).slice(0, 4);
|
|
2388
|
+
return role + "@" + startedText + "#" + suffix;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
function clockTmuxPreview(values, limit = 2) {
|
|
2392
|
+
const list = Array.isArray(values) ? values.filter((v) => textOf(v).trim() && textOf(v).trim() !== "—") : [];
|
|
2393
|
+
if (!list.length) return "—";
|
|
2394
|
+
const uniq = Array.from(new Set(list));
|
|
2395
|
+
const shown = uniq.slice(0, limit);
|
|
2396
|
+
if (uniq.length > limit) {
|
|
2397
|
+
shown.push("+" + String(uniq.length - limit));
|
|
2398
|
+
}
|
|
2399
|
+
return shown.join(", ");
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
function clockRecurrenceShort(task) {
|
|
2403
|
+
const rec = task && task.recurrence && typeof task.recurrence === "object" ? task.recurrence : null;
|
|
2404
|
+
if (!rec) return "1x";
|
|
2405
|
+
const kind = textOf(rec.kind || "").trim().toLowerCase();
|
|
2406
|
+
const maxRuns = Number(rec.maxRuns);
|
|
2407
|
+
let prefix = "R";
|
|
2408
|
+
if (kind === "daily") prefix = "D";
|
|
2409
|
+
else if (kind === "weekly") prefix = "W";
|
|
2410
|
+
else if (kind === "interval") {
|
|
2411
|
+
const everyMinutes = Number(rec.everyMinutes);
|
|
2412
|
+
prefix = Number.isFinite(everyMinutes) && everyMinutes > 0 ? "I" + String(Math.floor(everyMinutes)) + "m" : "I";
|
|
2413
|
+
}
|
|
2414
|
+
if (Number.isFinite(maxRuns) && maxRuns > 0) {
|
|
2415
|
+
return prefix + "/" + String(Math.floor(maxRuns));
|
|
2416
|
+
}
|
|
2417
|
+
return prefix;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
function clockBuildGroupKey(task) {
|
|
2421
|
+
const content = clockNormalizeContent(task && task.task);
|
|
2422
|
+
const tool = textOf(task && task.tool).trim();
|
|
2423
|
+
return [content || "(empty)", tool || "tool:none"].join("||");
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
function renderClockTabFromSnapshot() {
|
|
2427
|
+
const snap = UI.clockSnapshot && typeof UI.clockSnapshot === "object" ? UI.clockSnapshot : {};
|
|
2428
|
+
const sessions = Array.isArray(snap.sessions) ? snap.sessions : [];
|
|
2429
|
+
const records = Array.isArray(snap.records) ? snap.records : [];
|
|
2430
|
+
|
|
2431
|
+
const mergedBody = $("clockMergedTbody");
|
|
2432
|
+
const daemonBody = $("clockDaemonsTbody");
|
|
2433
|
+
const summaryBox = $("clockSummaryPills");
|
|
2434
|
+
const hint = $("clockHint");
|
|
2435
|
+
|
|
2436
|
+
if (mergedBody) mergedBody.replaceChildren();
|
|
2437
|
+
if (daemonBody) daemonBody.replaceChildren();
|
|
2438
|
+
if (summaryBox) summaryBox.replaceChildren();
|
|
2439
|
+
|
|
2440
|
+
const now = Date.now();
|
|
2441
|
+
const groups = new Map();
|
|
2442
|
+
const rows = [];
|
|
2443
|
+
const conversationToTmuxSession = new Map();
|
|
2444
|
+
for (const record of records) {
|
|
2445
|
+
const tmuxSession = textOf((record && record.tmuxSessionId) || (record && record.sessionId)).trim();
|
|
2446
|
+
if (!tmuxSession) continue;
|
|
2447
|
+
const conversationIds = Array.isArray(record && record.conversationSessionIds) ? record.conversationSessionIds : [];
|
|
2448
|
+
for (const cid of conversationIds) {
|
|
2449
|
+
const key = textOf(cid).trim();
|
|
2450
|
+
if (!key || conversationToTmuxSession.has(key)) continue;
|
|
2451
|
+
conversationToTmuxSession.set(key, tmuxSession);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
for (const session of sessions) {
|
|
2455
|
+
const sessionId = textOf(session && session.sessionId).trim();
|
|
2456
|
+
const mappedTmuxSessionId = textOf(conversationToTmuxSession.get(sessionId)).trim();
|
|
2457
|
+
const mappedTmuxShort = clockReadableTmuxSessionId(mappedTmuxSessionId);
|
|
2458
|
+
const tasks = session && Array.isArray(session.tasks) ? session.tasks : [];
|
|
2459
|
+
for (const task of tasks) {
|
|
2460
|
+
const key = clockBuildGroupKey(task);
|
|
2461
|
+
const dueMs = clockParseDueAtMs(task);
|
|
2462
|
+
const status = clockTaskStatusText(task, now);
|
|
2463
|
+
const statusKind = clockTaskStatusKind(task, now);
|
|
2464
|
+
const taskId = textOf(task && task.taskId).trim();
|
|
2465
|
+
const recurrence = clockRecurrenceText(task);
|
|
2466
|
+
const tool = textOf(task && task.tool).trim() || "—";
|
|
2467
|
+
const deliveryCount = Number(task && task.deliveryCount);
|
|
2468
|
+
const row = {
|
|
2469
|
+
groupKey: key,
|
|
2470
|
+
sessionId,
|
|
2471
|
+
sessionShort: clockShortLabel(sessionId, 6, 3),
|
|
2472
|
+
tmuxSessionId: mappedTmuxSessionId,
|
|
2473
|
+
tmuxShort: mappedTmuxShort,
|
|
2474
|
+
taskId,
|
|
2475
|
+
taskShort: clockShortLabel(taskId, 6, 4),
|
|
2476
|
+
content: textOf(task && task.task).trim(),
|
|
2477
|
+
dueMs,
|
|
2478
|
+
dueAt: Number.isFinite(dueMs) ? formatEpochWithDelta(dueMs) : "—",
|
|
2479
|
+
dueCompact: Number.isFinite(dueMs) ? formatEpochMs(dueMs) : "—",
|
|
2480
|
+
status,
|
|
2481
|
+
statusKind,
|
|
2482
|
+
recurrence,
|
|
2483
|
+
recurrenceShort: clockRecurrenceShort(task),
|
|
2484
|
+
deliveryCount,
|
|
2485
|
+
tool,
|
|
2486
|
+
toolShort: clockShortLabel(tool, 8, 3)
|
|
2487
|
+
};
|
|
2488
|
+
row.metaShort = row.toolShort + " · " + row.recurrenceShort + (Number.isFinite(row.deliveryCount) ? " · #" + String(Math.max(0, Math.floor(row.deliveryCount))) : "");
|
|
2489
|
+
rows.push(row);
|
|
2490
|
+
|
|
2491
|
+
if (!groups.has(key)) {
|
|
2492
|
+
groups.set(key, {
|
|
2493
|
+
groupKey: key,
|
|
2494
|
+
content: row.content || "(empty)",
|
|
2495
|
+
sessionIds: new Set(),
|
|
2496
|
+
tmuxDisplays: new Set(),
|
|
2497
|
+
taskCount: 0,
|
|
2498
|
+
dueMin: Number.isFinite(dueMs) ? dueMs : NaN,
|
|
2499
|
+
dueMax: Number.isFinite(dueMs) ? dueMs : NaN,
|
|
2500
|
+
dueCount: 0,
|
|
2501
|
+
overdueCount: 0,
|
|
2502
|
+
statusKind: row.statusKind,
|
|
2503
|
+
toolSet: new Set(),
|
|
2504
|
+
recurrenceSet: new Set(),
|
|
2505
|
+
rows: []
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
const group = groups.get(key);
|
|
2509
|
+
group.sessionIds.add(sessionId || "—");
|
|
2510
|
+
if (row.tmuxShort && row.tmuxShort !== "—") {
|
|
2511
|
+
group.tmuxDisplays.add(row.tmuxShort);
|
|
2512
|
+
}
|
|
2513
|
+
group.toolSet.add(row.toolShort);
|
|
2514
|
+
group.recurrenceSet.add(row.recurrenceShort);
|
|
2515
|
+
group.taskCount += 1;
|
|
2516
|
+
group.rows.push(row);
|
|
2517
|
+
if (row.status === "due") group.dueCount += 1;
|
|
2518
|
+
if (row.status === "overdue") group.overdueCount += 1;
|
|
2519
|
+
if (Number.isFinite(dueMs)) {
|
|
2520
|
+
if (!Number.isFinite(group.dueMin) || dueMs < group.dueMin) group.dueMin = dueMs;
|
|
2521
|
+
if (!Number.isFinite(group.dueMax) || dueMs > group.dueMax) group.dueMax = dueMs;
|
|
2522
|
+
}
|
|
2523
|
+
if (row.statusKind === "bad") group.statusKind = "bad";
|
|
2524
|
+
else if (row.statusKind === "warn" && group.statusKind !== "bad") group.statusKind = "warn";
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
const merged = Array.from(groups.values());
|
|
2529
|
+
merged.sort((a, b) => {
|
|
2530
|
+
const aDue = Number.isFinite(a.dueMin) ? a.dueMin : Number.POSITIVE_INFINITY;
|
|
2531
|
+
const bDue = Number.isFinite(b.dueMin) ? b.dueMin : Number.POSITIVE_INFINITY;
|
|
2532
|
+
if (aDue !== bDue) return aDue - bDue;
|
|
2533
|
+
return b.taskCount - a.taskCount;
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2536
|
+
rows.sort((a, b) => {
|
|
2537
|
+
const aDue = Number.isFinite(a.dueMs) ? a.dueMs : Number.POSITIVE_INFINITY;
|
|
2538
|
+
const bDue = Number.isFinite(b.dueMs) ? b.dueMs : Number.POSITIVE_INFINITY;
|
|
2539
|
+
if (aDue !== bDue) return aDue - bDue;
|
|
2540
|
+
return a.sessionId.localeCompare(b.sessionId);
|
|
2541
|
+
});
|
|
2542
|
+
|
|
2543
|
+
if (!UI.clockExpandedGroups || typeof UI.clockExpandedGroups !== "object") {
|
|
2544
|
+
UI.clockExpandedGroups = {};
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
if (summaryBox) {
|
|
2548
|
+
const dueNow = rows.filter((r) => r.status === "due").length;
|
|
2549
|
+
const overdue = rows.filter((r) => r.status === "overdue").length;
|
|
2550
|
+
const recurring = rows.filter((r) => r.recurrence !== "one-shot").length;
|
|
2551
|
+
const onlineDaemon = records.filter((record) => {
|
|
2552
|
+
const hb = Number(record && record.lastHeartbeatAtMs);
|
|
2553
|
+
return Number.isFinite(hb) && now - hb <= 45_000;
|
|
2554
|
+
}).length;
|
|
2555
|
+
const pills = [
|
|
2556
|
+
["sessions", sessions.length, ""],
|
|
2557
|
+
["tasks", rows.length, ""],
|
|
2558
|
+
["merged", merged.length, ""],
|
|
2559
|
+
["due", dueNow, dueNow ? "warn" : "ok"],
|
|
2560
|
+
["overdue", overdue, overdue ? "bad" : "ok"],
|
|
2561
|
+
["recurring", recurring, ""],
|
|
2562
|
+
["daemon", onlineDaemon + "/" + records.length, onlineDaemon ? "ok" : "warn"]
|
|
2563
|
+
];
|
|
2564
|
+
for (const [label, value, kind] of pills) {
|
|
2565
|
+
summaryBox.appendChild(pill(label + ": " + value, kind));
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
if (mergedBody) {
|
|
2570
|
+
if (!merged.length) {
|
|
2571
|
+
mergedBody.appendChild(createInfoRow(5, "No clock tasks."));
|
|
2572
|
+
} else {
|
|
2573
|
+
for (const item of merged) {
|
|
2574
|
+
const isExpanded = !!UI.clockExpandedGroups[item.groupKey];
|
|
2575
|
+
const tr = document.createElement("tr");
|
|
2576
|
+
tr.className = "group-row";
|
|
2577
|
+
const statusText = item.overdueCount > 0
|
|
2578
|
+
? "overdue " + item.overdueCount
|
|
2579
|
+
: item.dueCount > 0
|
|
2580
|
+
? "due " + item.dueCount
|
|
2581
|
+
: "scheduled";
|
|
2582
|
+
const statusTd = document.createElement("td");
|
|
2583
|
+
statusTd.appendChild(pill(statusText, item.statusKind));
|
|
2584
|
+
tr.appendChild(statusTd);
|
|
2585
|
+
|
|
2586
|
+
const taskTd = document.createElement("td");
|
|
2587
|
+
const toggleBtn = document.createElement("button");
|
|
2588
|
+
toggleBtn.type = "button";
|
|
2589
|
+
toggleBtn.className = "clock-group-toggle mono";
|
|
2590
|
+
toggleBtn.textContent = (isExpanded ? "▾ " : "▸ ") + (item.content || "(empty)");
|
|
2591
|
+
toggleBtn.title = isExpanded ? "collapse" : "expand";
|
|
2592
|
+
toggleBtn.addEventListener("click", () => {
|
|
2593
|
+
UI.clockExpandedGroups[item.groupKey] = !UI.clockExpandedGroups[item.groupKey];
|
|
2594
|
+
renderClockTabFromSnapshot();
|
|
2595
|
+
});
|
|
2596
|
+
taskTd.appendChild(toggleBtn);
|
|
2597
|
+
const groupSummary = document.createElement("div");
|
|
2598
|
+
groupSummary.className = "clock-group-summary mono";
|
|
2599
|
+
const toolPart = Array.from(item.toolSet).slice(0, 2).join(", ") || "—";
|
|
2600
|
+
const recPart = Array.from(item.recurrenceSet).slice(0, 2).join(", ") || "1x";
|
|
2601
|
+
groupSummary.textContent = "tool " + toolPart + " · rec " + recPart;
|
|
2602
|
+
groupSummary.title = groupSummary.textContent;
|
|
2603
|
+
taskTd.appendChild(groupSummary);
|
|
2604
|
+
tr.appendChild(taskTd);
|
|
2605
|
+
|
|
2606
|
+
const nextDueText = Number.isFinite(item.dueMin) ? formatEpochWithDelta(item.dueMin) : "—";
|
|
2607
|
+
tr.appendChild(createCell("td", nextDueText, "mono truncate", { title: true }));
|
|
2608
|
+
|
|
2609
|
+
const sortedDueRows = item.rows
|
|
2610
|
+
.filter((r) => Number.isFinite(r.dueMs))
|
|
2611
|
+
.sort((a, b) => a.dueMs - b.dueMs);
|
|
2612
|
+
let planText = "—";
|
|
2613
|
+
if (sortedDueRows.length) {
|
|
2614
|
+
planText = sortedDueRows.slice(0, 3).map((r) => r.dueCompact).join(" | ");
|
|
2615
|
+
if (sortedDueRows.length > 3) {
|
|
2616
|
+
planText += " +" + String(sortedDueRows.length - 3);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
tr.appendChild(createCell("td", planText, "mono truncate", { title: true }));
|
|
2620
|
+
|
|
2621
|
+
const sessionList = Array.from(item.sessionIds);
|
|
2622
|
+
const tmuxPreview = clockTmuxPreview(Array.from(item.tmuxDisplays));
|
|
2623
|
+
const scopeText = String(item.taskCount) + " task" + (item.taskCount > 1 ? "s" : "") + " · " + String(sessionList.length) + " session" + (sessionList.length > 1 ? "s" : "") + " · tmux " + tmuxPreview;
|
|
2624
|
+
tr.appendChild(createCell("td", scopeText, "mono truncate", { title: true }));
|
|
2625
|
+
mergedBody.appendChild(tr);
|
|
2626
|
+
|
|
2627
|
+
const detailTr = document.createElement("tr");
|
|
2628
|
+
detailTr.className = "clock-detail-row";
|
|
2629
|
+
if (!isExpanded) detailTr.style.display = "none";
|
|
2630
|
+
const detailTd = document.createElement("td");
|
|
2631
|
+
detailTd.colSpan = 5;
|
|
2632
|
+
if (isExpanded) {
|
|
2633
|
+
const detailList = document.createElement("div");
|
|
2634
|
+
detailList.className = "clock-detail-list";
|
|
2635
|
+
const detailRows = item.rows.slice().sort((a, b) => {
|
|
2636
|
+
const aDue = Number.isFinite(a.dueMs) ? a.dueMs : Number.POSITIVE_INFINITY;
|
|
2637
|
+
const bDue = Number.isFinite(b.dueMs) ? b.dueMs : Number.POSITIVE_INFINITY;
|
|
2638
|
+
if (aDue !== bDue) return aDue - bDue;
|
|
2639
|
+
return a.sessionId.localeCompare(b.sessionId);
|
|
2640
|
+
});
|
|
2641
|
+
for (const detail of detailRows.slice(0, 40)) {
|
|
2642
|
+
const line = document.createElement("div");
|
|
2643
|
+
line.className = "clock-detail-item mono";
|
|
2644
|
+
|
|
2645
|
+
const timeSpan = document.createElement("span");
|
|
2646
|
+
timeSpan.textContent = detail.dueCompact;
|
|
2647
|
+
timeSpan.title = detail.dueAt;
|
|
2648
|
+
line.appendChild(timeSpan);
|
|
2649
|
+
|
|
2650
|
+
line.appendChild(pill(detail.status, detail.statusKind));
|
|
2651
|
+
|
|
2652
|
+
const idSpan = document.createElement("span");
|
|
2653
|
+
const tmuxPrefix = detail.tmuxShort && detail.tmuxShort !== "—" ? "tmux " + detail.tmuxShort + " · " : "";
|
|
2654
|
+
idSpan.textContent = tmuxPrefix + "sid " + detail.sessionShort + " · id " + detail.taskShort + " · " + detail.metaShort;
|
|
2655
|
+
idSpan.title = "tmux " + (detail.tmuxSessionId || "—") + " | session " + detail.sessionId + " | task " + detail.taskId + " | " + detail.tool + " | " + detail.recurrence;
|
|
2656
|
+
line.appendChild(idSpan);
|
|
2657
|
+
|
|
2658
|
+
const contentSpan = document.createElement("span");
|
|
2659
|
+
contentSpan.className = "clock-detail-content";
|
|
2660
|
+
contentSpan.textContent = detail.content || "(empty)";
|
|
2661
|
+
contentSpan.title = detail.content || "(empty)";
|
|
2662
|
+
line.appendChild(contentSpan);
|
|
2663
|
+
|
|
2664
|
+
detailList.appendChild(line);
|
|
2665
|
+
}
|
|
2666
|
+
if (detailRows.length > 40) {
|
|
2667
|
+
const more = document.createElement("div");
|
|
2668
|
+
more.className = "mono muted";
|
|
2669
|
+
more.textContent = "… " + String(detailRows.length - 40) + " more";
|
|
2670
|
+
detailList.appendChild(more);
|
|
2671
|
+
}
|
|
2672
|
+
detailTd.appendChild(detailList);
|
|
2673
|
+
}
|
|
2674
|
+
detailTr.appendChild(detailTd);
|
|
2675
|
+
mergedBody.appendChild(detailTr);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
if (daemonBody) {
|
|
2681
|
+
if (!records.length) {
|
|
2682
|
+
daemonBody.appendChild(createInfoRow(7, "No clock daemon bindings."));
|
|
2683
|
+
} else {
|
|
2684
|
+
for (const rec of records) {
|
|
2685
|
+
const tr = document.createElement("tr");
|
|
2686
|
+
const daemonId = textOf(rec && rec.daemonId).trim() || "—";
|
|
2687
|
+
const tmuxSession = textOf((rec && rec.tmuxSessionId) || (rec && rec.sessionId)).trim() || "—";
|
|
2688
|
+
const conversations = Array.isArray(rec && rec.conversationSessionIds) ? rec.conversationSessionIds : [];
|
|
2689
|
+
const hb = Number(rec && rec.lastHeartbeatAtMs);
|
|
2690
|
+
const inject = Number(rec && rec.lastInjectAtMs);
|
|
2691
|
+
const errText = textOf(rec && rec.lastError).trim();
|
|
2692
|
+
const isOnline = Number.isFinite(hb) && now - hb <= 45_000;
|
|
2693
|
+
const statusKind = errText ? "bad" : isOnline ? "ok" : "warn";
|
|
2694
|
+
const statusText = errText ? "error" : isOnline ? "online" : "stale";
|
|
2695
|
+
|
|
2696
|
+
tr.appendChild(createCell("td", daemonId, "mono truncate", { title: true }));
|
|
2697
|
+
const tmuxReadable = clockReadableTmuxSessionId(tmuxSession);
|
|
2698
|
+
const tmuxCell = createCell("td", tmuxReadable, "mono truncate", { title: true });
|
|
2699
|
+
tmuxCell.title = tmuxSession || tmuxReadable;
|
|
2700
|
+
tr.appendChild(tmuxCell);
|
|
2701
|
+
tr.appendChild(createCell("td", String(conversations.length), "mono"));
|
|
2702
|
+
tr.appendChild(createCell("td", Number.isFinite(hb) ? formatEpochWithDelta(hb) : "—", "mono truncate", { title: true }));
|
|
2703
|
+
tr.appendChild(createCell("td", Number.isFinite(inject) ? formatEpochWithDelta(inject) : "—", "mono truncate", { title: true }));
|
|
2704
|
+
const statusTd = document.createElement("td");
|
|
2705
|
+
statusTd.appendChild(pill(statusText, statusKind));
|
|
2706
|
+
tr.appendChild(statusTd);
|
|
2707
|
+
tr.appendChild(createCell("td", errText || "—", "mono truncate", { title: true }));
|
|
2708
|
+
daemonBody.appendChild(tr);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
if (hint) {
|
|
2714
|
+
hint.textContent = "groups " + merged.length + " · tasks " + rows.length + " · updated: " + formatTs(UI.clockSnapshotUpdatedAt);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
async function refreshClockTab() {
|
|
2719
|
+
try {
|
|
2720
|
+
const filter = textOf($("clockSessionFilterInput") && $("clockSessionFilterInput").value).trim();
|
|
2721
|
+
const path = filter
|
|
2722
|
+
? "/daemon/clock/tasks?sessionId=" + encodeURIComponent(filter)
|
|
2723
|
+
: "/daemon/clock/tasks";
|
|
2724
|
+
const data = await apiFetch(path);
|
|
2725
|
+
UI.clockSnapshot = data && typeof data === "object" ? data : {};
|
|
2726
|
+
UI.clockSnapshotUpdatedAt = Date.now();
|
|
2727
|
+
renderClockTabFromSnapshot();
|
|
2728
|
+
} catch (e) {
|
|
2729
|
+
const mergedBody = $("clockMergedTbody");
|
|
2730
|
+
const daemonBody = $("clockDaemonsTbody");
|
|
2731
|
+
if (mergedBody) {
|
|
2732
|
+
mergedBody.replaceChildren();
|
|
2733
|
+
mergedBody.appendChild(createErrorRow(5, e && e.message ? e.message : String(e)));
|
|
2734
|
+
}
|
|
2735
|
+
if (daemonBody) {
|
|
2736
|
+
daemonBody.replaceChildren();
|
|
2737
|
+
daemonBody.appendChild(createInfoRow(7, "No daemon bindings."));
|
|
2738
|
+
}
|
|
2739
|
+
const hint = $("clockHint");
|
|
2740
|
+
if (hint) hint.textContent = "clock snapshot failed";
|
|
2741
|
+
if (e && e.status === 401) notifyUnauthorizedOnce("clock");
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2103
2745
|
async function refreshStatus() {
|
|
2104
2746
|
// /health is public; use it to always show version even before admin login.
|
|
2105
2747
|
try {
|
|
@@ -2121,6 +2763,7 @@
|
|
|
2121
2763
|
$("statusDot").classList.add("err");
|
|
2122
2764
|
$("serverId").textContent = "serverId: —";
|
|
2123
2765
|
}
|
|
2766
|
+
await refreshClockOverview();
|
|
2124
2767
|
}
|
|
2125
2768
|
|
|
2126
2769
|
function formatTs(ms) {
|
|
@@ -2672,32 +3315,59 @@
|
|
|
2672
3315
|
tr.appendChild(createCell("td", c.provider || "", "mono"));
|
|
2673
3316
|
tr.appendChild(createCell("td", c.alias || "", "mono"));
|
|
2674
3317
|
const statusTd = document.createElement("td");
|
|
2675
|
-
|
|
3318
|
+
const statusRaw = textOf(c.status || "").trim().toLowerCase();
|
|
3319
|
+
statusTd.appendChild(pill(c.status || "—", statusRaw === "valid" ? "ok" : "warn"));
|
|
2676
3320
|
const issue = findAuthIssueForProviderAlias(c.provider, c.alias);
|
|
2677
3321
|
if (issue) {
|
|
2678
3322
|
statusTd.appendChild(document.createTextNode(" "));
|
|
2679
3323
|
statusTd.appendChild(pill("verify required", "bad"));
|
|
2680
|
-
if (issue.url) {
|
|
2681
|
-
statusTd.appendChild(document.createTextNode(" "));
|
|
2682
|
-
const a = document.createElement("a");
|
|
2683
|
-
a.href = "#";
|
|
2684
|
-
a.textContent = "open";
|
|
2685
|
-
a.className = "mono";
|
|
2686
|
-
a.addEventListener("click", (ev) => {
|
|
2687
|
-
ev.preventDefault();
|
|
2688
|
-
void openAuthIssueInCamoufox(c.provider, c.alias, issue.url);
|
|
2689
|
-
});
|
|
2690
|
-
statusTd.appendChild(a);
|
|
2691
|
-
}
|
|
2692
3324
|
}
|
|
2693
3325
|
tr.appendChild(statusTd);
|
|
2694
3326
|
tr.appendChild(createCell("td", exp, "mono"));
|
|
2695
3327
|
tr.appendChild(createCell("td", c.secretRef || "—", "mono"));
|
|
3328
|
+
|
|
3329
|
+
const actionTd = document.createElement("td");
|
|
3330
|
+
actionTd.className = "actions-cell";
|
|
3331
|
+
const actions = document.createElement("div");
|
|
3332
|
+
actions.className = "auth-actions";
|
|
3333
|
+
let hasAction = false;
|
|
3334
|
+
|
|
3335
|
+
if (issue && issue.url) {
|
|
3336
|
+
const openVerify = document.createElement("button");
|
|
3337
|
+
openVerify.type = "button";
|
|
3338
|
+
openVerify.className = "auth-action-btn";
|
|
3339
|
+
openVerify.textContent = "Open Verify";
|
|
3340
|
+
openVerify.addEventListener("click", () => {
|
|
3341
|
+
void openAuthIssueInCamoufox(c.provider, c.alias, issue.url);
|
|
3342
|
+
});
|
|
3343
|
+
actions.appendChild(openVerify);
|
|
3344
|
+
hasAction = true;
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
if (textOf(c.kind || "").trim().toLowerCase() === "oauth" && (statusRaw === "expired" || statusRaw === "invalid")) {
|
|
3348
|
+
const recover = document.createElement("button");
|
|
3349
|
+
recover.type = "button";
|
|
3350
|
+
recover.className = "auth-action-btn primary";
|
|
3351
|
+
recover.textContent = "Recover";
|
|
3352
|
+
recover.addEventListener("click", () => {
|
|
3353
|
+
void runCredentialAutoRecovery(c);
|
|
3354
|
+
});
|
|
3355
|
+
actions.appendChild(recover);
|
|
3356
|
+
hasAction = true;
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
if (hasAction) {
|
|
3360
|
+
actionTd.appendChild(actions);
|
|
3361
|
+
} else {
|
|
3362
|
+
actionTd.classList.add("mono");
|
|
3363
|
+
actionTd.textContent = "—";
|
|
3364
|
+
}
|
|
3365
|
+
tr.appendChild(actionTd);
|
|
2696
3366
|
body.appendChild(tr);
|
|
2697
3367
|
}
|
|
2698
3368
|
updateOauthAuthIssueHint();
|
|
2699
3369
|
} catch (e) {
|
|
2700
|
-
body.appendChild(createErrorRow(
|
|
3370
|
+
body.appendChild(createErrorRow(7, e && e.message ? e.message : e));
|
|
2701
3371
|
updateOauthAuthIssueHint();
|
|
2702
3372
|
}
|
|
2703
3373
|
}
|
|
@@ -2727,6 +3397,7 @@
|
|
|
2727
3397
|
async function authorizeOauth(mode = "manual") {
|
|
2728
3398
|
const safeMode = mode === "auto" ? "auto" : "manual";
|
|
2729
3399
|
setLog("credentialOpLog", "");
|
|
3400
|
+
appendLog("credentialOpLog", `[${safeMode}] start authorize request ...`);
|
|
2730
3401
|
const provider = $("oauthProviderSelect").value;
|
|
2731
3402
|
const alias = ($("oauthAuthAliasInput").value || "default").trim() || "default";
|
|
2732
3403
|
updateOauthAuthIssueHint();
|
|
@@ -2762,6 +3433,167 @@
|
|
|
2762
3433
|
}
|
|
2763
3434
|
}
|
|
2764
3435
|
|
|
3436
|
+
function setOauthContext(providerRaw, aliasRaw) {
|
|
3437
|
+
const provider = textOf(providerRaw || "").trim().toLowerCase();
|
|
3438
|
+
const alias = textOf(aliasRaw || "default").trim() || "default";
|
|
3439
|
+
const providerSelect = $("oauthProviderSelect");
|
|
3440
|
+
if (providerSelect) {
|
|
3441
|
+
const optionExists = Array.from(providerSelect.options || []).some((opt) => textOf(opt.value).trim().toLowerCase() === provider);
|
|
3442
|
+
if (optionExists) {
|
|
3443
|
+
providerSelect.value = provider;
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
const aliasInput = $("oauthAuthAliasInput");
|
|
3447
|
+
if (aliasInput) {
|
|
3448
|
+
aliasInput.value = alias;
|
|
3449
|
+
}
|
|
3450
|
+
updateOauthAuthIssueHint();
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
async function openManualAuthFallback(providerRaw, aliasRaw, reason) {
|
|
3454
|
+
const provider = textOf(providerRaw || "").trim().toLowerCase();
|
|
3455
|
+
const alias = textOf(aliasRaw || "default").trim() || "default";
|
|
3456
|
+
selectTab("credentials");
|
|
3457
|
+
setOauthMode("manual");
|
|
3458
|
+
setOauthContext(provider, alias);
|
|
3459
|
+
if (reason) {
|
|
3460
|
+
appendLog("credentialOpLog", reason);
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
appendLog("credentialOpLog", "[step3] 自动启动 Manual Auth(headful)…");
|
|
3464
|
+
try {
|
|
3465
|
+
await apiFetch("/config/settings", {
|
|
3466
|
+
method: "PUT",
|
|
3467
|
+
body: JSON.stringify({ oauthBrowser: "camoufox" })
|
|
3468
|
+
});
|
|
3469
|
+
} catch (e) {
|
|
3470
|
+
appendLog("credentialOpLog", "[step3] set oauthBrowser=camoufox failed: " + (e && e.message ? e.message : String(e)));
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
try {
|
|
3474
|
+
const out = await apiFetch("/daemon/oauth/authorize", {
|
|
3475
|
+
method: "POST",
|
|
3476
|
+
body: JSON.stringify({
|
|
3477
|
+
provider,
|
|
3478
|
+
alias,
|
|
3479
|
+
mode: "manual",
|
|
3480
|
+
openBrowser: true,
|
|
3481
|
+
forceReauthorize: true,
|
|
3482
|
+
headful: true
|
|
3483
|
+
})
|
|
3484
|
+
});
|
|
3485
|
+
appendLog("credentialOpLog", "[step3] manual auth started: tokenFile=" + textOf(out && out.tokenFile ? out.tokenFile : "—"));
|
|
3486
|
+
appendLog("credentialOpLog", "[manual] 已尝试弹出认证窗口;若未自动完成,请在弹出的 portal 完成登录。");
|
|
3487
|
+
toast("Manual auth started: " + provider + "." + alias, "ok");
|
|
3488
|
+
return true;
|
|
3489
|
+
} catch (e) {
|
|
3490
|
+
appendLog("credentialOpLog", "[step3] manual auth auto-start failed: " + (e && e.message ? e.message : String(e)));
|
|
3491
|
+
appendLog("credentialOpLog", "[manual] 请点击 Start Manual Auth 重试。");
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
try {
|
|
3495
|
+
const btn = $("oauthAuthorizeManualBtn");
|
|
3496
|
+
if (btn && typeof btn.scrollIntoView === "function") {
|
|
3497
|
+
btn.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
3498
|
+
}
|
|
3499
|
+
if (btn && typeof btn.focus === "function") {
|
|
3500
|
+
btn.focus();
|
|
3501
|
+
}
|
|
3502
|
+
} catch {
|
|
3503
|
+
// ignore focus/scroll failures
|
|
3504
|
+
}
|
|
3505
|
+
return false;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
async function runCredentialAutoRecovery(cred) {
|
|
3509
|
+
const id = textOf(cred && cred.id ? cred.id : "").trim();
|
|
3510
|
+
const provider = textOf(cred && cred.provider ? cred.provider : "").trim().toLowerCase();
|
|
3511
|
+
const alias = textOf(cred && cred.alias ? cred.alias : "default").trim() || "default";
|
|
3512
|
+
if (!id || !provider) {
|
|
3513
|
+
setLog("credentialOpLog", "[recover] missing credential id/provider");
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
selectTab("credentials");
|
|
3518
|
+
setOauthContext(provider, alias);
|
|
3519
|
+
setLog("credentialOpLog", "[recover] " + provider + "." + alias + " (id=" + id + ")");
|
|
3520
|
+
|
|
3521
|
+
let refreshOk = false;
|
|
3522
|
+
appendLog("credentialOpLog", "[step1] 尝试 refresh token …");
|
|
3523
|
+
try {
|
|
3524
|
+
const out = await apiFetch("/daemon/credentials/" + encodeURIComponent(id) + "/refresh", {
|
|
3525
|
+
method: "POST"
|
|
3526
|
+
});
|
|
3527
|
+
appendLog(
|
|
3528
|
+
"credentialOpLog",
|
|
3529
|
+
"[step1] refresh result: status=" + textOf(out && out.status ? out.status : "—") + " refreshed=" + (out && out.refreshed === true ? "yes" : "no")
|
|
3530
|
+
);
|
|
3531
|
+
await refreshCredentials();
|
|
3532
|
+
const latest = findCredentialForSelection(provider, alias);
|
|
3533
|
+
const latestStatus = textOf(latest && latest.status ? latest.status : "").trim().toLowerCase();
|
|
3534
|
+
const exp = Number(latest && latest.expiresInSec != null ? latest.expiresInSec : NaN);
|
|
3535
|
+
if (latestStatus === "valid" && (!Number.isFinite(exp) || exp > 0)) {
|
|
3536
|
+
refreshOk = true;
|
|
3537
|
+
appendLog("credentialOpLog", "[done] refresh 后已恢复有效 token。");
|
|
3538
|
+
toast("OAuth refreshed: " + provider + "." + alias, "ok");
|
|
3539
|
+
}
|
|
3540
|
+
} catch (e) {
|
|
3541
|
+
appendLog("credentialOpLog", "[step1] refresh failed: " + (e && e.message ? e.message : String(e)));
|
|
3542
|
+
}
|
|
3543
|
+
if (refreshOk) {
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
appendLog("credentialOpLog", "[step2] refresh 未恢复,启动 Camoufox auto oauth …");
|
|
3548
|
+
try {
|
|
3549
|
+
await apiFetch("/config/settings", {
|
|
3550
|
+
method: "PUT",
|
|
3551
|
+
body: JSON.stringify({ oauthBrowser: "camoufox" })
|
|
3552
|
+
});
|
|
3553
|
+
} catch (e) {
|
|
3554
|
+
appendLog("credentialOpLog", "[step2] set oauthBrowser=camoufox failed: " + (e && e.message ? e.message : String(e)));
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
try {
|
|
3558
|
+
const out = await apiFetch("/daemon/oauth/authorize", {
|
|
3559
|
+
method: "POST",
|
|
3560
|
+
body: JSON.stringify({
|
|
3561
|
+
provider,
|
|
3562
|
+
alias,
|
|
3563
|
+
mode: "auto",
|
|
3564
|
+
openBrowser: true,
|
|
3565
|
+
forceReauthorize: false,
|
|
3566
|
+
headful: false
|
|
3567
|
+
})
|
|
3568
|
+
});
|
|
3569
|
+
appendLog("credentialOpLog", "[step2] auto oauth ok: tokenFile=" + textOf(out && out.tokenFile ? out.tokenFile : "—"));
|
|
3570
|
+
await refreshCredentials();
|
|
3571
|
+
if (provider === "antigravity") {
|
|
3572
|
+
try {
|
|
3573
|
+
await refreshQuota();
|
|
3574
|
+
await refreshCredentials();
|
|
3575
|
+
} catch {
|
|
3576
|
+
// ignore quota refresh errors in recovery path
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
const latest = findCredentialForSelection(provider, alias);
|
|
3580
|
+
const latestStatus = textOf(latest && latest.status ? latest.status : "").trim().toLowerCase();
|
|
3581
|
+
if (latestStatus === "valid") {
|
|
3582
|
+
appendLog("credentialOpLog", "[done] auto oauth 后 token 已恢复有效。");
|
|
3583
|
+
toast("OAuth recovered: " + provider + "." + alias, "ok");
|
|
3584
|
+
return;
|
|
3585
|
+
}
|
|
3586
|
+
appendLog("credentialOpLog", "[step2] auto oauth 执行完成,但状态仍非 valid。");
|
|
3587
|
+
} catch (e) {
|
|
3588
|
+
appendLog("credentialOpLog", "[step2] auto oauth failed: " + (e && e.message ? e.message : String(e)));
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
const manualStarted = await openManualAuthFallback(provider, alias, "[step3] 自动恢复失败,切换到手动认证。");
|
|
3592
|
+
if (!manualStarted) {
|
|
3593
|
+
toast("Auto recovery failed, switched to manual auth (" + provider + "." + alias + ").");
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
2765
3597
|
const routingEditorState = {
|
|
2766
3598
|
value: {},
|
|
2767
3599
|
dirty: false
|
|
@@ -2873,29 +3705,69 @@
|
|
|
2873
3705
|
return false;
|
|
2874
3706
|
}
|
|
2875
3707
|
|
|
2876
|
-
function resolveTargetToProviderKeys(target) {
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
3708
|
+
function resolveTargetToProviderKeys(target, providersOverride) {
|
|
3709
|
+
const raw = textOf(target).trim();
|
|
3710
|
+
if (!raw) return [];
|
|
3711
|
+
const list = Array.isArray(providersOverride)
|
|
3712
|
+
? providersOverride
|
|
3713
|
+
: (Array.isArray(UI.quotaProviders) ? UI.quotaProviders : []);
|
|
3714
|
+
const keys = list.map((p) => textOf(p && p.providerKey ? p.providerKey : "")).filter(Boolean);
|
|
3715
|
+
const known = new Set(keys);
|
|
3716
|
+
if (!known.size) return [];
|
|
3717
|
+
if (known.has(raw)) return [raw];
|
|
3718
|
+
|
|
3719
|
+
const candidates = new Set();
|
|
3720
|
+
const bracketMatch = raw.match(/^([^.[\]]+)\[([^\]]+)\]\.(.+)$/);
|
|
3721
|
+
if (bracketMatch) {
|
|
3722
|
+
const providerId = textOf(bracketMatch[1]).trim();
|
|
3723
|
+
const authAlias = textOf(bracketMatch[2]).trim();
|
|
3724
|
+
const modelId = textOf(bracketMatch[3]).trim();
|
|
3725
|
+
if (providerId && authAlias && modelId) {
|
|
3726
|
+
candidates.add(providerId + "." + authAlias + "." + modelId);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
const dotted = raw.split(".").filter(Boolean);
|
|
3731
|
+
if (dotted.length >= 3) {
|
|
3732
|
+
const providerId = dotted[0];
|
|
3733
|
+
const modelId = dotted.slice(2).join(".");
|
|
3734
|
+
if (providerId && modelId) {
|
|
3735
|
+
candidates.add(raw);
|
|
3736
|
+
candidates.add(providerId + "." + modelId);
|
|
3737
|
+
}
|
|
3738
|
+
} else if (dotted.length === 2) {
|
|
3739
|
+
candidates.add(raw);
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
for (const c of candidates) {
|
|
3743
|
+
if (known.has(c)) return [c];
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
const out = [];
|
|
3747
|
+
const addByProviderModel = (providerId, modelId) => {
|
|
3748
|
+
if (!providerId || !modelId) return;
|
|
3749
|
+
const prefix = providerId + ".";
|
|
3750
|
+
const suffix = "." + modelId;
|
|
3751
|
+
for (const k of keys) {
|
|
3752
|
+
if (k.startsWith(prefix) && k.endsWith(suffix)) out.push(k);
|
|
3753
|
+
}
|
|
3754
|
+
};
|
|
2897
3755
|
|
|
2898
|
-
|
|
3756
|
+
if (bracketMatch) {
|
|
3757
|
+
addByProviderModel(textOf(bracketMatch[1]).trim(), textOf(bracketMatch[3]).trim());
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
if (dotted.length >= 2) {
|
|
3761
|
+
addByProviderModel(dotted[0], dotted.slice(1).join("."));
|
|
3762
|
+
if (dotted.length >= 3) {
|
|
3763
|
+
addByProviderModel(dotted[0], dotted.slice(2).join("."));
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
return Array.from(new Set(out)).sort((a, b) => a.localeCompare(b));
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
function getQuotaStateByProviderKey(providerKey) {
|
|
2899
3771
|
const map = UI.quotaProviderMap instanceof Map ? UI.quotaProviderMap : null;
|
|
2900
3772
|
if (map && map.has(providerKey)) return map.get(providerKey);
|
|
2901
3773
|
const list = Array.isArray(UI.quotaProviders) ? UI.quotaProviders : [];
|
|
@@ -3378,31 +4250,13 @@
|
|
|
3378
4250
|
function resolveRoutedProviderKeys(routingTargets, providers) {
|
|
3379
4251
|
const targets = routingTargets instanceof Set ? Array.from(routingTargets) : [];
|
|
3380
4252
|
const list = Array.isArray(providers) ? providers : [];
|
|
3381
|
-
const keys = [];
|
|
3382
|
-
for (const p of list) {
|
|
3383
|
-
const k = textOf(p && p.providerKey ? p.providerKey : "");
|
|
3384
|
-
if (k) keys.push(k);
|
|
3385
|
-
}
|
|
3386
|
-
const known = new Set(keys);
|
|
3387
4253
|
const resolved = new Set();
|
|
3388
|
-
if (!targets.length || !
|
|
4254
|
+
if (!targets.length || !list.length) return resolved;
|
|
3389
4255
|
|
|
3390
4256
|
for (const t of targets) {
|
|
3391
|
-
const
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
resolved.add(target);
|
|
3395
|
-
continue;
|
|
3396
|
-
}
|
|
3397
|
-
const dot = target.indexOf(".");
|
|
3398
|
-
if (dot <= 0) continue;
|
|
3399
|
-
const providerId = target.slice(0, dot);
|
|
3400
|
-
const modelId = target.slice(dot + 1);
|
|
3401
|
-
if (!providerId || !modelId) continue;
|
|
3402
|
-
const prefix = `${providerId}.`;
|
|
3403
|
-
const suffix = `.${modelId}`;
|
|
3404
|
-
for (const k of known) {
|
|
3405
|
-
if (k.startsWith(prefix) && k.endsWith(suffix)) resolved.add(k);
|
|
4257
|
+
const mapped = resolveTargetToProviderKeys(t, list);
|
|
4258
|
+
for (const key of mapped) {
|
|
4259
|
+
if (key) resolved.add(key);
|
|
3406
4260
|
}
|
|
3407
4261
|
}
|
|
3408
4262
|
return resolved;
|
|
@@ -3557,16 +4411,16 @@
|
|
|
3557
4411
|
|
|
3558
4412
|
const filter = textOf($("quotaFilterInput").value || "").trim().toLowerCase();
|
|
3559
4413
|
const hideOk = Boolean($("quotaHideOkToggle").checked);
|
|
3560
|
-
|
|
3561
|
-
const routedProviderKeys = onlyRoutedTargets ? resolveRoutedProviderKeys(UI.routingTargets, UI.quotaProviders) : null;
|
|
4414
|
+
const routedProviderKeys = resolveRoutedProviderKeys(UI.routingTargets, UI.quotaProviders);
|
|
3562
4415
|
|
|
3563
4416
|
const list = Array.isArray(UI.quotaProviders) ? UI.quotaProviders : [];
|
|
3564
4417
|
const next = list
|
|
3565
4418
|
.filter((q) => {
|
|
3566
|
-
const
|
|
4419
|
+
const keyRaw = textOf(q && q.providerKey ? q.providerKey : "");
|
|
4420
|
+
const key = keyRaw.toLowerCase();
|
|
3567
4421
|
if (filter && !key.includes(filter)) return false;
|
|
3568
4422
|
if (hideOk && q && q.inPool === true) return false;
|
|
3569
|
-
if (
|
|
4423
|
+
if (!routedProviderKeys.has(keyRaw)) return false;
|
|
3570
4424
|
return true;
|
|
3571
4425
|
})
|
|
3572
4426
|
.slice();
|
|
@@ -4063,7 +4917,7 @@
|
|
|
4063
4917
|
body.appendChild(tr);
|
|
4064
4918
|
}
|
|
4065
4919
|
} catch (e) {
|
|
4066
|
-
body.appendChild(createErrorRow(
|
|
4920
|
+
body.appendChild(createErrorRow(7, e && e.message ? e.message : e));
|
|
4067
4921
|
}
|
|
4068
4922
|
}
|
|
4069
4923
|
|
|
@@ -4173,7 +5027,7 @@
|
|
|
4173
5027
|
}
|
|
4174
5028
|
|
|
4175
5029
|
// Bind events
|
|
4176
|
-
document.querySelectorAll(".tab").forEach((btn) => {
|
|
5030
|
+
document.querySelectorAll(".tab[data-tab]").forEach((btn) => {
|
|
4177
5031
|
btn.addEventListener("click", () => selectTab(btn.getAttribute("data-tab")));
|
|
4178
5032
|
});
|
|
4179
5033
|
|
|
@@ -4309,6 +5163,14 @@
|
|
|
4309
5163
|
.catch(() => {});
|
|
4310
5164
|
});
|
|
4311
5165
|
|
|
5166
|
+
$("refreshClockBtn").addEventListener("click", refreshClockTab);
|
|
5167
|
+
$("clockSessionFilterInput").addEventListener("keydown", (ev) => {
|
|
5168
|
+
if (ev && ev.key === "Enter") {
|
|
5169
|
+
ev.preventDefault();
|
|
5170
|
+
void refreshClockTab();
|
|
5171
|
+
}
|
|
5172
|
+
});
|
|
5173
|
+
|
|
4312
5174
|
$("restartRuntimeBtn").addEventListener("click", async () => {
|
|
4313
5175
|
setLog("providerOpLog", "");
|
|
4314
5176
|
if (!confirm("Reload config from disk and rebuild runtime now?")) return;
|
|
@@ -4455,10 +5317,22 @@
|
|
|
4455
5317
|
|
|
4456
5318
|
$("refreshCredentialsBtn").addEventListener("click", refreshCredentials);
|
|
4457
5319
|
$("saveOauthBrowserBtn").addEventListener("click", saveSettings);
|
|
4458
|
-
$("oauthModeManualBtn").addEventListener("click", () =>
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
5320
|
+
$("oauthModeManualBtn").addEventListener("click", () => {
|
|
5321
|
+
setOauthMode("manual");
|
|
5322
|
+
appendLog("credentialOpLog", "[ui] switched to manual mode");
|
|
5323
|
+
});
|
|
5324
|
+
$("oauthModeAutoBtn").addEventListener("click", () => {
|
|
5325
|
+
setOauthMode("auto");
|
|
5326
|
+
appendLog("credentialOpLog", "[ui] switched to auto mode");
|
|
5327
|
+
});
|
|
5328
|
+
$("oauthAuthorizeManualBtn").addEventListener("click", () => {
|
|
5329
|
+
appendLog("credentialOpLog", "[ui] click Start Manual Auth");
|
|
5330
|
+
void authorizeOauth("manual");
|
|
5331
|
+
});
|
|
5332
|
+
$("oauthAuthorizeAutoBtn").addEventListener("click", () => {
|
|
5333
|
+
appendLog("credentialOpLog", "[ui] click Run Auto Auth");
|
|
5334
|
+
void authorizeOauth("auto");
|
|
5335
|
+
});
|
|
4462
5336
|
$("oauthVerifyOpenBtn").addEventListener("click", () => void openSelectedVerifyUrl());
|
|
4463
5337
|
$("oauthVerifyCopyBtn").addEventListener("click", () => void copySelectedVerifyUrl());
|
|
4464
5338
|
$("oauthVerifyRecheckBtn").addEventListener("click", async () => {
|