dsh-config-manager 0.1.69 → 0.1.71
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/README.md +59 -18
- package/README.zh-CN.md +29 -9
- package/lib/adapters/credentials.js +6 -3
- package/lib/adapters/file-collection.d.ts +21 -1
- package/lib/adapters/file-collection.js +32 -8
- package/lib/adapters/mcp.d.ts +12 -2
- package/lib/adapters/mcp.js +30 -13
- package/lib/adapters/plugin-files.d.ts +8 -0
- package/lib/adapters/plugin-files.js +36 -4
- package/lib/adapters/plugins.d.ts +28 -2
- package/lib/adapters/plugins.js +204 -55
- package/lib/adapters/prompts.d.ts +12 -2
- package/lib/adapters/prompts.js +34 -19
- package/lib/adapters/providers.js +5 -4
- package/lib/adapters/self.d.ts +20 -0
- package/lib/adapters/self.js +73 -0
- package/lib/adapters/sessions.d.ts +8 -0
- package/lib/adapters/sessions.js +47 -1
- package/lib/adapters/settings.d.ts +21 -3
- package/lib/adapters/settings.js +44 -7
- package/lib/adapters/skills.d.ts +36 -4
- package/lib/adapters/skills.js +222 -3
- package/lib/adapters/test-helpers.d.ts +31 -2
- package/lib/adapters/test-helpers.js +42 -5
- package/lib/adapters/workspaces.js +97 -5
- package/lib/cli/actions.d.ts +50 -3
- package/lib/cli/actions.js +103 -11
- package/lib/cli/help.d.ts +65 -0
- package/lib/cli/help.js +517 -0
- package/lib/cli/import-source.d.ts +32 -0
- package/lib/cli/import-source.js +178 -0
- package/lib/cli/index.d.ts +18 -2
- package/lib/cli/index.js +118 -90
- package/lib/cli/web/client-script.d.ts +8 -0
- package/lib/cli/web/client-script.js +11 -0
- package/lib/cli/web/http.d.ts +5 -0
- package/lib/cli/web/http.js +2 -1
- package/lib/cli/web/page.d.ts +16 -6
- package/lib/cli/web/page.js +102 -25
- package/lib/cli/web/routes.js +24 -5
- package/lib/cli/web/server.js +23 -2
- package/lib/client.d.ts +1019 -36
- package/lib/client.js +5522 -1564
- package/lib/core/analyzer.d.ts +15 -1
- package/lib/core/analyzer.js +114 -13
- package/lib/core/backup-plan.js +17 -1
- package/lib/core/backup.d.ts +8 -0
- package/lib/core/backup.js +28 -6
- package/lib/core/cache-cleaner.d.ts +10 -0
- package/lib/core/cache-cleaner.js +63 -22
- package/lib/core/checkpoint.d.ts +362 -0
- package/lib/core/checkpoint.js +1309 -0
- package/lib/core/journal.d.ts +37 -0
- package/lib/core/journal.js +69 -0
- package/lib/core/messages.d.ts +13 -0
- package/lib/core/messages.js +26 -0
- package/lib/core/model-tools.js +3 -0
- package/lib/core/patch-layers.d.ts +83 -0
- package/lib/core/patch-layers.js +110 -0
- package/lib/core/restore.d.ts +21 -0
- package/lib/core/restore.js +37 -3
- package/lib/core/rollback.js +6 -3
- package/lib/core/self-update.d.ts +52 -0
- package/lib/core/self-update.js +70 -0
- package/lib/core/session-health.d.ts +29 -5
- package/lib/core/session-health.js +28 -3
- package/lib/core/types.d.ts +126 -0
- package/lib/foreign/antigravity.d.ts +28 -0
- package/lib/foreign/antigravity.js +157 -0
- package/lib/foreign/bundle.d.ts +25 -0
- package/lib/foreign/bundle.js +79 -0
- package/lib/foreign/chatgpt.d.ts +6 -0
- package/lib/foreign/chatgpt.js +25 -0
- package/lib/foreign/claude-code.d.ts +3 -0
- package/lib/foreign/claude-code.js +85 -0
- package/lib/foreign/claude-sessions.d.ts +108 -0
- package/lib/foreign/claude-sessions.js +299 -0
- package/lib/foreign/cline.d.ts +6 -0
- package/lib/foreign/cline.js +27 -0
- package/lib/foreign/codex.d.ts +83 -0
- package/lib/foreign/codex.js +348 -0
- package/lib/foreign/continue.d.ts +9 -0
- package/lib/foreign/continue.js +65 -0
- package/lib/foreign/copilot.d.ts +39 -0
- package/lib/foreign/copilot.js +95 -0
- package/lib/foreign/crush.d.ts +11 -0
- package/lib/foreign/crush.js +57 -0
- package/lib/foreign/cursor.d.ts +87 -0
- package/lib/foreign/cursor.js +171 -0
- package/lib/foreign/dsh.d.ts +30 -0
- package/lib/foreign/dsh.js +101 -0
- package/lib/foreign/dsh4.d.ts +3 -0
- package/lib/foreign/dsh4.js +13 -0
- package/lib/foreign/gemini.d.ts +7 -0
- package/lib/foreign/gemini.js +44 -0
- package/lib/foreign/goose.d.ts +11 -0
- package/lib/foreign/goose.js +53 -0
- package/lib/foreign/grokbuild.d.ts +8 -0
- package/lib/foreign/grokbuild.js +25 -0
- package/lib/foreign/hermes.d.ts +2 -0
- package/lib/foreign/hermes.js +78 -0
- package/lib/foreign/kernel.d.ts +132 -0
- package/lib/foreign/kernel.js +569 -0
- package/lib/foreign/kilocode.d.ts +11 -0
- package/lib/foreign/kilocode.js +51 -0
- package/lib/foreign/kimi.d.ts +7 -0
- package/lib/foreign/kimi.js +47 -0
- package/lib/foreign/mimocode.d.ts +11 -0
- package/lib/foreign/mimocode.js +51 -0
- package/lib/foreign/openclaw.d.ts +7 -0
- package/lib/foreign/openclaw.js +46 -0
- package/lib/foreign/opencode.d.ts +11 -0
- package/lib/foreign/opencode.js +52 -0
- package/lib/foreign/pi.d.ts +7 -0
- package/lib/foreign/pi.js +48 -0
- package/lib/foreign/platform-paths.d.ts +66 -0
- package/lib/foreign/platform-paths.js +141 -0
- package/lib/foreign/qoder.d.ts +10 -0
- package/lib/foreign/qoder.js +49 -0
- package/lib/foreign/qwen.d.ts +7 -0
- package/lib/foreign/qwen.js +45 -0
- package/lib/foreign/read-antigravity.d.ts +57 -0
- package/lib/foreign/read-antigravity.js +504 -0
- package/lib/foreign/read-chatgpt.d.ts +20 -0
- package/lib/foreign/read-chatgpt.js +509 -0
- package/lib/foreign/read-claude-code.d.ts +24 -0
- package/lib/foreign/read-claude-code.js +234 -0
- package/lib/foreign/read-cline.d.ts +53 -0
- package/lib/foreign/read-cline.js +479 -0
- package/lib/foreign/read-codex.d.ts +75 -0
- package/lib/foreign/read-codex.js +842 -0
- package/lib/foreign/read-continue.d.ts +87 -0
- package/lib/foreign/read-continue.js +489 -0
- package/lib/foreign/read-copilot.d.ts +42 -0
- package/lib/foreign/read-copilot.js +267 -0
- package/lib/foreign/read-crush.d.ts +54 -0
- package/lib/foreign/read-crush.js +478 -0
- package/lib/foreign/read-cursor.d.ts +141 -0
- package/lib/foreign/read-cursor.js +705 -0
- package/lib/foreign/read-dsh.d.ts +43 -0
- package/lib/foreign/read-dsh.js +205 -0
- package/lib/foreign/read-dsh4.d.ts +14 -0
- package/lib/foreign/read-dsh4.js +27 -0
- package/lib/foreign/read-gemini.d.ts +55 -0
- package/lib/foreign/read-gemini.js +273 -0
- package/lib/foreign/read-goose.d.ts +25 -0
- package/lib/foreign/read-goose.js +322 -0
- package/lib/foreign/read-grokbuild.d.ts +34 -0
- package/lib/foreign/read-grokbuild.js +455 -0
- package/lib/foreign/read-hermes.d.ts +74 -0
- package/lib/foreign/read-hermes.js +690 -0
- package/lib/foreign/read-kilocode.d.ts +17 -0
- package/lib/foreign/read-kilocode.js +46 -0
- package/lib/foreign/read-kimi.d.ts +63 -0
- package/lib/foreign/read-kimi.js +719 -0
- package/lib/foreign/read-mimocode.d.ts +12 -0
- package/lib/foreign/read-mimocode.js +56 -0
- package/lib/foreign/read-openclaw.d.ts +51 -0
- package/lib/foreign/read-openclaw.js +445 -0
- package/lib/foreign/read-opencode.d.ts +150 -0
- package/lib/foreign/read-opencode.js +896 -0
- package/lib/foreign/read-pi.d.ts +56 -0
- package/lib/foreign/read-pi.js +411 -0
- package/lib/foreign/read-qoder.d.ts +54 -0
- package/lib/foreign/read-qoder.js +317 -0
- package/lib/foreign/read-qwen.d.ts +40 -0
- package/lib/foreign/read-qwen.js +326 -0
- package/lib/foreign/read-reasonix.d.ts +66 -0
- package/lib/foreign/read-reasonix.js +537 -0
- package/lib/foreign/read-teleagent.d.ts +15 -0
- package/lib/foreign/read-teleagent.js +68 -0
- package/lib/foreign/read-trae.d.ts +67 -0
- package/lib/foreign/read-trae.js +492 -0
- package/lib/foreign/read-vibe.d.ts +28 -0
- package/lib/foreign/read-vibe.js +364 -0
- package/lib/foreign/read-workbuddy.d.ts +49 -0
- package/lib/foreign/read-workbuddy.js +346 -0
- package/lib/foreign/read-zcode.d.ts +12 -0
- package/lib/foreign/read-zcode.js +38 -0
- package/lib/foreign/read-zed.d.ts +30 -0
- package/lib/foreign/read-zed.js +473 -0
- package/lib/foreign/reasonix.d.ts +6 -0
- package/lib/foreign/reasonix.js +20 -0
- package/lib/foreign/registry.d.ts +196 -0
- package/lib/foreign/registry.js +573 -0
- package/lib/foreign/session-ir.d.ts +186 -0
- package/lib/foreign/session-ir.js +427 -0
- package/lib/foreign/session-read.d.ts +102 -0
- package/lib/foreign/session-read.js +254 -0
- package/lib/foreign/session-source.d.ts +216 -0
- package/lib/foreign/session-source.js +453 -0
- package/lib/foreign/source-modules.d.ts +45 -0
- package/lib/foreign/source-modules.js +52 -0
- package/lib/foreign/sqlite.d.ts +61 -0
- package/lib/foreign/sqlite.js +404 -0
- package/lib/foreign/teleagent.d.ts +11 -0
- package/lib/foreign/teleagent.js +53 -0
- package/lib/foreign/trae.d.ts +6 -0
- package/lib/foreign/trae.js +20 -0
- package/lib/foreign/truth-table.d.ts +62 -0
- package/lib/foreign/truth-table.js +333 -0
- package/lib/foreign/types.d.ts +272 -0
- package/lib/foreign/types.js +2 -0
- package/lib/foreign/vibe.d.ts +6 -0
- package/lib/foreign/vibe.js +23 -0
- package/lib/foreign/workbuddy.d.ts +7 -0
- package/lib/foreign/workbuddy.js +45 -0
- package/lib/foreign/zcode.d.ts +11 -0
- package/lib/foreign/zcode.js +51 -0
- package/lib/foreign/zed.d.ts +11 -0
- package/lib/foreign/zed.js +53 -0
- package/lib/index.d.ts +154 -13
- package/lib/index.js +661 -84
- package/lib/market/github-repos.d.ts +28 -0
- package/lib/market/github-repos.js +43 -1
- package/lib/market/types.d.ts +1 -3
- package/lib/profiles/dsh-profile-manager.d.ts +54 -2
- package/lib/profiles/dsh-profile-manager.js +182 -11
- package/lib/profiles/dsh-profile-shared.d.ts +47 -0
- package/lib/profiles/dsh-profile-shared.js +15 -1
- package/lib/profiles/index.d.ts +1 -1
- package/lib/profiles/index.js +1 -1
- package/lib/routes/consult.js +51 -5
- package/lib/routes/foreign-context.d.ts +43 -0
- package/lib/routes/foreign-context.js +65 -0
- package/lib/routes/foreign.d.ts +11 -0
- package/lib/routes/foreign.js +208 -0
- package/lib/routes/import.d.ts +8 -0
- package/lib/routes/import.js +22 -1
- package/lib/routes/index.d.ts +2 -2
- package/lib/routes/index.js +4 -0
- package/lib/routes/kit.d.ts +4 -3
- package/lib/routes/market.js +0 -1
- package/lib/routes/prefs.d.ts +2 -1
- package/lib/routes/prefs.js +14 -2
- package/lib/routes/recovery.js +363 -3
- package/lib/routes/session-export.d.ts +117 -0
- package/lib/routes/session-export.js +270 -0
- package/lib/routes/snapshots.js +12 -2
- package/lib/routes/sync.js +82 -10
- package/lib/security/redaction.d.ts +14 -0
- package/lib/security/redaction.js +24 -9
- package/lib/security/secret-scanner.d.ts +27 -0
- package/lib/security/secret-scanner.js +54 -1
- package/lib/sync/autosync-scheduler.d.ts +13 -1
- package/lib/sync/autosync-scheduler.js +49 -26
- package/lib/sync/backup-files.js +6 -0
- package/lib/sync/gist/gist-transport.d.ts +91 -0
- package/lib/sync/gist/gist-transport.js +406 -0
- package/lib/sync/gist/messages.d.ts +22 -0
- package/lib/sync/gist/messages.js +46 -0
- package/lib/sync/s3/messages.d.ts +53 -0
- package/lib/sync/s3/messages.js +83 -0
- package/lib/sync/s3/s3-providers.d.ts +78 -0
- package/lib/sync/s3/s3-providers.js +156 -0
- package/lib/sync/s3/s3-transport.d.ts +126 -0
- package/lib/sync/s3/s3-transport.js +551 -0
- package/lib/sync/s3/sigv4.d.ts +107 -0
- package/lib/sync/s3/sigv4.js +196 -0
- package/lib/sync/sync-config.d.ts +250 -4
- package/lib/sync/sync-config.js +658 -57
- package/lib/sync/sync-engine.d.ts +1 -0
- package/lib/sync/sync-engine.js +4 -2
- package/lib/sync/sync-history.d.ts +1 -1
- package/lib/sync/webdav/webdav-transport.d.ts +31 -3
- package/lib/sync/webdav/webdav-transport.js +102 -18
- package/lib/ui/commands.d.ts +4 -3
- package/lib/ui/commands.js +41 -9
- package/lib/ui/disk-usage-view.d.ts +44 -0
- package/lib/ui/disk-usage-view.js +55 -1
- package/lib/ui/dsh-profiles-view.d.ts +53 -0
- package/lib/ui/dsh-profiles-view.js +56 -0
- package/lib/ui/foreign-view.d.ts +162 -0
- package/lib/ui/foreign-view.js +225 -0
- package/lib/ui/i18n.d.ts +77 -0
- package/lib/ui/i18n.js +164 -0
- package/lib/ui/session-inventory-view.d.ts +106 -0
- package/lib/ui/session-inventory-view.js +58 -0
- package/lib/ui/session-layout-view.d.ts +265 -0
- package/lib/ui/session-layout-view.js +271 -0
- package/lib/ui/sync-repository-picker.d.ts +98 -0
- package/lib/ui/sync-repository-picker.js +145 -0
- package/lib/ui/sync-settings-view.d.ts +143 -4
- package/lib/ui/sync-settings-view.js +231 -7
- package/lib/ui/types.d.ts +47 -0
- package/lib/utils/asar-read.d.ts +113 -0
- package/lib/utils/asar-read.js +523 -0
- package/lib/utils/env-lock.js +7 -2
- package/lib/utils/patch-yaml.d.ts +25 -0
- package/lib/utils/patch-yaml.js +62 -0
- package/lib/utils/paths.d.ts +10 -1
- package/lib/utils/paths.js +24 -3
- package/lib/utils/recursive-walk.js +12 -13
- package/lib/utils/session-format.d.ts +35 -2
- package/lib/utils/session-format.js +131 -5
- package/lib/utils/session-health-scan.d.ts +7 -1
- package/lib/utils/session-health-scan.js +369 -12
- package/lib/utils/session-layout-repair-service.d.ts +98 -0
- package/lib/utils/session-layout-repair-service.js +457 -0
- package/lib/utils/session-log-repair.d.ts +27 -0
- package/lib/utils/session-log-repair.js +233 -62
- package/lib/utils/session-repair-service.d.ts +32 -3
- package/lib/utils/session-repair-service.js +148 -9
- package/lib/utils/session-row-facts.d.ts +73 -0
- package/lib/utils/session-row-facts.js +182 -0
- package/lib/utils/session-verify.d.ts +118 -0
- package/lib/utils/session-verify.js +705 -0
- package/lib/utils/shared-constants.d.ts +59 -0
- package/lib/utils/shared-constants.js +71 -0
- package/package.json +1 -1
- package/src/adapters/credentials.test.ts +27 -0
- package/src/adapters/credentials.ts +6 -3
- package/src/adapters/file-collection.ts +33 -8
- package/src/adapters/files.test.ts +222 -0
- package/src/adapters/mcp.test.ts +43 -0
- package/src/adapters/mcp.ts +37 -14
- package/src/adapters/plugin-files.ts +34 -4
- package/src/adapters/plugins.test.ts +241 -0
- package/src/adapters/plugins.ts +201 -53
- package/src/adapters/prompts.test.ts +49 -0
- package/src/adapters/prompts.ts +41 -20
- package/src/adapters/providers.test.ts +16 -0
- package/src/adapters/providers.ts +4 -3
- package/src/adapters/self.test.ts +77 -0
- package/src/adapters/self.ts +75 -0
- package/src/adapters/sessions.test.ts +77 -0
- package/src/adapters/sessions.ts +47 -1
- package/src/adapters/settings.test.ts +78 -0
- package/src/adapters/settings.ts +42 -6
- package/src/adapters/skills.ts +221 -3
- package/src/adapters/test-helpers.ts +43 -5
- package/src/adapters/workspaces-dup-id.test.ts +144 -0
- package/src/adapters/workspaces.test.ts +10 -0
- package/src/adapters/workspaces.ts +97 -5
- package/src/cli/actions.test.ts +117 -2
- package/src/cli/actions.ts +132 -12
- package/src/cli/help.ts +570 -0
- package/src/cli/import-source.test.ts +193 -0
- package/src/cli/import-source.ts +229 -0
- package/src/cli/index.ts +129 -94
- package/src/cli/web/client-script.ts +12 -0
- package/src/cli/web/console-fixes.test.ts +36 -0
- package/src/cli/web/http.ts +7 -1
- package/src/cli/web/page.ts +108 -25
- package/src/cli/web/routes.ts +23 -4
- package/src/cli/web/script-anchor.test.ts +33 -0
- package/src/cli/web/server.ts +28 -2
- package/src/cli/web/web.test.ts +447 -4
- package/src/client/ConfigManagerSection.tsx +118 -63
- package/src/client/about/AboutPanel.tsx +42 -3
- package/src/client/about/PluginUpdateDialog.tsx +185 -0
- package/src/client/about/ReleaseBody.tsx +144 -0
- package/src/client/about/ReleaseNotesDialog.tsx +3 -130
- package/src/client/about/about-view.ts +7 -5
- package/src/client/about/plugin-update-view.test.ts +25 -0
- package/src/client/about/plugin-update-view.ts +26 -0
- package/src/client/about/release-notes-view.test.ts +33 -0
- package/src/client/about/release-notes-view.ts +34 -0
- package/src/client/api.ts +145 -1
- package/src/client/common/Icon.tsx +3 -0
- package/src/client/common/RunsCenter.tsx +8 -2
- package/src/client/common/Select.tsx +9 -2
- package/src/client/common/info-hint-guard.test.ts +63 -18
- package/src/client/common/plan-text-redaction.test.ts +193 -8
- package/src/client/common/routes.ts +15 -0
- package/src/client/config-manager.module.css +506 -354
- package/src/client/css-scale-guard.test.ts +122 -0
- package/src/client/css-token-guard.test.ts +82 -0
- package/src/client/environment/EnvironmentPanel.tsx +45 -16
- package/src/client/{snapshots → environment/maintenance}/DiskUsageCard.tsx +21 -17
- package/src/client/environment/profiles-incomplete-ui.test.ts +103 -0
- package/src/client/export/ExportView.tsx +9 -2
- package/src/client/{snapshots → home}/BackupScheduleCard.tsx +13 -4
- package/src/client/home/HomePanel.tsx +69 -157
- package/src/client/icon-layer-guard.test.ts +1 -1
- package/src/client/import/ForeignImportView.tsx +318 -0
- package/src/client/import/ImportWizardView.tsx +3 -1
- package/src/client/import/foreign-import-view-guard.test.ts +195 -0
- package/src/client/import/import-file-select.test.ts +6 -6
- package/src/client/import/import-wizard-steps.tsx +17 -1
- package/src/client/import/use-import-wizard-controller.ts +3 -3
- package/src/client/index.ts +1 -1
- package/src/client/library/LibraryActions.tsx +31 -34
- package/src/client/library/LibraryPanel.tsx +4 -1
- package/src/client/{snapshots → library}/RestorePlanView.tsx +1 -1
- package/src/client/{snapshots → library}/files-layout.test.ts +2 -2
- package/src/client/{snapshots → library}/report-dismiss.test.ts +26 -3
- package/src/client/library/task-panel-visibility.test.ts +25 -17
- package/src/client/locales.ts +278 -36
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +11 -2
- package/src/client/recovery/IncompleteCopiesSection.tsx +132 -0
- package/src/client/recovery/RecoveryPanel.tsx +486 -59
- package/src/client/recovery/recovery-api.ts +30 -0
- package/src/client/recovery/recovery-incomplete-copies.test.ts +151 -0
- package/src/client/recovery/recovery-locales.test.ts +88 -0
- package/src/client/recovery/recovery-locales.ts +321 -6
- package/src/client/recovery/recovery-panel-layout.test.ts +52 -0
- package/src/client/recovery/recovery-view.ts +92 -2
- package/src/client/run-store.test.ts +201 -89
- package/src/client/run-store.ts +114 -143
- package/src/client/session-export/session-export-view.test.ts +103 -0
- package/src/client/session-export/session-export-view.ts +118 -0
- package/src/client/sync/ChannelConfigDialog.tsx +219 -14
- package/src/client/sync/SyncChannelEntryCard.tsx +52 -4
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +166 -102
- package/src/client/sync/SyncRepositoryPicker.tsx +216 -0
- package/src/client/sync/SyncSettingsView.test.ts +6 -1
- package/src/client/sync/history-model.test.ts +94 -57
- package/src/client/sync/history-model.ts +44 -25
- package/src/client/sync/sync-api.test.ts +56 -0
- package/src/client/sync/sync-api.ts +92 -0
- package/src/client/sync/sync-locales.ts +162 -2
- package/src/client/sync/sync-view.test.ts +3 -8
- package/src/client/sync/sync-view.ts +111 -21
- package/src/core/analyzer-cache.test.ts +47 -0
- package/src/core/analyzer.ts +115 -14
- package/src/core/backup-plan.test.ts +35 -0
- package/src/core/backup-plan.ts +19 -1
- package/src/core/backup.test.ts +37 -1
- package/src/core/backup.ts +27 -6
- package/src/core/cache-cleaner.test.ts +120 -1
- package/src/core/cache-cleaner.ts +59 -22
- package/src/core/checkpoint.test.ts +665 -0
- package/src/core/checkpoint.ts +1614 -0
- package/src/core/credential-plan-consistency.test.ts +93 -0
- package/src/core/incident-wiring.test.ts +6 -2
- package/src/core/journal.test.ts +70 -0
- package/src/core/journal.ts +68 -0
- package/src/core/messages.ts +26 -0
- package/src/core/model-tools.ts +3 -0
- package/src/core/patch-layers.test.ts +94 -0
- package/src/core/patch-layers.ts +150 -0
- package/src/core/restore.ts +59 -3
- package/src/core/rollback.ts +6 -3
- package/src/core/section-content-consistency.test.ts +185 -0
- package/src/core/self-update.test.ts +110 -0
- package/src/core/self-update.ts +118 -0
- package/src/core/session-health.test.ts +91 -1
- package/src/core/session-health.ts +56 -9
- package/src/core/types.ts +127 -1
- package/src/foreign/antigravity.test.ts +271 -0
- package/src/foreign/antigravity.ts +188 -0
- package/src/foreign/bundle.ts +108 -0
- package/src/foreign/chatgpt.test.ts +279 -0
- package/src/foreign/chatgpt.ts +30 -0
- package/src/foreign/claude-code.test.ts +234 -0
- package/src/foreign/claude-code.ts +106 -0
- package/src/foreign/claude-sessions-bytes.test.ts +227 -0
- package/src/foreign/claude-sessions.test.ts +180 -0
- package/src/foreign/claude-sessions.ts +461 -0
- package/src/foreign/cline.test.ts +274 -0
- package/src/foreign/cline.ts +32 -0
- package/src/foreign/codex.test.ts +585 -0
- package/src/foreign/codex.ts +409 -0
- package/src/foreign/continue.test.ts +365 -0
- package/src/foreign/continue.ts +73 -0
- package/src/foreign/copilot.test.ts +273 -0
- package/src/foreign/copilot.ts +133 -0
- package/src/foreign/crush.test.ts +234 -0
- package/src/foreign/crush.ts +66 -0
- package/src/foreign/cursor.test.ts +509 -0
- package/src/foreign/cursor.ts +259 -0
- package/src/foreign/dsh.test.ts +198 -0
- package/src/foreign/dsh.ts +117 -0
- package/src/foreign/dsh4.test.ts +127 -0
- package/src/foreign/dsh4.ts +15 -0
- package/src/foreign/file-budget.test.ts +256 -0
- package/src/foreign/fixtures/antigravity/README.md +18 -0
- package/src/foreign/fixtures/antigravity/basic/gemini/antigravity/mcp_config.json +13 -0
- package/src/foreign/fixtures/antigravity/basic/gemini/config/mcp_config.json +24 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_config.json +0 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_oauth_tokens.json +0 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/config/mcp_config.json +0 -0
- package/src/foreign/fixtures/codex/README.md +31 -0
- package/src/foreign/fixtures/codex/basic/.agents/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/codex/basic/.agents/skills/wrapper/nested/SKILL.md +6 -0
- package/src/foreign/fixtures/codex/basic/.codex/AGENTS.md +4 -0
- package/src/foreign/fixtures/codex/basic/.codex/config.toml +28 -0
- package/src/foreign/fixtures/codex/malformed/.codex/config.toml +3 -0
- package/src/foreign/fixtures/codex/override/.codex/AGENTS.md +1 -0
- package/src/foreign/fixtures/codex/override/.codex/AGENTS.override.md +3 -0
- package/src/foreign/fixtures/codex/sessions/.codex/archived_sessions/rollout-2026-10-05T09-00-00-22222222-2222-4222-8222-222222222222.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T10-00-00-11111111-1111-4111-8111-111111111111.jsonl +15 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-00-00-33333333-3333-4333-8333-333333333333.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-30-00-55555555-5555-4555-8555-555555555555.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T12-00-00-44444444-4444-4444-8444-444444444444.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/notes.jsonl +1 -0
- package/src/foreign/fixtures/copilot/README.md +22 -0
- package/src/foreign/fixtures/copilot/basic/copilot-instructions.md +3 -0
- package/src/foreign/fixtures/copilot/basic/instructions/empty.instructions.md +1 -0
- package/src/foreign/fixtures/copilot/basic/instructions/repo.instructions.md +5 -0
- package/src/foreign/fixtures/copilot/basic/mcp-config.json +24 -0
- package/src/foreign/fixtures/copilot/basic/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/copilot/overridden/mcp-config.json +10 -0
- package/src/foreign/fixtures/cursor/README.md +17 -0
- package/src/foreign/fixtures/cursor/alt/.cursor/mcp.json +5 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/mcp.json +18 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/agent.mdc +5 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/always.mdc +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/auto.mdc +7 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/manual.mdc +3 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/.hidden/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/notes.md +1 -0
- package/src/foreign/fixtures/cursor/basic/.cursorrules +1 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/mcp.json +9 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/rules/project-always.mdc +5 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursorrules +1 -0
- package/src/foreign/fixtures/cursor/empty/.cursor/mcp.json +0 -0
- package/src/foreign/fixtures/cursor/malformed/.cursor/mcp.json +3 -0
- package/src/foreign/fixtures/cursor/malformed/.cursor/rules/broken.mdc +6 -0
- package/src/foreign/fixtures/cursor/shape/.cursor/mcp.json +3 -0
- package/src/foreign/fixtures/hermes/README.md +14 -0
- package/src/foreign/fixtures/hermes/basic/SOUL.md +3 -0
- package/src/foreign/fixtures/hermes/basic/config.yaml +48 -0
- package/src/foreign/fixtures/hermes/basic/dotenv.fixture +1 -0
- package/src/foreign/fixtures/hermes/basic/memories/MEMORY.md +1 -0
- package/src/foreign/fixtures/hermes/basic/memories/USER.md +1 -0
- package/src/foreign/fixtures/hermes/basic/skills/se-team-design/SKILL.md +6 -0
- package/src/foreign/fixtures/hermes/basic/skills/se-team-design/examples/usage.md +1 -0
- package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/SKILL.md +6 -0
- package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/notes.md +1 -0
- package/src/foreign/fixtures/hermes/basic/state.db +1 -0
- package/src/foreign/fixtures/hermes/empty/config.yaml +0 -0
- package/src/foreign/gemini.test.ts +291 -0
- package/src/foreign/gemini.ts +52 -0
- package/src/foreign/goose.test.ts +196 -0
- package/src/foreign/goose.ts +62 -0
- package/src/foreign/grokbuild.test.ts +246 -0
- package/src/foreign/grokbuild.ts +30 -0
- package/src/foreign/hermes.test.ts +545 -0
- package/src/foreign/hermes.ts +86 -0
- package/src/foreign/kernel-credentials.test.ts +88 -0
- package/src/foreign/kernel-workspaces.test.ts +88 -0
- package/src/foreign/kernel.ts +584 -0
- package/src/foreign/kilocode.test.ts +186 -0
- package/src/foreign/kilocode.ts +60 -0
- package/src/foreign/kimi.test.ts +259 -0
- package/src/foreign/kimi.ts +55 -0
- package/src/foreign/mcp-value-shape.test.ts +286 -0
- package/src/foreign/mimocode.test.ts +178 -0
- package/src/foreign/mimocode.ts +60 -0
- package/src/foreign/openclaw.test.ts +248 -0
- package/src/foreign/openclaw.ts +54 -0
- package/src/foreign/opencode.test.ts +376 -0
- package/src/foreign/opencode.ts +61 -0
- package/src/foreign/pi.test.ts +275 -0
- package/src/foreign/pi.ts +56 -0
- package/src/foreign/platform-paths.ts +156 -0
- package/src/foreign/qoder.test.ts +266 -0
- package/src/foreign/qoder.ts +57 -0
- package/src/foreign/qwen.test.ts +203 -0
- package/src/foreign/qwen.ts +53 -0
- package/src/foreign/read-antigravity.ts +569 -0
- package/src/foreign/read-chatgpt.ts +506 -0
- package/src/foreign/read-claude-code.ts +264 -0
- package/src/foreign/read-cline.ts +517 -0
- package/src/foreign/read-codex.ts +823 -0
- package/src/foreign/read-continue.ts +557 -0
- package/src/foreign/read-copilot.ts +304 -0
- package/src/foreign/read-crush.ts +503 -0
- package/src/foreign/read-cursor.ts +778 -0
- package/src/foreign/read-dsh.ts +215 -0
- package/src/foreign/read-dsh4.ts +35 -0
- package/src/foreign/read-gemini.ts +306 -0
- package/src/foreign/read-goose.ts +333 -0
- package/src/foreign/read-grokbuild.ts +475 -0
- package/src/foreign/read-hermes.ts +724 -0
- package/src/foreign/read-kilocode.ts +55 -0
- package/src/foreign/read-kimi.ts +768 -0
- package/src/foreign/read-mimocode.ts +66 -0
- package/src/foreign/read-openclaw.ts +505 -0
- package/src/foreign/read-opencode.ts +994 -0
- package/src/foreign/read-pi.ts +459 -0
- package/src/foreign/read-qoder.ts +351 -0
- package/src/foreign/read-qwen.ts +342 -0
- package/src/foreign/read-reasonix.ts +590 -0
- package/src/foreign/read-teleagent.ts +82 -0
- package/src/foreign/read-trae.ts +488 -0
- package/src/foreign/read-vibe.ts +353 -0
- package/src/foreign/read-workbuddy.ts +374 -0
- package/src/foreign/read-zcode.ts +48 -0
- package/src/foreign/read-zed.ts +479 -0
- package/src/foreign/reasonix.test.ts +336 -0
- package/src/foreign/reasonix.ts +25 -0
- package/src/foreign/registry.test.ts +377 -0
- package/src/foreign/registry.ts +750 -0
- package/src/foreign/session-ir.test.ts +189 -0
- package/src/foreign/session-ir.ts +614 -0
- package/src/foreign/session-read.ts +295 -0
- package/src/foreign/session-source.ts +625 -0
- package/src/foreign/skill-file-caps.test.ts +171 -0
- package/src/foreign/source-modules.ts +86 -0
- package/src/foreign/source-registry.test.ts +190 -0
- package/src/foreign/sqlite.test.ts +313 -0
- package/src/foreign/sqlite.ts +446 -0
- package/src/foreign/teleagent.test.ts +147 -0
- package/src/foreign/teleagent.ts +62 -0
- package/src/foreign/trae.test.ts +316 -0
- package/src/foreign/trae.ts +25 -0
- package/src/foreign/truncation-report.test.ts +238 -0
- package/src/foreign/truth-table.ts +453 -0
- package/src/foreign/types.ts +312 -0
- package/src/foreign/vibe.test.ts +209 -0
- package/src/foreign/vibe.ts +28 -0
- package/src/foreign/workbuddy.test.ts +207 -0
- package/src/foreign/workbuddy.ts +53 -0
- package/src/foreign/zcode.test.ts +224 -0
- package/src/foreign/zcode.ts +60 -0
- package/src/foreign/zed.test.ts +240 -0
- package/src/foreign/zed.ts +62 -0
- package/src/index.facade.test.ts +254 -16
- package/src/index.ts +704 -82
- package/src/market/github-repos.test.ts +98 -1
- package/src/market/github-repos.ts +63 -1
- package/src/market/types.ts +3 -3
- package/src/profiles/dsh-profile-incomplete-copy.test.ts +177 -0
- package/src/profiles/dsh-profile-manager.test.ts +37 -0
- package/src/profiles/dsh-profile-manager.ts +188 -13
- package/src/profiles/dsh-profile-shared.ts +55 -1
- package/src/profiles/index.ts +2 -1
- package/src/routes/consult.ts +53 -4
- package/src/routes/foreign-context.ts +89 -0
- package/src/routes/foreign.test.ts +443 -0
- package/src/routes/foreign.ts +226 -0
- package/src/routes/import.ts +23 -1
- package/src/routes/index.ts +6 -2
- package/src/routes/kit.ts +4 -3
- package/src/routes/market.ts +0 -1
- package/src/routes/prefs.ts +15 -1
- package/src/routes/recovery.test.ts +167 -0
- package/src/routes/recovery.ts +331 -1
- package/src/routes/session-export.ts +308 -0
- package/src/routes/snapshots.ts +12 -2
- package/src/routes/sync.ts +91 -7
- package/src/security/redaction.test.ts +149 -0
- package/src/security/redaction.ts +25 -9
- package/src/security/secret-scanner.test.ts +186 -0
- package/src/security/secret-scanner.ts +57 -1
- package/src/sync/autosync-risk-tier.test.ts +61 -0
- package/src/sync/autosync-scheduler.test.ts +48 -1
- package/src/sync/autosync-scheduler.ts +50 -26
- package/src/sync/backup-files.ts +5 -0
- package/src/sync/gist/gist-msgkeys-catalog.test.ts +33 -0
- package/src/sync/gist/gist-msgkeys.test.ts +71 -0
- package/src/sync/gist/gist-transport.test.ts +308 -0
- package/src/sync/gist/gist-transport.ts +550 -0
- package/src/sync/gist/messages.ts +49 -0
- package/src/sync/retention-prune-all-off.test.ts +50 -0
- package/src/sync/s3/cloud-config.test.ts +283 -0
- package/src/sync/s3/messages.ts +102 -0
- package/src/sync/s3/s3-delete-index-404.test.ts +170 -0
- package/src/sync/s3/s3-delete-index-unreadable.test.ts +141 -0
- package/src/sync/s3/s3-msgkeys.test.ts +101 -0
- package/src/sync/s3/s3-providers.ts +213 -0
- package/src/sync/s3/s3-transport.test.ts +394 -0
- package/src/sync/s3/s3-transport.ts +651 -0
- package/src/sync/s3/s3-upload-index-404.test.ts +171 -0
- package/src/sync/s3/sigv4.test.ts +219 -0
- package/src/sync/s3/sigv4.ts +283 -0
- package/src/sync/sync-config.test.ts +237 -5
- package/src/sync/sync-config.ts +841 -52
- package/src/sync/sync-engine-download-tombstone.test.ts +73 -0
- package/src/sync/sync-engine.ts +5 -3
- package/src/sync/sync-history.ts +1 -1
- package/src/sync/webdav/webdav-delete-index-404.test.ts +178 -0
- package/src/sync/webdav/webdav-delete-index-unreadable.test.ts +143 -0
- package/src/sync/webdav/webdav-msgkeys.test.ts +53 -0
- package/src/sync/webdav/webdav-transport.test.ts +9 -5
- package/src/sync/webdav/webdav-transport.ts +99 -17
- package/src/sync/webdav/webdav-upload-index-404.test.ts +177 -0
- package/src/ui/commands.test.ts +4 -1
- package/src/ui/commands.ts +46 -12
- package/src/ui/disk-usage-view.test.ts +63 -1
- package/src/ui/disk-usage-view.ts +93 -1
- package/src/ui/dsh-profiles-incomplete-view.test.ts +102 -0
- package/src/ui/dsh-profiles-view.ts +94 -0
- package/src/ui/foreign-view.test.ts +241 -0
- package/src/ui/foreign-view.ts +362 -0
- package/src/ui/i18n.ts +164 -0
- package/src/ui/mid-ellipsis.test.ts +5 -10
- package/src/ui/session-inventory-view.test.ts +142 -2
- package/src/ui/session-inventory-view.ts +127 -0
- package/src/ui/session-layout-view.test.ts +361 -0
- package/src/ui/session-layout-view.ts +444 -0
- package/src/ui/sync-repository-picker.test.ts +130 -0
- package/src/ui/sync-repository-picker.ts +180 -0
- package/src/ui/sync-settings-view.test.ts +166 -0
- package/src/ui/sync-settings-view.ts +319 -9
- package/src/ui/types.ts +42 -0
- package/src/utils/asar-read.test.ts +330 -0
- package/src/utils/asar-read.ts +606 -0
- package/src/utils/env-lock.test.ts +55 -0
- package/src/utils/env-lock.ts +6 -1
- package/src/utils/patch-yaml.test.ts +112 -0
- package/src/utils/patch-yaml.ts +72 -0
- package/src/utils/paths.test.ts +63 -0
- package/src/utils/paths.ts +25 -3
- package/src/utils/recursive-walk.test.ts +20 -0
- package/src/utils/recursive-walk.ts +12 -13
- package/src/utils/session-format.test.ts +113 -2
- package/src/utils/session-format.ts +127 -5
- package/src/utils/session-health-scan.test.ts +364 -1
- package/src/utils/session-health-scan.ts +434 -15
- package/src/utils/session-layout-repair-service.test.ts +362 -0
- package/src/utils/session-layout-repair-service.ts +568 -0
- package/src/utils/session-log-repair.test.ts +260 -1
- package/src/utils/session-log-repair.ts +294 -55
- package/src/utils/session-repair-service.test.ts +148 -1
- package/src/utils/session-repair-service.ts +165 -10
- package/src/utils/session-row-facts.test.ts +159 -0
- package/src/utils/session-row-facts.ts +187 -0
- package/src/utils/session-verify.test.ts +762 -0
- package/src/utils/session-verify.ts +784 -0
- package/src/utils/shared-constants.ts +82 -0
package/src/cli/web/web.test.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* 钉住的语义(这些是安全边界,不是实现细节):
|
|
5
5
|
* - **一次性 token**:不带 token 的请求一律 403;token 只能换一次会话 cookie;
|
|
6
6
|
* - **cookie 不可猜**:换到 cookie 后可以正常访问页面,伪造 cookie 仍然 403;
|
|
7
|
-
* -
|
|
7
|
+
* - **页面只读、写动作显式**:页面只用 GET 渲染;写动作是独立的 POST 表单(一次性 action token
|
|
8
|
+
* + 安全门),任何不在声明内的方法一律 405 —— 且必须渲染成 HTML 错误页而不是裸 JSON(cli-F4);
|
|
8
9
|
* - **只在本机**:非回环来源一律 403(kit 的围栏);
|
|
9
10
|
* - **不写盘**:打开首页/磁盘/会话页不产生任何文件(零写入)。
|
|
10
11
|
*/
|
|
@@ -15,9 +16,29 @@ import { readFileSync } from 'node:fs'
|
|
|
15
16
|
import http from 'node:http'
|
|
16
17
|
import os from 'node:os'
|
|
17
18
|
import path from 'node:path'
|
|
19
|
+
import { fileURLToPath } from 'node:url'
|
|
18
20
|
|
|
19
21
|
import { startConsoleServer, CONSOLE_SESSION_COOKIE, type ConsoleServerHandle } from './server.ts'
|
|
20
|
-
import
|
|
22
|
+
import {
|
|
23
|
+
renderDiskPage,
|
|
24
|
+
renderExportPage,
|
|
25
|
+
renderHomePage,
|
|
26
|
+
renderLockPage,
|
|
27
|
+
renderMessagePage,
|
|
28
|
+
renderProfilesPage,
|
|
29
|
+
renderReinstallPage,
|
|
30
|
+
renderRestorePage,
|
|
31
|
+
renderResultPage,
|
|
32
|
+
renderSessionsPage,
|
|
33
|
+
renderUnlockPage,
|
|
34
|
+
renderVerifyPage,
|
|
35
|
+
type InlineRepairView,
|
|
36
|
+
} from './page.ts'
|
|
37
|
+
import type { LockReport, RescuePaths, RescueStatus, RestorePlanView } from '../actions.ts'
|
|
38
|
+
import type { SessionHealthScanResult } from '../../utils/session-health-scan.ts'
|
|
39
|
+
import { DISK_USAGE_AREAS, type DiskUsageArea, type DiskUsageAreaReport, type DiskUsageReport } from '../../core/disk-usage.ts'
|
|
40
|
+
import type { SnapshotMeta } from '../../core/restore.ts'
|
|
41
|
+
import type { BackupFileMeta } from '../../sync/backup-files.ts'
|
|
21
42
|
|
|
22
43
|
const silentIo = { log: () => undefined, error: () => undefined }
|
|
23
44
|
|
|
@@ -196,6 +217,22 @@ async function getPage(handle: ConsoleServerHandle, cookie: string, path: string
|
|
|
196
217
|
return await res.text()
|
|
197
218
|
}
|
|
198
219
|
|
|
220
|
+
/**
|
|
221
|
+
* 抠出某一节卡片的 HTML(`<h2>标题…</h2><section class="card">…</section>`)。
|
|
222
|
+
*
|
|
223
|
+
* 为什么必须按卡片切:页脚(renderFooter)**每一页**都印着
|
|
224
|
+
* `dsh-config-manager sessions repair --apply — 会话布局归位(本页按同一实现执行)`,
|
|
225
|
+
* 所以整页级断言分不清「修复二段把 sessions repair 说成等价通道」与「页脚那条成立的布局归位命令」
|
|
226
|
+
* —— 旧断言正是在这一点上是**假阴性**(C1b 实测)。
|
|
227
|
+
*/
|
|
228
|
+
function cardOf(html: string, titlePrefix: string): string {
|
|
229
|
+
const start = html.indexOf('<h2>' + titlePrefix)
|
|
230
|
+
assert.notEqual(start, -1, '页面上找不到卡片:' + titlePrefix)
|
|
231
|
+
const end = html.indexOf('</section>', start)
|
|
232
|
+
assert.notEqual(end, -1, '卡片未闭合:' + titlePrefix)
|
|
233
|
+
return html.slice(start, end)
|
|
234
|
+
}
|
|
235
|
+
|
|
199
236
|
async function postForm(
|
|
200
237
|
handle: ConsoleServerHandle,
|
|
201
238
|
cookie: string,
|
|
@@ -777,10 +814,316 @@ test('R1-02 就地修复入口:能力边界如实呈现(只做重放重复
|
|
|
777
814
|
// 分母必须给出来(「没发现」不能是拍胸脯)
|
|
778
815
|
assert.match(html, /探测了 <strong>0<\/strong> 条/)
|
|
779
816
|
assert.match(html, /本次没有发现可零损失修复的会话/)
|
|
780
|
-
assert.match(html, /dsh-config-manager sessions repair --apply/)
|
|
781
817
|
})
|
|
782
818
|
})
|
|
783
819
|
|
|
820
|
+
/**
|
|
821
|
+
* C1b:救急台「修复二」段不得再把 `sessions repair --apply` 说成等价通道(它不碰字节),
|
|
822
|
+
* 同时「修复一:会话布局归位」卡里**成立**的等价命令**必须还在** —— 防以后一刀切删掉那一处。
|
|
823
|
+
*/
|
|
824
|
+
test('R1-02b 「修复二」段不把 sessions repair 说成等价通道;「修复一」布局归位卡的等价命令仍在', async () => {
|
|
825
|
+
await withConsole(async (handle, paths) => {
|
|
826
|
+
const { cookie } = await bootstrap(handle)
|
|
827
|
+
|
|
828
|
+
// ① 空盘(没有可零损失修项):这一段最容易把「布局归位」说成「重放去重」的等价通道
|
|
829
|
+
const empty = await getPage(handle, cookie!, '/sessions')
|
|
830
|
+
const emptyInline = cardOf(empty, '修复二:重放重复行')
|
|
831
|
+
assert.doesNotMatch(emptyInline, /等价命令/, '修复二段不得再出现「等价命令」表述')
|
|
832
|
+
assert.doesNotMatch(emptyInline, /sessions repair --apply/, '修复二段不得把 sessions repair --apply 当成出口')
|
|
833
|
+
// 新表述:指向本救急台自己的就地操作 + 写明 sessions repair 的职责边界
|
|
834
|
+
assert.match(emptyInline, /职责边界/)
|
|
835
|
+
assert.match(emptyInline, /sessions repair \[--fix\]/)
|
|
836
|
+
assert.match(emptyInline, /不改写会话字节/)
|
|
837
|
+
assert.match(emptyInline, /dcm web/)
|
|
838
|
+
assert.match(emptyInline, /修复选中的会话(零损失)/)
|
|
839
|
+
|
|
840
|
+
// ② 造一条错位会话 → 出现可执行的布局归位表单:那里的等价命令是**成立**的,必须保留
|
|
841
|
+
const { encodeZstdFrame } = await import('../../utils/zstd-frame.ts')
|
|
842
|
+
const dir = path.join(paths.homeDir, 'sessions', '--D-Ghost-proj--', 'session-layout')
|
|
843
|
+
await fs.mkdir(dir, { recursive: true })
|
|
844
|
+
await fs.writeFile(path.join(dir, 'session.v3.jsonl.zstd'), Buffer.concat([
|
|
845
|
+
encodeZstdFrame(Buffer.from(JSON.stringify({ version: 3, id: 'session-layout', cwd: 'D:/Real/proj' }) + '\n', 'utf8')),
|
|
846
|
+
encodeZstdFrame(Buffer.from('{"seq":1}\n', 'utf8')),
|
|
847
|
+
]))
|
|
848
|
+
|
|
849
|
+
const page = await getPage(handle, cookie!, '/sessions')
|
|
850
|
+
const layout = cardOf(page, '修复一:会话布局归位')
|
|
851
|
+
assert.match(layout, /等价命令:dsh-config-manager sessions repair --apply/, '布局归位卡的等价命令是成立的,不得被一刀切删掉')
|
|
852
|
+
const inline = cardOf(page, '修复二:重放重复行')
|
|
853
|
+
assert.doesNotMatch(inline, /等价命令/, '修复二段(有可修项时)同样不得出现等价命令表述')
|
|
854
|
+
assert.match(inline, /职责边界/)
|
|
855
|
+
})
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* C1c:会话页的「写入口」事实必须唯一且真实,且卡片段里不得再把 `**` 当强调符号输出成字面星号。
|
|
860
|
+
*
|
|
861
|
+
* 这里**直接渲染** renderSessionsPage(页面是服务端直出 HTML,渲染结果就是用户看到的字节):
|
|
862
|
+
* 「修复二」段有两条分支(无零损失可修项 → 只报告分布段;有可修项 → 确认表单),
|
|
863
|
+
* 两条分支过去各有一处 `**`,走 HTTP + 造会话夹具只能覆盖其中一条。
|
|
864
|
+
*/
|
|
865
|
+
test('C1c 会话页写入口表述真实(不说「本页不提供」)+ 修复二段无字面 **', () => {
|
|
866
|
+
const paths: RescuePaths = {
|
|
867
|
+
homeDir: 'D:/home',
|
|
868
|
+
dataDir: 'D:/home/dsh-config-manager',
|
|
869
|
+
snapshotsDir: 'D:/home/dsh-config-manager/snapshots',
|
|
870
|
+
exportsDir: 'D:/home/dsh-config-manager/exports',
|
|
871
|
+
locksDir: 'D:/home/dsh-config-manager/locks',
|
|
872
|
+
controlRoots: ['D:/home/dsh-config-manager'],
|
|
873
|
+
profile: 'web',
|
|
874
|
+
}
|
|
875
|
+
const result: SessionHealthScanResult = {
|
|
876
|
+
rows: [{
|
|
877
|
+
unitId: '--D--/a',
|
|
878
|
+
sessionId: 'a',
|
|
879
|
+
projectKey: '--D--',
|
|
880
|
+
severity: 'nextRequestFails',
|
|
881
|
+
issues: [{ code: 'seq-gap', severity: 'nextRequestFails' }],
|
|
882
|
+
}],
|
|
883
|
+
summary: {
|
|
884
|
+
total: 1,
|
|
885
|
+
bySeverity: { blocksStartup: 0, unloadable: 0, nextRequestFails: 1, invisible: 0, ok: 0 },
|
|
886
|
+
structurallyChecked: 1,
|
|
887
|
+
deepVerified: 1,
|
|
888
|
+
deepUnverified: 0,
|
|
889
|
+
},
|
|
890
|
+
untested: 0,
|
|
891
|
+
unreadableEntries: 0,
|
|
892
|
+
sessionsDir: 'D:/home/sessions',
|
|
893
|
+
sessionsDirExists: true,
|
|
894
|
+
}
|
|
895
|
+
const blockedInline: InlineRepairView = { fixable: [], probed: 3, blocked: [{ reason: 'seq-gap', count: 2 }] }
|
|
896
|
+
const blocked = renderSessionsPage(result, undefined, '0.0.0-test', paths, null, 'tok', '', null, blockedInline)
|
|
897
|
+
|
|
898
|
+
// ① fixHint(有「需要处理」的行才渲染):不得再声称「本页不提供」——同页「修复一」卡就是本页提供的写动作
|
|
899
|
+
const problems = cardOf(blocked, '需要处理的会话(')
|
|
900
|
+
assert.doesNotMatch(problems, /本页不提供/, 'fixHint 不得再声称本页不提供该写动作(自相矛盾)')
|
|
901
|
+
assert.match(problems, /本页<strong>提供<\/strong>两个写入口/, '必须正向写清本页提供哪些写入口')
|
|
902
|
+
assert.match(problems, /sessions repair --apply/, '「修复一」的等价命令是成立的,仍要如实给出')
|
|
903
|
+
|
|
904
|
+
// ② 无零损失可修项 + 有「只报告」原因分布 → 该分支确实渲染,且强调是真 <strong> 而不是字面 **
|
|
905
|
+
assert.match(blockedInlineCard(blocked), /只报告/, '该分支必须真的渲染「只报告」文案(否则下面的断言是假阴性)')
|
|
906
|
+
assert.match(blockedInlineCard(blocked), /<strong>只报告<\/strong>/, '强调必须是真 <strong>')
|
|
907
|
+
assert.doesNotMatch(blockedInlineCard(blocked), /\*\*/, '卡片段里不得再出现字面 **')
|
|
908
|
+
|
|
909
|
+
// ③ 有零损失可修项 → 确认表单分支(consequence 里也曾有字面 **)
|
|
910
|
+
const fixable = renderSessionsPage(result, undefined, '0.0.0-test', paths, null, 'tok', '', null,
|
|
911
|
+
{ fixable: [{ unitId: '--D--/a', droppedRows: 3 }], probed: 1, blocked: [] })
|
|
912
|
+
const fixableCard = cardOf(fixable, '修复二:重放重复行')
|
|
913
|
+
assert.match(fixableCard, /修复选中的会话(零损失)/, '该分支必须真的渲染确认表单')
|
|
914
|
+
assert.doesNotMatch(fixableCard, /\*\*/, '确认表单段同样不得出现字面 **')
|
|
915
|
+
|
|
916
|
+
// ④ 不误伤:页脚速查表照旧;「修复一」卡里那处**成立**的等价命令仍在源码里(渲染路径见 R1-02b 的真实磁盘用例)
|
|
917
|
+
assert.match(fixable, /会话布局归位(本页按同一实现执行)/, '页脚速查表不得被误改')
|
|
918
|
+
|
|
919
|
+
// ⑤ 源码级:fixHint 那一小段里不得再有「本页不提供」(页面另外两处是别的写动作,别误伤)
|
|
920
|
+
const source = readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'page.ts'), 'utf8')
|
|
921
|
+
const at = source.indexOf('const fixHint =')
|
|
922
|
+
assert.notEqual(at, -1, '源码里找不到 fixHint')
|
|
923
|
+
const snippet = source.slice(at, source.indexOf('parts.push(section(', at))
|
|
924
|
+
assert.doesNotMatch(snippet, /本页不提供/, 'fixHint 源码段不得再出现「本页不提供」')
|
|
925
|
+
assert.match(snippet, /PAGE_SESSION_WRITE_ENTRIES/, 'fixHint 只能渲染单一事实源常量')
|
|
926
|
+
// 「修复一」布局归位卡的等价命令是**成立**的(本页与 CLI 同实现),不得被这轮收尾一刀切删掉
|
|
927
|
+
assert.ok(source.includes('等价命令:dsh-config-manager sessions repair --apply'), '布局归位卡的等价命令必须仍在')
|
|
928
|
+
})
|
|
929
|
+
|
|
930
|
+
/** 取「修复二」段卡片(C1c 用例内部用,避免页脚命中造成假阴性)。 */
|
|
931
|
+
function blockedInlineCard(html: string): string {
|
|
932
|
+
return cardOf(html, '修复二:重放重复行')
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* 取页面**正文**(`<body>` … `<footer>`)。
|
|
937
|
+
*
|
|
938
|
+
* 为什么要切片:页脚(renderFooter)**每一页**都印同一份速查表,整页 match 会把它命中成假阴性
|
|
939
|
+
* (C1b 实测过),`<head>` 里的样式/脚本也不是用户看到的正文。
|
|
940
|
+
*/
|
|
941
|
+
function bodyOf(html: string): string {
|
|
942
|
+
const start = html.indexOf('<body>')
|
|
943
|
+
assert.notEqual(start, -1, '页面没有 <body>')
|
|
944
|
+
const end = html.indexOf('<footer>', start)
|
|
945
|
+
assert.notEqual(end, -1, '页面没有 <footer>(速查表页脚是每页都渲染的)')
|
|
946
|
+
return html.slice(start, end)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* C1d:救急台是**多页**的,所以每一页正文里都不许出现
|
|
951
|
+
* ① 字面 `**`(强调符号被当正文输出,用户看到星号);
|
|
952
|
+
* ② 空泛的「本页不提供」(captain 裁决:要么指向**本页内真实存在**的入口,要么**点名到页**)。
|
|
953
|
+
*
|
|
954
|
+
* **覆盖口径**:枚举来源 = `page.ts` 导出的全部 `render*Page(`(= 整页渲染入口),并用集合相等断言钉住 ——
|
|
955
|
+
* 新增一个页面而不登记进 `pages` 会直接红(此前只渲染 7 页却宣称「各页正文」,t11 抓到)。
|
|
956
|
+
* 断言落在**正文切片**上(页脚不计入,避免 C1b 遇到过的假阴性);每页先跑一条**非空证明**,
|
|
957
|
+
* 证明该分支确实渲染了,否则「不存在」类断言会退化成空态假阴性。
|
|
958
|
+
*/
|
|
959
|
+
test('C1d 各页正文无字面 ** 且无空泛「本页不提供」(枚举 = page.ts 全部 render*Page)', () => {
|
|
960
|
+
const paths: RescuePaths = {
|
|
961
|
+
homeDir: 'D:/home',
|
|
962
|
+
dataDir: 'D:/home/dsh-config-manager',
|
|
963
|
+
snapshotsDir: 'D:/home/dsh-config-manager/snapshots',
|
|
964
|
+
exportsDir: 'D:/home/dsh-config-manager/exports',
|
|
965
|
+
locksDir: 'D:/home/dsh-config-manager/locks',
|
|
966
|
+
controlRoots: ['D:/home/dsh-config-manager'],
|
|
967
|
+
profile: 'web',
|
|
968
|
+
}
|
|
969
|
+
const version = '0.0.0-test'
|
|
970
|
+
const snapshot: SnapshotMeta = {
|
|
971
|
+
id: 'snap-1',
|
|
972
|
+
createdAt: '2026-10-07T00:00:00.000Z',
|
|
973
|
+
sourceZip: 'dsh-config.zip',
|
|
974
|
+
entryCount: 3,
|
|
975
|
+
hostFileBackupCount: 0,
|
|
976
|
+
beforePluginCount: 0,
|
|
977
|
+
}
|
|
978
|
+
// 选中某快照后的恢复计划:只有带危险动作,页面才会渲染 POST /restore/run 的确认表单
|
|
979
|
+
const restorePlan: RestorePlanView = {
|
|
980
|
+
ok: true,
|
|
981
|
+
code: 'ok',
|
|
982
|
+
message: '',
|
|
983
|
+
snapshotId: 'snap-1',
|
|
984
|
+
pluginBaselineConfirmed: false,
|
|
985
|
+
actions: [{ kind: 'hostFileRestore', description: '还原 settings', target: 'D:/home/settings.yaml', dangerous: true }],
|
|
986
|
+
summary: { hostFileRestores: 1 },
|
|
987
|
+
}
|
|
988
|
+
const lock: LockReport = { locksDir: paths.locksDir, present: true, state: 'LOCKED' }
|
|
989
|
+
const homeLock: LockReport = { locksDir: paths.locksDir, present: false, state: 'NONE' }
|
|
990
|
+
const status: RescueStatus = {
|
|
991
|
+
generatedAt: '2026-10-07T00:00:00.000Z',
|
|
992
|
+
paths,
|
|
993
|
+
safeMode: [],
|
|
994
|
+
lock: homeLock,
|
|
995
|
+
instances: [],
|
|
996
|
+
snapshots: [snapshot],
|
|
997
|
+
backups: [],
|
|
998
|
+
errors: [],
|
|
999
|
+
}
|
|
1000
|
+
const areas = Object.fromEntries(DISK_USAGE_AREAS.map((area) => [area, {
|
|
1001
|
+
sizeBytes: 0,
|
|
1002
|
+
fileCount: 0,
|
|
1003
|
+
policy: 'regenerable' as const,
|
|
1004
|
+
unreadable: false,
|
|
1005
|
+
}])) as Record<DiskUsageArea, DiskUsageAreaReport>
|
|
1006
|
+
const diskReport: DiskUsageReport = {
|
|
1007
|
+
dataDir: paths.dataDir,
|
|
1008
|
+
totalBytes: 0,
|
|
1009
|
+
totalFiles: 0,
|
|
1010
|
+
reclaimableBytes: 0,
|
|
1011
|
+
expiredBytes: 0,
|
|
1012
|
+
areas,
|
|
1013
|
+
backupRetention: { keepLast: 10, latestBackupBytes: 0, latestBackupAt: null },
|
|
1014
|
+
}
|
|
1015
|
+
const encrypted: BackupFileMeta[] = [{
|
|
1016
|
+
name: 'dsh-config-enc.zip',
|
|
1017
|
+
path: 'D:/home/dsh-config-manager/exports/dsh-config-enc.zip',
|
|
1018
|
+
sizeBytes: 2048,
|
|
1019
|
+
mtimeMs: 1_700_000_000_000,
|
|
1020
|
+
source: 'manual',
|
|
1021
|
+
}]
|
|
1022
|
+
|
|
1023
|
+
const home = renderHomePage({ status, version })
|
|
1024
|
+
const sessions = renderSessionsPage(
|
|
1025
|
+
{
|
|
1026
|
+
rows: [{
|
|
1027
|
+
unitId: '--D--/a', sessionId: 'a', projectKey: '--D--', severity: 'nextRequestFails',
|
|
1028
|
+
issues: [{ code: 'seq-gap', severity: 'nextRequestFails' }],
|
|
1029
|
+
}],
|
|
1030
|
+
summary: {
|
|
1031
|
+
total: 1, bySeverity: { blocksStartup: 0, unloadable: 0, nextRequestFails: 1, invisible: 0, ok: 0 },
|
|
1032
|
+
structurallyChecked: 1, deepVerified: 1, deepUnverified: 0,
|
|
1033
|
+
},
|
|
1034
|
+
untested: 0, unreadableEntries: 0, sessionsDir: 'D:/home/sessions', sessionsDirExists: true,
|
|
1035
|
+
},
|
|
1036
|
+
undefined, version, paths, null, 'tok', '', null,
|
|
1037
|
+
{ fixable: [], probed: 1, blocked: [{ reason: 'seq-gap', count: 1 }] },
|
|
1038
|
+
)
|
|
1039
|
+
const restore = renderRestorePage([snapshot], restorePlan, version, paths, 'tok')
|
|
1040
|
+
const disk = renderDiskPage(diskReport, version, paths, 'tok', '')
|
|
1041
|
+
const lockPage = renderLockPage(lock, version, paths, 'tok', '')
|
|
1042
|
+
const unlock = renderUnlockPage(encrypted, version, paths, 'tok', '')
|
|
1043
|
+
const reinstall = renderReinstallPage(
|
|
1044
|
+
[{ id: 'plugins', label: '插件', desc: '清空 ~/.dsh 的插件目录', destructive: true, defaultOn: false }],
|
|
1045
|
+
version, paths, 'tok',
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
// 导出页(本轮纳入 sweep 的那一页;banner 正文曾出现字面 **)
|
|
1049
|
+
const exportPage = renderExportPage(['settings', 'providers'], ['mcp'], version, paths, 'tok')
|
|
1050
|
+
const verify = renderVerifyPage({
|
|
1051
|
+
file: 'D:/home/dsh-config-manager/exports/dsh-config.zip', verdict: 'OK',
|
|
1052
|
+
sizeBytes: 2048, entryCount: 3, sections: ['settings'], errors: [], warnings: [],
|
|
1053
|
+
}, version, paths)
|
|
1054
|
+
const result = renderResultPage('就地修复完成(零损失)', ['✔ --D--/a:已丢弃 2 行重放重复事件'], version, paths, 'ok', '/sessions')
|
|
1055
|
+
const profiles = renderProfilesPage({
|
|
1056
|
+
ok: true, profilesDir: 'D:/home/profiles', errors: [],
|
|
1057
|
+
rows: [{
|
|
1058
|
+
name: 'web', shape: 'web', bundles: 3, dependencies: 12, hasNodeModules: true,
|
|
1059
|
+
patchEntryCount: 2, updatedAtMs: 1_700_000_000_000, dshVersion: '0.2.0', sessionFormatVersion: 4,
|
|
1060
|
+
launchable: true, owned: false, running: false, port: null, url: null, pid: null,
|
|
1061
|
+
}],
|
|
1062
|
+
}, version, paths, 'tok', '', '')
|
|
1063
|
+
const message = renderMessagePage('403 需要访问链接', '请回到终端,用打印出来的带 token 链接打开本页。', '链接用过即废。')
|
|
1064
|
+
|
|
1065
|
+
// 枚举来源:page.ts 里**全部** `export function render*Page(`(每个 = 一个整页渲染入口;
|
|
1066
|
+
// `renderLayout` 是外壳、`renderConfirmForm` 是片段,不在此列)。
|
|
1067
|
+
// 下面这条集合相等断言保证「新增页面不登记进 sweep」会红 —— 本轮之前 C1d 只渲染 7 页却宣称「各页正文」,
|
|
1068
|
+
// 正是缺这条(t11 抓到)。
|
|
1069
|
+
const declared = [...readFileSync(new URL('./page.ts', import.meta.url), 'utf8')
|
|
1070
|
+
.matchAll(/export function (render\w*Page)\s*\(/g)].map((m) => m[1] ?? '').sort()
|
|
1071
|
+
|
|
1072
|
+
const pages: Array<{ fn: string; name: string; html: string; slice?: (html: string) => string }> = [
|
|
1073
|
+
{ fn: 'renderHomePage', name: '首页', html: home },
|
|
1074
|
+
{ fn: 'renderSessionsPage', name: '会话', html: sessions },
|
|
1075
|
+
{ fn: 'renderRestorePage', name: '恢复', html: restore },
|
|
1076
|
+
{ fn: 'renderDiskPage', name: '磁盘', html: disk },
|
|
1077
|
+
{ fn: 'renderLockPage', name: '残留锁', html: lockPage },
|
|
1078
|
+
{ fn: 'renderUnlockPage', name: '解锁', html: unlock },
|
|
1079
|
+
{ fn: 'renderReinstallPage', name: '重装', html: reinstall },
|
|
1080
|
+
{ fn: 'renderVerifyPage', name: '备份自检', html: verify },
|
|
1081
|
+
{ fn: 'renderExportPage', name: '导出', html: exportPage },
|
|
1082
|
+
{ fn: 'renderProfilesPage', name: '档案', html: profiles },
|
|
1083
|
+
{ fn: 'renderResultPage', name: '写动作结果', html: result },
|
|
1084
|
+
// 403/404 极简页**没有 <footer>**(刻意不依赖任何目录事实),所以不走 bodyOf 的切片,取 <body> 起的整段。
|
|
1085
|
+
{ fn: 'renderMessagePage', name: '错误极简页', html: message, slice: (h) => h.slice(h.indexOf('<body>')) },
|
|
1086
|
+
]
|
|
1087
|
+
assert.deepEqual(
|
|
1088
|
+
pages.map((p) => p.fn).sort(),
|
|
1089
|
+
declared,
|
|
1090
|
+
'sweep 必须穷尽 page.ts 的全部 render*Page(枚举来源 = 该模块的 export function render*Page)',
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
// 非空证明表(每页一条):先证明「该段确实渲染了」,否则上面的「不得出现 **」会退化成空态假阴性
|
|
1094
|
+
const proofs: Record<string, RegExp> = {
|
|
1095
|
+
renderHomePage: /需要终端里打印的 6 位确认码/,
|
|
1096
|
+
renderSessionsPage: /需要处理的会话/,
|
|
1097
|
+
renderRestorePage: /恢复计划(/,
|
|
1098
|
+
renderDiskPage: /导出产物只在勾选时、且只按保留期回收/,
|
|
1099
|
+
renderLockPage: /不会自动回收/,
|
|
1100
|
+
renderUnlockPage: /会在内存中解出明文 ZIP/,
|
|
1101
|
+
renderReinstallPage: /只在终端里打印的 6 位确认码/,
|
|
1102
|
+
renderExportPage: /settings \/ providers \/ mcp 等结构化分区的值必须经 DSH 服务读取/,
|
|
1103
|
+
renderVerifyPage: /自检通过/,
|
|
1104
|
+
renderProfilesPage: /档案 = DSH 的 profile/,
|
|
1105
|
+
renderResultPage: /逐条结果/,
|
|
1106
|
+
renderMessagePage: /403/,
|
|
1107
|
+
}
|
|
1108
|
+
for (const page of pages) {
|
|
1109
|
+
const body = page.slice !== undefined ? page.slice(page.html) : bodyOf(page.html)
|
|
1110
|
+
const proof = proofs[page.fn]
|
|
1111
|
+
assert.ok(proof !== undefined, page.name + ':sweep 里每页都必须有非空证明(否则可能空态假阴性)')
|
|
1112
|
+
assert.match(body, proof, page.name + ':该段确实渲染(非空证明,避免空态假阴性)')
|
|
1113
|
+
assert.doesNotMatch(body, /\*\*/, page.name + ':正文不得出现字面 **(强调符号被当正文输出)')
|
|
1114
|
+
assert.doesNotMatch(body, /本页不提供/, page.name + ':正文不得出现空泛的「本页不提供」(要么指向本页入口,要么点名到页)')
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// 交叉证明(原有用例,保留):被点名的页 / 入口确实存在
|
|
1118
|
+
const homeBody = bodyOf(home)
|
|
1119
|
+
assert.match(homeBody, /请在「恢复」页执行/, '首页快照卡必须点名到「恢复」页')
|
|
1120
|
+
assert.match(homeBody, /本页(首页)不提供/, '「本页」必须限定为首页,不留空泛说法')
|
|
1121
|
+
const diskBody = bodyOf(disk)
|
|
1122
|
+
assert.match(diskBody, /清理是写动作,就在本页下方「清理磁盘(写动作)」卡里执行/, '磁盘保留期说明必须指向本页的清理入口')
|
|
1123
|
+
assert.match(diskBody, /action="\/disk\/cleanup"/, '被指向的入口在本页真实存在(裁决 (a) 成立)')
|
|
1124
|
+
assert.match(bodyOf(restore), /action="\/restore\/run"/, '点名到的「恢复」页确实提供该写动作(裁决 (b) 成立)')
|
|
1125
|
+
})
|
|
1126
|
+
|
|
784
1127
|
test('R1-03 就地修复写路由:token 一次性;没 token 一律 400(不写任何字节)', async () => {
|
|
785
1128
|
await withConsole(async (handle) => {
|
|
786
1129
|
const { cookie } = await bootstrap(handle)
|
|
@@ -953,7 +1296,9 @@ function runConsoleScript(cookie: string): {
|
|
|
953
1296
|
querySelectorAll: (sel: string) => (sel === '[data-dcm-theme]' ? buttons : []),
|
|
954
1297
|
addEventListener: (type: string, fn: (event: { target: unknown }) => void) => { if (type === 'click') clicks.push(fn) },
|
|
955
1298
|
}
|
|
956
|
-
|
|
1299
|
+
// issue #70:源码字面量守卫必须与检出 EOL 无关(Windows core.autocrlf=true ⇒ CRLF 工作区)。
|
|
1300
|
+
// 仓库既有 11 处同款先例(readFileSync 后折 LF),这三处此前漏了。
|
|
1301
|
+
const script = readFileSync(new URL('./client-script.ts', import.meta.url), 'utf8').replace(/\r\n/g, '\n')
|
|
957
1302
|
const body = /export const CONSOLE_SCRIPT = `([\s\S]*?)`\n/.exec(script)?.[1]
|
|
958
1303
|
assert.ok(body, 'client-script.ts 必须导出 CONSOLE_SCRIPT 模板字符串')
|
|
959
1304
|
// 只喂脚本真正会碰的浏览器 API(显式列出,不用 Proxy 兜底 —— 缺哪个就报错,别让假 DOM 悄悄骗过测试)
|
|
@@ -1001,3 +1346,101 @@ test('R3-04 主题脚本的真行为:cookie 解析 / 非法值回落自动 /
|
|
|
1001
1346
|
assert.equal(clicky.attrs['data-theme'], 'auto')
|
|
1002
1347
|
assert.match(clicky.cookieNow(), /dcm-theme=auto/)
|
|
1003
1348
|
})
|
|
1349
|
+
/* ------------------------------------------------ t26 新增回归(cli-F1/F3/F4/F6/F7) */
|
|
1350
|
+
|
|
1351
|
+
/** 造一条「本插件启动的实例」台账(pid = 当前进程,isProcessAlive 必然为真)。 */
|
|
1352
|
+
async function seedLaunchedInstance(paths: RescuePaths, name: string, url: string): Promise<void> {
|
|
1353
|
+
const profileDir = path.join(paths.homeDir, 'profiles', name)
|
|
1354
|
+
await fs.mkdir(profileDir, { recursive: true })
|
|
1355
|
+
await fs.writeFile(path.join(profileDir, 'package.json'), JSON.stringify({
|
|
1356
|
+
name, version: '0.0.0', dependencies: {},
|
|
1357
|
+
dsh: { profile: { bundles: ['@deepseek-ai/dsh-web-app'] } },
|
|
1358
|
+
}))
|
|
1359
|
+
await fs.mkdir(paths.dataDir, { recursive: true })
|
|
1360
|
+
await fs.writeFile(path.join(paths.dataDir, 'launches.json'), JSON.stringify({
|
|
1361
|
+
version: 1,
|
|
1362
|
+
launches: [{
|
|
1363
|
+
name, port: 3099, pid: process.pid, url,
|
|
1364
|
+
logFile: path.join(paths.dataDir, 'launch.log'), startedAt: new Date().toISOString(),
|
|
1365
|
+
}],
|
|
1366
|
+
}))
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
test('cli-F1 救急台回传的授权 URL 不得被脱敏抹掉(入口链接逐字节等于台账 URL)', async () => {
|
|
1370
|
+
await withConsole(async (handle, paths) => {
|
|
1371
|
+
const authUrl = 'http://127.0.0.1:3099/?token=probe-token-abc123'
|
|
1372
|
+
await seedLaunchedInstance(paths, 'probe-demo', authUrl)
|
|
1373
|
+
const { cookie } = await bootstrap(handle)
|
|
1374
|
+
const res = await fetch(handle.url.replace(/\/$/, '') + '/profiles', { headers: { cookie: cookie! } })
|
|
1375
|
+
assert.equal(res.status, 200)
|
|
1376
|
+
const html = await res.text()
|
|
1377
|
+
// 修复前:redact() 的 URL_QUERY_RE 把 token 值换成 ***REDACTED***,点开必然 401
|
|
1378
|
+
assert.ok(html.includes('<a href="' + authUrl + '">打开实例</a>'), '入口链接必须带真实 token')
|
|
1379
|
+
assert.doesNotMatch(html, /token=\*\*\*REDACTED\*\*\*/)
|
|
1380
|
+
})
|
|
1381
|
+
})
|
|
1382
|
+
|
|
1383
|
+
test('cli-F3 页面必须禁止被 iframe 嵌入(frame-ancestors + X-Frame-Options)', async () => {
|
|
1384
|
+
await withConsole(async (handle) => {
|
|
1385
|
+
const { cookie } = await bootstrap(handle)
|
|
1386
|
+
const res = await fetch(handle.url, { headers: { cookie: cookie! } })
|
|
1387
|
+
assert.equal(res.status, 200)
|
|
1388
|
+
const csp = res.headers.get('content-security-policy') ?? ''
|
|
1389
|
+
assert.match(csp, /frame-ancestors 'none'/, 'default-src 不覆盖 frame-ancestors,必须显式声明')
|
|
1390
|
+
assert.equal(res.headers.get('x-frame-options'), 'DENY')
|
|
1391
|
+
// 403 极简页走同一个写出点,也必须带上
|
|
1392
|
+
const anon = await fetch(handle.url)
|
|
1393
|
+
assert.equal(anon.status, 403)
|
|
1394
|
+
assert.match(anon.headers.get('content-security-policy') ?? '', /frame-ancestors 'none'/)
|
|
1395
|
+
assert.equal(anon.headers.get('x-frame-options'), 'DENY')
|
|
1396
|
+
})
|
|
1397
|
+
})
|
|
1398
|
+
|
|
1399
|
+
test('cli-F4 方法不被允许时必须是 HTML 错误页,不得吐插件 API 形状的裸 JSON', async () => {
|
|
1400
|
+
await withConsole(async (handle) => {
|
|
1401
|
+
const { cookie } = await bootstrap(handle)
|
|
1402
|
+
const res = await fetch(handle.url, { method: 'POST', headers: { cookie: cookie! } })
|
|
1403
|
+
assert.equal(res.status, 405)
|
|
1404
|
+
const body = await res.text()
|
|
1405
|
+
assert.match(body, /<html lang="zh-CN">/, '405 必须渲染成页面')
|
|
1406
|
+
assert.ok(!body.startsWith('{"error"'), '不得是 kit 的 JSON 出口')
|
|
1407
|
+
// 方法判定不得改变「路由未命中」语义
|
|
1408
|
+
const nope = await fetch(handle.url.replace(/\/$/, '') + '/nope', { method: 'POST', headers: { cookie: cookie! } })
|
|
1409
|
+
assert.equal(nope.status, 404)
|
|
1410
|
+
})
|
|
1411
|
+
})
|
|
1412
|
+
|
|
1413
|
+
test('cli-F6 /healthz 的写路由自述必须与真实声明同源且完整', async () => {
|
|
1414
|
+
await withConsole(async (handle) => {
|
|
1415
|
+
const { cookie } = await bootstrap(handle)
|
|
1416
|
+
const res = await fetch(handle.url.replace(/\/$/, '') + '/healthz', { headers: { cookie: cookie! } })
|
|
1417
|
+
const payload = await res.json() as { writes: string[]; writeRoutes?: string[] }
|
|
1418
|
+
assert.deepEqual(payload.writes, ['sessions-repair', 'disk-cleanup', 'recover-stale-lock'], '既有 writes 契约不变')
|
|
1419
|
+
assert.ok(Array.isArray(payload.writeRoutes), '/healthz 必须自述全部写路由')
|
|
1420
|
+
const declared = payload.writeRoutes as string[]
|
|
1421
|
+
for (const line of [
|
|
1422
|
+
'POST /sessions/repair', 'POST /sessions/inline-repair', 'POST /disk/cleanup', 'POST /lock/recover',
|
|
1423
|
+
'POST /profiles/launch', 'POST /profiles/stop', 'POST /unlock/run', 'POST /restore/run',
|
|
1424
|
+
'POST /export/run', 'POST /reinstall/plan', 'POST /reinstall/run',
|
|
1425
|
+
]) {
|
|
1426
|
+
assert.ok(declared.includes(line), '缺写路由自述: ' + line + ' → ' + declared.join(','))
|
|
1427
|
+
}
|
|
1428
|
+
assert.ok(!declared.some((line) => line.startsWith('GET ')), 'GET 路由不得混进写自述')
|
|
1429
|
+
})
|
|
1430
|
+
})
|
|
1431
|
+
|
|
1432
|
+
test('cli-F7 //x 形态的请求目标不得被解析成首页', async () => {
|
|
1433
|
+
await withConsole(async (handle) => {
|
|
1434
|
+
const { cookie } = await bootstrap(handle)
|
|
1435
|
+
// 协议相对目标:修复前 new URL('//evil') 得到 authority=evil / pathname='/' → 冒充首页
|
|
1436
|
+
const evil = await fetch(handle.url.replace(/\/$/, '') + '//evil', { headers: { cookie: cookie! } })
|
|
1437
|
+
assert.equal(evil.status, 404, '//evil 不是合法页面,不得返回首页')
|
|
1438
|
+
// //disk 折叠成 /disk:必须是**磁盘页**(有该页专属的 section 标题),不是首页
|
|
1439
|
+
const disk = await fetch(handle.url.replace(/\/$/, '') + '//disk', { headers: { cookie: cookie! } })
|
|
1440
|
+
assert.equal(disk.status, 200)
|
|
1441
|
+
assert.match(await disk.text(), /磁盘占用(只读体检)/, '//disk 必须落到磁盘页而不是首页')
|
|
1442
|
+
const home = await fetch(handle.url, { headers: { cookie: cookie! } })
|
|
1443
|
+
assert.equal(home.status, 200)
|
|
1444
|
+
})
|
|
1445
|
+
})
|
|
1446
|
+
|