@lazyingart/agintiflow 0.1.0 → 0.2.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/AGENTS.md +6 -3
- package/README.md +68 -15
- package/docker/sandbox.Dockerfile +8 -0
- package/package.json +11 -3
- package/public/app.js +644 -11
- package/public/index.html +165 -20
- package/public/styles.css +586 -13
- package/scripts/setup-agent-toolchain-docker.sh +51 -0
- package/scripts/smoke-coding-tools.js +138 -0
- package/scripts/smoke-toolchain-docker.js +185 -0
- package/scripts/smoke-web-api.js +217 -0
- package/src/agent-runner.js +286 -21
- package/src/artifact-tunnel.js +401 -0
- package/src/cli.js +47 -3
- package/src/command-policy.js +21 -0
- package/src/config.js +9 -2
- package/src/docker-sandbox.js +38 -8
- package/src/guardrails.js +31 -0
- package/src/model-client.js +300 -6
- package/src/model-routing.js +40 -0
- package/src/session-store.js +4 -0
- package/src/tool-wrappers.js +6 -0
- package/src/web-db.js +48 -5
- package/src/workspace-tools.js +438 -0
- package/web.js +265 -14
package/public/app.js
CHANGED
|
@@ -20,15 +20,18 @@ const translations = {
|
|
|
20
20
|
intro:
|
|
21
21
|
"Web-first agent platform with smart model routing, resumable runs, guarded tools, and optional external agent wrappers.",
|
|
22
22
|
routingModeLabel: "Routing policy",
|
|
23
|
-
routingSmartOption: "Smart:
|
|
23
|
+
routingSmartOption: "Smart: flash/pro/wrappers",
|
|
24
24
|
routingFastOption: "DeepSeek v4 flash",
|
|
25
25
|
routingComplexOption: "DeepSeek v4 pro",
|
|
26
26
|
routingManualOption: "Manual provider/model",
|
|
27
|
-
routingHintSmart:
|
|
27
|
+
routingHintSmart:
|
|
28
|
+
"Smart routing uses DeepSeek v4 flash for short work, DeepSeek v4 pro for complex tasks, and the selected wrapper only when wrapper tools are enabled.",
|
|
28
29
|
routingHintFast: "Fast route: DeepSeek v4 flash for normal browser, shell, and short coding tasks.",
|
|
29
30
|
routingHintComplex: "Complex route: DeepSeek v4 pro for deeper implementation, debugging, and design work.",
|
|
30
31
|
routingHintManual: "Manual route uses the provider and model fields exactly as entered.",
|
|
32
|
+
modelRouteStatus: "Active route",
|
|
31
33
|
providerLabel: "Provider",
|
|
34
|
+
mockProviderOption: "Mock local",
|
|
32
35
|
modelLabel: "Model",
|
|
33
36
|
goalLabel: "Goal",
|
|
34
37
|
goalPlaceholder: "Open a site and summarize it, or use run_command for simple terminal inspection.",
|
|
@@ -46,9 +49,15 @@ const translations = {
|
|
|
46
49
|
packageBlockOption: "Blocked",
|
|
47
50
|
packagePromptOption: "Require approval",
|
|
48
51
|
packageAllowOption: "Approved in Docker",
|
|
52
|
+
packageWarningBlock: "Package, conda, venv, and npm setup commands are blocked.",
|
|
53
|
+
packageWarningPrompt: "Package or environment setup will stop for approval before it can run.",
|
|
54
|
+
packageWarningAllow: "Package setup is approved only in Docker workspace-write mode.",
|
|
55
|
+
packageWarningHostAllow: "Host mode cannot run approved package setup; switch to Docker workspace-write.",
|
|
49
56
|
headlessLabel: "Headless browser",
|
|
50
57
|
shellToolLabel: "Enable shell tool",
|
|
58
|
+
fileToolLabel: "Enable file tools",
|
|
51
59
|
wrapperToolLabel: "Enable agent wrappers",
|
|
60
|
+
preferredWrapperLabel: "Preferred wrapper",
|
|
52
61
|
dockerSandboxLabel: "Use Docker sandbox",
|
|
53
62
|
allowPasswordsLabel: "Allow password typing",
|
|
54
63
|
allowDestructiveLabel: "Allow destructive actions",
|
|
@@ -67,6 +76,15 @@ const translations = {
|
|
|
67
76
|
youLabel: "You",
|
|
68
77
|
keysLabel: "Keys",
|
|
69
78
|
wrappersLabel: "Wrappers",
|
|
79
|
+
selectedWrapperLabel: "Selected",
|
|
80
|
+
wrappersOffLabel: "wrapper tools off",
|
|
81
|
+
wrapperCapabilityTitle: "Agent wrappers",
|
|
82
|
+
workspaceCapabilityTitle: "Workspace files",
|
|
83
|
+
workspaceToolsLabel: "File tools",
|
|
84
|
+
workspaceChangesEmpty: "No file changes yet.",
|
|
85
|
+
changedLabel: "changed",
|
|
86
|
+
blockedLabel: "blocked",
|
|
87
|
+
mockLabel: "Mock",
|
|
70
88
|
sandboxStatusTitle: "Sandbox status",
|
|
71
89
|
sandboxStatusEmpty: "Not checked yet.",
|
|
72
90
|
sandboxLogsEmpty: "No sandbox logs yet.",
|
|
@@ -85,6 +103,35 @@ const translations = {
|
|
|
85
103
|
sendingStatus: "Sending...",
|
|
86
104
|
runningStatus: "Running...",
|
|
87
105
|
failedContinue: "Failed to continue the conversation.",
|
|
106
|
+
conversationMenuLabel: "Manage conversations",
|
|
107
|
+
manageConversationsTitle: "Manage conversations",
|
|
108
|
+
manageConversationsHelp: "Rename, auto-rename, or delete saved chat history.",
|
|
109
|
+
conversationTitleLabel: "Conversation title",
|
|
110
|
+
conversationTitlePlaceholder: "Short title for this conversation",
|
|
111
|
+
autoRenameButton: "Auto rename",
|
|
112
|
+
renameButton: "Rename",
|
|
113
|
+
deleteButton: "Delete",
|
|
114
|
+
cancelButton: "Cancel",
|
|
115
|
+
noSessionsToManage: "No saved conversations yet.",
|
|
116
|
+
titleRequired: "Title is required.",
|
|
117
|
+
sessionRenamed: "Conversation renamed.",
|
|
118
|
+
sessionDeleted: "Conversation deleted.",
|
|
119
|
+
deleteConfirm: "Delete this conversation and its local session history?",
|
|
120
|
+
manageSessionFailed: "Failed to update conversation.",
|
|
121
|
+
artifactTunnelLabel: "Open canvas and artifacts",
|
|
122
|
+
artifactTunnelTitle: "Canvas & artifacts",
|
|
123
|
+
artifactTunnelHelp: "Agent-selected canvas items, generated files, screenshots, diffs, and final answers.",
|
|
124
|
+
artifactCanvasTab: "Canvas",
|
|
125
|
+
artifactExplorerTab: "Explorer",
|
|
126
|
+
artifactNotificationsTab: "Notifications",
|
|
127
|
+
artifactListTitle: "Artifacts",
|
|
128
|
+
artifactMarkSeenButton: "Mark seen",
|
|
129
|
+
artifactEmpty: "No artifacts yet.",
|
|
130
|
+
artifactViewerEmptyTitle: "Nothing selected",
|
|
131
|
+
artifactViewerEmpty: "Select an artifact to preview it here.",
|
|
132
|
+
artifactLoading: "Loading artifact...",
|
|
133
|
+
artifactLoadFailed: "Failed to load artifact.",
|
|
134
|
+
artifactSeenUpdated: "Notifications marked as seen.",
|
|
88
135
|
},
|
|
89
136
|
ar: {
|
|
90
137
|
documentTitle: "AgInTiFlow",
|
|
@@ -564,12 +611,20 @@ const routingModeField = document.querySelector("#routingMode");
|
|
|
564
611
|
const providerField = document.querySelector("#provider");
|
|
565
612
|
const modelField = document.querySelector("#model");
|
|
566
613
|
const routingHintEl = document.querySelector("#routing-hint");
|
|
614
|
+
const modelRouteStatusEl = document.querySelector("#model-route-status");
|
|
567
615
|
const sandboxModeField = document.querySelector("#sandboxMode");
|
|
568
616
|
const packageInstallPolicyField = document.querySelector("#packageInstallPolicy");
|
|
617
|
+
const packageWarningEl = document.querySelector("#package-warning");
|
|
569
618
|
const logsEl = document.querySelector("#logs");
|
|
570
619
|
const runMetaEl = document.querySelector("#run-meta");
|
|
571
620
|
const keyStatusEl = document.querySelector("#key-status");
|
|
621
|
+
const allowWrapperToolsField = document.querySelector("#allowWrapperTools");
|
|
622
|
+
const preferredWrapperField = document.querySelector("#preferredWrapper");
|
|
572
623
|
const wrapperStatusEl = document.querySelector("#wrapper-status");
|
|
624
|
+
const wrapperGridEl = document.querySelector("#wrapper-grid");
|
|
625
|
+
const workspaceStatusEl = document.querySelector("#workspace-status");
|
|
626
|
+
const workspaceToolsEl = document.querySelector("#workspace-tools");
|
|
627
|
+
const workspaceChangesEl = document.querySelector("#workspace-changes");
|
|
573
628
|
const sandboxStatusEl = document.querySelector("#sandbox-status");
|
|
574
629
|
const sandboxLogsEl = document.querySelector("#sandbox-logs");
|
|
575
630
|
const checkSandboxButton = document.querySelector("#check-sandbox");
|
|
@@ -579,12 +634,36 @@ const chatThreadEl = document.querySelector("#chat-thread");
|
|
|
579
634
|
const chatFormEl = document.querySelector("#chat-form");
|
|
580
635
|
const chatInputEl = document.querySelector("#chat-input");
|
|
581
636
|
const chatStatusEl = document.querySelector("#chat-status");
|
|
637
|
+
const manageSessionsButton = document.querySelector("#manage-sessions");
|
|
638
|
+
const openArtifactsButton = document.querySelector("#open-artifacts");
|
|
639
|
+
const artifactBadgeEl = document.querySelector("#artifact-badge");
|
|
640
|
+
const sessionManagerDialog = document.querySelector("#session-manager");
|
|
641
|
+
const closeSessionManagerButton = document.querySelector("#close-session-manager");
|
|
642
|
+
const cancelSessionManagerButton = document.querySelector("#cancel-session-manager");
|
|
643
|
+
const sessionManagerListEl = document.querySelector("#session-manager-list");
|
|
644
|
+
const sessionTitleInputEl = document.querySelector("#session-title-input");
|
|
645
|
+
const sessionManagerStatusEl = document.querySelector("#session-manager-status");
|
|
646
|
+
const autoRenameSessionButton = document.querySelector("#auto-rename-session");
|
|
647
|
+
const renameSessionButton = document.querySelector("#rename-session");
|
|
648
|
+
const deleteSessionButton = document.querySelector("#delete-session");
|
|
649
|
+
const artifactTunnelDialog = document.querySelector("#artifact-tunnel");
|
|
650
|
+
const closeArtifactTunnelButton = document.querySelector("#close-artifact-tunnel");
|
|
651
|
+
const artifactTabsEl = document.querySelector(".artifact-tabs");
|
|
652
|
+
const artifactListEl = document.querySelector("#artifact-list");
|
|
653
|
+
const artifactViewerTitleEl = document.querySelector("#artifact-viewer-title");
|
|
654
|
+
const artifactViewerMetaEl = document.querySelector("#artifact-viewer-meta");
|
|
655
|
+
const artifactViewerKindEl = document.querySelector("#artifact-viewer-kind");
|
|
656
|
+
const artifactViewerBodyEl = document.querySelector("#artifact-viewer-body");
|
|
657
|
+
const artifactStatusEl = document.querySelector("#artifact-status");
|
|
658
|
+
const markArtifactsSeenButton = document.querySelector("#mark-artifacts-seen");
|
|
582
659
|
const translatableNodes = [...document.querySelectorAll("[data-i18n]")];
|
|
583
660
|
const placeholderNodes = [...document.querySelectorAll("[data-i18n-placeholder]")];
|
|
661
|
+
const ariaLabelNodes = [...document.querySelectorAll("[data-i18n-aria-label]")];
|
|
584
662
|
|
|
585
663
|
const defaults = {
|
|
586
664
|
openai: "gpt-5.4-mini",
|
|
587
665
|
deepseek: "deepseek-v4-flash",
|
|
666
|
+
mock: "mock-agent",
|
|
588
667
|
};
|
|
589
668
|
|
|
590
669
|
let currentLanguage = "en";
|
|
@@ -597,6 +676,13 @@ let lastSessions = [];
|
|
|
597
676
|
let lastKeyStatus = null;
|
|
598
677
|
let lastWrappers = [];
|
|
599
678
|
let lastSandbox = null;
|
|
679
|
+
let lastWorkspace = null;
|
|
680
|
+
let lastWorkspaceActivity = [];
|
|
681
|
+
let managedSessionId = "";
|
|
682
|
+
let artifactItems = [];
|
|
683
|
+
let selectedArtifactId = "";
|
|
684
|
+
let currentArtifactTab = "canvas";
|
|
685
|
+
let artifactUnreadCount = 0;
|
|
600
686
|
|
|
601
687
|
function normalizeLanguage(language) {
|
|
602
688
|
const lower = String(language || "").toLowerCase();
|
|
@@ -619,18 +705,94 @@ function renderKeyStatus(status = lastKeyStatus) {
|
|
|
619
705
|
if (!status) return;
|
|
620
706
|
keyStatusEl.textContent = `${t("keysLabel")}: OpenAI ${
|
|
621
707
|
status.openai ? t("availableLabel") : t("missingLabel")
|
|
622
|
-
} · DeepSeek ${status.deepseek ? t("availableLabel") : t("missingLabel")}
|
|
708
|
+
} · DeepSeek ${status.deepseek ? t("availableLabel") : t("missingLabel")} · ${t("mockLabel")} ${
|
|
709
|
+
status.mock ? t("availableLabel") : t("missingLabel")
|
|
710
|
+
}`;
|
|
623
711
|
}
|
|
624
712
|
|
|
625
713
|
function renderWrapperStatus(wrappers = lastWrappers) {
|
|
626
714
|
lastWrappers = wrappers || [];
|
|
627
715
|
if (lastWrappers.length === 0) {
|
|
628
716
|
wrapperStatusEl.textContent = "";
|
|
717
|
+
wrapperGridEl.innerHTML = "";
|
|
629
718
|
return;
|
|
630
719
|
}
|
|
631
|
-
|
|
720
|
+
const selectedWrapper = preferredWrapperField?.value || "codex";
|
|
721
|
+
const selected = lastWrappers.find((wrapper) => wrapper.name === selectedWrapper);
|
|
722
|
+
const selectedLabel = selected?.label || selectedWrapper;
|
|
723
|
+
const enabledPrefix = allowWrapperToolsField?.checked ? t("selectedWrapperLabel") : t("wrappersOffLabel");
|
|
724
|
+
wrapperStatusEl.textContent = `${enabledPrefix}: ${selectedLabel} · ${t("wrappersLabel")}: ${lastWrappers
|
|
632
725
|
.map((wrapper) => `${wrapper.label || wrapper.name} ${wrapper.available ? t("availableLabel") : t("missingLabel")}`)
|
|
633
726
|
.join(" · ")}`;
|
|
727
|
+
wrapperGridEl.innerHTML = lastWrappers
|
|
728
|
+
.map(
|
|
729
|
+
(wrapper) => `
|
|
730
|
+
<div class="capability-chip" data-ready="${Boolean(wrapper.available)}" data-selected="${wrapper.name === selectedWrapper}">
|
|
731
|
+
<strong>${escapeHtml(wrapper.label || wrapper.name)}</strong>
|
|
732
|
+
<span>${wrapper.name === selectedWrapper ? `${t("selectedWrapperLabel")} · ` : ""}${wrapper.available ? t("availableLabel") : t("missingLabel")}</span>
|
|
733
|
+
</div>
|
|
734
|
+
`
|
|
735
|
+
)
|
|
736
|
+
.join("");
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function renderWorkspacePanel(workspace = lastWorkspace, activity = lastWorkspaceActivity) {
|
|
740
|
+
lastWorkspace = workspace;
|
|
741
|
+
lastWorkspaceActivity = activity || [];
|
|
742
|
+
|
|
743
|
+
if (!workspace) {
|
|
744
|
+
workspaceStatusEl.textContent = "";
|
|
745
|
+
workspaceToolsEl.innerHTML = "";
|
|
746
|
+
workspaceChangesEl.innerHTML = `<p class="subtle">${t("workspaceChangesEmpty")}</p>`;
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
workspaceStatusEl.textContent = `${workspace.enabled ? t("availableLabel") : t("missingLabel")} · ${
|
|
751
|
+
workspace.workspace || ""
|
|
752
|
+
}`;
|
|
753
|
+
workspaceToolsEl.innerHTML = (workspace.tools || [])
|
|
754
|
+
.map(
|
|
755
|
+
(tool) => `
|
|
756
|
+
<div class="capability-chip" data-ready="${Boolean(workspace.enabled)}">
|
|
757
|
+
<strong>${escapeHtml(tool)}</strong>
|
|
758
|
+
<span>${(workspace.writeTools || []).includes(tool) ? "write guarded" : "read guarded"}</span>
|
|
759
|
+
</div>
|
|
760
|
+
`
|
|
761
|
+
)
|
|
762
|
+
.join("");
|
|
763
|
+
|
|
764
|
+
if (!lastWorkspaceActivity.length) {
|
|
765
|
+
workspaceChangesEl.innerHTML = `<p class="subtle">${t("workspaceChangesEmpty")}</p>`;
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
workspaceChangesEl.innerHTML = lastWorkspaceActivity
|
|
770
|
+
.slice(0, 6)
|
|
771
|
+
.map((item) => {
|
|
772
|
+
const blocked = item.kind === "blocked";
|
|
773
|
+
const label = blocked ? t("blockedLabel") : t("changedLabel");
|
|
774
|
+
const path = escapeHtml(item.path || "");
|
|
775
|
+
const reason = blocked ? `<div class="subtle">${escapeHtml(item.reason || "")}</div>` : "";
|
|
776
|
+
const diff = item.diff ? `<pre class="change-diff">${escapeHtml(item.diff).slice(0, 1200)}</pre>` : "";
|
|
777
|
+
const hashes =
|
|
778
|
+
item.beforeHash || item.afterHash
|
|
779
|
+
? `<div class="change-meta"><span>before=${escapeHtml((item.beforeHash || "new").slice(0, 10))}</span><span>after=${escapeHtml((item.afterHash || "").slice(0, 10))}</span></div>`
|
|
780
|
+
: "";
|
|
781
|
+
return `
|
|
782
|
+
<article class="change-item ${blocked ? "blocked" : ""}">
|
|
783
|
+
<div class="change-meta">
|
|
784
|
+
<span>${label}</span>
|
|
785
|
+
<span>${escapeHtml(item.toolName || "")}</span>
|
|
786
|
+
<span>${item.at ? new Date(item.at).toLocaleString() : ""}</span>
|
|
787
|
+
</div>
|
|
788
|
+
<strong class="change-path">${path}</strong>
|
|
789
|
+
${reason}
|
|
790
|
+
${hashes}
|
|
791
|
+
${diff}
|
|
792
|
+
</article>
|
|
793
|
+
`;
|
|
794
|
+
})
|
|
795
|
+
.join("");
|
|
634
796
|
}
|
|
635
797
|
|
|
636
798
|
function renderSandboxStatus(status = lastSandbox) {
|
|
@@ -685,6 +847,24 @@ function updateRoutingHint() {
|
|
|
685
847
|
modelField.value = preset.model || modelField.value;
|
|
686
848
|
}
|
|
687
849
|
}
|
|
850
|
+
|
|
851
|
+
modelRouteStatusEl.textContent = `${t("modelRouteStatus")}: ${providerField.value} / ${
|
|
852
|
+
modelField.value.trim() || defaults[providerField.value] || ""
|
|
853
|
+
}`;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function updatePackageWarning() {
|
|
857
|
+
const policy = packageInstallPolicyField.value || "prompt";
|
|
858
|
+
const sandboxMode = sandboxModeField.value || "host";
|
|
859
|
+
const key =
|
|
860
|
+
policy === "allow" && sandboxMode === "host"
|
|
861
|
+
? "packageWarningHostAllow"
|
|
862
|
+
: policy === "allow"
|
|
863
|
+
? "packageWarningAllow"
|
|
864
|
+
: policy === "block"
|
|
865
|
+
? "packageWarningBlock"
|
|
866
|
+
: "packageWarningPrompt";
|
|
867
|
+
packageWarningEl.textContent = t(key);
|
|
688
868
|
}
|
|
689
869
|
|
|
690
870
|
function applyLanguage(language, { persist = true } = {}) {
|
|
@@ -703,12 +883,20 @@ function applyLanguage(language, { persist = true } = {}) {
|
|
|
703
883
|
node.placeholder = t(node.dataset.i18nPlaceholder);
|
|
704
884
|
}
|
|
705
885
|
|
|
886
|
+
for (const node of ariaLabelNodes) {
|
|
887
|
+
node.setAttribute("aria-label", t(node.dataset.i18nAriaLabel));
|
|
888
|
+
}
|
|
889
|
+
|
|
706
890
|
renderKeyStatus();
|
|
707
891
|
renderWrapperStatus();
|
|
892
|
+
renderWorkspacePanel();
|
|
708
893
|
renderSandboxStatus();
|
|
709
894
|
updateRoutingHint();
|
|
895
|
+
updatePackageWarning();
|
|
710
896
|
renderSessions(lastSessions);
|
|
897
|
+
renderSessionManager();
|
|
711
898
|
renderChat(lastChatEntries);
|
|
899
|
+
renderArtifactShell();
|
|
712
900
|
if (persist) schedulePreferenceSave();
|
|
713
901
|
}
|
|
714
902
|
|
|
@@ -726,7 +914,9 @@ function formPayload() {
|
|
|
726
914
|
packageInstallPolicy: packageInstallPolicyField.value,
|
|
727
915
|
headless: document.querySelector("#headless").checked,
|
|
728
916
|
allowShellTool: document.querySelector("#allowShellTool").checked,
|
|
729
|
-
|
|
917
|
+
allowFileTools: document.querySelector("#allowFileTools").checked,
|
|
918
|
+
allowWrapperTools: allowWrapperToolsField.checked,
|
|
919
|
+
preferredWrapper: preferredWrapperField.value,
|
|
730
920
|
useDockerSandbox: sandboxModeField.value !== "host",
|
|
731
921
|
dockerSandboxImage: document.querySelector("#dockerSandboxImage").value.trim(),
|
|
732
922
|
allowPasswords: document.querySelector("#allowPasswords").checked,
|
|
@@ -753,7 +943,10 @@ function renderLogs(run) {
|
|
|
753
943
|
}
|
|
754
944
|
|
|
755
945
|
function escapeHtml(value) {
|
|
756
|
-
return String(value || "").replace(
|
|
946
|
+
return String(value || "").replace(
|
|
947
|
+
/[&<>"']/g,
|
|
948
|
+
(ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ch]
|
|
949
|
+
);
|
|
757
950
|
}
|
|
758
951
|
|
|
759
952
|
function renderChat(chatEntries) {
|
|
@@ -781,14 +974,218 @@ function renderChat(chatEntries) {
|
|
|
781
974
|
chatThreadEl.scrollTop = chatThreadEl.scrollHeight;
|
|
782
975
|
}
|
|
783
976
|
|
|
977
|
+
function artifactReadKey(sessionId = currentSessionId) {
|
|
978
|
+
return `agintiflow.artifacts.readIds.${sessionId || "none"}`;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function getArtifactReadIds(sessionId = currentSessionId) {
|
|
982
|
+
try {
|
|
983
|
+
const raw = localStorage.getItem(artifactReadKey(sessionId)) || "[]";
|
|
984
|
+
const parsed = JSON.parse(raw);
|
|
985
|
+
return new Set(Array.isArray(parsed) ? parsed : []);
|
|
986
|
+
} catch {
|
|
987
|
+
return new Set();
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function saveArtifactReadIds(readIds, sessionId = currentSessionId) {
|
|
992
|
+
if (!sessionId) return;
|
|
993
|
+
try {
|
|
994
|
+
localStorage.setItem(artifactReadKey(sessionId), JSON.stringify([...readIds].slice(-800)));
|
|
995
|
+
} catch {
|
|
996
|
+
// Local storage is optional for notification state.
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function computeUnreadArtifactCount() {
|
|
1001
|
+
const readIds = getArtifactReadIds();
|
|
1002
|
+
return artifactItems.filter((item) => !readIds.has(item.id)).length;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function markArtifactRead(artifactId) {
|
|
1006
|
+
if (!artifactId) return;
|
|
1007
|
+
const readIds = getArtifactReadIds();
|
|
1008
|
+
readIds.add(artifactId);
|
|
1009
|
+
saveArtifactReadIds(readIds);
|
|
1010
|
+
artifactUnreadCount = computeUnreadArtifactCount();
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function markAllArtifactsRead() {
|
|
1014
|
+
const readIds = getArtifactReadIds();
|
|
1015
|
+
for (const item of artifactItems) readIds.add(item.id);
|
|
1016
|
+
saveArtifactReadIds(readIds);
|
|
1017
|
+
artifactUnreadCount = 0;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function renderArtifactBadge(count = computeUnreadArtifactCount()) {
|
|
1021
|
+
artifactUnreadCount = Number(count) || 0;
|
|
1022
|
+
if (!artifactBadgeEl) return;
|
|
1023
|
+
artifactBadgeEl.textContent = artifactUnreadCount > 99 ? "99+" : String(artifactUnreadCount);
|
|
1024
|
+
artifactBadgeEl.hidden = artifactUnreadCount <= 0;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function artifactLabel(item) {
|
|
1028
|
+
const source = item.source ? item.source.replace(/-/g, " ") : "artifact";
|
|
1029
|
+
const when = item.createdAt ? new Date(item.createdAt).toLocaleString() : "";
|
|
1030
|
+
return [item.kind, source, when].filter(Boolean).join(" · ");
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function renderArtifactList() {
|
|
1034
|
+
if (!artifactListEl) return;
|
|
1035
|
+
const visible = artifactItems.filter((item) => item.tab === currentArtifactTab);
|
|
1036
|
+
const readIds = getArtifactReadIds();
|
|
1037
|
+
|
|
1038
|
+
document.querySelectorAll(".artifact-tab").forEach((tab) => {
|
|
1039
|
+
tab.dataset.selected = String(tab.dataset.artifactTab === currentArtifactTab);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
if (!currentSessionId) {
|
|
1043
|
+
artifactListEl.innerHTML = `<p class="subtle">${t("selectSessionFirst")}</p>`;
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (visible.length === 0) {
|
|
1048
|
+
artifactListEl.innerHTML = `<p class="subtle">${t("artifactEmpty")}</p>`;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
artifactListEl.innerHTML = visible
|
|
1053
|
+
.map(
|
|
1054
|
+
(item) => {
|
|
1055
|
+
const read = readIds.has(item.id);
|
|
1056
|
+
return `
|
|
1057
|
+
<button class="artifact-row" type="button" data-artifact-id="${escapeHtml(item.id)}" data-selected="${item.id === selectedArtifactId}" data-read="${read}">
|
|
1058
|
+
<span class="artifact-row-top">
|
|
1059
|
+
<strong>${escapeHtml(item.title || item.path || item.kind)}</strong>
|
|
1060
|
+
<span>${read ? escapeHtml(item.kind) : `New · ${escapeHtml(item.kind)}`}</span>
|
|
1061
|
+
</span>
|
|
1062
|
+
<span class="artifact-row-meta">${escapeHtml(artifactLabel(item))}</span>
|
|
1063
|
+
<span class="artifact-row-preview">${escapeHtml(item.preview || item.path || "")}</span>
|
|
1064
|
+
</button>
|
|
1065
|
+
`;
|
|
1066
|
+
}
|
|
1067
|
+
)
|
|
1068
|
+
.join("");
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function resetArtifactViewer() {
|
|
1072
|
+
artifactViewerTitleEl.textContent = t("artifactViewerEmptyTitle");
|
|
1073
|
+
artifactViewerMetaEl.textContent = "";
|
|
1074
|
+
artifactViewerKindEl.textContent = "";
|
|
1075
|
+
artifactViewerBodyEl.innerHTML = `<p class="subtle">${t("artifactViewerEmpty")}</p>`;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function renderArtifactShell() {
|
|
1079
|
+
renderArtifactBadge();
|
|
1080
|
+
renderArtifactList();
|
|
1081
|
+
if (!selectedArtifactId || !artifactItems.some((item) => item.id === selectedArtifactId)) {
|
|
1082
|
+
resetArtifactViewer();
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function renderArtifactContent(content) {
|
|
1087
|
+
const item = artifactItems.find((candidate) => candidate.id === content.id);
|
|
1088
|
+
artifactViewerTitleEl.textContent = content.title || item?.title || t("artifactViewerEmptyTitle");
|
|
1089
|
+
artifactViewerMetaEl.textContent = [content.path || item?.path, item ? artifactLabel(item) : ""].filter(Boolean).join(" · ");
|
|
1090
|
+
artifactViewerKindEl.textContent = content.kind || item?.kind || "";
|
|
1091
|
+
|
|
1092
|
+
if (content.dataUrl && (content.kind === "pdf" || content.mime === "application/pdf")) {
|
|
1093
|
+
artifactViewerBodyEl.innerHTML = `
|
|
1094
|
+
<iframe class="artifact-pdf-frame" src="${content.dataUrl}" title="${escapeHtml(content.title || "Artifact PDF")}"></iframe>
|
|
1095
|
+
`;
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
if (content.dataUrl) {
|
|
1100
|
+
artifactViewerBodyEl.innerHTML = `
|
|
1101
|
+
<figure class="artifact-image-frame">
|
|
1102
|
+
<img class="artifact-preview-image" src="${content.dataUrl}" alt="${escapeHtml(content.title || "Artifact image")}" />
|
|
1103
|
+
</figure>
|
|
1104
|
+
`;
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const text = typeof content.text === "string" ? content.text : "";
|
|
1109
|
+
artifactViewerBodyEl.innerHTML = `
|
|
1110
|
+
<textarea class="artifact-editor" readonly spellcheck="false">${escapeHtml(text)}</textarea>
|
|
1111
|
+
`;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
async function refreshArtifacts({ loadSelected = false } = {}) {
|
|
1115
|
+
if (!currentSessionId) {
|
|
1116
|
+
artifactItems = [];
|
|
1117
|
+
selectedArtifactId = "";
|
|
1118
|
+
artifactUnreadCount = 0;
|
|
1119
|
+
renderArtifactShell();
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const response = await fetch(`/api/sessions/${encodeURIComponent(currentSessionId)}/artifacts`);
|
|
1124
|
+
if (!response.ok) return;
|
|
1125
|
+
const data = await response.json();
|
|
1126
|
+
artifactItems = data.items || [];
|
|
1127
|
+
artifactUnreadCount = computeUnreadArtifactCount();
|
|
1128
|
+
if (!selectedArtifactId || !artifactItems.some((item) => item.id === selectedArtifactId)) {
|
|
1129
|
+
selectedArtifactId = data.selectedItemId || artifactItems[0]?.id || "";
|
|
1130
|
+
}
|
|
1131
|
+
renderArtifactShell();
|
|
1132
|
+
if (loadSelected && selectedArtifactId) await selectArtifact(selectedArtifactId, { persist: false });
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
async function selectArtifact(artifactId, { persist = true } = {}) {
|
|
1136
|
+
if (!currentSessionId || !artifactId) return;
|
|
1137
|
+
selectedArtifactId = artifactId;
|
|
1138
|
+
renderArtifactList();
|
|
1139
|
+
artifactStatusEl.textContent = t("artifactLoading");
|
|
1140
|
+
|
|
1141
|
+
if (persist) {
|
|
1142
|
+
await fetch(`/api/sessions/${encodeURIComponent(currentSessionId)}/artifacts/select`, {
|
|
1143
|
+
method: "POST",
|
|
1144
|
+
headers: { "Content-Type": "application/json" },
|
|
1145
|
+
body: JSON.stringify({ artifactId }),
|
|
1146
|
+
}).catch(() => {});
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const response = await fetch(
|
|
1150
|
+
`/api/sessions/${encodeURIComponent(currentSessionId)}/artifacts/${encodeURIComponent(artifactId)}`
|
|
1151
|
+
);
|
|
1152
|
+
const data = await response.json().catch(() => ({}));
|
|
1153
|
+
if (!response.ok) {
|
|
1154
|
+
artifactStatusEl.textContent = data.error || t("artifactLoadFailed");
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
artifactStatusEl.textContent = "";
|
|
1159
|
+
markArtifactRead(artifactId);
|
|
1160
|
+
renderArtifactBadge();
|
|
1161
|
+
renderArtifactList();
|
|
1162
|
+
renderArtifactContent(data);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
async function openArtifactTunnel() {
|
|
1166
|
+
if (!currentSessionId) {
|
|
1167
|
+
chatStatusEl.textContent = t("selectSessionFirst");
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
artifactStatusEl.textContent = "";
|
|
1171
|
+
artifactTunnelDialog.showModal();
|
|
1172
|
+
await refreshArtifacts({ loadSelected: true });
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function closeArtifactTunnel() {
|
|
1176
|
+
artifactTunnelDialog.close();
|
|
1177
|
+
artifactStatusEl.textContent = "";
|
|
1178
|
+
}
|
|
1179
|
+
|
|
784
1180
|
function renderSessions(sessions) {
|
|
785
1181
|
lastSessions = sessions || [];
|
|
786
1182
|
const current = sessionSelectEl.value;
|
|
787
1183
|
const options = [`<option value="">${t("selectRecentSession")}</option>`]
|
|
788
1184
|
.concat(
|
|
789
1185
|
lastSessions.map((session) => {
|
|
790
|
-
const
|
|
791
|
-
const
|
|
1186
|
+
const rawTitle = session.title || session.goal || session.sessionId;
|
|
1187
|
+
const title = escapeHtml(`${rawTitle.slice(0, 60)}${rawTitle.length > 60 ? "..." : ""}`);
|
|
1188
|
+
const label = `${escapeHtml(session.provider)} · ${title}`;
|
|
792
1189
|
return `<option value="${escapeHtml(session.sessionId)}">${label}</option>`;
|
|
793
1190
|
})
|
|
794
1191
|
)
|
|
@@ -800,10 +1197,58 @@ function renderSessions(sessions) {
|
|
|
800
1197
|
}
|
|
801
1198
|
}
|
|
802
1199
|
|
|
1200
|
+
function currentManagedSession() {
|
|
1201
|
+
return lastSessions.find((session) => session.sessionId === managedSessionId) || lastSessions[0] || null;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function renderSessionManager(message = "") {
|
|
1205
|
+
if (!sessionManagerListEl) return;
|
|
1206
|
+
if (!managedSessionId && currentSessionId) managedSessionId = currentSessionId;
|
|
1207
|
+
if (!lastSessions.some((session) => session.sessionId === managedSessionId)) {
|
|
1208
|
+
managedSessionId = lastSessions[0]?.sessionId || "";
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const selected = currentManagedSession();
|
|
1212
|
+
sessionTitleInputEl.value = selected ? selected.title || selected.goal || "" : "";
|
|
1213
|
+
sessionTitleInputEl.disabled = !selected;
|
|
1214
|
+
autoRenameSessionButton.disabled = !selected;
|
|
1215
|
+
renameSessionButton.disabled = !selected;
|
|
1216
|
+
deleteSessionButton.disabled = !selected;
|
|
1217
|
+
sessionManagerStatusEl.textContent = message;
|
|
1218
|
+
|
|
1219
|
+
if (!lastSessions.length) {
|
|
1220
|
+
sessionManagerListEl.innerHTML = `<p class="subtle">${t("noSessionsToManage")}</p>`;
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
sessionManagerListEl.innerHTML = lastSessions
|
|
1225
|
+
.map((session) => {
|
|
1226
|
+
const title = session.title || session.goal || session.sessionId;
|
|
1227
|
+
const meta = [session.status, session.provider, session.updatedAt ? new Date(session.updatedAt).toLocaleString() : ""]
|
|
1228
|
+
.filter(Boolean)
|
|
1229
|
+
.join(" · ");
|
|
1230
|
+
return `
|
|
1231
|
+
<button class="session-row" type="button" data-session-id="${escapeHtml(session.sessionId)}" data-selected="${session.sessionId === managedSessionId}">
|
|
1232
|
+
<strong>${escapeHtml(title)}</strong>
|
|
1233
|
+
<span>${escapeHtml(meta)}</span>
|
|
1234
|
+
</button>
|
|
1235
|
+
`;
|
|
1236
|
+
})
|
|
1237
|
+
.join("");
|
|
1238
|
+
}
|
|
1239
|
+
|
|
803
1240
|
async function refreshSessions() {
|
|
804
1241
|
const response = await fetch("/api/sessions");
|
|
805
1242
|
const data = await response.json();
|
|
806
1243
|
renderSessions(data.sessions || []);
|
|
1244
|
+
renderSessionManager();
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
async function refreshWorkspaceChanges() {
|
|
1248
|
+
const response = await fetch("/api/workspace/changes");
|
|
1249
|
+
if (!response.ok) return;
|
|
1250
|
+
const data = await response.json();
|
|
1251
|
+
renderWorkspacePanel(data.workspace, data.activity || []);
|
|
807
1252
|
}
|
|
808
1253
|
|
|
809
1254
|
async function refreshRun() {
|
|
@@ -813,12 +1258,15 @@ async function refreshRun() {
|
|
|
813
1258
|
const run = await response.json();
|
|
814
1259
|
runMetaEl.textContent = `${run.status} · ${run.sessionId}`;
|
|
815
1260
|
renderLogs(run);
|
|
1261
|
+
await refreshArtifacts({ loadSelected: artifactTunnelDialog?.open });
|
|
816
1262
|
|
|
817
1263
|
if (run.status === "finished" || run.status === "failed") {
|
|
818
1264
|
clearInterval(pollTimer);
|
|
819
1265
|
pollTimer = null;
|
|
820
1266
|
await refreshChat();
|
|
821
1267
|
await refreshSessions();
|
|
1268
|
+
await refreshWorkspaceChanges();
|
|
1269
|
+
await refreshArtifacts({ loadSelected: artifactTunnelDialog?.open });
|
|
822
1270
|
}
|
|
823
1271
|
}
|
|
824
1272
|
|
|
@@ -880,6 +1328,88 @@ async function runSandboxPreflight() {
|
|
|
880
1328
|
.join("\n\n");
|
|
881
1329
|
}
|
|
882
1330
|
|
|
1331
|
+
async function openSessionManager() {
|
|
1332
|
+
await refreshSessions();
|
|
1333
|
+
managedSessionId = currentSessionId || managedSessionId || lastSessions[0]?.sessionId || "";
|
|
1334
|
+
renderSessionManager();
|
|
1335
|
+
sessionManagerDialog.showModal();
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function closeSessionManager() {
|
|
1339
|
+
sessionManagerDialog.close();
|
|
1340
|
+
sessionManagerStatusEl.textContent = "";
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
async function renameManagedSession() {
|
|
1344
|
+
const selected = currentManagedSession();
|
|
1345
|
+
if (!selected) return;
|
|
1346
|
+
|
|
1347
|
+
const title = sessionTitleInputEl.value.trim();
|
|
1348
|
+
if (!title) {
|
|
1349
|
+
sessionManagerStatusEl.textContent = t("titleRequired");
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
const response = await fetch(`/api/sessions/${encodeURIComponent(selected.sessionId)}`, {
|
|
1354
|
+
method: "PATCH",
|
|
1355
|
+
headers: { "Content-Type": "application/json" },
|
|
1356
|
+
body: JSON.stringify({ title }),
|
|
1357
|
+
});
|
|
1358
|
+
const data = await response.json().catch(() => ({}));
|
|
1359
|
+
if (!response.ok) {
|
|
1360
|
+
sessionManagerStatusEl.textContent = data.error || t("manageSessionFailed");
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
await refreshSessions();
|
|
1365
|
+
renderSessionManager(t("sessionRenamed"));
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
async function autoRenameManagedSession() {
|
|
1369
|
+
const selected = currentManagedSession();
|
|
1370
|
+
if (!selected) return;
|
|
1371
|
+
|
|
1372
|
+
const response = await fetch(`/api/sessions/${encodeURIComponent(selected.sessionId)}/auto-title`, {
|
|
1373
|
+
method: "POST",
|
|
1374
|
+
});
|
|
1375
|
+
const data = await response.json().catch(() => ({}));
|
|
1376
|
+
if (!response.ok) {
|
|
1377
|
+
sessionManagerStatusEl.textContent = data.error || t("manageSessionFailed");
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
await refreshSessions();
|
|
1382
|
+
renderSessionManager(t("sessionRenamed"));
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
async function deleteManagedSession() {
|
|
1386
|
+
const selected = currentManagedSession();
|
|
1387
|
+
if (!selected || !confirm(t("deleteConfirm"))) return;
|
|
1388
|
+
|
|
1389
|
+
const response = await fetch(`/api/sessions/${encodeURIComponent(selected.sessionId)}`, {
|
|
1390
|
+
method: "DELETE",
|
|
1391
|
+
});
|
|
1392
|
+
const data = await response.json().catch(() => ({}));
|
|
1393
|
+
if (!response.ok) {
|
|
1394
|
+
sessionManagerStatusEl.textContent = data.error || t("manageSessionFailed");
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (currentSessionId === selected.sessionId) {
|
|
1399
|
+
currentSessionId = "";
|
|
1400
|
+
sessionSelectEl.value = "";
|
|
1401
|
+
runMetaEl.textContent = "";
|
|
1402
|
+
setLogs(t("noRunSelected"), "empty");
|
|
1403
|
+
renderChat([]);
|
|
1404
|
+
artifactItems = [];
|
|
1405
|
+
selectedArtifactId = "";
|
|
1406
|
+
renderArtifactShell();
|
|
1407
|
+
}
|
|
1408
|
+
managedSessionId = "";
|
|
1409
|
+
await refreshSessions();
|
|
1410
|
+
renderSessionManager(t("sessionDeleted"));
|
|
1411
|
+
}
|
|
1412
|
+
|
|
883
1413
|
function schedulePreferenceSave() {
|
|
884
1414
|
clearTimeout(saveTimer);
|
|
885
1415
|
saveTimer = setTimeout(() => {
|
|
@@ -908,13 +1438,101 @@ preflightSandboxButton.addEventListener("click", () => {
|
|
|
908
1438
|
});
|
|
909
1439
|
});
|
|
910
1440
|
|
|
1441
|
+
manageSessionsButton.addEventListener("click", () => {
|
|
1442
|
+
openSessionManager().catch((error) => {
|
|
1443
|
+
chatStatusEl.textContent = String(error);
|
|
1444
|
+
});
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
openArtifactsButton.addEventListener("click", () => {
|
|
1448
|
+
openArtifactTunnel().catch((error) => {
|
|
1449
|
+
chatStatusEl.textContent = String(error);
|
|
1450
|
+
});
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
closeSessionManagerButton.addEventListener("click", closeSessionManager);
|
|
1454
|
+
cancelSessionManagerButton.addEventListener("click", closeSessionManager);
|
|
1455
|
+
closeArtifactTunnelButton.addEventListener("click", closeArtifactTunnel);
|
|
1456
|
+
|
|
1457
|
+
sessionManagerDialog.addEventListener("click", (event) => {
|
|
1458
|
+
if (event.target === sessionManagerDialog) closeSessionManager();
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
artifactTunnelDialog.addEventListener("click", (event) => {
|
|
1462
|
+
if (event.target === artifactTunnelDialog) closeArtifactTunnel();
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
artifactTabsEl.addEventListener("click", (event) => {
|
|
1466
|
+
const tab = event.target.closest("[data-artifact-tab]");
|
|
1467
|
+
if (!tab) return;
|
|
1468
|
+
currentArtifactTab = tab.dataset.artifactTab || "canvas";
|
|
1469
|
+
renderArtifactShell();
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
artifactListEl.addEventListener("click", (event) => {
|
|
1473
|
+
const row = event.target.closest("[data-artifact-id]");
|
|
1474
|
+
if (!row) return;
|
|
1475
|
+
selectArtifact(row.dataset.artifactId || "").catch((error) => {
|
|
1476
|
+
artifactStatusEl.textContent = String(error);
|
|
1477
|
+
});
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
markArtifactsSeenButton.addEventListener("click", () => {
|
|
1481
|
+
markAllArtifactsRead();
|
|
1482
|
+
renderArtifactBadge();
|
|
1483
|
+
renderArtifactList();
|
|
1484
|
+
artifactStatusEl.textContent = t("artifactSeenUpdated");
|
|
1485
|
+
refreshArtifacts({ loadSelected: false }).catch(() => {});
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
sessionManagerListEl.addEventListener("click", (event) => {
|
|
1489
|
+
const row = event.target.closest("[data-session-id]");
|
|
1490
|
+
if (!row) return;
|
|
1491
|
+
managedSessionId = row.dataset.sessionId || "";
|
|
1492
|
+
renderSessionManager();
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
renameSessionButton.addEventListener("click", () => {
|
|
1496
|
+
renameManagedSession().catch((error) => {
|
|
1497
|
+
sessionManagerStatusEl.textContent = String(error);
|
|
1498
|
+
});
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
autoRenameSessionButton.addEventListener("click", () => {
|
|
1502
|
+
autoRenameManagedSession().catch((error) => {
|
|
1503
|
+
sessionManagerStatusEl.textContent = String(error);
|
|
1504
|
+
});
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
deleteSessionButton.addEventListener("click", () => {
|
|
1508
|
+
deleteManagedSession().catch((error) => {
|
|
1509
|
+
sessionManagerStatusEl.textContent = String(error);
|
|
1510
|
+
});
|
|
1511
|
+
});
|
|
1512
|
+
|
|
911
1513
|
providerField.addEventListener("change", () => {
|
|
912
|
-
if (
|
|
1514
|
+
if (providerField.value === "mock") {
|
|
1515
|
+
routingModeField.value = "manual";
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
if (
|
|
1519
|
+
!modelField.value.trim() ||
|
|
1520
|
+
modelField.value === defaults.openai ||
|
|
1521
|
+
modelField.value === defaults.deepseek ||
|
|
1522
|
+
modelField.value === defaults.mock
|
|
1523
|
+
) {
|
|
913
1524
|
modelField.value = defaults[providerField.value] || "";
|
|
914
1525
|
}
|
|
1526
|
+
updateRoutingHint();
|
|
915
1527
|
schedulePreferenceSave();
|
|
916
1528
|
});
|
|
917
1529
|
|
|
1530
|
+
modelField.addEventListener("input", updateRoutingHint);
|
|
1531
|
+
sandboxModeField.addEventListener("change", updatePackageWarning);
|
|
1532
|
+
packageInstallPolicyField.addEventListener("change", updatePackageWarning);
|
|
1533
|
+
allowWrapperToolsField.addEventListener("change", () => renderWrapperStatus());
|
|
1534
|
+
preferredWrapperField.addEventListener("change", () => renderWrapperStatus());
|
|
1535
|
+
|
|
918
1536
|
form.addEventListener("input", schedulePreferenceSave);
|
|
919
1537
|
form.addEventListener("change", schedulePreferenceSave);
|
|
920
1538
|
|
|
@@ -953,6 +1571,8 @@ form.addEventListener("submit", async (event) => {
|
|
|
953
1571
|
runMetaEl.textContent = `${t("runningStatus").replace("...", "")} · ${currentSessionId}`;
|
|
954
1572
|
await refreshSessions();
|
|
955
1573
|
await refreshChat();
|
|
1574
|
+
await refreshWorkspaceChanges();
|
|
1575
|
+
await refreshArtifacts({ loadSelected: artifactTunnelDialog?.open });
|
|
956
1576
|
|
|
957
1577
|
if (pollTimer) clearInterval(pollTimer);
|
|
958
1578
|
pollTimer = setInterval(refreshRun, 1500);
|
|
@@ -965,10 +1585,14 @@ sessionSelectEl.addEventListener("change", async () => {
|
|
|
965
1585
|
runMetaEl.textContent = "";
|
|
966
1586
|
setLogs(t("noRunSelected"), "empty");
|
|
967
1587
|
renderChat([]);
|
|
1588
|
+
artifactItems = [];
|
|
1589
|
+
selectedArtifactId = "";
|
|
1590
|
+
renderArtifactShell();
|
|
968
1591
|
return;
|
|
969
1592
|
}
|
|
970
1593
|
await refreshRun();
|
|
971
1594
|
await refreshChat();
|
|
1595
|
+
await refreshArtifacts({ loadSelected: artifactTunnelDialog?.open });
|
|
972
1596
|
});
|
|
973
1597
|
|
|
974
1598
|
chatFormEl.addEventListener("submit", async (event) => {
|
|
@@ -1007,6 +1631,8 @@ chatFormEl.addEventListener("submit", async (event) => {
|
|
|
1007
1631
|
chatStatusEl.textContent = t("runningStatus");
|
|
1008
1632
|
await refreshSessions();
|
|
1009
1633
|
await refreshChat();
|
|
1634
|
+
await refreshWorkspaceChanges();
|
|
1635
|
+
await refreshArtifacts({ loadSelected: artifactTunnelDialog?.open });
|
|
1010
1636
|
|
|
1011
1637
|
if (pollTimer) clearInterval(pollTimer);
|
|
1012
1638
|
pollTimer = setInterval(refreshRun, 1500);
|
|
@@ -1020,6 +1646,7 @@ async function loadConfig() {
|
|
|
1020
1646
|
routingPresets = data.routing?.presets || {};
|
|
1021
1647
|
defaults.openai = data.defaults?.openai?.model || defaults.openai;
|
|
1022
1648
|
defaults.deepseek = routingPresets.fast?.model || data.defaults?.deepseek?.model || defaults.deepseek;
|
|
1649
|
+
defaults.mock = data.defaults?.mock?.model || defaults.mock;
|
|
1023
1650
|
|
|
1024
1651
|
applyLanguage(prefs.language || normalizeLanguage(navigator.language || "en"), { persist: false });
|
|
1025
1652
|
|
|
@@ -1031,18 +1658,23 @@ async function loadConfig() {
|
|
|
1031
1658
|
document.querySelector("#commandCwd").value = prefs.commandCwd || "/home/lachlan/ProjectsLFS/Agent";
|
|
1032
1659
|
document.querySelector("#headless").checked = prefs.headless ?? data.defaults.headless;
|
|
1033
1660
|
document.querySelector("#maxSteps").value = prefs.maxSteps ?? data.defaults.maxSteps;
|
|
1034
|
-
sandboxModeField.value = prefs.sandboxMode || (prefs.useDockerSandbox ? "docker-
|
|
1661
|
+
sandboxModeField.value = prefs.sandboxMode || (prefs.useDockerSandbox ? "docker-workspace" : "host");
|
|
1035
1662
|
packageInstallPolicyField.value = prefs.packageInstallPolicy || "prompt";
|
|
1036
1663
|
document.querySelector("#allowShellTool").checked = prefs.allowShellTool ?? true;
|
|
1037
|
-
document.querySelector("#
|
|
1664
|
+
document.querySelector("#allowFileTools").checked = prefs.allowFileTools ?? true;
|
|
1665
|
+
allowWrapperToolsField.checked = prefs.allowWrapperTools ?? false;
|
|
1666
|
+
preferredWrapperField.value = prefs.preferredWrapper || "codex";
|
|
1038
1667
|
document.querySelector("#dockerSandboxImage").value = prefs.dockerSandboxImage || "agintiflow-sandbox:latest";
|
|
1039
1668
|
document.querySelector("#allowPasswords").checked = prefs.allowPasswords ?? false;
|
|
1040
1669
|
document.querySelector("#allowDestructive").checked = prefs.allowDestructive ?? false;
|
|
1041
1670
|
|
|
1042
1671
|
renderKeyStatus(data.keyStatus);
|
|
1043
1672
|
renderWrapperStatus(data.wrappers || []);
|
|
1673
|
+
renderWorkspacePanel(data.workspace, []);
|
|
1674
|
+
await refreshWorkspaceChanges();
|
|
1044
1675
|
renderSandboxLogs(data.sandbox?.logs || []);
|
|
1045
1676
|
updateRoutingHint();
|
|
1677
|
+
updatePackageWarning();
|
|
1046
1678
|
|
|
1047
1679
|
renderSessions(data.sessions || []);
|
|
1048
1680
|
if (data.sessions && data.sessions.length > 0) {
|
|
@@ -1050,6 +1682,7 @@ async function loadConfig() {
|
|
|
1050
1682
|
sessionSelectEl.value = currentSessionId;
|
|
1051
1683
|
await refreshRun();
|
|
1052
1684
|
await refreshChat();
|
|
1685
|
+
await refreshArtifacts({ loadSelected: false });
|
|
1053
1686
|
}
|
|
1054
1687
|
}
|
|
1055
1688
|
|