@chrysb/alphaclaw 0.3.4 → 0.3.5-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/alphaclaw.js +17 -2
- package/lib/public/css/explorer.css +184 -3
- package/lib/public/css/theme.css +1 -1
- package/lib/public/js/app.js +57 -7
- package/lib/public/js/components/file-tree.js +49 -2
- package/lib/public/js/components/file-viewer.js +435 -204
- package/lib/public/js/components/gateway.js +12 -18
- package/lib/public/js/components/icons.js +13 -0
- package/lib/public/js/components/sidebar-git-panel.js +155 -28
- package/lib/public/js/components/sidebar.js +1 -1
- package/lib/public/js/components/watchdog-tab.js +0 -2
- package/lib/public/js/lib/api.js +15 -0
- package/lib/server/helpers.js +18 -5
- package/lib/server/internal-files-migration.js +93 -0
- package/lib/server/onboarding/cron.js +6 -4
- package/lib/server/onboarding/index.js +7 -0
- package/lib/server/onboarding/openclaw.js +6 -1
- package/lib/server/routes/browse.js +233 -28
- package/lib/server/routes/pairings.js +8 -2
- package/lib/server/routes/system.js +5 -1
- package/lib/server.js +7 -0
- package/package.json +1 -1
package/bin/alphaclaw.js
CHANGED
|
@@ -10,6 +10,9 @@ const {
|
|
|
10
10
|
validateGitSyncFilePath,
|
|
11
11
|
} = require("../lib/cli/git-sync");
|
|
12
12
|
const { buildSecretReplacements } = require("../lib/server/helpers");
|
|
13
|
+
const {
|
|
14
|
+
migrateManagedInternalFiles,
|
|
15
|
+
} = require("../lib/server/internal-files-migration");
|
|
13
16
|
|
|
14
17
|
const kUsageTrackerPluginPath = path.resolve(
|
|
15
18
|
__dirname,
|
|
@@ -143,6 +146,10 @@ if (portFlag) {
|
|
|
143
146
|
|
|
144
147
|
const openclawDir = path.join(rootDir, ".openclaw");
|
|
145
148
|
fs.mkdirSync(openclawDir, { recursive: true });
|
|
149
|
+
const { hourlyGitSyncPath } = migrateManagedInternalFiles({
|
|
150
|
+
fs,
|
|
151
|
+
openclawDir,
|
|
152
|
+
});
|
|
146
153
|
console.log(`[alphaclaw] Root directory: ${rootDir}`);
|
|
147
154
|
|
|
148
155
|
// Check for pending update marker (written by the update endpoint before restart).
|
|
@@ -542,7 +549,6 @@ if (!fs.existsSync(gogConfigFile)) {
|
|
|
542
549
|
// 8. Install/reconcile system cron entry
|
|
543
550
|
// ---------------------------------------------------------------------------
|
|
544
551
|
|
|
545
|
-
const hourlyGitSyncPath = path.join(openclawDir, "hourly-git-sync.sh");
|
|
546
552
|
const packagedHourlyGitSyncPath = path.join(setupDir, "hourly-git-sync.sh");
|
|
547
553
|
|
|
548
554
|
try {
|
|
@@ -822,7 +828,16 @@ if (fs.existsSync(configPath)) {
|
|
|
822
828
|
const replacements = buildSecretReplacements(process.env);
|
|
823
829
|
for (const [secret, envRef] of replacements) {
|
|
824
830
|
if (secret) {
|
|
825
|
-
|
|
831
|
+
// Only replace the secret if it is an exact match for a JSON string value
|
|
832
|
+
// This ensures we do not replace substrings inside other strings
|
|
833
|
+
const secretJson = JSON.stringify(secret);
|
|
834
|
+
content = content.replace(
|
|
835
|
+
new RegExp(
|
|
836
|
+
secretJson.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"),
|
|
837
|
+
"g",
|
|
838
|
+
),
|
|
839
|
+
JSON.stringify(envRef),
|
|
840
|
+
);
|
|
826
841
|
}
|
|
827
842
|
}
|
|
828
843
|
fs.writeFileSync(configPath, content);
|
|
@@ -105,6 +105,11 @@
|
|
|
105
105
|
padding: 6px 0 8px;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
.file-tree-wrap-loading {
|
|
109
|
+
min-height: 100%;
|
|
110
|
+
display: flex;
|
|
111
|
+
}
|
|
112
|
+
|
|
108
113
|
.file-tree-search {
|
|
109
114
|
padding: 0 8px 6px;
|
|
110
115
|
}
|
|
@@ -281,6 +286,14 @@
|
|
|
281
286
|
box-shadow: 0 0 6px rgba(45, 226, 255, 0.75);
|
|
282
287
|
}
|
|
283
288
|
|
|
289
|
+
.tree-lock-icon {
|
|
290
|
+
flex: 0 0 auto;
|
|
291
|
+
margin-left: auto;
|
|
292
|
+
width: 11px;
|
|
293
|
+
height: 11px;
|
|
294
|
+
color: var(--text-dim);
|
|
295
|
+
}
|
|
296
|
+
|
|
284
297
|
.tree-children {
|
|
285
298
|
list-style: none;
|
|
286
299
|
}
|
|
@@ -295,6 +308,15 @@
|
|
|
295
308
|
color: var(--text-muted);
|
|
296
309
|
}
|
|
297
310
|
|
|
311
|
+
.file-tree-state-loading {
|
|
312
|
+
width: 100%;
|
|
313
|
+
flex: 1 1 auto;
|
|
314
|
+
min-height: 100%;
|
|
315
|
+
display: flex;
|
|
316
|
+
align-items: center;
|
|
317
|
+
justify-content: center;
|
|
318
|
+
}
|
|
319
|
+
|
|
298
320
|
.file-tree-state-error {
|
|
299
321
|
color: #f87171;
|
|
300
322
|
}
|
|
@@ -331,6 +353,21 @@
|
|
|
331
353
|
background: rgba(234, 179, 8, 0.08);
|
|
332
354
|
}
|
|
333
355
|
|
|
356
|
+
.file-viewer-protected-banner.is-locked {
|
|
357
|
+
background: rgba(220, 38, 38, 0.16);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.file-viewer-protected-banner-icon {
|
|
361
|
+
width: 14px;
|
|
362
|
+
height: 14px;
|
|
363
|
+
color: #fca5a5;
|
|
364
|
+
flex-shrink: 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.file-viewer-protected-banner.is-locked .file-viewer-protected-banner-text {
|
|
368
|
+
color: #fecaca;
|
|
369
|
+
}
|
|
370
|
+
|
|
334
371
|
.file-viewer-protected-banner-text {
|
|
335
372
|
font-size: 12px;
|
|
336
373
|
color: #f7cc5e;
|
|
@@ -344,6 +381,15 @@
|
|
|
344
381
|
letter-spacing: 0.01em;
|
|
345
382
|
}
|
|
346
383
|
|
|
384
|
+
.file-viewer-diff-banner {
|
|
385
|
+
background: rgba(59, 130, 246, 0.12);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.file-viewer-diff-banner .file-viewer-protected-banner-text,
|
|
389
|
+
.file-viewer-diff-banner {
|
|
390
|
+
color: #bfdbfe;
|
|
391
|
+
}
|
|
392
|
+
|
|
347
393
|
.file-viewer-tabbar-spacer {
|
|
348
394
|
flex: 1;
|
|
349
395
|
}
|
|
@@ -559,6 +605,48 @@
|
|
|
559
605
|
align-items: stretch;
|
|
560
606
|
}
|
|
561
607
|
|
|
608
|
+
.file-viewer-diff-shell {
|
|
609
|
+
width: 100%;
|
|
610
|
+
min-height: 0;
|
|
611
|
+
height: 100%;
|
|
612
|
+
overflow: auto;
|
|
613
|
+
padding: 8px 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.file-viewer-diff-pre {
|
|
617
|
+
margin: 0;
|
|
618
|
+
padding: 0 12px 12px;
|
|
619
|
+
font-family: inherit;
|
|
620
|
+
font-size: 12px;
|
|
621
|
+
line-height: 1.45;
|
|
622
|
+
color: var(--text-muted);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.file-viewer-diff-line {
|
|
626
|
+
white-space: pre-wrap;
|
|
627
|
+
word-break: break-word;
|
|
628
|
+
padding: 1px 8px;
|
|
629
|
+
border-radius: 4px;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.file-viewer-diff-line.is-added {
|
|
633
|
+
color: #86efac;
|
|
634
|
+
background: rgba(34, 197, 94, 0.1);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.file-viewer-diff-line.is-removed {
|
|
638
|
+
color: #fca5a5;
|
|
639
|
+
background: rgba(239, 68, 68, 0.1);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.file-viewer-diff-line.is-hunk {
|
|
643
|
+
color: #93c5fd;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.file-viewer-diff-line.is-header {
|
|
647
|
+
color: var(--text-dim);
|
|
648
|
+
}
|
|
649
|
+
|
|
562
650
|
.file-viewer-editor-line-num-col {
|
|
563
651
|
width: 56px;
|
|
564
652
|
flex-shrink: 0;
|
|
@@ -975,17 +1063,110 @@
|
|
|
975
1063
|
color: var(--text-muted);
|
|
976
1064
|
}
|
|
977
1065
|
|
|
978
|
-
.sidebar-git-
|
|
1066
|
+
.sidebar-git-changes-label {
|
|
1067
|
+
padding: 7px 10px 4px;
|
|
1068
|
+
font-size: 10px;
|
|
1069
|
+
letter-spacing: 0.06em;
|
|
1070
|
+
text-transform: uppercase;
|
|
1071
|
+
color: var(--text-dim);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.sidebar-git-changes-list {
|
|
979
1075
|
list-style: none;
|
|
980
1076
|
display: flex;
|
|
981
1077
|
flex-direction: column;
|
|
982
|
-
gap:
|
|
983
|
-
padding: 6px
|
|
1078
|
+
gap: 2px;
|
|
1079
|
+
padding: 0 6px;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.sidebar-git-change-row {
|
|
1083
|
+
display: flex;
|
|
1084
|
+
align-items: center;
|
|
1085
|
+
justify-content: space-between;
|
|
1086
|
+
gap: 8px;
|
|
1087
|
+
min-height: 22px;
|
|
1088
|
+
padding: 2px 6px;
|
|
1089
|
+
border-radius: 6px;
|
|
1090
|
+
line-height: 1.25;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.sidebar-git-change-row.is-clickable {
|
|
1094
|
+
cursor: pointer;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.sidebar-git-change-row.is-clickable:hover {
|
|
1098
|
+
background: rgba(255, 255, 255, 0.04);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.sidebar-git-change-path {
|
|
1102
|
+
min-width: 0;
|
|
1103
|
+
white-space: nowrap;
|
|
1104
|
+
overflow: hidden;
|
|
1105
|
+
text-overflow: ellipsis;
|
|
1106
|
+
color: var(--text-muted);
|
|
1107
|
+
font-weight: 400;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
.sidebar-git-change-meta {
|
|
1111
|
+
display: inline-flex;
|
|
1112
|
+
align-items: center;
|
|
1113
|
+
gap: 6px;
|
|
1114
|
+
flex-shrink: 0;
|
|
1115
|
+
font-size: 10px;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
.sidebar-git-change-plus {
|
|
1119
|
+
color: #71f8a7;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
.sidebar-git-change-minus {
|
|
1123
|
+
color: #f3a86a;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.sidebar-git-change-status {
|
|
1127
|
+
font-size: 10px;
|
|
1128
|
+
letter-spacing: 0.06em;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.sidebar-git-change-row.is-untracked .sidebar-git-change-status {
|
|
1132
|
+
color: #71f8a7;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
.sidebar-git-change-row.is-modified .sidebar-git-change-status {
|
|
1136
|
+
color: #63ebff;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.sidebar-git-change-row.is-deleted .sidebar-git-change-status {
|
|
1140
|
+
color: #f87171;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.sidebar-git-change-row.is-deleted .sidebar-git-change-path {
|
|
1144
|
+
text-decoration: line-through;
|
|
1145
|
+
text-decoration-thickness: 1px;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
.sidebar-git-scroll {
|
|
984
1149
|
overflow-y: auto;
|
|
985
1150
|
min-height: 0;
|
|
986
1151
|
flex: 1 1 auto;
|
|
987
1152
|
}
|
|
988
1153
|
|
|
1154
|
+
.sidebar-git-actions {
|
|
1155
|
+
padding: 8px 10px 6px;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
.sidebar-git-sync-button {
|
|
1159
|
+
width: 100%;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.sidebar-git-list {
|
|
1163
|
+
list-style: none;
|
|
1164
|
+
display: flex;
|
|
1165
|
+
flex-direction: column;
|
|
1166
|
+
gap: 3px;
|
|
1167
|
+
padding: 6px 10px 0;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
989
1170
|
.sidebar-git-list li {
|
|
990
1171
|
display: flex;
|
|
991
1172
|
align-items: baseline;
|
package/lib/public/css/theme.css
CHANGED
package/lib/public/js/app.js
CHANGED
|
@@ -383,7 +383,9 @@ const App = () => {
|
|
|
383
383
|
const [acDismissed, setAcDismissed] = useState(false);
|
|
384
384
|
const [authEnabled, setAuthEnabled] = useState(false);
|
|
385
385
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
386
|
-
const [sidebarTab, setSidebarTab] = useState(
|
|
386
|
+
const [sidebarTab, setSidebarTab] = useState(() =>
|
|
387
|
+
location.startsWith("/browse") ? "browse" : "menu",
|
|
388
|
+
);
|
|
387
389
|
const [sidebarWidthPx, setSidebarWidthPx] = useState(() => {
|
|
388
390
|
const settings = readUiSettings();
|
|
389
391
|
if (!Number.isFinite(settings.sidebarWidthPx)) return kDefaultSidebarWidthPx;
|
|
@@ -591,21 +593,24 @@ const App = () => {
|
|
|
591
593
|
`;
|
|
592
594
|
}
|
|
593
595
|
|
|
594
|
-
const buildBrowseRoute = (relativePath) => {
|
|
596
|
+
const buildBrowseRoute = (relativePath, options = {}) => {
|
|
597
|
+
const view = String(options?.view || "edit");
|
|
595
598
|
const encodedPath = String(relativePath || "")
|
|
596
599
|
.split("/")
|
|
597
600
|
.filter(Boolean)
|
|
598
601
|
.map((segment) => encodeURIComponent(segment))
|
|
599
602
|
.join("/");
|
|
600
|
-
|
|
603
|
+
const baseRoute = encodedPath ? `/browse/${encodedPath}` : "/browse";
|
|
604
|
+
if (view === "diff" && encodedPath) return `${baseRoute}?view=diff`;
|
|
605
|
+
return baseRoute;
|
|
601
606
|
};
|
|
602
607
|
const navigateToSubScreen = (screen) => {
|
|
603
608
|
setLocation(`/${screen}`);
|
|
604
609
|
setMobileSidebarOpen(false);
|
|
605
610
|
};
|
|
606
|
-
const navigateToBrowseFile = (relativePath) => {
|
|
611
|
+
const navigateToBrowseFile = (relativePath, options = {}) => {
|
|
607
612
|
setBrowsePreviewPath("");
|
|
608
|
-
setLocation(buildBrowseRoute(relativePath));
|
|
613
|
+
setLocation(buildBrowseRoute(relativePath, options));
|
|
609
614
|
setMobileSidebarOpen(false);
|
|
610
615
|
};
|
|
611
616
|
const handleSidebarLogout = async () => {
|
|
@@ -668,8 +673,13 @@ const App = () => {
|
|
|
668
673
|
];
|
|
669
674
|
|
|
670
675
|
const isBrowseRoute = location.startsWith("/browse");
|
|
676
|
+
const browseRoutePath = isBrowseRoute ? String(location || "").split("?")[0] : "";
|
|
677
|
+
const browseRouteQuery =
|
|
678
|
+
isBrowseRoute && String(location || "").includes("?")
|
|
679
|
+
? String(location || "").split("?").slice(1).join("?")
|
|
680
|
+
: "";
|
|
671
681
|
const selectedBrowsePath = isBrowseRoute
|
|
672
|
-
?
|
|
682
|
+
? browseRoutePath
|
|
673
683
|
.replace(/^\/browse\/?/, "")
|
|
674
684
|
.split("/")
|
|
675
685
|
.filter(Boolean)
|
|
@@ -682,6 +692,12 @@ const App = () => {
|
|
|
682
692
|
})
|
|
683
693
|
.join("/")
|
|
684
694
|
: "";
|
|
695
|
+
const activeBrowsePath = browsePreviewPath || selectedBrowsePath;
|
|
696
|
+
const browseViewerMode =
|
|
697
|
+
!browsePreviewPath &&
|
|
698
|
+
new URLSearchParams(browseRouteQuery).get("view") === "diff"
|
|
699
|
+
? "diff"
|
|
700
|
+
: "edit";
|
|
685
701
|
const selectedNavId = isBrowseRoute
|
|
686
702
|
? "browse"
|
|
687
703
|
: location === "/telegram"
|
|
@@ -727,6 +743,28 @@ const App = () => {
|
|
|
727
743
|
);
|
|
728
744
|
}, [isBrowseRoute, selectedBrowsePath]);
|
|
729
745
|
|
|
746
|
+
useEffect(() => {
|
|
747
|
+
const handleBrowseGitSynced = () => {
|
|
748
|
+
if (!isBrowseRoute || browseViewerMode !== "diff") return;
|
|
749
|
+
const activePath = String(selectedBrowsePath || "").trim();
|
|
750
|
+
if (!activePath) return;
|
|
751
|
+
setLocation(buildBrowseRoute(activePath, { view: "edit" }));
|
|
752
|
+
};
|
|
753
|
+
window.addEventListener("alphaclaw:browse-git-synced", handleBrowseGitSynced);
|
|
754
|
+
return () => {
|
|
755
|
+
window.removeEventListener(
|
|
756
|
+
"alphaclaw:browse-git-synced",
|
|
757
|
+
handleBrowseGitSynced,
|
|
758
|
+
);
|
|
759
|
+
};
|
|
760
|
+
}, [
|
|
761
|
+
isBrowseRoute,
|
|
762
|
+
browseViewerMode,
|
|
763
|
+
selectedBrowsePath,
|
|
764
|
+
setLocation,
|
|
765
|
+
buildBrowseRoute,
|
|
766
|
+
]);
|
|
767
|
+
|
|
730
768
|
useEffect(() => {
|
|
731
769
|
const settings = readUiSettings();
|
|
732
770
|
settings.sidebarWidthPx = sidebarWidthPx;
|
|
@@ -848,11 +886,23 @@ const App = () => {
|
|
|
848
886
|
${isBrowseRoute
|
|
849
887
|
? html`
|
|
850
888
|
<${FileViewer}
|
|
851
|
-
filePath=${
|
|
889
|
+
filePath=${activeBrowsePath}
|
|
852
890
|
isPreviewOnly=${Boolean(
|
|
853
891
|
browsePreviewPath &&
|
|
854
892
|
browsePreviewPath !== selectedBrowsePath,
|
|
855
893
|
)}
|
|
894
|
+
browseView=${browseViewerMode}
|
|
895
|
+
onRequestEdit=${(targetPath) => {
|
|
896
|
+
const normalizedTargetPath = String(targetPath || "");
|
|
897
|
+
if (
|
|
898
|
+
normalizedTargetPath &&
|
|
899
|
+
normalizedTargetPath !== selectedBrowsePath
|
|
900
|
+
) {
|
|
901
|
+
navigateToBrowseFile(normalizedTargetPath, { view: "edit" });
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
setLocation(buildBrowseRoute(selectedBrowsePath, { view: "edit" }));
|
|
905
|
+
}}
|
|
856
906
|
/>
|
|
857
907
|
`
|
|
858
908
|
: html`
|
|
@@ -23,7 +23,9 @@ import {
|
|
|
23
23
|
FileCodeLineIcon,
|
|
24
24
|
Database2LineIcon,
|
|
25
25
|
HashtagIcon,
|
|
26
|
+
LockLineIcon,
|
|
26
27
|
} from "./icons.js";
|
|
28
|
+
import { LoadingSpinner } from "./loading-spinner.js";
|
|
27
29
|
|
|
28
30
|
const html = htm.bind(h);
|
|
29
31
|
const kTreeIndentPx = 9;
|
|
@@ -32,6 +34,34 @@ const kFileBasePaddingPx = 14;
|
|
|
32
34
|
const kTreeRefreshIntervalMs = 5000;
|
|
33
35
|
const kCollapsedFoldersStorageKey = "alphaclaw.browse.collapsedFolders";
|
|
34
36
|
const kLegacyCollapsedFoldersStorageKey = "alphaclawBrowseCollapsedFolders";
|
|
37
|
+
const kLockedBrowsePaths = new Set([
|
|
38
|
+
"hooks/bootstrap/agents.md",
|
|
39
|
+
"hooks/bootstrap/tools.md",
|
|
40
|
+
".alphaclaw/hourly-git-sync.sh",
|
|
41
|
+
".alphaclaw/.cli-device-auto-approved",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const normalizePolicyPath = (inputPath) =>
|
|
45
|
+
String(inputPath || "")
|
|
46
|
+
.replaceAll("\\", "/")
|
|
47
|
+
.replace(/^\.\/+/, "")
|
|
48
|
+
.replace(/^\/+/, "")
|
|
49
|
+
.trim()
|
|
50
|
+
.toLowerCase();
|
|
51
|
+
|
|
52
|
+
const matchesPolicyPath = (policyPathSet, normalizedPath) => {
|
|
53
|
+
const safeNormalizedPath = String(normalizedPath || "").trim();
|
|
54
|
+
if (!safeNormalizedPath) return false;
|
|
55
|
+
for (const policyPath of policyPathSet) {
|
|
56
|
+
if (
|
|
57
|
+
safeNormalizedPath === policyPath ||
|
|
58
|
+
safeNormalizedPath.endsWith(`/${policyPath}`)
|
|
59
|
+
) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
};
|
|
35
65
|
|
|
36
66
|
const readStoredCollapsedPaths = () => {
|
|
37
67
|
try {
|
|
@@ -172,6 +202,10 @@ const TreeNode = ({
|
|
|
172
202
|
const isActive = selectedPath === node.path;
|
|
173
203
|
const isSearchActiveNode = searchActivePath === node.path;
|
|
174
204
|
const hasDraft = draftPaths.has(node.path || "");
|
|
205
|
+
const isLocked = matchesPolicyPath(
|
|
206
|
+
kLockedBrowsePaths,
|
|
207
|
+
normalizePolicyPath(node.path || ""),
|
|
208
|
+
);
|
|
175
209
|
const fileIconMeta = getFileIconMeta(node.name);
|
|
176
210
|
const FileTypeIcon = fileIconMeta.icon;
|
|
177
211
|
return html`
|
|
@@ -186,7 +220,14 @@ const TreeNode = ({
|
|
|
186
220
|
>
|
|
187
221
|
<${FileTypeIcon} className=${fileIconMeta.className} />
|
|
188
222
|
<span class="tree-label">${node.name}</span>
|
|
189
|
-
${
|
|
223
|
+
${isLocked
|
|
224
|
+
? html`<${LockLineIcon}
|
|
225
|
+
className="tree-lock-icon"
|
|
226
|
+
title="Managed by Alpha Claw"
|
|
227
|
+
/>`
|
|
228
|
+
: hasDraft
|
|
229
|
+
? html`<span class="tree-draft-dot" aria-hidden="true"></span>`
|
|
230
|
+
: null}
|
|
190
231
|
</a>
|
|
191
232
|
</li>
|
|
192
233
|
`;
|
|
@@ -458,7 +499,13 @@ export const FileTree = ({
|
|
|
458
499
|
};
|
|
459
500
|
|
|
460
501
|
if (loading) {
|
|
461
|
-
return html
|
|
502
|
+
return html`
|
|
503
|
+
<div class="file-tree-wrap file-tree-wrap-loading">
|
|
504
|
+
<div class="file-tree-state file-tree-state-loading">
|
|
505
|
+
<${LoadingSpinner} className="h-5 w-5 text-gray-400" />
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
`;
|
|
462
509
|
}
|
|
463
510
|
if (error) {
|
|
464
511
|
return html`<div class="file-tree-state file-tree-state-error">
|