@lazyingart/agintiflow 0.20.166 → 0.20.169
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 +112 -21
- package/public/index.html +37 -17
- package/public/styles.css +194 -14
- package/scripts/smoke-cli-chat.js +60 -0
- package/scripts/smoke-web-api.js +6 -2
- package/scripts/smoke-web-ui.js +23 -0
- package/scripts/smoke-webapp-command.js +21 -11
- package/src/interactive-cli.js +16 -0
- package/src/web-autostart.js +2 -1
- package/web.js +88 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.169",
|
|
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
|
@@ -52,6 +52,7 @@ const translations = {
|
|
|
52
52
|
goalPlaceholder: "Open a site and summarize it, or use run_command for simple terminal inspection.",
|
|
53
53
|
runDefaultsTitle: "Run defaults",
|
|
54
54
|
runDefaultsHelp: "The chat box is the first goal for a new run. These controls set how that run starts.",
|
|
55
|
+
runDefaultsFoldHint: "Routing, safety, tool toggles, and key status.",
|
|
55
56
|
scsModeLabel: "SCS",
|
|
56
57
|
scsOnOption: "On",
|
|
57
58
|
scsAutoOption: "Auto",
|
|
@@ -64,6 +65,8 @@ const translations = {
|
|
|
64
65
|
allowedDomainsLabel: "Allowed domains",
|
|
65
66
|
allowedDomainsPlaceholder: "news.ycombinator.com,github.com",
|
|
66
67
|
commandCwdLabel: "Working directory",
|
|
68
|
+
commandCwdHelp: "Runs start from this working directory.",
|
|
69
|
+
commandCwdPlaceholder: "/home/user/project",
|
|
67
70
|
maxStepsLabel: "Max steps",
|
|
68
71
|
sandboxModeLabel: "Sandbox mode",
|
|
69
72
|
permissionModeLabel: "Permission shortcut",
|
|
@@ -718,6 +721,8 @@ const setupApiKeyField = document.querySelector("#setup-api-key");
|
|
|
718
721
|
const saveApiKeyButton = document.querySelector("#save-api-key");
|
|
719
722
|
const setupStatusEl = document.querySelector("#setup-status");
|
|
720
723
|
const taskProfileField = document.querySelector("#taskProfile");
|
|
724
|
+
const commandCwdField = document.querySelector("#commandCwd");
|
|
725
|
+
const commandCwdSuggestionsEl = document.querySelector("#command-cwd-suggestions");
|
|
721
726
|
const permissionModeField = document.querySelector("#permissionMode");
|
|
722
727
|
const permissionHintEl = document.querySelector("#permission-hint");
|
|
723
728
|
const sandboxModeField = document.querySelector("#sandboxMode");
|
|
@@ -727,6 +732,7 @@ const logsEl = document.querySelector("#logs");
|
|
|
727
732
|
const runMetaEl = document.querySelector("#run-meta");
|
|
728
733
|
const stopRunButton = document.querySelector("#stop-run");
|
|
729
734
|
const keyStatusEl = document.querySelector("#key-status");
|
|
735
|
+
const keyStatusListEl = document.querySelector("#key-status-list");
|
|
730
736
|
const allowAuxiliaryToolsField = document.querySelector("#allowAuxiliaryTools");
|
|
731
737
|
const allowWebSearchField = document.querySelector("#allowWebSearch");
|
|
732
738
|
const allowMcpToolsField = document.querySelector("#allowMcpTools");
|
|
@@ -928,31 +934,99 @@ function setRunStatus(status = "", { sessionId, detail = "", announce = false }
|
|
|
928
934
|
function renderKeyStatus(status = lastKeyStatus) {
|
|
929
935
|
lastKeyStatus = status;
|
|
930
936
|
if (!status) return;
|
|
931
|
-
|
|
932
|
-
status.openai
|
|
933
|
-
|
|
934
|
-
status.qwen
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
937
|
+
const providers = [
|
|
938
|
+
["OpenAI", status.openai],
|
|
939
|
+
["DeepSeek", status.deepseek],
|
|
940
|
+
["Qwen", status.qwen],
|
|
941
|
+
["Venice", status.venice],
|
|
942
|
+
["GRS AI", status.grsai],
|
|
943
|
+
[t("mockLabel"), status.mock],
|
|
944
|
+
];
|
|
945
|
+
const available = providers.filter(([, ready]) => Boolean(ready)).length;
|
|
946
|
+
if (keyStatusEl) {
|
|
947
|
+
keyStatusEl.textContent = `${t("keysLabel")} ${available}/${providers.length}`;
|
|
948
|
+
keyStatusEl.dataset.ready = String(available > 0);
|
|
949
|
+
}
|
|
950
|
+
if (keyStatusListEl) {
|
|
951
|
+
keyStatusListEl.replaceChildren(
|
|
952
|
+
...providers.map(([name, ready]) => {
|
|
953
|
+
const chip = document.createElement("div");
|
|
954
|
+
chip.className = "key-status-chip";
|
|
955
|
+
chip.dataset.ready = String(Boolean(ready));
|
|
956
|
+
const title = document.createElement("strong");
|
|
957
|
+
title.textContent = name;
|
|
958
|
+
const value = document.createElement("span");
|
|
959
|
+
value.textContent = ready ? t("availableLabel") : t("missingLabel");
|
|
960
|
+
chip.append(title, value);
|
|
961
|
+
return chip;
|
|
962
|
+
})
|
|
963
|
+
);
|
|
964
|
+
}
|
|
942
965
|
if (setupCardEl) setupCardEl.hidden = Boolean(status.openai || status.deepseek || status.qwen || status.venice);
|
|
943
966
|
}
|
|
944
967
|
|
|
945
968
|
function renderProjectStatus(info = projectInfo) {
|
|
946
969
|
projectInfo = info;
|
|
947
970
|
if (!projectStatusEl || !info) return;
|
|
948
|
-
|
|
949
|
-
info.platform?.label
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
].
|
|
971
|
+
const rows = [
|
|
972
|
+
["OS", info.platform?.label || ""],
|
|
973
|
+
["Root", info.root || ""],
|
|
974
|
+
["CWD", info.commandCwd || ""],
|
|
975
|
+
["Sessions", info.sessionsDir || ""],
|
|
976
|
+
["Database", info.sessionDbPath || ""],
|
|
977
|
+
["Shared", info.sharedSessionFolder ? "yes" : "no"],
|
|
978
|
+
].filter(([, value]) => value !== "");
|
|
979
|
+
projectStatusEl.replaceChildren(
|
|
980
|
+
...rows.map(([label, value]) => {
|
|
981
|
+
const chip = document.createElement("div");
|
|
982
|
+
chip.className = "project-status-chip";
|
|
983
|
+
if (String(value).length > 54) chip.dataset.wide = "true";
|
|
984
|
+
const title = document.createElement("strong");
|
|
985
|
+
title.textContent = label;
|
|
986
|
+
const body = document.createElement("span");
|
|
987
|
+
body.textContent = value;
|
|
988
|
+
chip.append(title, body);
|
|
989
|
+
return chip;
|
|
990
|
+
})
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function baseDirectorySuggestions(info = projectInfo) {
|
|
995
|
+
const projectLfsRoot = info?.root?.includes("/ProjectsLFS/")
|
|
996
|
+
? `${info.root.split("/ProjectsLFS/")[0]}/ProjectsLFS`
|
|
997
|
+
: "";
|
|
998
|
+
const values = [
|
|
999
|
+
info?.commandCwd,
|
|
1000
|
+
info?.root,
|
|
1001
|
+
info?.sessionsDir ? info.sessionsDir.replace(/\/\.agintiflow\/sessions$/, "") : "",
|
|
1002
|
+
projectLfsRoot,
|
|
1003
|
+
].filter(Boolean);
|
|
1004
|
+
return [...new Set(values)];
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function renderCommandCwdSuggestions(suggestions = []) {
|
|
1008
|
+
if (!commandCwdSuggestionsEl) return;
|
|
1009
|
+
commandCwdSuggestionsEl.replaceChildren(
|
|
1010
|
+
...suggestions.slice(0, 16).map((value) => {
|
|
1011
|
+
const option = document.createElement("option");
|
|
1012
|
+
option.value = value;
|
|
1013
|
+
return option;
|
|
1014
|
+
})
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
async function refreshCommandCwdSuggestions(query = "") {
|
|
1019
|
+
const fallback = baseDirectorySuggestions();
|
|
1020
|
+
renderCommandCwdSuggestions(fallback);
|
|
1021
|
+
try {
|
|
1022
|
+
const response = await fetch(`/api/path-suggestions?q=${encodeURIComponent(query || "")}`);
|
|
1023
|
+
const data = await response.json();
|
|
1024
|
+
if (response.ok && Array.isArray(data.suggestions)) {
|
|
1025
|
+
renderCommandCwdSuggestions([...new Set([...data.suggestions, ...fallback])]);
|
|
1026
|
+
}
|
|
1027
|
+
} catch {
|
|
1028
|
+
renderCommandCwdSuggestions(fallback);
|
|
1029
|
+
}
|
|
956
1030
|
}
|
|
957
1031
|
|
|
958
1032
|
function renderTaskProfiles(selected = "auto") {
|
|
@@ -1584,7 +1658,7 @@ function formPayload() {
|
|
|
1584
1658
|
auxiliaryModel: fieldValue(auxiliaryModelField) || "nano-banana-2",
|
|
1585
1659
|
startUrl: document.querySelector("#startUrl").value.trim(),
|
|
1586
1660
|
allowedDomains: document.querySelector("#allowedDomains").value.trim(),
|
|
1587
|
-
commandCwd:
|
|
1661
|
+
commandCwd: commandCwdField?.value.trim() || "",
|
|
1588
1662
|
maxSteps: Number(document.querySelector("#maxSteps").value) || 24,
|
|
1589
1663
|
permissionMode: permissionModeField?.value || "normal",
|
|
1590
1664
|
sandboxMode: sandboxModeField.value,
|
|
@@ -3146,6 +3220,22 @@ saveApiKeyButton?.addEventListener("click", async () => {
|
|
|
3146
3220
|
form.addEventListener("input", schedulePreferenceSave);
|
|
3147
3221
|
form.addEventListener("change", schedulePreferenceSave);
|
|
3148
3222
|
|
|
3223
|
+
let commandCwdSuggestTimer = null;
|
|
3224
|
+
commandCwdField?.addEventListener("focus", () => {
|
|
3225
|
+
refreshCommandCwdSuggestions(commandCwdField.value).catch(() => {});
|
|
3226
|
+
});
|
|
3227
|
+
commandCwdField?.addEventListener("input", () => {
|
|
3228
|
+
schedulePreferenceSave();
|
|
3229
|
+
clearTimeout(commandCwdSuggestTimer);
|
|
3230
|
+
commandCwdSuggestTimer = setTimeout(() => {
|
|
3231
|
+
refreshCommandCwdSuggestions(commandCwdField.value).catch(() => {});
|
|
3232
|
+
}, 120);
|
|
3233
|
+
});
|
|
3234
|
+
commandCwdField?.addEventListener("change", () => {
|
|
3235
|
+
schedulePreferenceSave();
|
|
3236
|
+
refreshCommandCwdSuggestions(commandCwdField.value).catch(() => {});
|
|
3237
|
+
});
|
|
3238
|
+
|
|
3149
3239
|
chatInputEl.addEventListener("keydown", (event) => {
|
|
3150
3240
|
if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
3151
3241
|
event.preventDefault();
|
|
@@ -3343,7 +3433,7 @@ async function loadConfig() {
|
|
|
3343
3433
|
renderTaskProfiles(prefs.taskProfile || "auto");
|
|
3344
3434
|
document.querySelector("#startUrl").value = prefs.startUrl || "";
|
|
3345
3435
|
document.querySelector("#allowedDomains").value = prefs.allowedDomains || "";
|
|
3346
|
-
|
|
3436
|
+
if (commandCwdField) commandCwdField.value = prefs.commandCwd || data.project?.root || "";
|
|
3347
3437
|
document.querySelector("#headless").checked = prefs.headless ?? data.defaults.headless;
|
|
3348
3438
|
document.querySelector("#maxSteps").value = prefs.maxSteps ?? data.defaults.maxSteps;
|
|
3349
3439
|
if (permissionModeField) permissionModeField.value = prefs.permissionMode || "normal";
|
|
@@ -3366,6 +3456,7 @@ async function loadConfig() {
|
|
|
3366
3456
|
|
|
3367
3457
|
renderKeyStatus(data.keyStatus);
|
|
3368
3458
|
renderProjectStatus(data.project);
|
|
3459
|
+
await refreshCommandCwdSuggestions(commandCwdField?.value || "");
|
|
3369
3460
|
renderWrapperStatus(data.wrappers || []);
|
|
3370
3461
|
refreshProviderDropdowns();
|
|
3371
3462
|
renderWorkspacePanel(data.workspace, []);
|
package/public/index.html
CHANGED
|
@@ -36,13 +36,30 @@
|
|
|
36
36
|
<p class="subtle" data-i18n="intro">
|
|
37
37
|
Project-aware, low-cost agent for real problems
|
|
38
38
|
</p>
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
<section class="workspace-search-card" aria-labelledby="workspace-search-title">
|
|
40
|
+
<div class="workspace-search-head">
|
|
41
|
+
<div>
|
|
42
|
+
<strong id="workspace-search-title" data-i18n="projectStatusTitle">Project folder</strong>
|
|
43
|
+
<p class="subtle" data-i18n="commandCwdHelp">Runs start from this working directory.</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<label class="workspace-search-field">
|
|
47
|
+
<span data-i18n="commandCwdLabel">Working directory</span>
|
|
48
|
+
<input
|
|
49
|
+
id="commandCwd"
|
|
50
|
+
name="commandCwd"
|
|
51
|
+
type="search"
|
|
52
|
+
value="/home/lachlan/ProjectsLFS/Agent"
|
|
53
|
+
list="command-cwd-suggestions"
|
|
54
|
+
autocomplete="off"
|
|
55
|
+
spellcheck="false"
|
|
56
|
+
data-i18n-placeholder="commandCwdPlaceholder"
|
|
57
|
+
placeholder="/home/lachlan/ProjectsLFS/Agent"
|
|
58
|
+
/>
|
|
59
|
+
<datalist id="command-cwd-suggestions"></datalist>
|
|
60
|
+
</label>
|
|
61
|
+
<div id="project-status" class="project-status-grid" aria-live="polite">Loading project context...</div>
|
|
62
|
+
</section>
|
|
46
63
|
|
|
47
64
|
<section id="setup-card" class="setup-card" hidden>
|
|
48
65
|
<div>
|
|
@@ -84,14 +101,20 @@
|
|
|
84
101
|
</div>
|
|
85
102
|
</section>
|
|
86
103
|
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
<
|
|
104
|
+
<details id="run-defaults-card" class="run-defaults-card">
|
|
105
|
+
<summary class="run-defaults-summary">
|
|
106
|
+
<span>
|
|
90
107
|
<strong data-i18n="runDefaultsTitle">Run defaults</strong>
|
|
108
|
+
<span class="subtle" data-i18n="runDefaultsFoldHint">Routing, safety, tool toggles, and key status.</span>
|
|
109
|
+
</span>
|
|
110
|
+
<span id="key-status" class="key-status-summary"></span>
|
|
111
|
+
<span class="project-card-toggle" aria-hidden="true"></span>
|
|
112
|
+
</summary>
|
|
113
|
+
<form id="run-form" class="run-defaults-form">
|
|
114
|
+
<div class="run-defaults-head">
|
|
91
115
|
<p class="subtle" data-i18n="runDefaultsHelp">The chat box is the first goal for a new run. These controls set how that run starts.</p>
|
|
116
|
+
<div id="key-status-list" class="key-status-list" aria-live="polite"></div>
|
|
92
117
|
</div>
|
|
93
|
-
<span id="key-status" class="subtle"></span>
|
|
94
|
-
</div>
|
|
95
118
|
|
|
96
119
|
<label>
|
|
97
120
|
<span data-i18n="routingModeLabel">Routing policy</span>
|
|
@@ -135,10 +158,6 @@
|
|
|
135
158
|
</label>
|
|
136
159
|
|
|
137
160
|
<div class="grid">
|
|
138
|
-
<label>
|
|
139
|
-
<span data-i18n="commandCwdLabel">Working directory</span>
|
|
140
|
-
<input id="commandCwd" name="commandCwd" type="text" value="/home/lachlan/ProjectsLFS/Agent" />
|
|
141
|
-
</label>
|
|
142
161
|
<label>
|
|
143
162
|
<span data-i18n="maxStepsLabel">Max steps</span>
|
|
144
163
|
<input id="maxSteps" name="maxSteps" type="number" min="1" max="50" value="24" />
|
|
@@ -202,7 +221,8 @@
|
|
|
202
221
|
</div>
|
|
203
222
|
|
|
204
223
|
<button id="open-settings" type="button" class="secondary settings-button">Advanced settings</button>
|
|
205
|
-
|
|
224
|
+
</form>
|
|
225
|
+
</details>
|
|
206
226
|
</section>
|
|
207
227
|
|
|
208
228
|
<section class="stack">
|
package/public/styles.css
CHANGED
|
@@ -131,6 +131,108 @@ h1 {
|
|
|
131
131
|
.run-defaults-form {
|
|
132
132
|
display: grid;
|
|
133
133
|
gap: 14px;
|
|
134
|
+
margin-top: 12px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.run-defaults-card {
|
|
138
|
+
min-width: 0;
|
|
139
|
+
margin-top: 16px;
|
|
140
|
+
padding: 12px;
|
|
141
|
+
border: 1px solid rgba(15, 118, 110, 0.18);
|
|
142
|
+
border-radius: 18px;
|
|
143
|
+
background:
|
|
144
|
+
linear-gradient(135deg, rgba(236, 253, 245, 0.72), rgba(255, 253, 250, 0.9)),
|
|
145
|
+
#fffdfa;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.run-defaults-summary {
|
|
149
|
+
display: grid;
|
|
150
|
+
grid-template-columns: minmax(0, 1fr) max-content max-content;
|
|
151
|
+
gap: 10px;
|
|
152
|
+
align-items: center;
|
|
153
|
+
list-style: none;
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.run-defaults-summary::-webkit-details-marker {
|
|
158
|
+
display: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.run-defaults-summary strong,
|
|
162
|
+
.workspace-search-head strong {
|
|
163
|
+
display: block;
|
|
164
|
+
color: #0f766e;
|
|
165
|
+
text-transform: uppercase;
|
|
166
|
+
letter-spacing: 0.08em;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.run-defaults-summary .subtle {
|
|
170
|
+
display: block;
|
|
171
|
+
margin-top: 2px;
|
|
172
|
+
font-size: 0.78rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.key-status-summary {
|
|
176
|
+
display: inline-flex;
|
|
177
|
+
min-height: 32px;
|
|
178
|
+
align-items: center;
|
|
179
|
+
padding: 5px 10px;
|
|
180
|
+
border: 1px solid rgba(15, 118, 110, 0.18);
|
|
181
|
+
border-radius: 999px;
|
|
182
|
+
background: rgba(255, 253, 250, 0.82);
|
|
183
|
+
color: var(--muted);
|
|
184
|
+
font-size: 0.78rem;
|
|
185
|
+
font-weight: 850;
|
|
186
|
+
white-space: nowrap;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.key-status-summary[data-ready="true"] {
|
|
190
|
+
border-color: rgba(15, 118, 110, 0.32);
|
|
191
|
+
background: rgba(204, 251, 241, 0.52);
|
|
192
|
+
color: #0f766e;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.key-status-list,
|
|
196
|
+
.project-status-grid {
|
|
197
|
+
display: grid;
|
|
198
|
+
gap: 8px;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.key-status-list {
|
|
202
|
+
grid-template-columns: repeat(auto-fit, minmax(116px, 1fr));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.key-status-chip,
|
|
206
|
+
.project-status-chip {
|
|
207
|
+
min-width: 0;
|
|
208
|
+
padding: 9px 10px;
|
|
209
|
+
border: 1px solid rgba(95, 86, 74, 0.16);
|
|
210
|
+
border-radius: 14px;
|
|
211
|
+
background: rgba(255, 253, 250, 0.78);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.key-status-chip[data-ready="true"] {
|
|
215
|
+
border-color: rgba(15, 118, 110, 0.28);
|
|
216
|
+
background: rgba(204, 251, 241, 0.42);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.key-status-chip strong,
|
|
220
|
+
.project-status-chip strong {
|
|
221
|
+
display: block;
|
|
222
|
+
color: #0f766e;
|
|
223
|
+
font-size: 0.72rem;
|
|
224
|
+
letter-spacing: 0.06em;
|
|
225
|
+
text-transform: uppercase;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.key-status-chip span,
|
|
229
|
+
.project-status-chip span {
|
|
230
|
+
display: block;
|
|
231
|
+
min-width: 0;
|
|
232
|
+
overflow-wrap: anywhere;
|
|
233
|
+
color: var(--muted);
|
|
234
|
+
font-size: 0.8rem;
|
|
235
|
+
line-height: 1.35;
|
|
134
236
|
}
|
|
135
237
|
|
|
136
238
|
.run-defaults-head {
|
|
@@ -142,14 +244,8 @@ h1 {
|
|
|
142
244
|
background: rgba(204, 251, 241, 0.28);
|
|
143
245
|
}
|
|
144
246
|
|
|
145
|
-
.run-defaults-head strong {
|
|
146
|
-
color: #0f766e;
|
|
147
|
-
text-transform: uppercase;
|
|
148
|
-
letter-spacing: 0.08em;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
247
|
.run-defaults-head p {
|
|
152
|
-
margin:
|
|
248
|
+
margin: 0;
|
|
153
249
|
font-size: 0.88rem;
|
|
154
250
|
line-height: 1.45;
|
|
155
251
|
}
|
|
@@ -316,6 +412,7 @@ h1 {
|
|
|
316
412
|
background: rgba(204, 251, 241, 0.52);
|
|
317
413
|
}
|
|
318
414
|
|
|
415
|
+
.workspace-search-card,
|
|
319
416
|
.project-card,
|
|
320
417
|
.setup-card {
|
|
321
418
|
min-width: 0;
|
|
@@ -331,6 +428,61 @@ h1 {
|
|
|
331
428
|
gap: 10px;
|
|
332
429
|
}
|
|
333
430
|
|
|
431
|
+
.workspace-search-card {
|
|
432
|
+
display: grid;
|
|
433
|
+
gap: 12px;
|
|
434
|
+
margin-top: 16px;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.workspace-search-head {
|
|
438
|
+
display: flex;
|
|
439
|
+
min-width: 0;
|
|
440
|
+
gap: 12px;
|
|
441
|
+
align-items: start;
|
|
442
|
+
justify-content: space-between;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.workspace-search-head p {
|
|
446
|
+
margin: 3px 0 0;
|
|
447
|
+
line-height: 1.45;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.workspace-search-field {
|
|
451
|
+
position: relative;
|
|
452
|
+
display: grid;
|
|
453
|
+
gap: 6px;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.workspace-search-field input[type="search"] {
|
|
457
|
+
min-height: 48px;
|
|
458
|
+
padding: 12px 14px 12px 42px;
|
|
459
|
+
border-color: rgba(15, 118, 110, 0.24);
|
|
460
|
+
border-radius: 999px;
|
|
461
|
+
background:
|
|
462
|
+
linear-gradient(90deg, rgba(204, 251, 241, 0.28), rgba(255, 253, 250, 0.96)),
|
|
463
|
+
#fffdfa;
|
|
464
|
+
font-size: 0.94rem;
|
|
465
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.workspace-search-field::before {
|
|
469
|
+
position: absolute;
|
|
470
|
+
bottom: 12px;
|
|
471
|
+
left: 15px;
|
|
472
|
+
color: #0f766e;
|
|
473
|
+
font-size: 1rem;
|
|
474
|
+
content: "⌕";
|
|
475
|
+
pointer-events: none;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.project-status-grid {
|
|
479
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.project-status-chip[data-wide="true"] {
|
|
483
|
+
grid-column: 1 / -1;
|
|
484
|
+
}
|
|
485
|
+
|
|
334
486
|
.project-card {
|
|
335
487
|
display: block;
|
|
336
488
|
}
|
|
@@ -554,13 +706,18 @@ textarea {
|
|
|
554
706
|
}
|
|
555
707
|
|
|
556
708
|
.compact-switch {
|
|
557
|
-
|
|
709
|
+
width: max-content;
|
|
710
|
+
grid-template-columns: auto auto;
|
|
711
|
+
height: 50px;
|
|
712
|
+
min-height: 50px;
|
|
558
713
|
padding: 8px 10px;
|
|
714
|
+
gap: 8px;
|
|
715
|
+
white-space: nowrap;
|
|
559
716
|
}
|
|
560
717
|
|
|
561
718
|
.compact-switch .switch {
|
|
562
|
-
width:
|
|
563
|
-
min-width:
|
|
719
|
+
width: 44px;
|
|
720
|
+
min-width: 44px;
|
|
564
721
|
}
|
|
565
722
|
|
|
566
723
|
.compact-switch .switch::before {
|
|
@@ -568,7 +725,7 @@ textarea {
|
|
|
568
725
|
}
|
|
569
726
|
|
|
570
727
|
.compact-switch input[type="checkbox"]:checked + .switch::before {
|
|
571
|
-
transform: translateX(
|
|
728
|
+
transform: translateX(18px);
|
|
572
729
|
}
|
|
573
730
|
|
|
574
731
|
.settings-button {
|
|
@@ -644,7 +801,7 @@ button.danger {
|
|
|
644
801
|
|
|
645
802
|
.quick-mode-bar {
|
|
646
803
|
display: grid;
|
|
647
|
-
grid-template-columns:
|
|
804
|
+
grid-template-columns: max-content max-content max-content minmax(0, 1fr);
|
|
648
805
|
gap: 10px;
|
|
649
806
|
align-items: center;
|
|
650
807
|
padding: 10px;
|
|
@@ -656,8 +813,16 @@ button.danger {
|
|
|
656
813
|
}
|
|
657
814
|
|
|
658
815
|
.quick-mode-select {
|
|
659
|
-
display:
|
|
660
|
-
|
|
816
|
+
display: inline-flex;
|
|
817
|
+
width: max-content;
|
|
818
|
+
height: 50px;
|
|
819
|
+
min-height: 50px;
|
|
820
|
+
align-items: center;
|
|
821
|
+
gap: 8px;
|
|
822
|
+
padding: 8px 10px;
|
|
823
|
+
border: 1px solid var(--line);
|
|
824
|
+
border-radius: 14px;
|
|
825
|
+
background: rgba(255, 253, 250, 0.72);
|
|
661
826
|
}
|
|
662
827
|
|
|
663
828
|
.quick-mode-select span {
|
|
@@ -668,6 +833,13 @@ button.danger {
|
|
|
668
833
|
text-transform: uppercase;
|
|
669
834
|
}
|
|
670
835
|
|
|
836
|
+
.quick-mode-select select {
|
|
837
|
+
width: 68px;
|
|
838
|
+
min-width: 68px;
|
|
839
|
+
padding: 5px 20px 5px 9px;
|
|
840
|
+
border-radius: 10px;
|
|
841
|
+
}
|
|
842
|
+
|
|
671
843
|
.quick-mode-status {
|
|
672
844
|
min-width: 0;
|
|
673
845
|
overflow: hidden;
|
|
@@ -1851,6 +2023,14 @@ button.danger {
|
|
|
1851
2023
|
grid-template-columns: 1fr;
|
|
1852
2024
|
}
|
|
1853
2025
|
|
|
2026
|
+
.run-defaults-summary {
|
|
2027
|
+
grid-template-columns: 1fr max-content;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
.run-defaults-summary .key-status-summary {
|
|
2031
|
+
justify-self: start;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
1854
2034
|
.quick-mode-status {
|
|
1855
2035
|
white-space: normal;
|
|
1856
2036
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
classifyEscapeAction,
|
|
16
16
|
formatElapsedDuration,
|
|
17
17
|
formatWorkspaceChange,
|
|
18
|
+
shouldIgnorePromptSubmission,
|
|
18
19
|
stripMarkdown,
|
|
19
20
|
} from "../src/interactive-cli.js";
|
|
20
21
|
import { formatSessionChoices, parseArgs, parseResumeCommandArgs, splitResumeCommandArgv } from "../src/cli.js";
|
|
@@ -218,6 +219,60 @@ async function runTmuxInterruptSmoke({ key, expected }) {
|
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
async function runTmuxEmptyEnterSmoke() {
|
|
223
|
+
const session = `aginti-empty-enter-${process.pid}`;
|
|
224
|
+
const command = [
|
|
225
|
+
"env",
|
|
226
|
+
`AGINTIFLOW_HOME=${shellQuote(agintiflowHome)}`,
|
|
227
|
+
"AGINTIFLOW_RUNTIME_DIR=",
|
|
228
|
+
"AGINTIFLOW_NO_WEB_AUTO_START=1",
|
|
229
|
+
"AGINTI_LANGUAGE=en",
|
|
230
|
+
shellQuote(process.execPath),
|
|
231
|
+
shellQuote(binPath),
|
|
232
|
+
"chat",
|
|
233
|
+
"--provider",
|
|
234
|
+
"mock",
|
|
235
|
+
"--routing",
|
|
236
|
+
"manual",
|
|
237
|
+
"--profile",
|
|
238
|
+
"code",
|
|
239
|
+
"--sandbox-mode",
|
|
240
|
+
"host",
|
|
241
|
+
].join(" ");
|
|
242
|
+
const shellCommand = `cd ${shellQuote(tempRoot)} && ${command}`;
|
|
243
|
+
try {
|
|
244
|
+
tmux(["kill-session", "-t", session], { timeout: 2000 });
|
|
245
|
+
} catch {
|
|
246
|
+
// Session does not exist.
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
tmux(["new-session", "-d", "-s", session, "bash", "-lc", `${shellCommand}; printf '\\nEXIT:%s\\n' "$?"; sleep 3`]);
|
|
250
|
+
const before = await waitForTmuxText(session, /user>/, 10000);
|
|
251
|
+
const beforeCount = (before.match(/^\s*user>/gm) || []).length;
|
|
252
|
+
if (beforeCount < 1) throw new Error(`empty-enter smoke did not reach the composer\n${before}`);
|
|
253
|
+
tmux(["send-keys", "-t", session, "Enter"]);
|
|
254
|
+
await new Promise((resolve) => setTimeout(resolve, 750));
|
|
255
|
+
const after = tmuxCapture(session);
|
|
256
|
+
const afterCount = (after.match(/^\s*user>/gm) || []).length;
|
|
257
|
+
if (afterCount !== beforeCount) {
|
|
258
|
+
throw new Error(`empty Enter committed a blank user turn: before=${beforeCount} after=${afterCount}\n${after}`);
|
|
259
|
+
}
|
|
260
|
+
if (/Mock run complete|status=running|status=idle session=/.test(after)) {
|
|
261
|
+
throw new Error(`empty Enter started an agent run\n${after}`);
|
|
262
|
+
}
|
|
263
|
+
tmux(["send-keys", "-t", session, "-l", "/exit"]);
|
|
264
|
+
tmux(["send-keys", "-t", session, "Enter"]);
|
|
265
|
+
const exited = await waitForTmuxText(session, /EXIT:0/, 8000);
|
|
266
|
+
if (!/EXIT:0/.test(exited)) throw new Error(`empty-enter smoke did not exit cleanly\n${exited}`);
|
|
267
|
+
} finally {
|
|
268
|
+
try {
|
|
269
|
+
tmux(["kill-session", "-t", session], { timeout: 2000 });
|
|
270
|
+
} catch {
|
|
271
|
+
// Session may already have exited.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
221
276
|
try {
|
|
222
277
|
const translatedHelpKeys = [
|
|
223
278
|
"helpHelp",
|
|
@@ -607,6 +662,9 @@ try {
|
|
|
607
662
|
) {
|
|
608
663
|
throw new Error("slash command prompt canonicalization did not preserve final submitted command correctly");
|
|
609
664
|
}
|
|
665
|
+
if (!shouldIgnorePromptSubmission("") || !shouldIgnorePromptSubmission(" \n\t ") || shouldIgnorePromptSubmission("run analysis")) {
|
|
666
|
+
throw new Error("empty prompt submission guard did not classify blank input correctly");
|
|
667
|
+
}
|
|
610
668
|
const scsBooleanArgs = parseArgs(["--scs", "fix this project"]);
|
|
611
669
|
if (scsBooleanArgs.enableScs !== "on" || scsBooleanArgs.goal !== "fix this project") {
|
|
612
670
|
throw new Error("--scs should behave as a boolean flag when followed by a task");
|
|
@@ -865,6 +923,7 @@ try {
|
|
|
865
923
|
|
|
866
924
|
await runTmuxInterruptSmoke({ key: "Escape", expected: /Active run stopped|Session saved/ });
|
|
867
925
|
await runTmuxInterruptSmoke({ key: "C-c", expected: /EXIT:130|Session stopped by ctrl-c|Interrupted\. Session saved/ });
|
|
926
|
+
await runTmuxEmptyEnterSmoke();
|
|
868
927
|
|
|
869
928
|
console.log(
|
|
870
929
|
JSON.stringify(
|
|
@@ -909,6 +968,7 @@ try {
|
|
|
909
968
|
"one-shot-cwd",
|
|
910
969
|
"tmux-escape-active-run-stop",
|
|
911
970
|
"tmux-ctrl-c-session-stop",
|
|
971
|
+
"tmux-empty-enter-noop",
|
|
912
972
|
"resume-latest",
|
|
913
973
|
"resume-history-metadata",
|
|
914
974
|
"resume-history-prompt-labels",
|
package/scripts/smoke-web-api.js
CHANGED
|
@@ -48,7 +48,7 @@ async function fetchJson(pathname, options = {}) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
async function waitForHealth() {
|
|
51
|
-
const deadline = Date.now() +
|
|
51
|
+
const deadline = Date.now() + 30000;
|
|
52
52
|
while (Date.now() < deadline) {
|
|
53
53
|
if (server.exitCode !== null) break;
|
|
54
54
|
try {
|
|
@@ -96,7 +96,7 @@ try {
|
|
|
96
96
|
if (chatThreadIndex < 0 || chatPendingIndex < 0 || chatPendingIndex < chatThreadIndex) {
|
|
97
97
|
throw new Error("pending message panel must render below the chat thread");
|
|
98
98
|
}
|
|
99
|
-
for (const marker of ['id="new-session"', 'id="run-state"', 'id="toast-region"', 'id="chat-submit"', 'id="enableScs"', 'id="aapsModeToggle"', 'id="veniceModeToggle"', 'id="dynamicSteps"']) {
|
|
99
|
+
for (const marker of ['id="new-session"', 'id="run-state"', 'id="toast-region"', 'id="chat-submit"', 'id="command-cwd-suggestions"', 'id="enableScs"', 'id="aapsModeToggle"', 'id="veniceModeToggle"', 'id="dynamicSteps"']) {
|
|
100
100
|
if (!webAppHtml.includes(marker)) throw new Error(`web UI is missing ${marker}`);
|
|
101
101
|
}
|
|
102
102
|
if (webAppHtml.includes('id="goal"') || /<button[^>]+type="submit"[^>]*>\s*Start run\s*<\/button>/i.test(webAppHtml)) {
|
|
@@ -109,6 +109,10 @@ try {
|
|
|
109
109
|
if (config.preferences?.preferredWrapper !== "codex") throw new Error("Codex is not the default preferred wrapper");
|
|
110
110
|
if (config.project?.root !== runtimeDir) throw new Error("web project root did not default to launch directory");
|
|
111
111
|
if (config.preferences?.commandCwd !== runtimeDir) throw new Error("commandCwd did not default to project root");
|
|
112
|
+
const pathSuggest = await fetchJson(`/api/path-suggestions?q=${encodeURIComponent(runtimeDir.slice(0, runtimeDir.lastIndexOf("/")))}`);
|
|
113
|
+
if (!pathSuggest.ok || !Array.isArray(pathSuggest.suggestions) || !pathSuggest.suggestions.some((item) => item === runtimeDir)) {
|
|
114
|
+
throw new Error(`path suggestions did not include the runtime directory: ${JSON.stringify(pathSuggest)}`);
|
|
115
|
+
}
|
|
112
116
|
if (config.preferences?.sandboxMode !== "docker-workspace") throw new Error("web did not default to docker workspace");
|
|
113
117
|
if (config.preferences?.packageInstallPolicy !== "allow") throw new Error("web did not default to Docker package installs");
|
|
114
118
|
if (config.preferences?.permissionMode !== "normal") throw new Error("web did not default to normal permission mode");
|
package/scripts/smoke-web-ui.js
CHANGED
|
@@ -88,6 +88,12 @@ try {
|
|
|
88
88
|
if (!/start new run/i.test(initialSubmit)) throw new Error(`composer did not default to new-run mode: ${initialSubmit}`);
|
|
89
89
|
if (await page.locator("#stop-run").isVisible()) throw new Error("stop button is visible before a run starts");
|
|
90
90
|
if ((await page.locator("#goal").count()) !== 0) throw new Error("old standalone goal textarea is still rendered");
|
|
91
|
+
if (!(await page.locator("#commandCwd").isVisible())) throw new Error("working directory search field is not visible at the top level");
|
|
92
|
+
if (await page.locator("#run-defaults-card").evaluate((node) => node.open)) throw new Error("run defaults should start folded");
|
|
93
|
+
await page.locator("#commandCwd").fill(runtimeDir.slice(0, Math.max(runtimeDir.lastIndexOf("/"), 1)));
|
|
94
|
+
await page.waitForFunction(() => document.querySelectorAll("#command-cwd-suggestions option").length > 0);
|
|
95
|
+
if ((await page.locator(".project-status-chip").count()) < 4) throw new Error("project folder status did not render structured chips");
|
|
96
|
+
await page.locator("#run-defaults-card summary").click();
|
|
91
97
|
|
|
92
98
|
await page.selectOption("#enableScs", "auto");
|
|
93
99
|
await page.locator("label:has(#aapsModeToggle)").click();
|
|
@@ -99,6 +105,19 @@ try {
|
|
|
99
105
|
if (!/scs=auto/.test(quickStatus) || !/profile=aaps/.test(quickStatus) || !/route venice\//.test(quickStatus)) {
|
|
100
106
|
throw new Error(`quick mode status did not reflect CLI modes: ${quickStatus}`);
|
|
101
107
|
}
|
|
108
|
+
const quickBoxes = await Promise.all([
|
|
109
|
+
page.locator(".quick-mode-select").boundingBox(),
|
|
110
|
+
page.locator("label:has(#aapsModeToggle)").boundingBox(),
|
|
111
|
+
page.locator("label:has(#veniceModeToggle)").boundingBox(),
|
|
112
|
+
]);
|
|
113
|
+
if (quickBoxes.some((box) => !box)) throw new Error("quick mode controls did not render measurable boxes");
|
|
114
|
+
const centers = quickBoxes.map((box) => box.y + box.height / 2);
|
|
115
|
+
if (Math.max(...centers) - Math.min(...centers) > 2) {
|
|
116
|
+
throw new Error(`quick mode controls are not vertically aligned: ${JSON.stringify(quickBoxes)}`);
|
|
117
|
+
}
|
|
118
|
+
if (quickBoxes.some((box) => box.height < 48 || box.height > 52) || quickBoxes.some((box) => box.width > 128)) {
|
|
119
|
+
throw new Error(`quick mode controls are not compact button-sized elements: ${JSON.stringify(quickBoxes)}`);
|
|
120
|
+
}
|
|
102
121
|
|
|
103
122
|
await page.selectOption("#routingMode", "manual");
|
|
104
123
|
await page.selectOption("#provider", "mock");
|
|
@@ -154,7 +173,11 @@ try {
|
|
|
154
173
|
checks: [
|
|
155
174
|
"composer-starts-new-run",
|
|
156
175
|
"old-goal-form-hidden",
|
|
176
|
+
"working-directory-search-top",
|
|
177
|
+
"project-status-chips",
|
|
178
|
+
"run-defaults-folded",
|
|
157
179
|
"quick-scs-aaps-venice-controls",
|
|
180
|
+
"quick-mode-control-alignment",
|
|
158
181
|
"dynamic-steps-dropdown",
|
|
159
182
|
"composer-goal-payload",
|
|
160
183
|
"running-stop-button-visible",
|
|
@@ -18,8 +18,15 @@ function occurrenceCount(value, pattern) {
|
|
|
18
18
|
return String(value || "").split(pattern).length - 1;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function latestWebappEvent(value, statePattern = "started|restarted|reused|stopped") {
|
|
22
|
+
const pattern = new RegExp(`webapp=(http://127\\.0\\.0\\.1:(\\d+)) (${statePattern})`, "g");
|
|
23
|
+
const matches = [...String(value || "").matchAll(pattern)];
|
|
24
|
+
const latest = matches.at(-1);
|
|
25
|
+
return latest ? { url: latest[1], port: Number(latest[2]), state: latest[3] } : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
async function waitFor(predicate, child, label, output) {
|
|
22
|
-
const deadline = Date.now() +
|
|
29
|
+
const deadline = Date.now() + 30000;
|
|
23
30
|
while (Date.now() < deadline) {
|
|
24
31
|
if (predicate()) return true;
|
|
25
32
|
if (child.exitCode !== null) break;
|
|
@@ -67,12 +74,15 @@ async function runCase({ port, env = {}, expectHeader, label }) {
|
|
|
67
74
|
|
|
68
75
|
try {
|
|
69
76
|
await waitFor(() => output.stdout.includes(expectHeader), child, `${label} launch header`, output);
|
|
77
|
+
await waitFor(() => output.stdout.includes("status=idle") && output.stdout.includes("user>"), child, `${label} interactive ready`, output);
|
|
70
78
|
child.stdin.write(`/webapp ${port}\n`);
|
|
71
|
-
await waitFor(() => output.stdout
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
await waitFor(() => latestWebappEvent(output.stdout, "started|reused"), child, `${label} /webapp command`, output);
|
|
80
|
+
let active = latestWebappEvent(output.stdout, "started|reused");
|
|
81
|
+
child.stdin.write(`/webapp restart ${active.port}\n`);
|
|
82
|
+
await waitFor(() => latestWebappEvent(output.stdout, "restarted"), child, `${label} /webapp restart command`, output);
|
|
83
|
+
active = latestWebappEvent(output.stdout, "restarted");
|
|
84
|
+
const health = await fetch(`${active.url}/health`).then((response) => response.json());
|
|
85
|
+
if (!health.ok || health.app !== "agintiflow" || Number(health.port) !== active.port) {
|
|
76
86
|
throw new Error(`invalid /webapp health response for ${label}: ${JSON.stringify(health)}`);
|
|
77
87
|
}
|
|
78
88
|
if (path.resolve(health.agintiflowHome) !== path.resolve(path.join(runtimeDir, `.agintiflow-web-home-${label}`))) {
|
|
@@ -85,19 +95,19 @@ async function runCase({ port, env = {}, expectHeader, label }) {
|
|
|
85
95
|
await waitFor(() => occurrenceCount(output.stdout, "webapp auto-start=disabled") > disabledCount, child, `${label} /webapp status disabled command`, output);
|
|
86
96
|
child.stdin.write(`/webapp enable\n`);
|
|
87
97
|
await waitFor(() => output.stdout.includes("webapp auto-start=enabled"), child, `${label} /webapp enable command`, output);
|
|
88
|
-
child.stdin.write(`/webapp stop ${port}\n`);
|
|
89
|
-
await waitFor(() => output.stdout
|
|
98
|
+
child.stdin.write(`/webapp stop ${active.port}\n`);
|
|
99
|
+
await waitFor(() => latestWebappEvent(output.stdout, "stopped")?.port === active.port, child, `${label} /webapp stop command`, output);
|
|
90
100
|
let stopped = false;
|
|
91
101
|
try {
|
|
92
|
-
await fetch(
|
|
102
|
+
await fetch(`${active.url}/health`);
|
|
93
103
|
} catch {
|
|
94
104
|
stopped = true;
|
|
95
105
|
}
|
|
96
106
|
if (!stopped) {
|
|
97
107
|
throw new Error(`webapp still responded after /webapp stop for ${label}`);
|
|
98
108
|
}
|
|
99
|
-
child.stdin.write(`/webapp ${port}\n`);
|
|
100
|
-
await waitFor(() => output.stdout
|
|
109
|
+
child.stdin.write(`/webapp ${active.port}\n`);
|
|
110
|
+
await waitFor(() => latestWebappEvent(output.stdout, "started|reused")?.port === active.port, child, `${label} /webapp restart after stop`, output);
|
|
101
111
|
} finally {
|
|
102
112
|
child.kill("SIGTERM");
|
|
103
113
|
await killPort(port);
|
package/src/interactive-cli.js
CHANGED
|
@@ -539,6 +539,10 @@ export function canonicalSlashPromptBuffer(value = "") {
|
|
|
539
539
|
return resolved === raw ? text : `/${resolved}`;
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
+
export function shouldIgnorePromptSubmission(value = "") {
|
|
543
|
+
return canonicalSlashPromptBuffer(value).trim().length === 0;
|
|
544
|
+
}
|
|
545
|
+
|
|
542
546
|
function clamp(value, min, max) {
|
|
543
547
|
return Math.min(Math.max(value, min), max);
|
|
544
548
|
}
|
|
@@ -1487,6 +1491,18 @@ function readTtyPrompt(options = {}) {
|
|
|
1487
1491
|
buffer = canonical;
|
|
1488
1492
|
cursor = buffer.length;
|
|
1489
1493
|
}
|
|
1494
|
+
if (shouldIgnorePromptSubmission(buffer)) {
|
|
1495
|
+
if (buffer.length > 0 || cursor !== 0) {
|
|
1496
|
+
buffer = "";
|
|
1497
|
+
cursor = 0;
|
|
1498
|
+
preferredColumn = null;
|
|
1499
|
+
suggestionAnchor = "";
|
|
1500
|
+
suggestionIndex = 0;
|
|
1501
|
+
promptHistory.resetBrowsing();
|
|
1502
|
+
redraw();
|
|
1503
|
+
}
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1490
1506
|
clearPromptPanel();
|
|
1491
1507
|
cleanup();
|
|
1492
1508
|
printCommittedUserInput(buffer);
|
package/src/web-autostart.js
CHANGED
|
@@ -138,7 +138,8 @@ function compatibleHealth(health = {}, { cwd = "", home = "", packageDir = "" }
|
|
|
138
138
|
if (!health.runtimeDir || !health.agintiflowHome) return false;
|
|
139
139
|
if (!samePath(health.runtimeDir, cwd)) return false;
|
|
140
140
|
if (!samePath(health.agintiflowHome, home)) return false;
|
|
141
|
-
|
|
141
|
+
// Package paths legitimately change after npm updates or local installs. The
|
|
142
|
+
// safe ownership boundary for reuse/stop is the same app, runtime, and home.
|
|
142
143
|
return true;
|
|
143
144
|
}
|
|
144
145
|
|
package/web.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createReadStream } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import express from "express";
|
|
4
|
+
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { runAgent } from "./src/agent-runner.js";
|
|
@@ -357,6 +358,88 @@ function publicKeyStatus(projectRoot = baseDir) {
|
|
|
357
358
|
};
|
|
358
359
|
}
|
|
359
360
|
|
|
361
|
+
function expandUserPath(value = "") {
|
|
362
|
+
const text = String(value || "").trim();
|
|
363
|
+
if (text === "~") return os.homedir();
|
|
364
|
+
if (text.startsWith("~/")) return path.join(os.homedir(), text.slice(2));
|
|
365
|
+
return text;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function uniquePaths(values = []) {
|
|
369
|
+
const seen = new Set();
|
|
370
|
+
const result = [];
|
|
371
|
+
for (const value of values) {
|
|
372
|
+
if (!value) continue;
|
|
373
|
+
const resolved = path.resolve(expandUserPath(value));
|
|
374
|
+
if (seen.has(resolved)) continue;
|
|
375
|
+
seen.add(resolved);
|
|
376
|
+
result.push(resolved);
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function readableDirectory(value = "") {
|
|
382
|
+
try {
|
|
383
|
+
const stat = await fs.stat(value);
|
|
384
|
+
return stat.isDirectory();
|
|
385
|
+
} catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function directoryChildren(parent = "", prefix = "", limit = 20) {
|
|
391
|
+
const entries = [];
|
|
392
|
+
try {
|
|
393
|
+
const dirents = await fs.readdir(parent, { withFileTypes: true });
|
|
394
|
+
const normalizedPrefix = prefix.toLowerCase();
|
|
395
|
+
for (const entry of dirents) {
|
|
396
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
397
|
+
if (normalizedPrefix && !entry.name.toLowerCase().startsWith(normalizedPrefix)) continue;
|
|
398
|
+
entries.push(path.join(parent, entry.name));
|
|
399
|
+
if (entries.length >= limit) break;
|
|
400
|
+
}
|
|
401
|
+
} catch {
|
|
402
|
+
return [];
|
|
403
|
+
}
|
|
404
|
+
return entries.sort((left, right) => left.localeCompare(right));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function pathSuggestions(query = "") {
|
|
408
|
+
const raw = String(query || "").trim();
|
|
409
|
+
const home = os.homedir();
|
|
410
|
+
const projectsLfs = path.join(home, "ProjectsLFS");
|
|
411
|
+
const baseCandidates = uniquePaths([
|
|
412
|
+
baseDir,
|
|
413
|
+
path.dirname(baseDir),
|
|
414
|
+
process.cwd(),
|
|
415
|
+
home,
|
|
416
|
+
projectsLfs,
|
|
417
|
+
path.join(home, "Documents"),
|
|
418
|
+
path.join(home, "Desktop"),
|
|
419
|
+
]);
|
|
420
|
+
const suggestions = [];
|
|
421
|
+
for (const candidate of baseCandidates) {
|
|
422
|
+
if (await readableDirectory(candidate)) suggestions.push(candidate);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (raw) {
|
|
426
|
+
const expanded = expandUserPath(raw);
|
|
427
|
+
const absolute = path.isAbsolute(expanded) ? expanded : path.resolve(baseDir, expanded);
|
|
428
|
+
const parent = raw.endsWith("/") || raw.endsWith(path.sep) ? absolute : path.dirname(absolute);
|
|
429
|
+
const prefix = raw.endsWith("/") || raw.endsWith(path.sep) ? "" : path.basename(absolute);
|
|
430
|
+
suggestions.push(...(await directoryChildren(parent, prefix, 16)));
|
|
431
|
+
for (const root of baseCandidates.slice(0, 5)) {
|
|
432
|
+
suggestions.push(...(await directoryChildren(root, path.basename(expanded), 4)));
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
for (const root of baseCandidates.slice(0, 4)) {
|
|
436
|
+
suggestions.push(...(await directoryChildren(root, "", 4)));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return uniquePaths(suggestions).slice(0, 20);
|
|
441
|
+
}
|
|
442
|
+
|
|
360
443
|
function buildRunConfig(body, overrides = {}) {
|
|
361
444
|
const preferences = normalizePreferencePayload(body, db.getPreferences());
|
|
362
445
|
const merged = {
|
|
@@ -841,6 +924,11 @@ app.get("/api/config", async (_req, res) => {
|
|
|
841
924
|
});
|
|
842
925
|
});
|
|
843
926
|
|
|
927
|
+
app.get("/api/path-suggestions", async (req, res) => {
|
|
928
|
+
const suggestions = await pathSuggestions(String(req.query.q || ""));
|
|
929
|
+
res.json({ ok: true, suggestions });
|
|
930
|
+
});
|
|
931
|
+
|
|
844
932
|
app.get("/api/keys/status", (_req, res) => {
|
|
845
933
|
res.json({ ok: true, keyStatus: publicKeyStatus(baseDir) });
|
|
846
934
|
});
|