@lazyingart/agintiflow 0.20.1 → 0.20.3

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.1",
3
+ "version": "0.20.3",
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,20 +1127,18 @@ 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 =
1046
1137
  mode === "manual"
1047
1138
  ? `${provider} · ${primary}`
1048
- : `${mode} · route ${routeLabel} · main ${mainLabel}`;
1139
+ : `${mode} · ${routeLabel} -> ${mainLabel}`;
1140
+ modelRoutePillEl.title =
1141
+ mode === "manual" ? `Manual route: ${provider}/${primary}` : `Route: ${routeLabel}. Main: ${mainLabel}.`;
1049
1142
  }
1050
1143
  if (!modelCatalogEl) return;
1051
1144
  if (options.length === 0) {
@@ -1055,7 +1148,7 @@ function renderModelOptions() {
1055
1148
  modelCatalogEl.innerHTML = options
1056
1149
  .map(
1057
1150
  (item) => `
1058
- <button class="model-chip" type="button" data-model-id="${escapeHtml(item.id)}" data-active="${item.id === modelField.value.trim()}">
1151
+ <button class="model-chip" type="button" data-model-id="${escapeHtml(item.id)}" data-active="${item.id === fieldValue(modelField)}">
1059
1152
  <strong>${escapeHtml(item.label || item.id)}</strong>
1060
1153
  <span>${escapeHtml([item.bucket || item.role, item.context, item.reasoning?.length ? `reasoning ${item.reasoning.join("/")}` : ""].filter(Boolean).join(" · "))}</span>
1061
1154
  </button>
@@ -1093,7 +1186,7 @@ function updateRoutingHint() {
1093
1186
  }
1094
1187
 
1095
1188
  modelRouteStatusEl.textContent = `${t("modelRouteStatus")}: ${providerField.value} / ${
1096
- modelField.value.trim() || defaults[providerField.value] || ""
1189
+ fieldValue(modelField) || defaults[providerField.value] || ""
1097
1190
  }`;
1098
1191
  renderModelOptions();
1099
1192
  }
@@ -1152,18 +1245,18 @@ function formPayload() {
1152
1245
  language: languageField.value,
1153
1246
  routingMode: routingModeField.value,
1154
1247
  provider: providerField.value,
1155
- model: modelField.value.trim(),
1248
+ model: fieldValue(modelField),
1156
1249
  routeProvider: routeProviderField?.value || "deepseek",
1157
- routeModel: routeModelField?.value.trim() || "deepseek-v4-flash",
1250
+ routeModel: fieldValue(routeModelField) || "deepseek-v4-flash",
1158
1251
  mainProvider: mainProviderField?.value || "deepseek",
1159
- mainModel: mainModelField?.value.trim() || "deepseek-v4-pro",
1252
+ mainModel: fieldValue(mainModelField) || "deepseek-v4-pro",
1160
1253
  spareProvider: spareProviderField?.value || "openai",
1161
- spareModel: spareModelField?.value.trim() || "gpt-5.4",
1254
+ spareModel: fieldValue(spareModelField) || "gpt-5.4",
1162
1255
  spareReasoning: spareReasoningField?.value || "medium",
1163
- wrapperModel: wrapperModelField?.value.trim() || "gpt-5.5",
1256
+ wrapperModel: fieldValue(wrapperModelField) || "gpt-5.5",
1164
1257
  wrapperReasoning: wrapperReasoningField?.value || "medium",
1165
1258
  auxiliaryProvider: auxiliaryProviderField?.value || "grsai",
1166
- auxiliaryModel: auxiliaryModelField?.value.trim() || "nano-banana-2",
1259
+ auxiliaryModel: fieldValue(auxiliaryModelField) || "nano-banana-2",
1167
1260
  startUrl: document.querySelector("#startUrl").value.trim(),
1168
1261
  allowedDomains: document.querySelector("#allowedDomains").value.trim(),
1169
1262
  commandCwd: document.querySelector("#commandCwd").value.trim(),
@@ -2343,10 +2436,27 @@ openArtifactsButton.addEventListener("click", () => {
2343
2436
  });
2344
2437
  });
2345
2438
 
2439
+ function closeSettings() {
2440
+ settingsDialog?.close();
2441
+ renderModelOptions();
2442
+ schedulePreferenceSave();
2443
+ }
2444
+
2445
+ openSettingsButton?.addEventListener("click", () => {
2446
+ refreshModelDropdowns();
2447
+ settingsDialog?.showModal();
2448
+ });
2449
+
2450
+ closeSettingsButton?.addEventListener("click", closeSettings);
2451
+ doneSettingsButton?.addEventListener("click", closeSettings);
2346
2452
  closeSessionManagerButton.addEventListener("click", closeSessionManager);
2347
2453
  cancelSessionManagerButton.addEventListener("click", closeSessionManager);
2348
2454
  closeArtifactTunnelButton.addEventListener("click", closeArtifactTunnel);
2349
2455
 
2456
+ settingsDialog?.addEventListener("click", (event) => {
2457
+ if (event.target === settingsDialog) closeSettings();
2458
+ });
2459
+
2350
2460
  sessionManagerDialog.addEventListener("click", (event) => {
2351
2461
  if (event.target === sessionManagerDialog) closeSessionManager();
2352
2462
  });
@@ -2425,7 +2535,7 @@ providerField.addEventListener("change", () => {
2425
2535
  }
2426
2536
 
2427
2537
  if (
2428
- !modelField.value.trim() ||
2538
+ !fieldValue(modelField) ||
2429
2539
  modelField.value === defaults.openai ||
2430
2540
  modelField.value === defaults.deepseek ||
2431
2541
  modelField.value === defaults.qwen ||
@@ -2446,16 +2556,20 @@ modelCatalogEl?.addEventListener("click", (event) => {
2446
2556
  schedulePreferenceSave();
2447
2557
  });
2448
2558
 
2449
- modelField.addEventListener("input", updateRoutingHint);
2559
+ modelField.addEventListener("change", updateRoutingHint);
2450
2560
  [routeProviderField, routeModelField, mainProviderField, mainModelField, spareProviderField, spareModelField, spareReasoningField, wrapperModelField, wrapperReasoningField, auxiliaryProviderField, auxiliaryModelField]
2451
2561
  .filter(Boolean)
2452
2562
  .forEach((field) => {
2453
2563
  field.addEventListener("input", () => {
2564
+ if (field === routeProviderField || field === mainProviderField || field === spareProviderField || field === auxiliaryProviderField) refreshModelDropdowns();
2454
2565
  renderModelRoles();
2566
+ renderModelOptions();
2455
2567
  schedulePreferenceSave();
2456
2568
  });
2457
2569
  field.addEventListener("change", () => {
2570
+ if (field === routeProviderField || field === mainProviderField || field === spareProviderField || field === auxiliaryProviderField) refreshModelDropdowns();
2458
2571
  renderModelRoles();
2572
+ renderModelOptions();
2459
2573
  schedulePreferenceSave();
2460
2574
  });
2461
2575
  });
@@ -2698,18 +2812,38 @@ async function loadConfig() {
2698
2812
 
2699
2813
  routingModeField.value = prefs.routingMode || "smart";
2700
2814
  providerField.value = prefs.provider || "deepseek";
2701
- modelField.value = prefs.model || defaults[providerField.value] || "deepseek-v4-flash";
2702
2815
  if (routeProviderField) routeProviderField.value = prefs.routeProvider || modelRoles.route?.provider || "deepseek";
2703
- if (routeModelField) routeModelField.value = prefs.routeModel || modelRoles.route?.model || "deepseek-v4-flash";
2704
2816
  if (mainProviderField) mainProviderField.value = prefs.mainProvider || modelRoles.main?.provider || "deepseek";
2705
- if (mainModelField) mainModelField.value = prefs.mainModel || modelRoles.main?.model || "deepseek-v4-pro";
2706
2817
  if (spareProviderField) spareProviderField.value = prefs.spareProvider || modelRoles.spare?.provider || "openai";
2707
- if (spareModelField) spareModelField.value = prefs.spareModel || modelRoles.spare?.model || "gpt-5.4";
2708
2818
  if (spareReasoningField) spareReasoningField.value = prefs.spareReasoning || modelRoles.spare?.reasoning || "medium";
2709
- if (wrapperModelField) wrapperModelField.value = prefs.wrapperModel || modelRoles.wrapper?.model || "gpt-5.5";
2710
2819
  if (wrapperReasoningField) wrapperReasoningField.value = prefs.wrapperReasoning || modelRoles.wrapper?.reasoning || "medium";
2711
2820
  if (auxiliaryProviderField) auxiliaryProviderField.value = prefs.auxiliaryProvider || modelRoles.auxiliary?.provider || "grsai";
2712
- if (auxiliaryModelField) auxiliaryModelField.value = prefs.auxiliaryModel || modelRoles.auxiliary?.model || "nano-banana-2";
2821
+ setSelectOptions(modelField, providerModelOptions(providerField.value), prefs.model || defaults[providerField.value], defaults[providerField.value]);
2822
+ setSelectOptions(
2823
+ routeModelField,
2824
+ providerModelOptions(routeProviderField?.value || "deepseek"),
2825
+ prefs.routeModel || modelRoles.route?.model,
2826
+ "deepseek-v4-flash"
2827
+ );
2828
+ setSelectOptions(
2829
+ mainModelField,
2830
+ providerModelOptions(mainProviderField?.value || "deepseek"),
2831
+ prefs.mainModel || modelRoles.main?.model,
2832
+ "deepseek-v4-pro"
2833
+ );
2834
+ setSelectOptions(
2835
+ spareModelField,
2836
+ providerModelOptions(spareProviderField?.value || "openai"),
2837
+ prefs.spareModel || modelRoles.spare?.model,
2838
+ "gpt-5.4"
2839
+ );
2840
+ setSelectOptions(wrapperModelField, wrapperModelOptions(), prefs.wrapperModel || modelRoles.wrapper?.model, "gpt-5.5");
2841
+ setSelectOptions(
2842
+ auxiliaryModelField,
2843
+ auxiliaryModelOptions(auxiliaryProviderField?.value || "grsai"),
2844
+ prefs.auxiliaryModel || modelRoles.auxiliary?.model,
2845
+ "nano-banana-2"
2846
+ );
2713
2847
  renderTaskProfiles(prefs.taskProfile || "auto");
2714
2848
  document.querySelector("#startUrl").value = prefs.startUrl || "";
2715
2849
  document.querySelector("#allowedDomains").value = prefs.allowedDomains || "";