@lazyingart/agintiflow 0.20.0 → 0.20.2

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.
@@ -33,9 +33,12 @@ Interactive commands:
33
33
 
34
34
  ```text
35
35
  /models
36
+ /provider
36
37
  /venice
38
+ /venice off
37
39
  /route
38
40
  /model
41
+ /spare
39
42
  /route deepseek/deepseek-v4-flash
40
43
  /model deepseek/deepseek-v4-pro
41
44
  /spare openai/gpt-5.4 medium
@@ -43,16 +46,23 @@ Interactive commands:
43
46
  /auxiliary model grsai/nano-banana-2
44
47
  ```
45
48
 
46
- In the interactive CLI, `/route` and `/model` without arguments open a selector. Use Up/Down/Left/Right to move through choices, Enter to confirm, and Esc to cancel. Slash-command hints use the same arrow selection behavior: type a prefix such as `/mo`, use arrows to choose `/model` or `/models`, then press Enter or Tab.
49
+ In the interactive CLI, `/provider`, `/route`, `/model`, `/spare`, and `/auxiliary model` without arguments open selectors. Use Up/Down/Left/Right to move through choices, Enter to confirm, and Esc to cancel. Slash-command hints use the same arrow selection behavior: type a prefix such as `/mo`, use arrows to choose `/model` or `/models`, then press Enter or Tab.
47
50
 
48
- `/venice` is a shortcut for:
51
+ `/venice` is a toggle. When off, it enables:
49
52
 
50
53
  ```text
51
54
  /route venice/venice-uncensored-1-2
52
55
  /main venice/venice-uncensored-1-2
53
56
  ```
54
57
 
55
- It keeps smart routing enabled, so normal and complex tasks still use the same route/main policy, but both roles resolve to Venice Uncensored 1.2. If the Venice key is missing, run `/auth venice`.
58
+ When already on, `/venice` switches back to the DeepSeek defaults:
59
+
60
+ ```text
61
+ /route deepseek/deepseek-v4-flash
62
+ /model deepseek/deepseek-v4-pro
63
+ ```
64
+
65
+ It keeps smart routing enabled. If the Venice key is missing, run `/auth venice`.
56
66
 
57
67
  ## Provider Buckets
58
68
 
@@ -80,4 +90,4 @@ aginti keys status
80
90
  printf '%s' "$VENICE_API_KEY" | aginti keys set venice --stdin
81
91
  ```
82
92
 
83
- The web UI mirrors the same roles in its **Model roles** panel. Use the top provider/model fields for manual runs; use the role fields to change how smart routing chooses route, main, spare, wrapper, and auxiliary models.
93
+ The web UI mirrors the same roles with dropdowns. Keep the left panel for daily controls: routing policy, provider/model, profile, goal, workspace, sandbox, and common tools. Use **Advanced settings** for model roles, browser start URL, wrapper/scout settings, Docker image, password/destructive toggles, and auxiliary image models. Runtime logs stay in the right column, followed by wrapper, workspace, and sandbox capability panels.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
6
6
  "license": "Apache-2.0",
package/public/app.js CHANGED
@@ -649,7 +649,6 @@ const languageField = document.querySelector("#language");
649
649
  const routingModeField = document.querySelector("#routingMode");
650
650
  const providerField = document.querySelector("#provider");
651
651
  const modelField = document.querySelector("#model");
652
- const modelOptionsEl = document.querySelector("#model-options");
653
652
  const modelCatalogEl = document.querySelector("#model-catalog");
654
653
  const modelRoleGridEl = document.querySelector("#model-role-grid");
655
654
  const modelRoutePillEl = document.querySelector("#model-route-pill");
@@ -725,6 +724,10 @@ const artifactViewerKindEl = document.querySelector("#artifact-viewer-kind");
725
724
  const artifactViewerBodyEl = document.querySelector("#artifact-viewer-body");
726
725
  const artifactStatusEl = document.querySelector("#artifact-status");
727
726
  const markArtifactsSeenButton = document.querySelector("#mark-artifacts-seen");
727
+ const settingsDialog = document.querySelector("#settings-modal");
728
+ const openSettingsButton = document.querySelector("#open-settings");
729
+ const closeSettingsButton = document.querySelector("#close-settings");
730
+ const doneSettingsButton = document.querySelector("#done-settings");
728
731
  const translatableNodes = [...document.querySelectorAll("[data-i18n]")];
729
732
  const placeholderNodes = [...document.querySelectorAll("[data-i18n-placeholder]")];
730
733
  const ariaLabelNodes = [...document.querySelectorAll("[data-i18n-aria-label]")];
@@ -981,10 +984,102 @@ function renderSandboxLogs(logs) {
981
984
  .join("\n\n");
982
985
  }
983
986
 
987
+ function fieldValue(field) {
988
+ return String(field?.value || "").trim();
989
+ }
990
+
984
991
  function providerModelOptions(provider = providerField.value) {
985
992
  return modelCatalog[provider] || [];
986
993
  }
987
994
 
995
+ function mergeModelOptions(...groups) {
996
+ const seen = new Set();
997
+ const merged = [];
998
+ for (const group of groups) {
999
+ for (const item of group || []) {
1000
+ const id = String(item?.id || item?.model || item || "").trim();
1001
+ if (!id || seen.has(id)) continue;
1002
+ seen.add(id);
1003
+ merged.push(typeof item === "string" ? { id, label: id } : { ...item, id });
1004
+ }
1005
+ }
1006
+ return merged;
1007
+ }
1008
+
1009
+ function wrapperModelOptions() {
1010
+ const openai = providerModelOptions("openai").filter((item) => /gpt|codex/i.test(item.id || item.label || ""));
1011
+ return mergeModelOptions(
1012
+ [
1013
+ { id: "gpt-5.5", label: "GPT-5.5", bucket: "Codex wrapper" },
1014
+ { id: "gpt-5.4", label: "GPT-5.4", bucket: "Codex wrapper" },
1015
+ { id: "gpt-5.4-mini", label: "GPT-5.4 Mini", bucket: "Codex wrapper" },
1016
+ { id: "gpt-5.3-codex", label: "GPT-5.3 Codex", bucket: "Codex wrapper" },
1017
+ { id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark", bucket: "Codex wrapper" },
1018
+ { id: "gpt-5.2", label: "GPT-5.2", bucket: "Codex wrapper" },
1019
+ ],
1020
+ openai
1021
+ );
1022
+ }
1023
+
1024
+ function auxiliaryModelOptions(provider = auxiliaryProviderField?.value || "grsai") {
1025
+ if (provider === "venice") {
1026
+ return mergeModelOptions(auxiliaryModelCatalog["venice-image"], [
1027
+ { id: "nano-banana-2", label: "Nano Banana 2", bucket: "Venice image" },
1028
+ { id: "gpt-image-2", label: "GPT Image 2", bucket: "Venice image" },
1029
+ { id: "gpt-image-2-edit", label: "GPT Image 2 Edit", bucket: "Venice image" },
1030
+ { id: "bria-bg-remover", label: "Background Remover", bucket: "Venice image" },
1031
+ ]);
1032
+ }
1033
+ return mergeModelOptions(auxiliaryModelCatalog.grsai, [
1034
+ { id: "nano-banana-2", label: "Nano Banana 2", bucket: "GRS AI" },
1035
+ { id: "gpt-image-2", label: "GPT Image 2", bucket: "GRS AI" },
1036
+ ]);
1037
+ }
1038
+
1039
+ function setSelectOptions(select, options, selectedValue = "", fallbackValue = "") {
1040
+ if (!select) return "";
1041
+ const selected = String(selectedValue || fallbackValue || "").trim();
1042
+ const merged = mergeModelOptions(options);
1043
+ if (selected && !merged.some((item) => item.id === selected)) {
1044
+ merged.unshift({ id: selected, label: `Custom: ${selected}` });
1045
+ }
1046
+ select.innerHTML = merged
1047
+ .map((item) => `<option value="${escapeHtml(item.id)}">${escapeHtml(item.label || item.id)}</option>`)
1048
+ .join("");
1049
+ select.value = selected && merged.some((item) => item.id === selected) ? selected : merged[0]?.id || "";
1050
+ return select.value;
1051
+ }
1052
+
1053
+ function refreshModelDropdowns() {
1054
+ const provider = providerField?.value || "deepseek";
1055
+ setSelectOptions(modelField, providerModelOptions(provider), fieldValue(modelField), defaults[provider]);
1056
+ setSelectOptions(
1057
+ routeModelField,
1058
+ providerModelOptions(routeProviderField?.value || "deepseek"),
1059
+ fieldValue(routeModelField),
1060
+ modelRoles.route?.model || "deepseek-v4-flash"
1061
+ );
1062
+ setSelectOptions(
1063
+ mainModelField,
1064
+ providerModelOptions(mainProviderField?.value || "deepseek"),
1065
+ fieldValue(mainModelField),
1066
+ modelRoles.main?.model || "deepseek-v4-pro"
1067
+ );
1068
+ setSelectOptions(
1069
+ spareModelField,
1070
+ providerModelOptions(spareProviderField?.value || "openai"),
1071
+ fieldValue(spareModelField),
1072
+ modelRoles.spare?.model || "gpt-5.4"
1073
+ );
1074
+ setSelectOptions(wrapperModelField, wrapperModelOptions(), fieldValue(wrapperModelField), modelRoles.wrapper?.model || "gpt-5.5");
1075
+ setSelectOptions(
1076
+ auxiliaryModelField,
1077
+ auxiliaryModelOptions(auxiliaryProviderField?.value || "grsai"),
1078
+ fieldValue(auxiliaryModelField),
1079
+ modelRoles.auxiliary?.model || "nano-banana-2"
1080
+ );
1081
+ }
1082
+
988
1083
  function renderModelRoles() {
989
1084
  if (!modelRoleGridEl) return;
990
1085
  const roles = {
@@ -1032,14 +1127,10 @@ function renderModelRoles() {
1032
1127
  function renderModelOptions() {
1033
1128
  const provider = providerField.value || "deepseek";
1034
1129
  const options = providerModelOptions(provider);
1035
- if (modelOptionsEl) {
1036
- modelOptionsEl.innerHTML = options
1037
- .map((item) => `<option value="${escapeHtml(item.id)}">${escapeHtml(item.label || item.id)}</option>`)
1038
- .join("");
1039
- }
1130
+ refreshModelDropdowns();
1040
1131
  if (modelRoutePillEl) {
1041
1132
  const mode = routingModeField.value || "smart";
1042
- const primary = modelField.value.trim() || defaults[provider] || "";
1133
+ const primary = fieldValue(modelField) || defaults[provider] || "";
1043
1134
  const routeLabel = `${routeProviderField?.value || "deepseek"}/${routeModelField?.value || "deepseek-v4-flash"}`;
1044
1135
  const mainLabel = `${mainProviderField?.value || "deepseek"}/${mainModelField?.value || "deepseek-v4-pro"}`;
1045
1136
  modelRoutePillEl.textContent =
@@ -1055,7 +1146,7 @@ function renderModelOptions() {
1055
1146
  modelCatalogEl.innerHTML = options
1056
1147
  .map(
1057
1148
  (item) => `
1058
- <button class="model-chip" type="button" data-model-id="${escapeHtml(item.id)}" data-active="${item.id === modelField.value.trim()}">
1149
+ <button class="model-chip" type="button" data-model-id="${escapeHtml(item.id)}" data-active="${item.id === fieldValue(modelField)}">
1059
1150
  <strong>${escapeHtml(item.label || item.id)}</strong>
1060
1151
  <span>${escapeHtml([item.bucket || item.role, item.context, item.reasoning?.length ? `reasoning ${item.reasoning.join("/")}` : ""].filter(Boolean).join(" · "))}</span>
1061
1152
  </button>
@@ -1093,7 +1184,7 @@ function updateRoutingHint() {
1093
1184
  }
1094
1185
 
1095
1186
  modelRouteStatusEl.textContent = `${t("modelRouteStatus")}: ${providerField.value} / ${
1096
- modelField.value.trim() || defaults[providerField.value] || ""
1187
+ fieldValue(modelField) || defaults[providerField.value] || ""
1097
1188
  }`;
1098
1189
  renderModelOptions();
1099
1190
  }
@@ -1152,18 +1243,18 @@ function formPayload() {
1152
1243
  language: languageField.value,
1153
1244
  routingMode: routingModeField.value,
1154
1245
  provider: providerField.value,
1155
- model: modelField.value.trim(),
1246
+ model: fieldValue(modelField),
1156
1247
  routeProvider: routeProviderField?.value || "deepseek",
1157
- routeModel: routeModelField?.value.trim() || "deepseek-v4-flash",
1248
+ routeModel: fieldValue(routeModelField) || "deepseek-v4-flash",
1158
1249
  mainProvider: mainProviderField?.value || "deepseek",
1159
- mainModel: mainModelField?.value.trim() || "deepseek-v4-pro",
1250
+ mainModel: fieldValue(mainModelField) || "deepseek-v4-pro",
1160
1251
  spareProvider: spareProviderField?.value || "openai",
1161
- spareModel: spareModelField?.value.trim() || "gpt-5.4",
1252
+ spareModel: fieldValue(spareModelField) || "gpt-5.4",
1162
1253
  spareReasoning: spareReasoningField?.value || "medium",
1163
- wrapperModel: wrapperModelField?.value.trim() || "gpt-5.5",
1254
+ wrapperModel: fieldValue(wrapperModelField) || "gpt-5.5",
1164
1255
  wrapperReasoning: wrapperReasoningField?.value || "medium",
1165
1256
  auxiliaryProvider: auxiliaryProviderField?.value || "grsai",
1166
- auxiliaryModel: auxiliaryModelField?.value.trim() || "nano-banana-2",
1257
+ auxiliaryModel: fieldValue(auxiliaryModelField) || "nano-banana-2",
1167
1258
  startUrl: document.querySelector("#startUrl").value.trim(),
1168
1259
  allowedDomains: document.querySelector("#allowedDomains").value.trim(),
1169
1260
  commandCwd: document.querySelector("#commandCwd").value.trim(),
@@ -2343,10 +2434,27 @@ openArtifactsButton.addEventListener("click", () => {
2343
2434
  });
2344
2435
  });
2345
2436
 
2437
+ function closeSettings() {
2438
+ settingsDialog?.close();
2439
+ renderModelOptions();
2440
+ schedulePreferenceSave();
2441
+ }
2442
+
2443
+ openSettingsButton?.addEventListener("click", () => {
2444
+ refreshModelDropdowns();
2445
+ settingsDialog?.showModal();
2446
+ });
2447
+
2448
+ closeSettingsButton?.addEventListener("click", closeSettings);
2449
+ doneSettingsButton?.addEventListener("click", closeSettings);
2346
2450
  closeSessionManagerButton.addEventListener("click", closeSessionManager);
2347
2451
  cancelSessionManagerButton.addEventListener("click", closeSessionManager);
2348
2452
  closeArtifactTunnelButton.addEventListener("click", closeArtifactTunnel);
2349
2453
 
2454
+ settingsDialog?.addEventListener("click", (event) => {
2455
+ if (event.target === settingsDialog) closeSettings();
2456
+ });
2457
+
2350
2458
  sessionManagerDialog.addEventListener("click", (event) => {
2351
2459
  if (event.target === sessionManagerDialog) closeSessionManager();
2352
2460
  });
@@ -2425,7 +2533,7 @@ providerField.addEventListener("change", () => {
2425
2533
  }
2426
2534
 
2427
2535
  if (
2428
- !modelField.value.trim() ||
2536
+ !fieldValue(modelField) ||
2429
2537
  modelField.value === defaults.openai ||
2430
2538
  modelField.value === defaults.deepseek ||
2431
2539
  modelField.value === defaults.qwen ||
@@ -2446,16 +2554,20 @@ modelCatalogEl?.addEventListener("click", (event) => {
2446
2554
  schedulePreferenceSave();
2447
2555
  });
2448
2556
 
2449
- modelField.addEventListener("input", updateRoutingHint);
2557
+ modelField.addEventListener("change", updateRoutingHint);
2450
2558
  [routeProviderField, routeModelField, mainProviderField, mainModelField, spareProviderField, spareModelField, spareReasoningField, wrapperModelField, wrapperReasoningField, auxiliaryProviderField, auxiliaryModelField]
2451
2559
  .filter(Boolean)
2452
2560
  .forEach((field) => {
2453
2561
  field.addEventListener("input", () => {
2562
+ if (field === routeProviderField || field === mainProviderField || field === spareProviderField || field === auxiliaryProviderField) refreshModelDropdowns();
2454
2563
  renderModelRoles();
2564
+ renderModelOptions();
2455
2565
  schedulePreferenceSave();
2456
2566
  });
2457
2567
  field.addEventListener("change", () => {
2568
+ if (field === routeProviderField || field === mainProviderField || field === spareProviderField || field === auxiliaryProviderField) refreshModelDropdowns();
2458
2569
  renderModelRoles();
2570
+ renderModelOptions();
2459
2571
  schedulePreferenceSave();
2460
2572
  });
2461
2573
  });
@@ -2698,18 +2810,38 @@ async function loadConfig() {
2698
2810
 
2699
2811
  routingModeField.value = prefs.routingMode || "smart";
2700
2812
  providerField.value = prefs.provider || "deepseek";
2701
- modelField.value = prefs.model || defaults[providerField.value] || "deepseek-v4-flash";
2702
2813
  if (routeProviderField) routeProviderField.value = prefs.routeProvider || modelRoles.route?.provider || "deepseek";
2703
- if (routeModelField) routeModelField.value = prefs.routeModel || modelRoles.route?.model || "deepseek-v4-flash";
2704
2814
  if (mainProviderField) mainProviderField.value = prefs.mainProvider || modelRoles.main?.provider || "deepseek";
2705
- if (mainModelField) mainModelField.value = prefs.mainModel || modelRoles.main?.model || "deepseek-v4-pro";
2706
2815
  if (spareProviderField) spareProviderField.value = prefs.spareProvider || modelRoles.spare?.provider || "openai";
2707
- if (spareModelField) spareModelField.value = prefs.spareModel || modelRoles.spare?.model || "gpt-5.4";
2708
2816
  if (spareReasoningField) spareReasoningField.value = prefs.spareReasoning || modelRoles.spare?.reasoning || "medium";
2709
- if (wrapperModelField) wrapperModelField.value = prefs.wrapperModel || modelRoles.wrapper?.model || "gpt-5.5";
2710
2817
  if (wrapperReasoningField) wrapperReasoningField.value = prefs.wrapperReasoning || modelRoles.wrapper?.reasoning || "medium";
2711
2818
  if (auxiliaryProviderField) auxiliaryProviderField.value = prefs.auxiliaryProvider || modelRoles.auxiliary?.provider || "grsai";
2712
- if (auxiliaryModelField) auxiliaryModelField.value = prefs.auxiliaryModel || modelRoles.auxiliary?.model || "nano-banana-2";
2819
+ setSelectOptions(modelField, providerModelOptions(providerField.value), prefs.model || defaults[providerField.value], defaults[providerField.value]);
2820
+ setSelectOptions(
2821
+ routeModelField,
2822
+ providerModelOptions(routeProviderField?.value || "deepseek"),
2823
+ prefs.routeModel || modelRoles.route?.model,
2824
+ "deepseek-v4-flash"
2825
+ );
2826
+ setSelectOptions(
2827
+ mainModelField,
2828
+ providerModelOptions(mainProviderField?.value || "deepseek"),
2829
+ prefs.mainModel || modelRoles.main?.model,
2830
+ "deepseek-v4-pro"
2831
+ );
2832
+ setSelectOptions(
2833
+ spareModelField,
2834
+ providerModelOptions(spareProviderField?.value || "openai"),
2835
+ prefs.spareModel || modelRoles.spare?.model,
2836
+ "gpt-5.4"
2837
+ );
2838
+ setSelectOptions(wrapperModelField, wrapperModelOptions(), prefs.wrapperModel || modelRoles.wrapper?.model, "gpt-5.5");
2839
+ setSelectOptions(
2840
+ auxiliaryModelField,
2841
+ auxiliaryModelOptions(auxiliaryProviderField?.value || "grsai"),
2842
+ prefs.auxiliaryModel || modelRoles.auxiliary?.model,
2843
+ "nano-banana-2"
2844
+ );
2713
2845
  renderTaskProfiles(prefs.taskProfile || "auto");
2714
2846
  document.querySelector("#startUrl").value = prefs.startUrl || "";
2715
2847
  document.querySelector("#allowedDomains").value = prefs.allowedDomains || "";