@lazyingart/agintiflow 0.20.186 → 0.20.187
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/package.json +1 -1
- package/public/app.js +101 -29
- package/public/styles.css +155 -8
- package/references/agintiflow-npm-publication-memory.md +11 -0
- package/scripts/smoke-web-api.js +18 -0
- package/scripts/smoke-web-ui.js +15 -0
- package/web.js +24 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.187",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/public/app.js
CHANGED
|
@@ -1251,6 +1251,37 @@ function workspaceFilesByLane() {
|
|
|
1251
1251
|
return groups;
|
|
1252
1252
|
}
|
|
1253
1253
|
|
|
1254
|
+
function buildWorkspaceFolderTree(files = []) {
|
|
1255
|
+
const root = { name: "", path: "", dirs: new Map(), files: [] };
|
|
1256
|
+
for (const file of files) {
|
|
1257
|
+
const parts = String(file.path || "")
|
|
1258
|
+
.split("/")
|
|
1259
|
+
.filter(Boolean);
|
|
1260
|
+
if (!parts.length) continue;
|
|
1261
|
+
const fileName = parts.pop();
|
|
1262
|
+
let node = root;
|
|
1263
|
+
let currentPath = "";
|
|
1264
|
+
for (const part of parts) {
|
|
1265
|
+
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
1266
|
+
if (!node.dirs.has(part)) node.dirs.set(part, { name: part, path: currentPath, dirs: new Map(), files: [] });
|
|
1267
|
+
node = node.dirs.get(part);
|
|
1268
|
+
}
|
|
1269
|
+
node.files.push({ ...file, name: file.name || fileName });
|
|
1270
|
+
}
|
|
1271
|
+
return root;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
function workspaceFolderCount(node) {
|
|
1275
|
+
if (!node) return 0;
|
|
1276
|
+
let count = node.files?.length || 0;
|
|
1277
|
+
for (const child of node.dirs?.values?.() || []) count += workspaceFolderCount(child);
|
|
1278
|
+
return count;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function sortWorkspaceFiles(left, right) {
|
|
1282
|
+
return String(left.path || "").localeCompare(String(right.path || ""));
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1254
1285
|
function workspaceActiveFile() {
|
|
1255
1286
|
return workspaceBrowser.files.find((file) => file.path === workspaceBrowser.activePath) || null;
|
|
1256
1287
|
}
|
|
@@ -1279,12 +1310,18 @@ function renderWorkspaceExplorer() {
|
|
|
1279
1310
|
const activeLane = workspaceBrowser.activeLane || "all";
|
|
1280
1311
|
const warnings = workspaceBrowser.warnings?.length ? ` · ${workspaceBrowser.warnings.length} warning(s)` : "";
|
|
1281
1312
|
const truncated = workspaceBrowser.truncated ? " · truncated" : "";
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1313
|
+
if (workspaceBrowser.loading) {
|
|
1314
|
+
workspaceBrowserStatusEl.textContent = "Loading workspace...";
|
|
1315
|
+
} else if (workspaceBrowser.error) {
|
|
1316
|
+
workspaceBrowserStatusEl.textContent = `Workspace unavailable: ${workspaceBrowser.error}`;
|
|
1317
|
+
} else {
|
|
1318
|
+
workspaceBrowserStatusEl.innerHTML = `
|
|
1319
|
+
<span class="workspace-status-chip">${workspaceBrowser.files.length} files</span>
|
|
1320
|
+
<span class="workspace-status-chip">${escapeHtml(activeLane)}</span>
|
|
1321
|
+
<code class="workspace-root-path">${escapeHtml(workspaceBrowser.root || workspaceRoot())}</code>
|
|
1322
|
+
${warnings || truncated ? `<span class="workspace-status-warn">${escapeHtml(`${warnings}${truncated}`.replace(/^ · /, ""))}</span>` : ""}
|
|
1323
|
+
`;
|
|
1324
|
+
}
|
|
1288
1325
|
|
|
1289
1326
|
workspaceLanesEl.innerHTML = WORKSPACE_LANES.map((lane) => {
|
|
1290
1327
|
const count = groups[lane.id]?.length || 0;
|
|
@@ -1302,34 +1339,69 @@ function renderWorkspaceExplorer() {
|
|
|
1302
1339
|
`;
|
|
1303
1340
|
}).join("");
|
|
1304
1341
|
|
|
1305
|
-
const visibleFiles = (groups[activeLane] || []).slice().sort(
|
|
1342
|
+
const visibleFiles = (groups[activeLane] || []).slice().sort(sortWorkspaceFiles);
|
|
1306
1343
|
if (!visibleFiles.length) {
|
|
1307
1344
|
workspaceTreeEl.innerHTML = `<p class="subtle workspace-empty">No files in this category.</p>`;
|
|
1308
1345
|
return;
|
|
1309
1346
|
}
|
|
1310
1347
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1348
|
+
const tree = buildWorkspaceFolderTree(visibleFiles);
|
|
1349
|
+
workspaceTreeEl.innerHTML = `
|
|
1350
|
+
<div class="workspace-tree-toolbar">
|
|
1351
|
+
<span>${escapeHtml(WORKSPACE_LANES.find((lane) => lane.id === activeLane)?.label || "Files")}</span>
|
|
1352
|
+
<strong>${visibleFiles.length}</strong>
|
|
1353
|
+
</div>
|
|
1354
|
+
<div class="workspace-folder-root">
|
|
1355
|
+
${renderWorkspaceFolderNode(tree, 0)}
|
|
1356
|
+
</div>
|
|
1357
|
+
`;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function renderWorkspaceFileNode(file, depth = 0) {
|
|
1361
|
+
const lane = inferWorkspaceLane(file);
|
|
1362
|
+
const active = file.path === workspaceBrowser.activePath;
|
|
1363
|
+
const meta = [lane, formatBytes(file.size), file.binary ? "binary" : "text"].join(" · ");
|
|
1364
|
+
return `
|
|
1365
|
+
<button
|
|
1366
|
+
type="button"
|
|
1367
|
+
class="workspace-file-row workspace-tree-file"
|
|
1368
|
+
data-workspace-file="${escapeHtml(file.path)}"
|
|
1369
|
+
data-selected="${active}"
|
|
1370
|
+
draggable="true"
|
|
1371
|
+
style="--depth: ${depth};"
|
|
1372
|
+
>
|
|
1373
|
+
<span class="workspace-file-icon" aria-hidden="true">${file.binary ? "bin" : "txt"}</span>
|
|
1374
|
+
<span class="workspace-file-main">
|
|
1375
|
+
<strong>${escapeHtml(workspaceBaseName(file.path))}</strong>
|
|
1376
|
+
<small>${escapeHtml(workspaceDir(file.path) || ".")}</small>
|
|
1377
|
+
</span>
|
|
1378
|
+
<span class="workspace-file-meta">${escapeHtml(meta)}</span>
|
|
1379
|
+
</button>
|
|
1380
|
+
`;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function renderWorkspaceFolderNode(node, depth = 0) {
|
|
1384
|
+
const dirs = [...(node.dirs?.values?.() || [])].sort((left, right) => left.name.localeCompare(right.name));
|
|
1385
|
+
const files = (node.files || []).slice().sort(sortWorkspaceFiles);
|
|
1386
|
+
const children = [
|
|
1387
|
+
...dirs.map((child) => renderWorkspaceFolderNode(child, depth + 1)),
|
|
1388
|
+
...files.map((file) => renderWorkspaceFileNode(file, depth)),
|
|
1389
|
+
].join("");
|
|
1390
|
+
if (depth === 0) return children;
|
|
1391
|
+
const activeInside = workspaceBrowser.activePath && workspaceBrowser.activePath.startsWith(`${node.path}/`);
|
|
1392
|
+
const open = depth <= 2 || activeInside;
|
|
1393
|
+
return `
|
|
1394
|
+
<details class="workspace-folder" ${open ? "open" : ""} style="--depth: ${Math.max(depth - 1, 0)};">
|
|
1395
|
+
<summary>
|
|
1396
|
+
<span class="workspace-folder-icon" aria-hidden="true">></span>
|
|
1397
|
+
<span class="workspace-folder-name">${escapeHtml(node.name)}</span>
|
|
1398
|
+
<small>${workspaceFolderCount(node)}</small>
|
|
1399
|
+
</summary>
|
|
1400
|
+
<div class="workspace-folder-children">
|
|
1401
|
+
${children}
|
|
1402
|
+
</div>
|
|
1403
|
+
</details>
|
|
1404
|
+
`;
|
|
1333
1405
|
}
|
|
1334
1406
|
|
|
1335
1407
|
function renderWorkspaceWorkbench() {
|
package/public/styles.css
CHANGED
|
@@ -959,10 +959,43 @@ button.danger {
|
|
|
959
959
|
}
|
|
960
960
|
|
|
961
961
|
.workspace-browser-status {
|
|
962
|
+
display: flex;
|
|
963
|
+
flex-wrap: wrap;
|
|
964
|
+
align-items: center;
|
|
965
|
+
gap: 7px;
|
|
962
966
|
line-height: 1.35;
|
|
963
967
|
overflow-wrap: anywhere;
|
|
964
968
|
}
|
|
965
969
|
|
|
970
|
+
.workspace-status-chip {
|
|
971
|
+
padding: 4px 8px;
|
|
972
|
+
border: 1px solid rgba(15, 118, 110, 0.16);
|
|
973
|
+
border-radius: 999px;
|
|
974
|
+
background: rgba(255, 253, 250, 0.86);
|
|
975
|
+
color: var(--ink);
|
|
976
|
+
font-size: 0.76rem;
|
|
977
|
+
font-weight: 700;
|
|
978
|
+
text-transform: capitalize;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
.workspace-root-path {
|
|
982
|
+
min-width: 0;
|
|
983
|
+
max-width: 100%;
|
|
984
|
+
padding: 5px 8px;
|
|
985
|
+
border: 1px solid rgba(15, 118, 110, 0.14);
|
|
986
|
+
border-radius: 10px;
|
|
987
|
+
background: rgba(13, 148, 136, 0.07);
|
|
988
|
+
color: #155e75;
|
|
989
|
+
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
|
990
|
+
font-size: 0.76rem;
|
|
991
|
+
overflow-wrap: anywhere;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.workspace-status-warn {
|
|
995
|
+
color: #b45309;
|
|
996
|
+
font-size: 0.76rem;
|
|
997
|
+
}
|
|
998
|
+
|
|
966
999
|
.workspace-lanes {
|
|
967
1000
|
display: grid;
|
|
968
1001
|
grid-template-columns: repeat(auto-fit, minmax(82px, 1fr));
|
|
@@ -995,11 +1028,105 @@ button.danger {
|
|
|
995
1028
|
}
|
|
996
1029
|
|
|
997
1030
|
.workspace-tree {
|
|
998
|
-
display: grid;
|
|
999
|
-
gap: 7px;
|
|
1000
1031
|
max-height: 440px;
|
|
1001
1032
|
overflow: auto;
|
|
1002
|
-
padding
|
|
1033
|
+
padding: 8px;
|
|
1034
|
+
border: 1px solid rgba(15, 118, 110, 0.12);
|
|
1035
|
+
border-radius: 16px;
|
|
1036
|
+
background:
|
|
1037
|
+
linear-gradient(180deg, rgba(255, 253, 250, 0.88), rgba(240, 253, 250, 0.58)),
|
|
1038
|
+
rgba(255, 255, 255, 0.72);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
.workspace-tree-toolbar {
|
|
1042
|
+
position: sticky;
|
|
1043
|
+
top: 0;
|
|
1044
|
+
z-index: 1;
|
|
1045
|
+
display: flex;
|
|
1046
|
+
justify-content: space-between;
|
|
1047
|
+
align-items: center;
|
|
1048
|
+
gap: 10px;
|
|
1049
|
+
margin: -8px -8px 8px;
|
|
1050
|
+
padding: 8px 10px;
|
|
1051
|
+
border-bottom: 1px solid rgba(15, 118, 110, 0.1);
|
|
1052
|
+
background: rgba(255, 253, 250, 0.95);
|
|
1053
|
+
color: var(--muted);
|
|
1054
|
+
font-size: 0.78rem;
|
|
1055
|
+
font-weight: 700;
|
|
1056
|
+
text-transform: uppercase;
|
|
1057
|
+
letter-spacing: 0.03em;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.workspace-tree-toolbar strong {
|
|
1061
|
+
color: var(--accent);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.workspace-folder-root,
|
|
1065
|
+
.workspace-folder-children {
|
|
1066
|
+
display: grid;
|
|
1067
|
+
gap: 4px;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
.workspace-folder {
|
|
1071
|
+
margin-left: calc(var(--depth, 0) * 12px);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.workspace-folder summary {
|
|
1075
|
+
display: flex;
|
|
1076
|
+
align-items: center;
|
|
1077
|
+
gap: 7px;
|
|
1078
|
+
min-height: 32px;
|
|
1079
|
+
padding: 6px 8px;
|
|
1080
|
+
border: 1px solid transparent;
|
|
1081
|
+
border-radius: 11px;
|
|
1082
|
+
color: var(--ink);
|
|
1083
|
+
cursor: pointer;
|
|
1084
|
+
list-style: none;
|
|
1085
|
+
user-select: none;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.workspace-folder summary::-webkit-details-marker {
|
|
1089
|
+
display: none;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.workspace-folder summary:hover {
|
|
1093
|
+
border-color: rgba(15, 118, 110, 0.16);
|
|
1094
|
+
background: rgba(240, 253, 250, 0.72);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.workspace-folder-icon {
|
|
1098
|
+
display: inline-grid;
|
|
1099
|
+
place-items: center;
|
|
1100
|
+
width: 18px;
|
|
1101
|
+
height: 18px;
|
|
1102
|
+
border: 1px solid rgba(15, 118, 110, 0.18);
|
|
1103
|
+
border-radius: 6px;
|
|
1104
|
+
color: var(--accent);
|
|
1105
|
+
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
|
1106
|
+
font-size: 0.68rem;
|
|
1107
|
+
transition: transform 0.16s ease;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
.workspace-folder[open] > summary .workspace-folder-icon {
|
|
1111
|
+
transform: rotate(90deg);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
.workspace-folder-name {
|
|
1115
|
+
min-width: 0;
|
|
1116
|
+
overflow: hidden;
|
|
1117
|
+
text-overflow: ellipsis;
|
|
1118
|
+
white-space: nowrap;
|
|
1119
|
+
font-weight: 750;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
.workspace-folder summary small {
|
|
1123
|
+
margin-left: auto;
|
|
1124
|
+
color: var(--muted);
|
|
1125
|
+
font-size: 0.72rem;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
.workspace-folder-children {
|
|
1129
|
+
margin-top: 4px;
|
|
1003
1130
|
}
|
|
1004
1131
|
|
|
1005
1132
|
.workspace-empty {
|
|
@@ -1011,18 +1138,23 @@ button.danger {
|
|
|
1011
1138
|
|
|
1012
1139
|
.workspace-file-row {
|
|
1013
1140
|
display: grid;
|
|
1014
|
-
grid-template-columns: minmax(0, 1fr);
|
|
1015
|
-
gap:
|
|
1141
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1142
|
+
gap: 6px 8px;
|
|
1143
|
+
align-items: center;
|
|
1016
1144
|
width: 100%;
|
|
1017
|
-
padding: 10px
|
|
1145
|
+
padding: 8px 10px;
|
|
1018
1146
|
border: 1px solid rgba(217, 119, 6, 0.16);
|
|
1019
|
-
border-radius:
|
|
1147
|
+
border-radius: 12px;
|
|
1020
1148
|
background: rgba(255, 253, 250, 0.72);
|
|
1021
1149
|
color: var(--ink);
|
|
1022
1150
|
cursor: pointer;
|
|
1023
1151
|
text-align: left;
|
|
1024
1152
|
}
|
|
1025
1153
|
|
|
1154
|
+
.workspace-tree-file {
|
|
1155
|
+
margin-left: calc(var(--depth, 0) * 12px);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1026
1158
|
.workspace-file-row:hover {
|
|
1027
1159
|
border-color: rgba(15, 118, 110, 0.34);
|
|
1028
1160
|
background: rgba(240, 253, 250, 0.72);
|
|
@@ -1030,10 +1162,24 @@ button.danger {
|
|
|
1030
1162
|
|
|
1031
1163
|
.workspace-file-row[data-selected="true"] {
|
|
1032
1164
|
border-color: rgba(15, 118, 110, 0.58);
|
|
1033
|
-
background: linear-gradient(135deg, rgba(15, 118, 110, 0.
|
|
1165
|
+
background: linear-gradient(135deg, rgba(15, 118, 110, 0.22), rgba(255, 255, 255, 0.96));
|
|
1034
1166
|
box-shadow: 0 0 0 2px rgba(15, 118, 110, 0.1);
|
|
1035
1167
|
}
|
|
1036
1168
|
|
|
1169
|
+
.workspace-file-icon {
|
|
1170
|
+
display: inline-grid;
|
|
1171
|
+
place-items: center;
|
|
1172
|
+
width: 30px;
|
|
1173
|
+
height: 24px;
|
|
1174
|
+
border-radius: 8px;
|
|
1175
|
+
background: rgba(15, 118, 110, 0.09);
|
|
1176
|
+
color: #155e75;
|
|
1177
|
+
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
|
1178
|
+
font-size: 0.62rem;
|
|
1179
|
+
font-weight: 800;
|
|
1180
|
+
text-transform: uppercase;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1037
1183
|
.workspace-file-main {
|
|
1038
1184
|
display: grid;
|
|
1039
1185
|
gap: 1px;
|
|
@@ -1050,6 +1196,7 @@ button.danger {
|
|
|
1050
1196
|
|
|
1051
1197
|
.workspace-file-main small,
|
|
1052
1198
|
.workspace-file-meta {
|
|
1199
|
+
grid-column: 2;
|
|
1053
1200
|
color: var(--muted);
|
|
1054
1201
|
font-size: 0.76rem;
|
|
1055
1202
|
}
|
|
@@ -10,6 +10,17 @@ This note records the working publication route for AgInTiFlow so future release
|
|
|
10
10
|
|
|
11
11
|
Latest verified release:
|
|
12
12
|
|
|
13
|
+
- Version: `0.20.186`
|
|
14
|
+
- Commit: `f32a748`
|
|
15
|
+
- GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26700782132`
|
|
16
|
+
- Workflow result: success
|
|
17
|
+
- npm registry check: `npm view @lazyingart/agintiflow version dist-tags.latest --registry=https://registry.npmjs.org` returned `0.20.186`.
|
|
18
|
+
- Installed verification: `npm install -g @lazyingart/agintiflow@0.20.186` then `aginti --version` returned `0.20.186`.
|
|
19
|
+
- Webapp verification: `aginti webapp restart --port 3210` then `curl -fsS http://127.0.0.1:3210/health` returned `version":"0.20.186"` from the global npm package path.
|
|
20
|
+
- Direct image CLI verification: `aginti image --json --dry-run --format svg` returned `requestedFormat:"svg"`, `actualFormat:"png"`, and a clear raster PNG fallback notice.
|
|
21
|
+
|
|
22
|
+
Previous verified release:
|
|
23
|
+
|
|
13
24
|
- Version: `0.20.185`
|
|
14
25
|
- Commit: `58d6087`
|
|
15
26
|
- GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26700387931`
|
package/scripts/smoke-web-api.js
CHANGED
|
@@ -93,6 +93,7 @@ try {
|
|
|
93
93
|
await fs.mkdir(path.join(runtimeDir, "data"), { recursive: true });
|
|
94
94
|
await fs.writeFile(path.join(runtimeDir, "notes", "workspace-smoke.md"), "# Workspace smoke\n\nEditable text.\n");
|
|
95
95
|
await fs.writeFile(path.join(runtimeDir, "data", "workspace-smoke.csv"), "sample,value\nalpha,1\n");
|
|
96
|
+
await fs.writeFile(path.join(runtimeDir, ".aginti", ".env"), "DEEPSEEK_API_KEY=should-not-render\n");
|
|
96
97
|
|
|
97
98
|
const webAppHtml = await fs.readFile(path.join(repoRoot, "public", "index.html"), "utf8");
|
|
98
99
|
const chatThreadIndex = webAppHtml.indexOf('id="chat-thread"');
|
|
@@ -145,6 +146,9 @@ try {
|
|
|
145
146
|
if (!workspaceSnapshot.files?.some((file) => file.path === "notes/workspace-smoke.md" && file.content.includes("Editable text."))) {
|
|
146
147
|
throw new Error(`workspace snapshot did not include editable note: ${JSON.stringify(workspaceSnapshot.files?.slice(0, 5))}`);
|
|
147
148
|
}
|
|
149
|
+
if (workspaceSnapshot.files?.some((file) => file.path === ".aginti/.env" || file.path === ".aginti/mcp.json" || file.path === ".env")) {
|
|
150
|
+
throw new Error(`workspace snapshot exposed protected internal/secret files: ${JSON.stringify(workspaceSnapshot.files?.slice(0, 10))}`);
|
|
151
|
+
}
|
|
148
152
|
const rawWorkspaceResponse = await fetch(
|
|
149
153
|
`${baseUrl}/api/workspace/raw?commandCwd=${encodeURIComponent(runtimeDir)}&path=${encodeURIComponent("notes/workspace-smoke.md")}`
|
|
150
154
|
);
|
|
@@ -152,6 +156,20 @@ try {
|
|
|
152
156
|
if (!rawWorkspaceResponse.ok || !rawWorkspaceText.includes("Workspace smoke")) {
|
|
153
157
|
throw new Error(`workspace raw endpoint failed: status=${rawWorkspaceResponse.status} body=${rawWorkspaceText.slice(0, 120)}`);
|
|
154
158
|
}
|
|
159
|
+
const protectedRawResponse = await fetch(
|
|
160
|
+
`${baseUrl}/api/workspace/raw?commandCwd=${encodeURIComponent(runtimeDir)}&path=${encodeURIComponent(".aginti/.env")}`
|
|
161
|
+
);
|
|
162
|
+
if (protectedRawResponse.ok) {
|
|
163
|
+
throw new Error("workspace raw endpoint exposed protected .aginti/.env");
|
|
164
|
+
}
|
|
165
|
+
const protectedWriteResponse = await fetch(`${baseUrl}/api/workspace/write`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { "Content-Type": "application/json" },
|
|
168
|
+
body: JSON.stringify({ commandCwd: runtimeDir, path: ".env", content: "SECRET=blocked\n" }),
|
|
169
|
+
});
|
|
170
|
+
if (protectedWriteResponse.ok) {
|
|
171
|
+
throw new Error("workspace write endpoint allowed protected .env");
|
|
172
|
+
}
|
|
155
173
|
const writtenWorkspace = await fetchJson("/api/workspace/write", {
|
|
156
174
|
method: "POST",
|
|
157
175
|
headers: { "Content-Type": "application/json" },
|
package/scripts/smoke-web-ui.js
CHANGED
|
@@ -73,8 +73,10 @@ let browser;
|
|
|
73
73
|
|
|
74
74
|
try {
|
|
75
75
|
await waitForHealth();
|
|
76
|
+
await fs.mkdir(path.join(runtimeDir, ".aginti"), { recursive: true });
|
|
76
77
|
await fs.mkdir(path.join(runtimeDir, "notes"), { recursive: true });
|
|
77
78
|
await fs.mkdir(path.join(runtimeDir, "data"), { recursive: true });
|
|
79
|
+
await fs.writeFile(path.join(runtimeDir, ".aginti", ".env"), "DEEPSEEK_API_KEY=should-not-render\n");
|
|
78
80
|
await fs.writeFile(path.join(runtimeDir, "notes", "workspace-ui.md"), "# Workspace UI\n\ninitial body\n");
|
|
79
81
|
await fs.writeFile(path.join(runtimeDir, "data", "workspace-ui.csv"), "sample,value\nalpha,1\n");
|
|
80
82
|
browser = await chromium.launch({ headless: true });
|
|
@@ -102,6 +104,16 @@ try {
|
|
|
102
104
|
if ((await page.locator("#workspace-lanes [data-workspace-lane]").count()) < 8) {
|
|
103
105
|
throw new Error("workspace explorer did not render general category lanes");
|
|
104
106
|
}
|
|
107
|
+
if ((await page.locator('[data-workspace-file=".aginti/.env"]').count()) !== 0) {
|
|
108
|
+
throw new Error("workspace explorer exposed protected .aginti/.env");
|
|
109
|
+
}
|
|
110
|
+
if ((await page.locator("#workspace-tree details.workspace-folder").count()) < 1) {
|
|
111
|
+
throw new Error("workspace explorer did not render a folder tree");
|
|
112
|
+
}
|
|
113
|
+
const statusText = await page.locator("#workspace-browser-status").innerText();
|
|
114
|
+
if (!statusText.includes(runtimeDir) || !/files/i.test(statusText)) {
|
|
115
|
+
throw new Error(`workspace explorer status did not show file count and root path: ${statusText}`);
|
|
116
|
+
}
|
|
105
117
|
if ((await page.locator('[data-workspace-file="notes/workspace-ui.md"]').getAttribute("draggable")) !== "true") {
|
|
106
118
|
throw new Error("workspace file rows are not draggable");
|
|
107
119
|
}
|
|
@@ -305,6 +317,9 @@ try {
|
|
|
305
317
|
"working-directory-search-top",
|
|
306
318
|
"project-status-chips",
|
|
307
319
|
"workspace-explorer-general-lanes",
|
|
320
|
+
"workspace-explorer-folder-tree",
|
|
321
|
+
"workspace-explorer-protected-files-hidden",
|
|
322
|
+
"workspace-explorer-status-root",
|
|
308
323
|
"workspace-file-selection",
|
|
309
324
|
"workspace-editor-save-persists",
|
|
310
325
|
"workspace-file-drop-to-chat",
|
package/web.js
CHANGED
|
@@ -778,8 +778,11 @@ const WORKSPACE_SKIP_NAMES = new Set([
|
|
|
778
778
|
".git",
|
|
779
779
|
".hg",
|
|
780
780
|
".svn",
|
|
781
|
+
".aginti",
|
|
782
|
+
".aginti-work",
|
|
781
783
|
".aginti-sessions",
|
|
782
784
|
".agintiflow-home",
|
|
785
|
+
".sessions",
|
|
783
786
|
".cache",
|
|
784
787
|
".pytest_cache",
|
|
785
788
|
".ruff_cache",
|
|
@@ -790,9 +793,26 @@ const WORKSPACE_SKIP_NAMES = new Set([
|
|
|
790
793
|
"dist",
|
|
791
794
|
"node_modules",
|
|
792
795
|
]);
|
|
796
|
+
const WORKSPACE_PROTECTED_DIRS = new Set([".aginti", ".aginti-work", ".aginti-sessions", ".agintiflow-home", ".sessions", ".git", ".hg", ".svn"]);
|
|
797
|
+
const WORKSPACE_PROTECTED_FILES = new Set([".env", ".npmrc", ".pypirc", ".netrc"]);
|
|
793
798
|
const WORKSPACE_MAX_FILES = 1600;
|
|
794
799
|
const WORKSPACE_MAX_TEXT_BYTES = 512 * 1024;
|
|
795
800
|
|
|
801
|
+
function isProtectedEnvName(name = "") {
|
|
802
|
+
const value = String(name || "");
|
|
803
|
+
if (value === ".env") return true;
|
|
804
|
+
if (!value.startsWith(".env.")) return false;
|
|
805
|
+
return !/\.(example|sample|template)$/i.test(value);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function isProtectedWorkspacePath(relativePath = "") {
|
|
809
|
+
const normalized = String(relativePath || "")
|
|
810
|
+
.replace(/\\/g, "/")
|
|
811
|
+
.replace(/^\/+/, "");
|
|
812
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
813
|
+
return parts.some((part) => WORKSPACE_PROTECTED_DIRS.has(part) || WORKSPACE_PROTECTED_FILES.has(part) || isProtectedEnvName(part));
|
|
814
|
+
}
|
|
815
|
+
|
|
796
816
|
function isInsideDirectory(parent, candidate) {
|
|
797
817
|
const relative = path.relative(parent, candidate);
|
|
798
818
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
@@ -820,6 +840,9 @@ async function resolveWorkspaceFilePath(root, relativePath = "", { mustExist = f
|
|
|
820
840
|
.replace(/\\/g, "/")
|
|
821
841
|
.replace(/^\/+/, "");
|
|
822
842
|
if (!normalizedRelative || normalizedRelative.includes("\0")) throw new Error("Workspace file path is required.");
|
|
843
|
+
if (isProtectedWorkspacePath(normalizedRelative)) {
|
|
844
|
+
throw new Error("Workspace file is protected because it may contain secrets or AgInTiFlow internals.");
|
|
845
|
+
}
|
|
823
846
|
const absolutePath = path.resolve(root, normalizedRelative);
|
|
824
847
|
if (!isInsideDirectory(root, absolutePath)) throw new Error("Workspace file path escapes the selected folder.");
|
|
825
848
|
|
|
@@ -892,6 +915,7 @@ async function buildWorkspaceSnapshot(root) {
|
|
|
892
915
|
|
|
893
916
|
const absolutePath = path.join(currentDir, entry.name);
|
|
894
917
|
const relativePath = relativeWorkspacePath(root, absolutePath);
|
|
918
|
+
if (isProtectedWorkspacePath(relativePath)) continue;
|
|
895
919
|
let stat;
|
|
896
920
|
let realPath = absolutePath;
|
|
897
921
|
let symlink = false;
|