dsh-plugin-tool-management 0.1.1 → 0.1.3

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/lib/client.js CHANGED
@@ -32,16 +32,17 @@ window.__ModuleLoader__.load({
32
32
  '.dsm-detail-title-row{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
33
33
  '.dsm-note-user{color:var(--dsw-alias-label-tertiary)}' +
34
34
  `
35
- .dsm-section{box-sizing:border-box;display:flex;width:100%;max-width:820px;min-width:0;margin:0 auto;padding:2px 0 36px;container-type:inline-size;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary);font-family:inherit}.dsm-head{display:flex;flex-direction:column;align-items:stretch;gap:16px}.dsm-title-block{min-width:0}.dsm-title-row{display:flex;align-items:center;gap:8px 12px;min-width:0;flex-wrap:wrap}.dsm-feedback-links{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsm-title{margin:0;font-size:24px;line-height:32px;font-weight:600;letter-spacing:-.4px;white-space:nowrap}.dsm-feedback-link{display:inline-flex;min-height:28px;align-items:center;gap:5px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dsm-feedback-link:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsm-feedback-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsm-feedback-link svg{flex:none}.dsm-desc{margin:12px 0 0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}.dsm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;flex:none}
35
+ .dsm-tabs{display:flex;gap:4px;border-bottom:1px solid var(--dsw-alias-border-l1);margin-bottom:14px}.dsm-tab{padding:8px 14px;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.dsm-tab-active{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-success-primary)}.dsm-section{box-sizing:border-box;display:flex;width:100%;max-width:820px;min-width:0;margin:0 auto;padding:2px 0 36px;container-type:inline-size;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary);font-family:inherit}.dsm-head{display:flex;flex-direction:column;align-items:stretch;gap:16px}.dsm-title-block{min-width:0}.dsm-title-row{display:flex;align-items:center;gap:8px 12px;min-width:0;flex-wrap:wrap}.dsm-feedback-links{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsm-title{margin:0;font-size:24px;line-height:32px;font-weight:600;letter-spacing:-.4px;white-space:nowrap}.dsm-feedback-link{display:inline-flex;min-height:28px;align-items:center;gap:5px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dsm-feedback-link:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsm-feedback-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsm-feedback-link svg{flex:none}.dsm-desc{margin:12px 0 0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}.dsm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;flex:none}
36
36
  .dsm-btn{box-sizing:border-box;display:inline-flex;min-height:34px;align-items:center;justify-content:center;padding:0 13px;border:1px solid transparent;border-radius:8px;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;font-size:13px;font-weight:580;white-space:nowrap;cursor:pointer}.dsm-btn:hover:not(:disabled){filter:brightness(1.08)}.dsm-btn:disabled{opacity:.48;cursor:default}.dsm-btn-secondary,.dsm-btn-quiet{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsm-btn-quiet{min-height:28px;padding:0 9px;color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-btn-danger{border-color:var(--dsw-alias-state-error-primary);background:transparent;color:var(--dsw-alias-state-error-primary)}.dsm-btn:focus-visible,.dsm-control:focus-visible,.dsm-select-trigger:focus-visible,.dsm-source-head:focus-visible,.dsm-switch:focus-visible,.dsm-upload-link:focus-visible,.dsm-file-remove:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}
37
37
  .dsm-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-stat{padding:12px 14px;border-right:1px solid var(--dsw-alias-border-l1);font-size:13px;color:var(--dsw-alias-label-secondary)}.dsm-stat:last-child{border-right:0}.dsm-stat strong{margin-right:5px;color:var(--dsw-alias-label-primary);font-size:17px;font-weight:680}.dsm-filters{display:flex;gap:9px}.dsm-search{flex:1}.dsm-source-filter{width:210px;flex:none}
38
38
  .dsm-control,.dsm-select-trigger{box-sizing:border-box;width:100%;min-height:34px;padding:0 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}.dsm-control::placeholder{color:var(--dsw-alias-label-tertiary)}textarea.dsm-control{min-height:160px;padding-top:9px;resize:vertical;line-height:20px}.dsm-select{position:relative}.dsm-select-trigger{display:flex;align-items:center;justify-content:space-between;text-align:left;cursor:pointer}.dsm-select-menu{position:absolute;z-index:40;top:calc(100% + 5px);right:0;left:0;display:flex;max-height:260px;padding:5px;overflow:auto;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2)}.dsm-option{padding:8px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;text-align:left;cursor:pointer}.dsm-option:hover,.dsm-option[aria-selected=true]{background:var(--dsw-alias-interactive-bg-hover)}
39
- .dsm-sources{display:flex;flex-direction:column;gap:9px}.dsm-source{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-source-head{box-sizing:border-box;display:flex;width:100%;min-height:48px;align-items:center;padding:0 13px}.dsm-source-head-main{display:flex;min-width:0;min-height:48px;flex:1;align-items:center;gap:10px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.dsm-source-head:hover,.dsm-row:hover,.dsm-trash-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-source-title{font-size:14px;font-weight:650}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px}.dsm-path{min-width:0;margin-left:auto;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.dsm-source-actions{display:flex;align-items:center;gap:9px;margin-left:8px}.dsm-source-body{border-top:1px solid var(--dsw-alias-border-l1)}.dsm-source-note{padding:9px 13px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
39
+ .dsm-sources{display:flex;flex-direction:column;gap:9px}.dsm-source{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-source-head{box-sizing:border-box;display:flex;width:100%;min-height:48px;align-items:center;padding:0 13px}.dsm-source-head-main{display:flex;min-width:0;min-height:48px;flex:1;align-items:center;gap:10px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.dsm-source-head:hover,.dsm-row:hover,.dsm-trash-row:hover,.dsm-hist-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-source-title{font-size:14px;font-weight:650}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px}.dsm-path{min-width:0;margin-left:auto;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.dsm-source-actions{display:flex;align-items:center;gap:9px;margin-left:8px}.dsm-source-body{border-top:1px solid var(--dsw-alias-border-l1)}.dsm-source-note{padding:9px 13px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
40
40
  .dsm-table-head,.dsm-row{display:grid;grid-template-columns:minmax(180px,1fr) 120px 90px max-content;align-items:center;column-gap:12px;padding:0 13px}.dsm-table-head{min-height:32px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-row{min-height:58px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-row:last-child{border-bottom:0}.dsm-main{min-width:0}.dsm-name{overflow:hidden;font-size:13px;font-weight:570;text-overflow:ellipsis;white-space:nowrap}.dsm-note{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-tags{display:flex;align-items:center;gap:5px;flex-wrap:wrap}.dsm-tag{display:inline-flex;min-height:19px;align-items:center;padding:0 6px;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;color:var(--dsw-alias-label-secondary);font-size:10px;white-space:nowrap}.dsm-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.dsm-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.dsm-enabled{color:var(--dsw-alias-state-success-primary);font-size:12px;white-space:nowrap}.dsm-disabled{color:#d49245;font-size:12px;white-space:nowrap}.dsm-shadowed{color:var(--dsw-alias-label-tertiary);font-size:12px;white-space:nowrap}.dsm-row-actions{display:flex;align-items:center;justify-content:flex-end;gap:7px}
41
41
  .dsm-switch{position:relative;width:34px;height:20px;flex:none;padding:0;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}.dsm-switch:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:50%;background:#fff;content:"";transition:transform 160ms ease}.dsm-switch-on{background:var(--dsw-alias-state-success-primary)}.dsm-switch-on:after{transform:translateX(14px)}.dsm-switch:disabled{opacity:.45;cursor:default}.dsm-trash-row{display:flex;min-height:48px;align-items:center;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);color:inherit;font:inherit;font-size:13px;cursor:pointer}.dsm-trash-count{margin-left:auto;padding:2px 7px;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:11px}.dsm-empty{padding:25px 14px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-align:center}.dsm-feedback{padding:9px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-warning{border-color:#d49245;color:#d49245}.dsm-error{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
42
- .dsm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.dsm-modal{box-sizing:border-box;display:flex;width:min(560px,100%)!important;max-height:min(760px,calc(100vh - 48px));min-width:0;flex-direction:column;gap:16px;padding:22px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-wide{width:min(720px,100%)!important}.dsm-modal-import{width:min(480px,100%)!important;padding:24px}.dsm-modal-head{display:flex;align-items:flex-start;gap:12px}.dsm-modal-title{margin:0;flex:1;font-size:17px;line-height:24px;font-weight:670}.dsm-form,.dsm-field,.dsm-detail-section{display:flex;flex-direction:column}.dsm-form{gap:12px}.dsm-field{gap:6px}.dsm-label,.dsm-detail-title{font-size:12px}.dsm-label{color:var(--dsw-alias-label-secondary)}.dsm-detail-title{font-weight:650}.dsm-help{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.dsm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.dsm-hidden-input{display:none}.dsm-dropzone{box-sizing:border-box;display:flex;width:100%;min-height:170px;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:22px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);transition:border-color 180ms ease,background 180ms ease}.dsm-dropzone:hover,.dsm-dropzone-active{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsm-dropzone-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:620}.dsm-dropzone-copy{font-size:12px;line-height:18px;text-align:center}.dsm-upload-choices{display:flex;align-items:center;gap:7px}.dsm-upload-link,.dsm-file-remove{padding:0;border:0;background:transparent;font:inherit;font-size:12px;cursor:pointer}.dsm-upload-link{color:var(--dsw-alias-label-secondary)}.dsm-upload-link:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.dsm-upload-link:disabled{opacity:.45;cursor:default}.dsm-upload-divider{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-file{display:flex;align-items:center;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px}.dsm-file-kind{display:inline-flex;min-width:30px;height:24px;align-items:center;justify-content:center;border-radius:5px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:9px;font-weight:700}.dsm-file-name{min-width:0;overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.dsm-file-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}.dsm-file-remove{width:24px;height:24px;color:var(--dsw-alias-label-secondary);font-size:18px}.dsm-upload-requirements{padding:1px 1px 0}.dsm-upload-requirements ul{display:flex;margin:7px 0 0;padding-left:18px;flex-direction:column;gap:5px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.dsm-detail-section{gap:7px}.dsm-detail-path,.dsm-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}.dsm-detail-path{padding:8px 10px;border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);word-break:break-all}.dsm-code{max-height:280px;margin:0;padding:12px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);line-height:18px;white-space:pre-wrap}.dsm-diag{padding:8px 10px;border-left:2px solid #d49245;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-trash-item{display:flex;align-items:center;gap:10px;padding:11px 0;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-trash-item:last-child{border-bottom:0}.dsm-trash-main{min-width:0;flex:1}
42
+ .dsm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.dsm-modal{box-sizing:border-box;display:flex;width:min(560px,100%)!important;max-height:min(760px,calc(100vh - 48px));min-width:0;flex-direction:column;gap:16px;padding:22px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-wide{width:min(720px,100%)!important}.dsm-modal-import{width:min(480px,100%)!important;padding:24px}.dsm-modal-head{display:flex;align-items:flex-start;gap:12px}.dsm-modal-title{margin:0;flex:1;font-size:17px;line-height:24px;font-weight:670}.dsm-form,.dsm-field,.dsm-detail-section{display:flex;flex-direction:column}.dsm-form{gap:12px}.dsm-field{gap:6px}.dsm-label,.dsm-detail-title{font-size:12px}.dsm-label{color:var(--dsw-alias-label-secondary)}.dsm-detail-title{font-weight:650}.dsm-help{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.dsm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.dsm-hidden-input{display:none}.dsm-dropzone{box-sizing:border-box;display:flex;width:100%;min-height:170px;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:22px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);transition:border-color 180ms ease,background 180ms ease;cursor:pointer}.dsm-dropzone:hover,.dsm-dropzone-active{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsm-dropzone-title{color:var(--dsw-alias-label-primary);font-size:18px;font-weight:700}.dsm-dropzone-copy{font-size:12px;line-height:18px;text-align:center}.dsm-upload-choices{display:flex;align-items:center;gap:7px}.dsm-upload-link,.dsm-file-remove{padding:0;border:0;background:transparent;font:inherit;font-size:12px;cursor:pointer}.dsm-upload-link{color:var(--dsw-alias-label-secondary)}.dsm-upload-link:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.dsm-upload-link:disabled{opacity:.45;cursor:default}.dsm-upload-divider{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-file{display:flex;align-items:center;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px}.dsm-file-kind{display:inline-flex;min-width:30px;height:24px;align-items:center;justify-content:center;border-radius:5px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:9px;font-weight:700}.dsm-file-name{min-width:0;overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.dsm-file-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}.dsm-file-remove{width:24px;height:24px;color:var(--dsw-alias-label-secondary);font-size:18px}.dsm-upload-requirements{padding:1px 1px 0}.dsm-upload-requirements ul{display:flex;margin:7px 0 0;padding-left:18px;flex-direction:column;gap:5px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.dsm-detail-section{gap:7px}.dsm-detail-path,.dsm-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}.dsm-detail-path{padding:8px 10px;border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);word-break:break-all}.dsm-code{max-height:280px;margin:0;padding:12px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);line-height:18px;white-space:pre-wrap}.dsm-diag{padding:8px 10px;border-left:2px solid #d49245;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-trash-item{display:flex;align-items:center;gap:10px;padding:11px 0;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-trash-item:last-child{border-bottom:0}.dsm-trash-main{min-width:0;flex:1}
43
43
  .dsm-fm{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-fm-row{display:grid;grid-template-columns:minmax(96px,150px) minmax(0,1fr);gap:10px;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:18px}.dsm-fm-row:last-child{border-bottom:0}.dsm-fm-key{color:var(--dsw-alias-label-secondary);word-break:break-word}.dsm-fm-raw{margin-left:5px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:10px}.dsm-fm-val{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-word;white-space:pre-wrap}
44
44
  @container(max-width:780px){.dsm-table-head{display:none}.dsm-row{grid-template-columns:minmax(0,1fr) max-content;gap:8px;padding:11px 13px}.dsm-row>.dsm-tags,.dsm-row>.dsm-status{grid-column:1}.dsm-row-actions{grid-column:2;grid-row:1 / span 3}.dsm-path{display:none}}@media(max-width:760px){.dsm-summary{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:720px){.dsm-title-row{flex-wrap:wrap}}@container(max-width:520px){.dsm-head{flex-direction:column}.dsm-actions{width:100%;margin-left:0}.dsm-actions .dsm-btn{flex:1}.dsm-filters{flex-direction:column}.dsm-source-filter{width:100%}.dsm-summary{grid-template-columns:1fr}}
45
+ .dsm-hist-row{display:flex;align-items:center;gap:11px;padding:10px 13px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-hist-row:last-child{border-bottom:0}.dsm-hist-main{min-width:0;flex:1}.dsm-hist-title{overflow:hidden;font-size:13px;font-weight:580;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd-missing{color:var(--dsw-alias-state-error-primary)}.dsm-hist-actions{display:flex;gap:6px;flex:none}.dsm-hist-check,.dsm-hist-group-check{flex:none;width:15px;height:15px;margin:0 4px 0 0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}.dsm-hist-group-head{gap:10px}.dsm-hist-batch{display:flex;align-items:center;gap:8px;margin-left:2px;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l2)}.dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-hist-batch-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px;flex:none}.dsm-dir-row{display:flex;gap:8px}.dsm-dir-row .dsm-control{flex:1}.dsm-dir-list{display:flex;max-height:220px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-dir-item{display:flex;width:100%;align-items:center;padding:8px 12px;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;cursor:pointer}.dsm-dir-item:last-child{border-bottom:0}.dsm-dir-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-dir-item::before{content:"📁";margin-right:8px;font-size:12px}
45
46
  `
46
47
 
47
48
  function ensureCss() {
@@ -121,14 +122,11 @@ window.__ModuleLoader__.load({
121
122
  return s.length <= 4 ? '****' : s.slice(0, 4) + '****'
122
123
  }
123
124
 
124
- // 项目与问题反馈链接:MCP / Skills 两个设置页共用同一组地址。
125
- // 注意:引用了本作用域的 GithubMark16 / primitives,必须定义在 apply 里。
125
+ // 顶部栏反馈入口:合并成一个「GitHub 和反馈」链接,指向仓库(issues 在那)。
126
126
  function FeedbackLinks() {
127
127
  return React.createElement('div', { className: 'dsm-feedback-links' },
128
128
  React.createElement('a', { className: 'dsm-feedback-link', href: 'https://github.com/ouli-1242/dsh-plugin-tool-management', target: '_blank', rel: 'noreferrer', 'aria-label': 'GitHub' },
129
- React.createElement(GithubMark16, null), 'GitHub'),
130
- React.createElement('a', { className: 'dsm-feedback-link', href: 'https://github.com/ouli-1242/dsh-plugin-tool-management/issues', target: '_blank', rel: 'noreferrer', 'aria-label': '问题反馈' },
131
- React.createElement(primitives.IconListPenOutline16, null), '问题反馈'))
129
+ React.createElement(GithubMark16, null), 'GitHub'))
132
130
  }
133
131
 
134
132
  // MCP 服务页:与 Skills 页共用 dsm-* 设计语言(统计卡 / 筛选 / 分组卡片 / 模态框)。
@@ -517,8 +515,7 @@ window.__ModuleLoader__.load({
517
515
  React.createElement('div', { className: 'dsm-title-block' },
518
516
  React.createElement('div', { className: 'dsm-title-row' },
519
517
  React.createElement('h2', { className: 'dsm-title' }, 'MCP'),
520
- React.createElement(VersionBadge, null),
521
- React.createElement(FeedbackLinks, null)),
518
+ React.createElement(VersionBadge, null)),
522
519
  React.createElement('p', { className: 'dsm-desc' }, '统一管理本机的 MCP 服务与运行状态。')),
523
520
  React.createElement('div', { className: 'dsm-actions' },
524
521
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading, onClick: () => refresh() }, '刷新'),
@@ -544,22 +541,151 @@ window.__ModuleLoader__.load({
544
541
  confirmNode)
545
542
  }
546
543
 
547
- // 两个独立设置页:MCP 与 Skills,各自占一个侧栏项。
548
- slots.inject('settings.section', () => slots.register(
549
- { name: 'settings.section', id: 'dsm-mcp', order: 16, label: 'MCP' },
550
- () => React.createElement(MCPPage)
551
- ))
544
+ // ---------- AGENTS.md 预设页:多套全局指令基线,应用=写入 ~/.dsh/AGENTS.md ----------
545
+ function AgentsMdPage() {
546
+ var st = React.useState({ loading: true, error: null, presets: [], current: null })
547
+ var state = st[0], setState = st[1]
548
+ var busyState = React.useState(null), busy = busyState[0], setBusy = busyState[1]
549
+ var em = React.useState(null), editModal = em[0], setEditModal = em[1]
550
+ var cm = React.useState(null), createModal = cm[0], setCreateModal = cm[1]
551
+ var ac = React.useState(null), applyConfirm = ac[0], setApplyConfirm = ac[1]
552
+ var importInput = React.useRef(null)
553
+ function doImport(file) {
554
+ if (!file) return
555
+ var reader = new FileReader()
556
+ reader.onload = function () {
557
+ var id = String(file.name).replace(/\.(md|markdown|txt)$/i, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'imported'
558
+ setBusy('import')
559
+ apiCall('agentsmd-import', { id: id, content: String(reader.result) }).then(function (res) {
560
+ setBusy(null)
561
+ if (res && res.ok) refresh()
562
+ else if (res) setState(function (s) { return Object.assign({}, s, { error: res.error }) })
563
+ }).catch(function () { setBusy(null) })
564
+ }
565
+ reader.readAsText(file)
566
+ }
567
+
568
+ function refresh() {
569
+ apiCall('agentsmd-list', {}).then(function (res) {
570
+ setState(function (s) { return { loading: false, error: res && res.ok ? null : ((res && res.error) || '加载失败'), presets: (res && res.presets) || [], current: s.current } })
571
+ }).catch(function (e) { setState({ loading: false, error: String((e && e.message) || e), presets: [], current: null }) })
572
+ apiCall('agentsmd-get-current', {}).then(function (res) {
573
+ if (res && res.ok) setState(function (s) { return Object.assign({}, s, { current: res }) })
574
+ }).catch(function () {})
575
+ }
576
+ React.useEffect(function () { refresh() }, [])
577
+
578
+ var cur = state.current
579
+ var curLabel = cur && cur.exists ? (cur.presetId || '(已外部修改)') : '(未设置)'
580
+
581
+ function openEdit(p) {
582
+ apiCall('agentsmd-read', { id: p.id }).then(function (res) {
583
+ if (res && res.ok) setEditModal({ id: p.id, content: res.content })
584
+ }).catch(function () {})
585
+ }
586
+ function doApply(id) {
587
+ setBusy('apply-' + id)
588
+ apiCall('agentsmd-apply', { id: id }).then(function () { setBusy(null); setApplyConfirm(null); refresh() }).catch(function () { setBusy(null) })
589
+ }
590
+ function doRemove(p) {
591
+ setBusy('rm-' + p.id)
592
+ apiCall('agentsmd-remove', { id: p.id }).then(function () { setBusy(null); setApplyConfirm(null); refresh() }).catch(function () { setBusy(null) })
593
+ }
594
+ function doCreate(id, from) {
595
+ setBusy('create')
596
+ apiCall('agentsmd-create', { id: id, from: from || undefined }).then(function (res) {
597
+ setBusy(null)
598
+ if (res && res.ok) { setCreateModal(null); refresh() }
599
+ else if (res) setCreateModal(Object.assign({}, createModal, { error: res.error }))
600
+ }).catch(function () { setBusy(null) })
601
+ }
602
+ function doUpdate() {
603
+ setBusy('update')
604
+ apiCall('agentsmd-update', { id: editModal.id, content: editModal.content }).then(function (res) {
605
+ setBusy(null)
606
+ if (res && res.ok) { setEditModal(null); refresh() }
607
+ }).catch(function () { setBusy(null) })
608
+ }
609
+
610
+ var body = state.loading
611
+ ? React.createElement('div', { className: 'dsm-empty' }, '加载中…')
612
+ : state.error
613
+ ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, state.error)
614
+ : !state.presets.length
615
+ ? React.createElement('div', { className: 'dsm-empty' }, '暂无预设,点「新建预设」创建。')
616
+ : React.createElement('div', { className: 'dsm-sources' }, state.presets.map(function (p) {
617
+ return React.createElement('div', { key: p.id, className: 'dsm-source' },
618
+ React.createElement('div', { className: 'dsm-source-head' },
619
+ React.createElement('div', { className: 'dsm-source-head-main' },
620
+ React.createElement('span', { className: 'dsm-source-title' }, p.id),
621
+ p.active ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, '生效中') : null),
622
+ React.createElement('div', { className: 'dsm-source-actions' },
623
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: function () { openEdit(p) } }, '编辑'),
624
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null, onClick: function () { setApplyConfirm({ id: p.id }) }, title: '应用后新会话生效,当前会话不变' }, p.active ? '重新应用' : '应用'),
625
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy !== null, onClick: function () { setApplyConfirm({ id: p.id, remove: true }) } }, '删除'))))
626
+ }))
627
+
628
+ return React.createElement('section', { className: 'dsm-section' },
629
+ React.createElement('div', { className: 'dsm-head' },
630
+ React.createElement('div', { className: 'dsm-title-block' },
631
+ React.createElement('div', { className: 'dsm-title-row' },
632
+ React.createElement('h2', { className: 'dsm-title' }, 'AGENTS.md'),
633
+ React.createElement(VersionBadge, null)),
634
+ React.createElement('p', { className: 'dsm-desc' }, '管理多套全局指令基线;「应用」写入 ~/.dsh/AGENTS.md,新会话生效,当前会话不变。')),
635
+ React.createElement('div', { className: 'dsm-actions' },
636
+ React.createElement('button', { type: 'button', className: 'dsm-btn', onClick: function () { setCreateModal({ id: '', from: '', error: null }) } }, '新建预设'),
637
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null, onClick: function () { if (importInput.current) importInput.current.click() } }, '导入'))),
638
+ body,
639
+ editModal ? React.createElement(Modal, { title: '编辑预设 · ' + editModal.id, closeLabel: '取消', onClose: function () { setEditModal(null) } },
640
+ React.createElement('textarea', { className: 'dsm-control', style: { minHeight: '320px' }, value: editModal.content, onChange: function (e) { setEditModal(Object.assign({}, editModal, { content: e.target.value })) } }),
641
+ React.createElement('div', { className: 'dsm-modal-actions' },
642
+ React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null, onClick: doUpdate }, '保存'))) : null,
643
+ createModal ? React.createElement(Modal, { title: '新建预设', closeLabel: '取消', onClose: function () { setCreateModal(null) } },
644
+ React.createElement('div', { className: 'dsm-field' },
645
+ React.createElement('label', { className: 'dsm-label' }, 'id(小写字母/数字/连字符)'),
646
+ React.createElement('input', { className: 'dsm-control', value: createModal.id, onChange: function (e) { setCreateModal(Object.assign({}, createModal, { id: e.target.value })) } })),
647
+ React.createElement('div', { className: 'dsm-field' },
648
+ React.createElement('label', { className: 'dsm-label' }, '从现有预设复制(可选,留空=空白模板)'),
649
+ React.createElement('input', { className: 'dsm-control', value: createModal.from, onChange: function (e) { setCreateModal(Object.assign({}, createModal, { from: e.target.value })) } })),
650
+ createModal && createModal.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, createModal.error) : null,
651
+ React.createElement('div', { className: 'dsm-modal-actions' },
652
+ React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null, onClick: function () { doCreate(createModal.id, createModal.from) } }, '创建'))) : null,
653
+ applyConfirm ? React.createElement(Modal, { title: applyConfirm.remove ? ('删除预设 · ' + applyConfirm.id) : ('应用预设 · ' + applyConfirm.id), closeLabel: '取消', onClose: function () { setApplyConfirm(null) } },
654
+ applyConfirm.remove
655
+ ? React.createElement('p', { className: 'dsm-help' }, '删除预设 ' + applyConfirm.id + ' ?此操作不可恢复。')
656
+ : React.createElement('div', null,
657
+ React.createElement('p', { className: 'dsm-help' }, '将把 ' + applyConfirm.id + ' 的内容写入 ~/.dsh/AGENTS.md。'),
658
+ React.createElement('div', { className: 'dsm-feedback dsm-warning' }, '新会话生效,当前会话不变;当前内容已自动备份到 __last-applied__。')),
659
+ React.createElement('div', { className: 'dsm-modal-actions' },
660
+ React.createElement('button', { type: 'button', className: 'dsm-btn ' + (applyConfirm.remove ? 'dsm-btn-danger' : ''), disabled: busy !== null, onClick: function () { if (applyConfirm.remove) doRemove({ id: applyConfirm.id }); else doApply(applyConfirm.id) } }, applyConfirm.remove ? '确认删除' : '确认应用'))) : null,
661
+ React.createElement('input', { ref: importInput, type: 'file', accept: '.md,.markdown,.txt', className: 'dsm-hidden-input', onChange: function (e) { if (e.target.files && e.target.files[0]) doImport(e.target.files[0]); e.target.value = ''; } }))
662
+ }
663
+
664
+ // TOOLS 设置页:一个侧栏项,内部 tab 切换 MCP / Skills / AGENTS.md / History。
665
+ function ToolsSection() {
666
+ var tabState = React.useState('mcp')
667
+ var active = tabState[0], setActive = tabState[1]
668
+ var page = active === 'mcp' ? React.createElement(MCPPage)
669
+ : active === 'skills' ? React.createElement(SkillManagerSection, { t: t })
670
+ : active === 'agents-md' ? React.createElement(AgentsMdPage)
671
+ : React.createElement(HistoryPage)
672
+ return React.createElement('section', { className: 'dsm-section' },
673
+ React.createElement('div', { className: 'dsm-tabs' },
674
+ React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'mcp' ? ' dsm-tab-active' : ''), onClick: function () { setActive('mcp') } }, 'MCP'),
675
+ React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'skills' ? ' dsm-tab-active' : ''), onClick: function () { setActive('skills') } }, 'Skills'),
676
+ React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'agents-md' ? ' dsm-tab-active' : ''), onClick: function () { setActive('agents-md') } }, 'AGENTS.md'),
677
+ React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'history' ? ' dsm-tab-active' : ''), onClick: function () { setActive('history') } }, 'History'),
678
+ React.createElement('div', { style: { marginLeft: 'auto' } }, React.createElement(FeedbackLinks, null))),
679
+ page)
680
+ }
552
681
  slots.inject('settings.section', () => slots.register(
553
- { name: 'settings.section', id: 'dsm-skills', order: 17, label: 'Skills' },
554
- () => React.createElement(SkillManagerSection, { t: t })
682
+ { name: 'settings.section', id: 'dsm-tools', order: 16, label: 'TOOLS' },
683
+ () => React.createElement(ToolsSection)
555
684
  ))
556
685
 
557
686
 
558
687
  // ---------- Skills 页(移植自 dsh-skills-manager,Apache-2.0;类名前缀 dsm-) ----------
559
688
  var react = React // 上游组件使用小写 react,别名到本文件的 React
560
- // 上游 UI 依赖 @deepseek-ai/dsh-client-ui-primitives 的单个图标;
561
- // 这里用本地内联 SVG 替代,避免为客户端引入额外 peer 依赖。
562
- var primitives = { IconListPenOutline16: function () { return h('svg', { viewBox: '0 0 16 16', width: 16, height: 16, 'aria-hidden': 'true', focusable: 'false' }, h('path', { fill: 'currentColor', d: 'M3 2h10v12H3zM5 5h6M5 8h6M5 11h4' })) } }
563
689
  var h = React.createElement
564
690
 
565
691
  // SKILL.md frontmatter 常见键 → 中文标签(未知键原样显示,已知键附原始键名提示)。
@@ -592,9 +718,10 @@ window.__ModuleLoader__.load({
592
718
  zh: {
593
719
  "title": "Skills", "desc": "统一加载和管理本机 Agent Skills。", "link.project": "GitHub", "link.feedback": "问题反馈",
594
720
  "btn.create": "创建技能", "btn.import": "导入", "btn.refresh": "刷新", "btn.cancel": "取消", "btn.close": "关闭", "btn.detail": "查看详情", "btn.trash": "移到回收站", "btn.restore": "恢复", "btn.delete.forever": "永久删除", "btn.file.pick": "选择文件", "btn.folder.pick": "选择文件夹", "btn.import.now": "安装", "btn.create.now": "创建技能", "btn.disable": "停用", "btn.enable": "启用", "btn.open.editor": "用系统编辑器打开",
595
- "btn.custom.add": "添加目录", "btn.custom.remove": "移除",
721
+ "btn.custom.add": "添加目录", "btn.custom.remove": "移除", "btn.openDir": "选择",
596
722
  "custom.add.title": "添加自定义技能目录", "custom.add.submit": "添加",
597
723
  "custom.add.path": "目录绝对路径", "custom.add.path.placeholder": "例如 D:\\skills\\my-skills 或 /home/me/skills",
724
+ "custom.add.openDir.title": "浏览并选择文件夹", "custom.add.picker.title": "选择文件夹",
598
725
  "custom.add.label": "显示名称(可选)", "custom.add.label.placeholder": "自定义目录",
599
726
  "custom.add.help": "以只读方式接入该目录:自动发现 SKILL.md 子目录与顶层 .md 单文件,可在来源卡片上启停整个目录或单个技能;与现有来源重叠的目录会被拒绝,目录与源文件都不会被修改。",
600
727
  "confirm.custom.remove.title": "移除自定义目录",
@@ -607,7 +734,7 @@ window.__ModuleLoader__.load({
607
734
  "source.external.note": "通过 Skills Manager 接入,启停不会改写源文件。", "source.dsh.note": "DSH 本地技能可创建、导入和移到回收站;启停只更新管理器状态。",
608
735
  "detail.title": "技能详情", "detail.body": "正文", "detail.frontmatter": "元数据", "detail.noFrontmatter": "该技能未提供元数据。", "detail.diagnostics": "诊断", "detail.path": "源文件", "detail.noIssues": "未发现诊断问题。",
609
736
  "create.title": "创建技能", "create.target": "创建位置", "create.name": "名称", "create.name.placeholder": "例如 code-review-helper", "create.description": "简介", "create.description.placeholder": "一句话说明什么时候使用", "create.body": "正文(Markdown)", "create.body.placeholder": "写下技能要遵循的指令、步骤和边界…", "create.chat.note": "对话中的 create_skill 仍创建用户级 DSH Skill;项目 Skill 可在这里选择活动项目后创建。",
610
- "import.title": "导入技能", "upload.drop.title": "拖拽技能到此处", "upload.drop.copy": "支持 .zip、技能文件夹或单个 SKILL.md", "upload.selected.one": "{count} 个文件 · {size}", "upload.selected.other": "{count} 个文件 · {size}", "upload.remove": "移除所选内容", "upload.requirements": "文件要求", "upload.requirement.skill": "压缩包或文件夹需包含 SKILL.md", "upload.requirement.frontmatter": "SKILL.md 需包含 YAML 格式的技能名称和描述", "upload.requirement.copy": "导入时复制完整内容,不修改原始来源", "upload.importing": "正在安装…", "status.selected": "已选择", "select.file.invalid": "请选择 .zip 或单个 SKILL.md。", "select.folder.invalid": "所选文件夹中没有找到 SKILL.md。", "error.browse.absolute": "目录路径必须是绝对路径:{path}", "error.browse.unreadable": "无法读取目录:{path}", "error.browse.notDirectory": "不是目录:{path}",
737
+ "import.title": "导入技能", "upload.drop.title": "点击或拖入此处", "upload.drop.copy": "支持的文件类型:.zip、技能文件夹或单个 SKILL.md", "upload.selected.one": "{count} 个文件 · {size}", "upload.selected.other": "{count} 个文件 · {size}", "upload.remove": "移除所选内容", "upload.requirements": "文件要求", "upload.requirement.skill": "压缩包或文件夹需包含 SKILL.md", "upload.requirement.frontmatter": "SKILL.md 需包含 YAML 格式的技能名称和描述", "upload.requirement.copy": "导入时复制完整内容,不修改原始来源", "upload.importing": "正在安装…", "status.selected": "已选择", "select.file.invalid": "请选择 .zip 或单个 SKILL.md。", "select.folder.invalid": "所选文件夹中没有找到 SKILL.md。", "error.browse.absolute": "目录路径必须是绝对路径:{path}", "error.browse.unreadable": "无法读取目录:{path}", "error.browse.notDirectory": "不是目录:{path}",
611
738
  "trash.title": "回收站", "trash.count.one": "{count} 个待处理技能", "trash.count.other": "{count} 个待处理技能", "trash.empty": "回收站为空。", "trash.deletedAt": "删除于 {time}", "trash.source": "来源:{source}",
612
739
  "confirm.trash.title": "移到回收站?", "confirm.trash.desc": "“{name}”将从当前技能来源移入回收站,之后可以恢复到原位置。", "confirm.delete.title": "永久删除?", "confirm.delete.desc": "“{name}”将从回收站永久删除,无法恢复。",
613
740
  "result.created": "已创建技能:{name}", "result.imported": "导入完成:{names}", "result.importPartial": "已导入:{imported};已跳过同名技能:{skipped}", "result.importSkipped": "未导入任何技能;已跳过同名技能:{names}", "result.importEmpty": "未导入任何技能。", "result.importWarnings": "{result};警告:{warnings}", "result.restored": "已恢复技能:{name}", "result.trashed": "已移到回收站:{name}", "result.deleted": "已永久删除:{name}", "result.updated": "状态已更新。", "result.opened": "已用系统默认程序打开:{path}", "error.action": "操作失败:{error}",
@@ -634,7 +761,7 @@ window.__ModuleLoader__.load({
634
761
  "source.external.note": "Managed through Skills Manager; toggles never rewrite source files.", "source.dsh.note": "Local DSH skills can be created, imported, and moved to trash; toggles update manager state only.",
635
762
  "detail.title": "Skill details", "detail.body": "Body", "detail.frontmatter": "Metadata", "detail.noFrontmatter": "This skill provides no metadata.", "detail.diagnostics": "Diagnostics", "detail.path": "Source file", "detail.noIssues": "No diagnostic issues found.",
636
763
  "create.title": "Create skill", "create.target": "Create in", "create.name": "Name", "create.name.placeholder": "e.g. code-review-helper", "create.description": "Description", "create.description.placeholder": "One sentence describing when to use it", "create.body": "Body (Markdown)", "create.body.placeholder": "Write the instructions, steps, and boundaries…", "create.chat.note": "The conversational create_skill tool still creates a user-level DSH Skill; choose an active project here for a project Skill.",
637
- "import.title": "Import skill", "upload.drop.title": "Drop a skill here", "upload.drop.copy": "Supports .zip, a skill folder, or one SKILL.md", "upload.selected.one": "{count} file · {size}", "upload.selected.other": "{count} files · {size}", "upload.remove": "Remove selection", "upload.requirements": "File requirements", "upload.requirement.skill": "Archives and folders must contain SKILL.md", "upload.requirement.frontmatter": "SKILL.md must include a YAML name and description", "upload.requirement.copy": "Import copies all content and never modifies the source", "upload.importing": "Installing…", "status.selected": "Selected", "select.file.invalid": "Choose a .zip archive or one SKILL.md.", "select.folder.invalid": "No SKILL.md was found in the selected folder.", "error.browse.absolute": "Folder path must be absolute: {path}", "error.browse.unreadable": "Could not read folder: {path}", "error.browse.notDirectory": "Not a folder: {path}",
764
+ "import.title": "Import skill", "upload.drop.title": "Click or drop here", "upload.drop.copy": "Supported: .zip, a skill folder, or one SKILL.md", "upload.selected.one": "{count} file · {size}", "upload.selected.other": "{count} files · {size}", "upload.remove": "Remove selection", "upload.requirements": "File requirements", "upload.requirement.skill": "Archives and folders must contain SKILL.md", "upload.requirement.frontmatter": "SKILL.md must include a YAML name and description", "upload.requirement.copy": "Import copies all content and never modifies the source", "upload.importing": "Installing…", "status.selected": "Selected", "select.file.invalid": "Choose a .zip archive or one SKILL.md.", "select.folder.invalid": "No SKILL.md was found in the selected folder.", "error.browse.absolute": "Folder path must be absolute: {path}", "error.browse.unreadable": "Could not read folder: {path}", "error.browse.notDirectory": "Not a folder: {path}",
638
765
  "trash.title": "Trash", "trash.count.one": "{count} skill pending", "trash.count.other": "{count} skills pending", "trash.empty": "Trash is empty.", "trash.deletedAt": "Deleted {time}", "trash.source": "Source: {source}",
639
766
  "confirm.trash.title": "Move to trash?", "confirm.trash.desc": "“{name}” will move out of its current skill source and can be restored to the same location later.", "confirm.delete.title": "Delete forever?", "confirm.delete.desc": "“{name}” will be permanently deleted from trash and cannot be recovered.",
640
767
  "result.created": "Created skill: {name}", "result.imported": "Import complete: {names}", "result.importPartial": "Imported: {imported}; skipped existing skills: {skipped}", "result.importSkipped": "No skills were imported; existing skills were skipped: {names}", "result.importEmpty": "No skills were imported.", "result.importWarnings": "{result}; warnings: {warnings}", "result.restored": "Restored skill: {name}", "result.trashed": "Moved to trash: {name}", "result.deleted": "Permanently deleted: {name}", "result.updated": "Status updated.", "error.action": "Action failed: {error}",
@@ -741,7 +868,47 @@ function callApi(path, options) {
741
868
  }
742
869
  function Switch(props) { return h("button", { type: "button", className: "dsm-switch" + (props.on ? " dsm-switch-on" : ""), role: "switch", "aria-checked": props.on, "aria-label": props.label, disabled: props.disabled, onClick: props.onClick }); }
743
870
  function GithubMark16() { return h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": true, focusable: "false" }, h("path", { fill: "currentColor", d: "M8 0a8 8 0 0 0-2.53 15.59c.4.074.547-.173.547-.385 0-.19-.007-.693-.01-1.36-2.226.484-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.872.87 2.328.665.072-.517.28-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.956 0-.874.31-1.588.823-2.148-.083-.202-.357-1.017.078-2.12 0 0 .672-.215 2.2.82A7.65 7.65 0 0 1 8 4.8c.68.003 1.365.092 2.004.27 1.527-1.035 2.197-.82 2.197-.82.437 1.103.162 1.918.08 2.12.513.56.822 1.274.822 2.148 0 3.076-1.872 3.752-3.654 3.95.288.248.544.735.544 1.482 0 1.07-.01 1.932-.01 2.195 0 .214.144.463.55.384A8.001 8.001 0 0 0 8 0Z" })); }
744
- function Modal(props) { var ref = react.useRef(null); react.useEffect(function () { if (ref.current) ref.current.focus(); }, []); return h("div", { className: "dsm-mask", onMouseDown: function (e) { if (e.target === e.currentTarget) props.onClose(); } }, h("div", { ref: ref, tabIndex: -1, className: "dsm-modal" + (props.wide ? " dsm-modal-wide" : "") + (props.className ? " " + props.className : ""), role: "dialog", "aria-modal": "true", onKeyDown: function (e) { if (!handleModalEscape(e, props.onClose)) trapModalFocus(e.currentTarget, e); } }, h("div", { className: "dsm-modal-head" }, h("h3", { className: "dsm-modal-title" }, props.title), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: props.onClose }, props.closeLabel)), props.children)); }
871
+ function Modal(props) { var ref = react.useRef(null); react.useEffect(function () { if (ref.current) ref.current.focus(); }, []); return h("div", { className: "dsm-mask", onMouseDown: function (e) { if (e.target === e.currentTarget) props.onClose(); } }, h("div", { ref: ref, tabIndex: -1, className: "dsm-modal" + (props.wide ? " dsm-modal-wide" : "") + (props.className ? " " + props.className : ""), role: "dialog", "aria-modal": "true", onKeyDown: function (e) { if (!handleModalEscape(e, props.onClose)) trapModalFocus(e.currentTarget, e); } }, h("div", { className: "dsm-modal-head" }, h("h3", { className: "dsm-modal-title" }, props.title), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: props.onClose }, props.closeLabel)), props.children)); }
872
+
873
+ // 目录选择器:经 host 的 dir-list 逐级浏览目录树,选中后把绝对路径回填给调用方。
874
+ // 标准浏览器不暴露所选文件夹的绝对路径(File.path 仅 Electron 可用),故不走
875
+ // webkitdirectory,改由宿主端列目录 + 前端目录树完成「选择文件夹」。
876
+ function DirPickerModal(props) {
877
+ var stState = react.useState({ loading: true, error: null, current: '', parent: null, entries: [] })
878
+ var st = stState[0], setSt = stState[1]
879
+ var txState = react.useState(props.initial || '')
880
+ var text = txState[0], setText = txState[1]
881
+ function load(dir) {
882
+ setSt({ loading: true, error: null, current: dir || '', parent: null, entries: [] })
883
+ apiCall('dir-list', { dir: dir || '' }).then(function (res) {
884
+ if (res && res.ok) setSt({ loading: false, error: null, current: res.current || '', parent: res.parent || null, entries: res.entries || [] })
885
+ else setSt({ loading: false, error: (res && res.error) || '无法读取目录', current: dir || '', parent: null, entries: [] })
886
+ }).catch(function () {
887
+ setSt({ loading: false, error: '无法读取目录', current: dir || '', parent: null, entries: [] })
888
+ })
889
+ }
890
+ react.useEffect(function () { load(props.initial || '') }, [])
891
+ function jump() { var v = String(text || '').trim(); if (v) load(v) }
892
+ function goUp() { if (!st.parent) return; setText(st.parent); load(st.parent) }
893
+ function enter(path) { setText(path); load(path) }
894
+ function pick() { var target = String(st.current || '').trim() || String(text || '').trim(); if (target && props.onPick) props.onPick(target) }
895
+ return h(Modal, { title: props.title || '选择文件夹', closeLabel: props.closeLabel || '关闭', onClose: props.onClose },
896
+ h("div", { className: "dsm-form" },
897
+ h("label", { className: "dsm-field" },
898
+ h("span", { className: "dsm-label" }, '当前路径'),
899
+ h("div", { className: "dsm-dir-row" },
900
+ h("input", { className: "dsm-control", value: text, placeholder: '输入目录路径,回车跳转', onChange: function (e) { setText(e.target.value); }, onKeyDown: function (e) { if (e.key === 'Enter') { e.preventDefault(); jump(); } } }),
901
+ h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: st.loading || !String(text || '').trim(), onClick: jump }, '跳转'))),
902
+ st.error ? h("div", { className: "dsm-feedback dsm-error", role: "alert" }, st.error) : null,
903
+ h("div", { className: "dsm-dir-list" },
904
+ st.loading ? h("div", { className: "dsm-empty" }, '加载中…')
905
+ : st.entries.length ? st.entries.map(function (e) { return h("button", { key: e.path, type: "button", className: "dsm-dir-item", title: e.path, onClick: function () { enter(e.path); } }, e.name); })
906
+ : h("div", { className: "dsm-empty" }, '(当前目录没有子文件夹)')),
907
+ h("div", { className: "dsm-modal-actions" },
908
+ h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: st.loading || !st.parent, onClick: goUp }, '上级'),
909
+ h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: props.onClose }, '取消'),
910
+ h("button", { type: "button", className: "dsm-btn", disabled: st.loading || !(String(st.current || '').trim() || String(text || '').trim()), onClick: pick }, '选择此文件夹'))))
911
+ }
745
912
 
746
913
  function SkillManagerSection(props) {
747
914
  var t = props.t;
@@ -767,6 +934,10 @@ function callApi(path, options) {
767
934
  function openSource() { if (!detail || !detail.path) return; setBusy(true); setResult(null); apiCall("skill-open", { path: detail.path }).then(function (res) { if (res && res.ok) setResult({ ok: true, text: t("result.opened", { path: detail.path }) }); else setResult({ ok: false, text: t("error.action", { error: translateError(t, res) }) }); }).catch(function (error) { setResult({ ok: false, text: t("error.action", { error: translateError(t, error) }) }); }).finally(function () { setBusy(false); }); }
768
935
  function updateForm(key, value) { setForm(Object.assign({}, form, { [key]: value })); }
769
936
  // 自定义技能目录:添加 / 移除(走 skill-custom-* ops,成功后静默刷新)。
937
+ function openCustomDir() {
938
+ if (busy) return;
939
+ setCustomForm(Object.assign({}, customForm, { picking: true }));
940
+ }
770
941
  function submitCustomAdd() {
771
942
  var payload = { path: String(customForm.path || "").trim(), label: String(customForm.label || "").trim() };
772
943
  if (!payload.path) return;
@@ -805,17 +976,15 @@ function callApi(path, options) {
805
976
  }
806
977
 
807
978
  var summary = data.summary || { total: 0, enabled: 0, disabled: 0, issues: 0 };
808
- var content = [h("style", { key: "css" }, CSS), h("div", { key: "head", className: "dsm-head" }, h("div", { className: "dsm-title-block" }, h("div", { className: "dsm-title-row" }, h("h2", { className: "dsm-title" }, t("title")), h(VersionBadge, null), h(FeedbackLinks, null)), h("p", { className: "dsm-desc" }, t("desc"))), h("div", { className: "dsm-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy || snapshot.loading, onClick: function () { refresh(false); } }, t("btn.refresh")), h("button", { type: "button", className: "dsm-btn", onClick: openCreate }, t("btn.create")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setResult(null); setUpload(null); setModal("import"); } }, t("btn.import")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: function () { setResult(null); setCustomForm({ path: "", label: "" }); setModal("custom-add"); } }, t("btn.custom.add")))), h("div", { key: "summary", className: "dsm-summary dsm-summary-4" }, [[summary.total, "summary.total"], [summary.enabled, "summary.enabled"], [summary.disabled, "summary.disabled"], [summary.issues || 0, "summary.issues"]].map(function (item) { return h("div", { key: item[1], className: "dsm-stat" }, h("strong", null, item[0]), t(countKey(item[1], item[0]), { count: item[0] }).replace(String(item[0]), "")); })), h("div", { key: "filters", className: "dsm-filters" }, h("input", { className: "dsm-control dsm-search", value: query, "aria-label": t("search"), placeholder: t("search.placeholder"), onChange: function (e) { setQuery(e.target.value); } }), h("div", { className: "dsm-source-filter" }, h(SourceSelect, { value: activeSource, options: options, onChange: setSource }))), result && modal !== "import" ? h("div", { key: "result", className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error") }, result.text) : null].concat((data.warnings || []).map(function (warning, index) { return h("div", { key: "warning-" + index, className: "dsm-feedback dsm-warning", role: "alert" }, translateError(t, warning)); }), [snapshot.error ? h("div", { key: "error", className: "dsm-feedback dsm-error" }, snapshot.error) : null, snapshot.loading && !snapshot.data ? h("div", { key: "loading", className: "dsm-empty" }, t("loading")) : h("div", { key: "sources", className: "dsm-sources" }, roots.map(renderRoot)), h("button", { key: "trash", type: "button", className: "dsm-trash-row", onClick: function () { setModal("trash"); } }, h("span", null, t("trash.title")), h("span", { className: "dsm-trash-count" }, (data.trash || []).length))]);
979
+ var content = [h("style", { key: "css" }, CSS), h("div", { key: "head", className: "dsm-head" }, h("div", { className: "dsm-title-block" }, h("div", { className: "dsm-title-row" }, h("h2", { className: "dsm-title" }, t("title")), h(VersionBadge, null)), h("p", { className: "dsm-desc" }, t("desc"))), h("div", { className: "dsm-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy || snapshot.loading, onClick: function () { refresh(false); } }, t("btn.refresh")), h("button", { type: "button", className: "dsm-btn", onClick: openCreate }, t("btn.create")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setResult(null); setUpload(null); setModal("import"); } }, t("btn.import")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: function () { setResult(null); setCustomForm({ path: "", label: "" }); setModal("custom-add"); } }, t("btn.custom.add")))), h("div", { key: "summary", className: "dsm-summary dsm-summary-4" }, [[summary.total, "summary.total"], [summary.enabled, "summary.enabled"], [summary.disabled, "summary.disabled"], [summary.issues || 0, "summary.issues"]].map(function (item) { return h("div", { key: item[1], className: "dsm-stat" }, h("strong", null, item[0]), t(countKey(item[1], item[0]), { count: item[0] }).replace(String(item[0]), "")); })), h("div", { key: "filters", className: "dsm-filters" }, h("input", { className: "dsm-control dsm-search", value: query, "aria-label": t("search"), placeholder: t("search.placeholder"), onChange: function (e) { setQuery(e.target.value); } }), h("div", { className: "dsm-source-filter" }, h(SourceSelect, { value: activeSource, options: options, onChange: setSource }))), result && modal !== "import" ? h("div", { key: "result", className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error") }, result.text) : null].concat((data.warnings || []).map(function (warning, index) { return h("div", { key: "warning-" + index, className: "dsm-feedback dsm-warning", role: "alert" }, translateError(t, warning)); }), [snapshot.error ? h("div", { key: "error", className: "dsm-feedback dsm-error" }, snapshot.error) : null, snapshot.loading && !snapshot.data ? h("div", { key: "loading", className: "dsm-empty" }, t("loading")) : h("div", { key: "sources", className: "dsm-sources" }, roots.map(renderRoot)), h("button", { key: "trash", type: "button", className: "dsm-trash-row", onClick: function () { setModal("trash"); } }, h("span", null, t("trash.title")), h("span", { className: "dsm-trash-count" }, (data.trash || []).length))]);
809
980
 
810
981
  if (modal === "create") content.push(h(Modal, { key: "create", title: t("create.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-form" }, h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("create.target")), h(SourceSelect, { value: form.root, options: createOptions, onChange: function (value) { updateForm("root", value); } })), [["name", "create.name", "create.name.placeholder"], ["description", "create.description", "create.description.placeholder"]].map(function (field) { return h("label", { key: field[0], className: "dsm-field" }, h("span", { className: "dsm-label" }, t(field[1])), h("input", { className: "dsm-control", value: form[field[0]], placeholder: t(field[2]), onChange: function (e) { updateForm(field[0], e.target.value); } })); }), h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("create.body")), h("textarea", { className: "dsm-control", value: form.body, placeholder: t("create.body.placeholder"), onChange: function (e) { updateForm("body", e.target.value); } })), h("p", { className: "dsm-help" }, t("create.chat.note"))), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { className: "dsm-btn", disabled: busy || !form.name.trim() || !form.description.trim() || !form.body.trim(), onClick: submitCreate }, t("btn.create.now")))));
811
982
  if (modal === "import") content.push(h(Modal, { key: "import", className: "dsm-modal-import", title: t("import.title"), closeLabel: t("btn.close"), onClose: function () { pickerOpenRef.current = false; setUpload(null); setResult(null); setModal(null); } },
812
983
  h("input", { ref: importInputRef, className: "dsm-hidden-input", type: "file", accept: ".zip,.md", onChange: function (event) { selectUploadFiles(event.target.files); event.target.value = ""; } }),
813
984
  h("input", { ref: folderInputRef, className: "dsm-hidden-input", type: "file", multiple: true, webkitdirectory: "", directory: "", onChange: function (event) { selectUploadFiles(event.target.files); event.target.value = ""; } }),
814
- h("div", { className: "dsm-dropzone", onDragOver: function (event) { event.preventDefault(); event.currentTarget.classList.add("dsm-dropzone-active"); }, onDragLeave: function (event) { event.currentTarget.classList.remove("dsm-dropzone-active"); }, onDrop: function (event) { event.preventDefault(); event.currentTarget.classList.remove("dsm-dropzone-active"); droppedFiles(event.dataTransfer).then(selectUploadFiles).catch(function (error) { setResult({ ok: false, text: translateError(t, error) }); }); } },
985
+ h("div", { className: "dsm-dropzone", onClick: function () { openNativePicker(importInputRef); }, onDragOver: function (event) { event.preventDefault(); event.currentTarget.classList.add("dsm-dropzone-active"); }, onDragLeave: function (event) { event.currentTarget.classList.remove("dsm-dropzone-active"); }, onDrop: function (event) { event.preventDefault(); event.currentTarget.classList.remove("dsm-dropzone-active"); droppedFiles(event.dataTransfer).then(selectUploadFiles).catch(function (error) { setResult({ ok: false, text: translateError(t, error) }); }); } },
815
986
  h("span", { className: "dsm-dropzone-title" }, t("upload.drop.title")),
816
- h("span", { className: "dsm-dropzone-copy" }, t("upload.drop.copy")),
817
- h("div", { className: "dsm-upload-choices" }, h("button", { type: "button", className: "dsm-upload-link", disabled: busy, onClick: function (event) { event.stopPropagation(); openNativePicker(folderInputRef); } }, t("btn.folder.pick")), h("span", { className: "dsm-upload-divider" }, "/"), h("button", { type: "button", className: "dsm-upload-link", disabled: busy, onClick: function (event) { event.stopPropagation(); openNativePicker(importInputRef); } }, t("btn.file.pick")))
818
- ),
987
+ h("span", { className: "dsm-dropzone-copy" }, t("upload.drop.copy"))),
819
988
  upload ? h("div", { className: "dsm-file", title: upload.name }, h("span", { className: "dsm-file-kind", "aria-hidden": "true" }, upload.kind === "zip" ? "ZIP" : upload.kind === "folder" ? "DIR" : "MD"), h("span", { className: "dsm-file-name" }, upload.name), h("span", { className: "dsm-file-meta" }, t(countKey("upload.selected", upload.count), { count: upload.count, size: upload.size < 1024 ? upload.size + " B" : Math.ceil(upload.size / 1024) + " KB" })), h("button", { type: "button", className: "dsm-file-remove", "aria-label": t("upload.remove"), onClick: function () { setUpload(null); } }, "×")) : null,
820
989
  result ? h("div", { className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error"), role: "alert" }, result.text) : null,
821
990
  h("div", { className: "dsm-upload-requirements" }, h("div", { className: "dsm-label" }, t("upload.requirements")), h("ul", null, h("li", null, t("upload.requirement.skill")), h("li", null, t("upload.requirement.frontmatter")), h("li", null, t("upload.requirement.copy")))),
@@ -824,7 +993,7 @@ function callApi(path, options) {
824
993
  if (modal === "trash") content.push(h(Modal, { key: "trash-modal", title: t("trash.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-count" }, t(countKey("trash.count", data.trash.length), { count: data.trash.length })), data.trash.length ? data.trash.map(function (item) { return h("div", { key: item.id, className: "dsm-trash-item" }, h("div", { className: "dsm-trash-main" }, h("div", { className: "dsm-name" }, item.name), h("div", { className: "dsm-note" }, t("trash.deletedAt", { time: new Date(item.deletedAt).toLocaleString() }) + " · " + t("trash.source", { source: trashRootLabel(item) }))), h("button", { className: "dsm-btn dsm-btn-quiet", disabled: busy, onClick: function () { post("/trash-restore", { id: item.id }, "result.restored", { name: item.name }); } }, t("btn.restore")), h("button", { className: "dsm-btn dsm-btn-quiet dsm-btn-danger", disabled: busy, onClick: function () { setModal({ type: "delete-confirm", id: item.id, name: item.name }); } }, t("btn.delete.forever"))); }) : h("div", { className: "dsm-empty" }, t("trash.empty"))));
825
994
  if (modal && modal.type === "trash-confirm") content.push(h(Modal, { key: "trash-confirm", title: t("confirm.trash.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("p", { className: "dsm-desc" }, t("confirm.trash.desc", { name: modal.name })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { className: "dsm-btn", disabled: busy, onClick: function () { post("/delete", { root: modal.root, name: modal.name }, "result.trashed", { name: modal.name }).then(function () { setModal(null); }).catch(function () {}); } }, t("btn.trash")))));
826
995
  if (modal && modal.type === "delete-confirm") content.push(h(Modal, { key: "delete-confirm", title: t("confirm.delete.title"), closeLabel: t("btn.close"), onClose: function () { setModal("trash"); } }, h("p", { className: "dsm-desc" }, t("confirm.delete.desc", { name: modal.name })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal("trash"); } }, t("btn.cancel")), h("button", { className: "dsm-btn dsm-btn-danger", disabled: busy, onClick: function () { post("/trash-delete", { id: modal.id }, "result.deleted", { name: modal.name }).then(function () { setModal("trash"); }).catch(function () {}); } }, t("btn.delete.forever")))));
827
- if (modal === "custom-add") content.push(h(Modal, { key: "custom-add", title: t("custom.add.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-form" }, h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.path")), h("input", { className: "dsm-control", value: customForm.path, placeholder: t("custom.add.path.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { path: e.target.value })); } })), h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.label")), h("input", { className: "dsm-control", value: customForm.label, placeholder: t("custom.add.label.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { label: e.target.value })); } })), h("p", { className: "dsm-help" }, t("custom.add.help")), result ? h("div", { className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error"), role: "alert" }, result.text) : null), h("div", { className: "dsm-modal-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { type: "button", className: "dsm-btn", disabled: busy || !String(customForm.path || "").trim(), onClick: submitCustomAdd }, t("custom.add.submit")))));
996
+ if (modal === "custom-add") content.push(h(Modal, { key: "custom-add", title: t("custom.add.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-form" }, h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.path")), h("div", { className: "dsm-dir-row" }, h("input", { className: "dsm-control", value: customForm.path, placeholder: t("custom.add.path.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { path: e.target.value })); } }), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: openCustomDir, title: t("custom.add.openDir.title") }, t("btn.openDir")))), h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.label")), h("input", { className: "dsm-control", value: customForm.label, placeholder: t("custom.add.label.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { label: e.target.value })); } })), h("p", { className: "dsm-help" }, t("custom.add.help")), result ? h("div", { className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error"), role: "alert" }, result.text) : null), customForm.picking ? h(DirPickerModal, { key: "custom-dir-picker", title: t("custom.add.picker.title"), initial: String(customForm.path || ""), closeLabel: t("btn.close"), onClose: function () { setCustomForm(Object.assign({}, customForm, { picking: false })); }, onPick: function (path) { setCustomForm(Object.assign({}, customForm, { path: path, picking: false })); } }) : null, h("div", { className: "dsm-modal-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { type: "button", className: "dsm-btn", disabled: busy || !String(customForm.path || "").trim(), onClick: submitCustomAdd }, t("custom.add.submit")))));
828
997
  if (modal && modal.type === "custom-remove-confirm") content.push(h(Modal, { key: "custom-remove-confirm", title: t("confirm.custom.remove.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("p", { className: "dsm-desc" }, t("confirm.custom.remove.desc", { name: modal.name })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { className: "dsm-btn dsm-btn-danger", disabled: busy, onClick: function () { submitCustomRemove(modal.key, modal.name); } }, t("btn.custom.remove")))));
829
998
  return h("section", { className: "dsm-section" }, content);
830
999
  }
@@ -842,7 +1011,445 @@ function callApi(path, options) {
842
1011
  }
843
1012
  } catch (e) { /* locale 服务缺失时退回内置词典 */ }
844
1013
 
1014
+ // ---------- History 页(归档会话管理:恢复 / 永久删除 / 保留期)----------
1015
+ // 折叠自 dsh-archive-manager 的归档会话设置页,但客户端经本插件 HTTP API
1016
+ // 驱动(history-list / -archive / -unarchive / -delete / -retention-*),不依赖
1017
+ // ui-workspace 客户端 store 或 typert remote——会话标题由 host 端 best-effort 读取。
1018
+ function HistoryPage() {
1019
+ var state = React.useState({ loading: true, items: [], workspaces: {}, retentionDays: 0, error: null })
1020
+ var data = state[0], setData = state[1]
1021
+ var modalState = React.useState(null)
1022
+ var modal = modalState[0], setModal = modalState[1]
1023
+ var searchState = React.useState('')
1024
+ var query = searchState[0], setQuery = searchState[1]
1025
+ var busyState = React.useState(false)
1026
+ var busy = busyState[0], setBusy = busyState[1]
1027
+ // 多选集合:勾选的 sessionId。最小 React 支持函数式初值(Set 惰性创建)。
1028
+ var selState = React.useState(function () { return new Set() })
1029
+ var selected = selState[0], setSelected = selState[1]
1030
+ // 导入对话弹窗:null = 关闭;{ busy, error, result, cwd } = 打开。
1031
+ var impState = React.useState(null)
1032
+ var imp = impState[0], setImp = impState[1]
1033
+ var importInputRef = React.useRef(null)
1034
+ // 导出对话弹窗:null = 关闭;{ busy, error, result, outDir, format, wsFilter, selectedIds } = 打开。
1035
+ var expState = React.useState(null)
1036
+ var exp = expState[0], setExp = expState[1]
1037
+ // 分组卡片折叠状态:key → true(收起)。默认全部展开。
1038
+ var collState = React.useState({})
1039
+ var collapsed = collState[0], setCollapsed = collState[1]
1040
+
1041
+ function refresh() {
1042
+ apiCall('history-list', {}).then(function (r) {
1043
+ if (r && r.ok) {
1044
+ var items = r.items || []
1045
+ setData({ loading: false, items: items, workspaces: r.workspaces || {}, retentionDays: r.retentionDays || 0, error: null })
1046
+ // 裁剪选中集合:已被外部恢复/删除的会话不再计入。
1047
+ setSelected(function (prev) {
1048
+ var has = new Set(items.map(function (it) { return it.sessionId }))
1049
+ var next = new Set()
1050
+ prev.forEach(function (id) { if (has.has(id)) next.add(id) })
1051
+ return next
1052
+ })
1053
+ } else setData({ loading: false, items: [], workspaces: {}, retentionDays: 0, error: (r && r.error) || '加载失败' })
1054
+ })
1055
+ }
1056
+ React.useEffect(function () { refresh() }, [])
1057
+
1058
+ function shortId(id) { var s = String(id || ''); return s.length > 12 ? s.slice(0, 8) + '…' + s.slice(-4) : s }
1059
+
1060
+ var filtered = data.items
1061
+ if (query) {
1062
+ var q = query.toLowerCase()
1063
+ filtered = data.items.filter(function (it) {
1064
+ return (it.title && it.title.toLowerCase().indexOf(q) >= 0)
1065
+ || (it.sessionId && it.sessionId.toLowerCase().indexOf(q) >= 0)
1066
+ || (it.cwd && it.cwd.toLowerCase().indexOf(q) >= 0)
1067
+ })
1068
+ }
1069
+
1070
+ // 按项目分组(host 提供 workspaces 时启用;否则扁平列表)。
1071
+ var grouped = !!(data.workspaces && Object.keys(data.workspaces).length)
1072
+ var visibleIds = filtered.map(function (it) { return it.sessionId })
1073
+ var groups = []
1074
+ if (grouped) {
1075
+ var byWs = new Map()
1076
+ filtered.forEach(function (it) {
1077
+ var wid = data.workspaces[it.workspaceId] ? it.workspaceId : null
1078
+ var key = wid ? ('ws:' + wid) : 'ungrouped'
1079
+ if (!byWs.has(key)) byWs.set(key, [])
1080
+ byWs.get(key).push(it)
1081
+ })
1082
+ byWs.forEach(function (items2, key) {
1083
+ var wid = key === 'ungrouped' ? null : key.slice(3)
1084
+ var wsRec = wid ? data.workspaces[wid] : null
1085
+ var title = wid ? ((wsRec && wsRec.title) || wid) : '未分组'
1086
+ items2.sort(function (a, b) { return ((b.archivedAt || b.createdAt) || 0) - ((a.archivedAt || a.createdAt) || 0) })
1087
+ var maxAt = 0
1088
+ items2.forEach(function (it) { var at = (it.archivedAt || it.createdAt) || 0; if (at > maxAt) maxAt = at })
1089
+ groups.push({ key: key, title: title, workspaceId: wid, path: wsRec && wsRec.path, items: items2, maxAt: maxAt })
1090
+ })
1091
+ // 组按组内最新时间戳降序;未分组固定最后。
1092
+ groups.sort(function (g1, g2) {
1093
+ if (g1.key === 'ungrouped') return 1
1094
+ if (g2.key === 'ungrouped') return -1
1095
+ return g2.maxAt - g1.maxAt
1096
+ })
1097
+ }
1098
+
1099
+ // 「全选」状态:当前过滤结果是否全部选中(按钮文案切换为「取消全选」)。
1100
+ var allVisible = filtered.length > 0 && visibleIds.every(function (id) { return selected.has(id) })
1101
+
1102
+ function doUnarchive(sessionId) {
1103
+ setBusy(true)
1104
+ apiCall('history-unarchive', { sessionId: sessionId }).then(function (r) {
1105
+ setBusy(false)
1106
+ if (r && r.ok) refresh(); else setModal({ type: 'error', message: (r && r.error) || '恢复失败' })
1107
+ })
1108
+ }
1109
+ function doDelete(sessionId) {
1110
+ setBusy(true)
1111
+ apiCall('history-delete', { sessionId: sessionId }).then(function (r) {
1112
+ setBusy(false)
1113
+ if (r && r.ok) { setModal(null); refresh() } else setModal({ type: 'error', message: (r && r.error) || '删除失败' })
1114
+ })
1115
+ }
1116
+ function setRetention(days) {
1117
+ apiCall('history-retention-set', { retentionDays: days }).then(function (r) {
1118
+ if (r && r.ok) setData(Object.assign({}, data, { retentionDays: days }))
1119
+ else setModal({ type: 'error', message: (r && r.error) || '设置失败' })
1120
+ })
1121
+ }
1122
+
1123
+ // ---------- 多选 / 批量交互 ----------
1124
+ function toggleSelect(id) {
1125
+ setSelected(function (prev) {
1126
+ var next = new Set(prev)
1127
+ if (next.has(id)) next.delete(id); else next.add(id)
1128
+ return next
1129
+ })
1130
+ }
1131
+ function toggleGroup(ids) {
1132
+ setSelected(function (prev) {
1133
+ var next = new Set(prev)
1134
+ var all = ids.every(function (id) { return next.has(id) })
1135
+ ids.forEach(function (id) { if (all) next.delete(id); else next.add(id) })
1136
+ return next
1137
+ })
1138
+ }
1139
+ function toggleAllVisible(ids) {
1140
+ if (!ids.length) return
1141
+ setSelected(function (prev) {
1142
+ var next = new Set(prev)
1143
+ var all = ids.every(function (id) { return next.has(id) })
1144
+ ids.forEach(function (id) { if (all) next.delete(id); else next.add(id) })
1145
+ return next
1146
+ })
1147
+ }
1148
+ function doUnarchiveBatch() {
1149
+ var ids = Array.from(selected)
1150
+ if (!ids.length) return
1151
+ setBusy(true)
1152
+ apiCall('history-unarchive-batch', { target: { scope: 'sessions', sessionIds: ids } }).then(function (r) {
1153
+ setBusy(false)
1154
+ if (r && r.ok) {
1155
+ var gone = new Set(r.unarchivedSessionIds || [])
1156
+ setSelected(function (prev) { var next = new Set(prev); gone.forEach(function (id) { next.delete(id) }); return next })
1157
+ refresh()
1158
+ } else setModal({ type: 'error', message: (r && r.error) || '恢复失败' })
1159
+ })
1160
+ }
1161
+ function doDeleteBatch() {
1162
+ var ids = Array.from(selected)
1163
+ if (!ids.length) return
1164
+ setBusy(true)
1165
+ apiCall('history-delete-batch', { target: { scope: 'sessions', sessionIds: ids } }).then(function (r) {
1166
+ setBusy(false)
1167
+ if (r && r.ok) { setModal(null); setSelected(new Set()); refresh() }
1168
+ else setModal({ type: 'error', message: (r && r.error) || '删除失败' })
1169
+ })
1170
+ }
1171
+
1172
+ // ---------- 导入对话(从其他 Agent)----------
1173
+ function doImportFile(file) {
1174
+ if (!file || !imp) return
1175
+ var reader = new FileReader()
1176
+ reader.onload = function () {
1177
+ var snap = imp
1178
+ setImp(Object.assign({}, imp, { busy: true, error: null, result: null }))
1179
+ apiCall('history-import', {
1180
+ fileName: String(file.name || 'transcript.txt'),
1181
+ content: String(reader.result || ''),
1182
+ cwd: String(snap.cwd || '').trim(),
1183
+ }).then(function (res) {
1184
+ if (res && res.ok) {
1185
+ setImp(Object.assign({}, snap, { busy: false, result: res }))
1186
+ refresh()
1187
+ } else setImp(Object.assign({}, snap, { busy: false, error: (res && res.error) || '导入失败' }))
1188
+ }).catch(function () {
1189
+ setImp(Object.assign({}, snap, { busy: false, error: '导入失败' }))
1190
+ })
1191
+ }
1192
+ reader.readAsText(file)
1193
+ }
1194
+ function onImportDrop(e) {
1195
+ if (e && e.preventDefault) e.preventDefault()
1196
+ if (imp && imp.busy) return
1197
+ droppedFiles(e && e.dataTransfer).then(function (files) {
1198
+ if (files && files.length) doImportFile(files[0])
1199
+ }).catch(function () {})
1200
+ }
1201
+ function openImportPicker() {
1202
+ setImp({ busy: false, error: null, result: null, cwd: '' })
1203
+ }
1204
+ // ---------- 导出对话:弹窗内选范围/工作区 → 勾选会话 → 写入指定目录 ----------
1205
+ function openExportPicker() {
1206
+ setExp({ busy: false, error: null, result: null, outDir: '', format: 'markdown', wsFilter: '', archFilter: 'all', items: [], loading: true, selectedIds: new Set() })
1207
+ // 拉取全部持久化会话(含未归档/冷会话),供选择导出。
1208
+ apiCall('history-sessions', {}).then(function (res) {
1209
+ setExp(function (prev) {
1210
+ if (!prev) return prev
1211
+ return Object.assign({}, prev, {
1212
+ loading: false,
1213
+ error: (res && res.ok) ? prev.error : ((res && res.error) || '加载会话列表失败'),
1214
+ items: (res && res.ok && Array.isArray(res.items)) ? res.items : prev.items,
1215
+ })
1216
+ })
1217
+ }).catch(function () {
1218
+ setExp(function (prev) { return prev ? Object.assign({}, prev, { loading: false, error: '加载会话列表失败' }) : prev })
1219
+ })
1220
+ // 拉取默认导出目录(桌面),仅当用户尚未输入时填充。
1221
+ apiCall('history-export-defaults', {}).then(function (res) {
1222
+ setExp(function (prev) {
1223
+ if (!prev || prev.outDir) return prev
1224
+ if (res && res.ok && typeof res.defaultDir === 'string' && res.defaultDir) {
1225
+ return Object.assign({}, prev, { outDir: res.defaultDir })
1226
+ }
1227
+ return prev
1228
+ })
1229
+ }).catch(function () {})
1230
+ }
1231
+ // 弹出目录选择器:选中后把导出目录回填到输入框。
1232
+ function openExportDir() {
1233
+ if (exp.busy) return
1234
+ setExp(Object.assign({}, exp, { pickingDir: true }))
1235
+ }
1236
+ function toggleExportSelect(id) {
1237
+ setExp(function (prev) {
1238
+ var next = new Set(prev.selectedIds)
1239
+ if (next.has(id)) next.delete(id); else next.add(id)
1240
+ return Object.assign({}, prev, { selectedIds: next })
1241
+ })
1242
+ }
1243
+ function doExport() {
1244
+ var ids = Array.from(exp.selectedIds)
1245
+ if (!ids.length || !String(exp.outDir || '').trim()) return
1246
+ setExp(Object.assign({}, exp, { busy: true, error: null, result: null }))
1247
+ apiCall('history-export', { sessionIds: ids, format: exp.format, outDir: String(exp.outDir).trim() }).then(function (res) {
1248
+ if (res && res.ok) setExp(Object.assign({}, exp, { busy: false, result: res }))
1249
+ else setExp(Object.assign({}, exp, { busy: false, error: (res && res.error) || '导出失败' }))
1250
+ }).catch(function () {
1251
+ setExp(Object.assign({}, exp, { busy: false, error: '导出失败' }))
1252
+ })
1253
+ }
1254
+ // 把选中的会话批量归档到 History(纳入保留期管理),随后刷新列表与页面。
1255
+ function doArchiveSelected() {
1256
+ var ids = Array.from(exp.selectedIds)
1257
+ if (!ids.length || exp.busy) return
1258
+ setExp(Object.assign({}, exp, { busy: true, error: null, result: null }))
1259
+ apiCall('history-archive-batch', { sessionIds: ids }).then(function (res) {
1260
+ if (res && res.ok) {
1261
+ refresh()
1262
+ return apiCall('history-sessions', {}).then(function (r2) {
1263
+ setExp(function (prev) {
1264
+ if (!prev) return prev
1265
+ return Object.assign({}, prev, {
1266
+ busy: false,
1267
+ selectedIds: new Set(),
1268
+ result: { archived: (res.archived || []).length },
1269
+ items: (r2 && r2.ok && Array.isArray(r2.items)) ? r2.items : prev.items,
1270
+ })
1271
+ })
1272
+ })
1273
+ }
1274
+ setExp(function (prev) { return prev ? Object.assign({}, prev, { busy: false, error: (res && res.error) || '归档失败' }) : prev })
1275
+ }).catch(function () {
1276
+ setExp(function (prev) { return prev ? Object.assign({}, prev, { busy: false, error: '归档失败' }) : prev })
1277
+ })
1278
+ }
1279
+
1280
+ var RETENTION_OPTS = [
1281
+ { value: 0, label: '永久保留' },
1282
+ { value: 7, label: '7 天' },
1283
+ { value: 30, label: '30 天' },
1284
+ ]
1285
+ var EXPORT_FORMAT_OPTS = [
1286
+ { value: 'markdown', label: 'Markdown' },
1287
+ { value: 'jsonl', label: 'JSONL' },
1288
+ ]
1289
+ var EXPORT_ARCH_OPTS = [
1290
+ { value: 'all', label: '全部会话' },
1291
+ { value: 'archived', label: '仅已归档' },
1292
+ { value: 'live', label: '仅未归档' },
1293
+ { value: 'missing', label: '仅目录丢失' },
1294
+ ]
1295
+ // 导出弹窗内的工作区筛选选项与候选会话(随弹窗状态/数据刷新重算)。
1296
+ var exportWsOptions = [{ value: '', label: '全部工作区' }]
1297
+ if (exp && data.workspaces) {
1298
+ Object.keys(data.workspaces).forEach(function (wid) {
1299
+ exportWsOptions.push({ value: 'ws:' + wid, label: (data.workspaces[wid].title || wid) })
1300
+ })
1301
+ exportWsOptions.push({ value: 'ungrouped', label: '未分组' })
1302
+ }
1303
+ var exportItems = []
1304
+ if (exp) {
1305
+ exportItems = (exp.items || []).filter(function (it) {
1306
+ if (exp.archFilter === 'archived' && !it.archived) return false
1307
+ if (exp.archFilter === 'live' && it.archived) return false
1308
+ if (exp.archFilter === 'missing' && !it.cwdMissing) return false
1309
+ if (!exp.wsFilter) return true
1310
+ if (exp.wsFilter === 'ungrouped') return !it.workspaceId || !data.workspaces[it.workspaceId]
1311
+ var wid = exp.wsFilter.slice(3)
1312
+ return it.workspaceId === wid && !!data.workspaces[wid]
1313
+ })
1314
+ }
1315
+
1316
+ function renderRow(it) {
1317
+ return React.createElement('div', { className: 'dsm-hist-row', key: it.sessionId },
1318
+ React.createElement('input', { type: 'checkbox', className: 'dsm-hist-check', checked: selected.has(it.sessionId), onChange: function () { toggleSelect(it.sessionId) } }),
1319
+ React.createElement('div', { className: 'dsm-hist-main' },
1320
+ React.createElement('div', { className: 'dsm-hist-title' }, it.title || ('Session ' + shortId(it.sessionId))),
1321
+ it.cwd ? React.createElement('div', { className: 'dsm-hist-cwd', title: it.cwd }, it.cwd) : null),
1322
+ React.createElement('div', { className: 'dsm-hist-actions' },
1323
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { doUnarchive(it.sessionId) } }, '恢复'),
1324
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { setModal({ type: 'delete', sessionId: it.sessionId, title: it.title || ('Session ' + shortId(it.sessionId)) }) } }, '永久删除')))
1325
+ }
1326
+
1327
+ // 分组卡片:dsm-source 卡片 + 可折叠表头(与 MCP / Skills 页一致的源卡片语言)。
1328
+ function toggleGroupCollapse(key) {
1329
+ setCollapsed(function (prev) {
1330
+ var next = Object.assign({}, prev)
1331
+ next[key] = !next[key]
1332
+ return next
1333
+ })
1334
+ }
1335
+ function renderGroup(g) {
1336
+ var groupIds = g.items.map(function (it) { return it.sessionId })
1337
+ var allChecked = groupIds.length > 0 && groupIds.every(function (id) { return selected.has(id) })
1338
+ var open = !collapsed[g.key]
1339
+ return React.createElement('div', { className: 'dsm-source', key: 'g:' + g.key },
1340
+ React.createElement('div', { className: 'dsm-source-head dsm-hist-group-head' },
1341
+ React.createElement('input', { type: 'checkbox', className: 'dsm-hist-group-check', checked: allChecked, onChange: function () { toggleGroup(groupIds) }, 'aria-label': '全选组 ' + g.title }),
1342
+ React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: function () { toggleGroupCollapse(g.key) } },
1343
+ React.createElement('span', { className: 'dsm-source-title dsm-hist-group-title', title: g.title }, g.title),
1344
+ React.createElement('span', { className: 'dsm-count' }, g.items.length + ' 个')),
1345
+ g.path ? React.createElement('span', { className: 'dsm-path', title: g.path }, g.path) : null),
1346
+ open ? React.createElement('div', { className: 'dsm-source-body' }, g.items.map(renderRow)) : null)
1347
+ }
1348
+
1349
+ return React.createElement('section', { className: 'dsm-section' },
1350
+ React.createElement('div', { className: 'dsm-head' },
1351
+ React.createElement('div', { className: 'dsm-title-block' },
1352
+ React.createElement('div', { className: 'dsm-title-row' },
1353
+ React.createElement('h2', { className: 'dsm-title' }, 'History'),
1354
+ React.createElement(VersionBadge, null)),
1355
+ React.createElement('p', { className: 'dsm-desc' }, '管理已归档会话:恢复、永久删除或批量操作;归档满保留期后自动清理。')),
1356
+ React.createElement('div', { className: 'dsm-actions' },
1357
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: !filtered.length, onClick: function () { toggleAllVisible(visibleIds) } }, allVisible ? '取消全选' : '全选'),
1358
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openImportPicker }, '导入对话'),
1359
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openExportPicker }, '导出对话'),
1360
+ selected.size > 0 ? React.createElement('div', { className: 'dsm-hist-batch' },
1361
+ React.createElement('span', { className: 'dsm-hist-batch-count' }, '已选 ' + selected.size + ' 项'),
1362
+ React.createElement('div', { className: 'dsm-hist-batch-actions' },
1363
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { doUnarchiveBatch() } }, '恢复所选 (' + selected.size + ')'),
1364
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { setModal({ type: 'deleteBatch', count: selected.size }) } }, '删除所选 (' + selected.size + ')'))) : null)),
1365
+ React.createElement('div', { className: 'dsm-summary dsm-summary-4' },
1366
+ [[data.items.length, '个归档'], [grouped ? groups.length : '—', '个项目'], [data.retentionDays === 0 ? '永久' : data.retentionDays + ' 天', '保留期'], [selected.size, '个已选']].map(function (item) {
1367
+ return React.createElement('div', { key: item[1], className: 'dsm-stat' },
1368
+ React.createElement('strong', null, item[0]), item[1])
1369
+ })),
1370
+ React.createElement('div', { className: 'dsm-filters' },
1371
+ React.createElement('input', { className: 'dsm-control dsm-search', type: 'text', placeholder: '搜索标题 / 会话 ID / 项目路径', value: query, onChange: function (e) { setQuery(e.target.value) } }),
1372
+ React.createElement('div', { className: 'dsm-source-filter' },
1373
+ React.createElement(SourceSelect, {
1374
+ options: RETENTION_OPTS, value: data.retentionDays, onChange: function (v) { setRetention(Number(v)) },
1375
+ }))),
1376
+ data.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(data.error)) : null,
1377
+ data.loading ? React.createElement('div', { className: 'dsm-empty' }, '加载中…')
1378
+ : filtered.length === 0 ? React.createElement('div', { className: 'dsm-empty' }, query ? '无匹配的归档会话' : '暂无归档会话')
1379
+ : React.createElement('div', { className: 'dsm-sources' },
1380
+ grouped ? groups.map(renderGroup) : React.createElement('div', { className: 'dsm-source' }, filtered.map(renderRow))),
1381
+ modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: '永久删除?', closeLabel: '取消', onClose: function () { setModal(null) } },
1382
+ React.createElement('p', { className: 'dsm-help' }, '将永久删除会话「' + modal.title + '」及其全部记录,此操作不可撤销。'),
1383
+ React.createElement('div', { className: 'dsm-modal-actions' },
1384
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { setModal(null) } }, '取消'),
1385
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { doDelete(modal.sessionId) } }, '确认删除'))) : null,
1386
+ modal && modal.type === 'deleteBatch' ? React.createElement(Modal, { key: 'delb', title: '永久删除?', closeLabel: '取消', onClose: function () { setModal(null) } },
1387
+ React.createElement('p', { className: 'dsm-help' }, '将永久删除所选 ' + modal.count + ' 个会话及其全部记录,此操作不可撤销。'),
1388
+ React.createElement('div', { className: 'dsm-modal-actions' },
1389
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { setModal(null) } }, '取消'),
1390
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { doDeleteBatch() } }, '确认删除'))) : null,
1391
+ imp ? React.createElement(Modal, { key: 'imp', className: 'dsm-modal-import', title: '导入对话', closeLabel: '关闭', onClose: function () { setImp(null) } },
1392
+ React.createElement('p', { className: 'dsm-help' }, '从其他 Agent 导入对话记录,生成可在 DSH 中继续对话的新会话。支持 Claude Code / Cursor JSONL、Codex Markdown 与通用文本。'),
1393
+ React.createElement('div', { className: 'dsm-field' },
1394
+ React.createElement('label', { className: 'dsm-label' }, '项目目录(可留空)'),
1395
+ React.createElement('input', { className: 'dsm-control', type: 'text', placeholder: '绝对路径,可留空(会话归入未分组)', value: imp.cwd || '', onChange: function (e) { setImp(Object.assign({}, imp, { cwd: e.target.value })) } })),
1396
+ React.createElement('div', { className: 'dsm-dropzone', onDragOver: function (e) { if (e.preventDefault) e.preventDefault() }, onDrop: onImportDrop, onClick: function () { if (importInputRef.current) importInputRef.current.click() } },
1397
+ React.createElement('div', { className: 'dsm-dropzone-title' }, '选择或拖入对话文件'),
1398
+ React.createElement('div', { className: 'dsm-dropzone-copy' }, '.jsonl / .json / .md / .txt')),
1399
+ React.createElement('input', { type: 'file', ref: importInputRef, className: 'dsm-hidden-input', accept: '.jsonl,.json,.md,.markdown,.txt', onChange: function (e) { var f = e.target && e.target.files && e.target.files[0]; if (f) doImportFile(f) } }),
1400
+ imp.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(imp.error)) : null,
1401
+ imp.result ? React.createElement('div', { className: 'dsm-feedback' }, '已创建会话 ' + imp.result.sessionId + '(' + imp.result.count + ' 条消息),可在 DSH 会话列表中继续对话。') : null) : null,
1402
+ exp ? React.createElement(Modal, { key: 'exp', className: 'dsm-modal-import', title: '导出对话', closeLabel: '关闭', onClose: function () { setExp(null) } },
1403
+ React.createElement('p', { className: 'dsm-help' }, '把选中的会话导出为转录文件,写入指定目录;文件可在「导入对话」中重新导入。'),
1404
+ React.createElement('div', { className: 'dsm-field' },
1405
+ React.createElement('label', { className: 'dsm-label' }, '导出目录(绝对路径,自动创建)'),
1406
+ React.createElement('div', { className: 'dsm-dir-row' },
1407
+ React.createElement('input', { className: 'dsm-control', type: 'text', placeholder: 'D:\\backups\\dsh\\exports', value: exp.outDir || '', onChange: function (e) { setExp(Object.assign({}, exp, { outDir: e.target.value })) } }),
1408
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: exp.busy, onClick: openExportDir, title: '浏览并选择文件夹' }, '选择')),
1409
+ exp.pickingDir ? React.createElement(DirPickerModal, { key: 'exp-dir-picker', title: '选择导出目录', initial: String(exp.outDir || ''), closeLabel: '关闭', onClose: function () { setExp(Object.assign({}, exp, { pickingDir: false })) }, onPick: function (path) { setExp(Object.assign({}, exp, { outDir: path, pickingDir: false })) } }) : null),
1410
+ React.createElement('div', { className: 'dsm-field' },
1411
+ React.createElement('label', { className: 'dsm-label' }, '格式'),
1412
+ React.createElement(SourceSelect, { options: EXPORT_FORMAT_OPTS, value: exp.format, onChange: function (v) { setExp(Object.assign({}, exp, { format: v })) } })),
1413
+ React.createElement('div', { className: 'dsm-field' },
1414
+ React.createElement('label', { className: 'dsm-label' }, '会话范围'),
1415
+ React.createElement(SourceSelect, { options: EXPORT_ARCH_OPTS, value: exp.archFilter, onChange: function (v) { setExp(Object.assign({}, exp, { archFilter: v, selectedIds: new Set() })) } })),
1416
+ React.createElement('div', { className: 'dsm-field' },
1417
+ React.createElement('label', { className: 'dsm-label' }, '工作区'),
1418
+ React.createElement(SourceSelect, { options: exportWsOptions, value: exp.wsFilter, onChange: function (v) { setExp(Object.assign({}, exp, { wsFilter: v, selectedIds: new Set() })) } })),
1419
+ React.createElement('div', { className: 'dsm-field' },
1420
+ React.createElement('div', { className: 'dsm-label' }, '会话(' + exportItems.length + ' 个)'),
1421
+ React.createElement('div', { className: 'dsm-source' },
1422
+ exp.loading ? React.createElement('div', { className: 'dsm-empty' }, '加载会话列表…')
1423
+ : exportItems.length ? exportItems.map(function (it) {
1424
+ return React.createElement('label', { key: it.sessionId, className: 'dsm-hist-row' },
1425
+ React.createElement('input', { type: 'checkbox', className: 'dsm-hist-check', checked: exp.selectedIds.has(it.sessionId), onChange: function () { toggleExportSelect(it.sessionId) } }),
1426
+ React.createElement('div', { className: 'dsm-hist-main' },
1427
+ React.createElement('div', { className: 'dsm-hist-title' }, it.title || ('Session ' + shortId(it.sessionId))),
1428
+ it.cwd ? React.createElement('div', { className: 'dsm-hist-cwd' + (it.cwdMissing ? ' dsm-hist-cwd-missing' : ''), title: (it.cwdMissing ? '⚠ 工作区目录已不存在:' : '') + it.cwd }, (it.cwdMissing ? '⚠ ' : '') + it.cwd) : null),
1429
+ React.createElement('div', { className: 'dsm-hist-actions' },
1430
+ React.createElement('span', { className: 'dsm-tag' + (it.archived ? '' : ' dsm-tag-on') }, it.archived ? '已归档' : '未归档')))
1431
+ }) : React.createElement('div', { className: 'dsm-empty' }, '没有符合条件的会话'))),
1432
+ exp.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(exp.error)) : null,
1433
+ exp.result ? React.createElement('div', { className: 'dsm-feedback' },
1434
+ exp.result.exported
1435
+ ? ('已导出 ' + (exp.result.exported || []).length + ' 个会话到 ' + String(exp.outDir || '').trim()
1436
+ + ((exp.result.skipped && exp.result.skipped.length) ? ';跳过 ' + exp.result.skipped.length + ' 个(' + exp.result.skipped.map(function (s) { return s.sessionId }).join('、') + ')' : ''))
1437
+ : ('已归档 ' + exp.result.archived + ' 个会话到 History,可到 History 页继续管理')) : null,
1438
+ React.createElement('div', { className: 'dsm-modal-actions' },
1439
+ React.createElement('span', { className: 'dsm-hist-batch-count' }, '已选 ' + exp.selectedIds.size + ' 个'),
1440
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: exp.busy || !exp.selectedIds.size, onClick: doArchiveSelected, title: '把选中的会话收进 History,纳入保留期管理' }, '归档所选'),
1441
+ React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: exp.busy || !exp.selectedIds.size || !String(exp.outDir || '').trim(), onClick: doExport }, exp.busy ? '处理中…' : '导出 ' + exp.selectedIds.size + ' 个会话'))) : null,
1442
+ modal && modal.type === 'error' ? React.createElement(Modal, { key: 'err', title: '操作失败', closeLabel: '关闭', onClose: function () { setModal(null) } },
1443
+ React.createElement('p', { className: 'dsm-help' }, String(modal.message || '未知错误')),
1444
+ React.createElement('div', { className: 'dsm-modal-actions' },
1445
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { setModal(null) } }, '关闭'))) : null)
1446
+ }
1447
+
845
1448
  module.exports.DICT = DICT
1449
+ // Test-only export: page 组件 + i18n t,让 client 测试直接渲染 page(绕开
1450
+ // ToolsSection 的子组件嵌套——minimal React 不支持子组件 hook 隔离)。
1451
+ // 生产环境浏览器 React 会正确递归渲染 ToolsSection 内的 page。
1452
+ module.exports._pages = { MCPPage: MCPPage, SkillManagerSection: SkillManagerSection, AgentsMdPage: AgentsMdPage, HistoryPage: HistoryPage, t: t }
846
1453
  },
847
1454
  }
848
1455
  return module.exports