@runeya/runeya 2.0.152 → 2.0.154

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.
Files changed (108) hide show
  1. package/{chat-Z7SFBEXS.js → chat-COP27XB3.js} +3 -3
  2. package/{chunk-EUTW2S4P.js → chunk-OLHL5NFP.js} +7 -4
  3. package/chunk-OLHL5NFP.js.map +1 -0
  4. package/{chunk-QJSIN26R.js → chunk-VKYSHTYT.js} +443 -78
  5. package/chunk-VKYSHTYT.js.map +1 -0
  6. package/{companion-ZDEGNZ2G.js → companion-3MW23ONE.js} +2 -2
  7. package/index.js +7 -7
  8. package/{install-gate-TSCXUCYE.js → install-gate-3UVXPA57.js} +2 -2
  9. package/package.json +1 -1
  10. package/{src-NWPED6TK.js → src-K4E6GUCZ.js} +37 -25
  11. package/src-K4E6GUCZ.js.map +1 -0
  12. package/web/assets/{AgentScalarChatInterface.vue-0pB6D9jL.js → AgentScalarChatInterface.vue-BtA-f77q.js} +3 -3
  13. package/web/assets/{AgentTerminalPage-D1c5A1Ol.js → AgentTerminalPage-2Ky-vEkw.js} +1 -1
  14. package/web/assets/AiChatModals-BV3vIIqN.js +4 -0
  15. package/web/assets/AiChatModals-DS5R3Oz1.css +1 -0
  16. package/web/assets/AppLayout-BgPr-urw.css +1 -0
  17. package/web/assets/AppLayout-DsczwAFI.js +1 -0
  18. package/web/assets/{AuditPage-B_Ee8f0S.js → AuditPage-DoKHFRJ7.js} +1 -1
  19. package/web/assets/{CloudAccountPage-D1L8Z_V3.js → CloudAccountPage-CDQNalgx.js} +1 -1
  20. package/web/assets/{CloudMessagingPage-CyZF0sdY.js → CloudMessagingPage-DduCz3KB.js} +1 -1
  21. package/web/assets/{DashboardPage-BeWd0GWF.js → DashboardPage-ClJR5DiP.js} +1 -1
  22. package/web/assets/{DirectoryPickerDialog-BGhbB66i.js → DirectoryPickerDialog-CVVg3SzI.js} +1 -1
  23. package/web/assets/{EditorIcon-CcU8Z46_.js → EditorIcon-DksI79ZN.js} +1 -1
  24. package/web/assets/{EnvironmentForm-B0FYydAK.js → EnvironmentForm-eMpLFv_-.js} +1 -1
  25. package/web/assets/{EnvironmentFormPage-BdTdOUBD.js → EnvironmentFormPage-DXH2lgk_.js} +1 -1
  26. package/web/assets/{EnvironmentListPage-Cy9Kg5pY.js → EnvironmentListPage-D-g_8lQw.js} +1 -1
  27. package/web/assets/{FlagChip-maG1f7pl.js → FlagChip-DjC14z4J.js} +1 -1
  28. package/web/assets/KanbanBoardPage-BeSkCN6I.js +4 -0
  29. package/web/assets/KanbanBoardPage-BrFqn87r.css +1 -0
  30. package/web/assets/{KanbanListPage-BigioApQ.css → KanbanListPage-DCK-N3nw.css} +1 -1
  31. package/web/assets/{KanbanListPage-BOxLKDpW.js → KanbanListPage-DnhCC5ET.js} +1 -1
  32. package/web/assets/{LaunchProjectPage-zlY3PK3t.js → LaunchProjectPage-DyNqhic1.js} +1 -1
  33. package/web/assets/{LaunchServicesPage-BsM0bP8N.js → LaunchServicesPage-bI7pVeU7.js} +1 -1
  34. package/web/assets/{LaunchShell-BznQ7GLD.js → LaunchShell-CokT4E5l.js} +1 -1
  35. package/web/assets/{LogViewer-9e3e9X-R.js → LogViewer-Cs3Y8vaZ.js} +1 -1
  36. package/web/assets/{OrgKeyPrompt-Ckq1sJ8-.js → OrgKeyPrompt-DtgelhnI.js} +1 -1
  37. package/web/assets/{ProcessControls-Br_aLfW7.js → ProcessControls-CxlDLGiO.js} +1 -1
  38. package/web/assets/{ProcessDetailPage-D3BNcm7N.js → ProcessDetailPage-D93cKCHb.js} +1 -1
  39. package/web/assets/{ProcessMetrics-D-rQkzqK.js → ProcessMetrics-C_7_boMv.js} +1 -1
  40. package/web/assets/{ProjectCreateDialog-BzJRqXun.js → ProjectCreateDialog-Bko_A5wp.js} +1 -1
  41. package/web/assets/{ProjectLocationGate-DjBwSAKc.js → ProjectLocationGate-jsy8WetY.js} +1 -1
  42. package/web/assets/{ProjectPublishDialog-yakoPg1D.js → ProjectPublishDialog-7kR1zZAo.js} +1 -1
  43. package/web/assets/{RandomJobTestPage-Dh0-shVq.js → RandomJobTestPage-Dp9prwa7.js} +1 -1
  44. package/web/assets/{ScenarioEditorPage-DEdlwmf3.js → ScenarioEditorPage-fv9yLH75.js} +1 -1
  45. package/web/assets/{SendToChatDialog-_ai4UP8l.js → SendToChatDialog-BVSbXcbf.js} +1 -1
  46. package/web/assets/{ServiceControlsBar-Bo-jHhJK.js → ServiceControlsBar-DFECGWL8.js} +1 -1
  47. package/web/assets/{ServiceDetailPage-CWB3XIMA.js → ServiceDetailPage-BQA74240.js} +1 -1
  48. package/web/assets/{ServiceFormPage-BzARwyAF.js → ServiceFormPage-ChsRsoLt.js} +1 -1
  49. package/web/assets/SettingsPage-BXKsN9Py.js +2086 -0
  50. package/web/assets/{SettingsPage-NpFX7FAy.css → SettingsPage-Dsuzjo8r.css} +1 -1
  51. package/web/assets/{UnlockPage-C08Itng5.js → UnlockPage-DhEN16uX.js} +1 -1
  52. package/web/assets/{VaultMigrationPage-BLZc5pXH.js → VaultMigrationPage-BgRNUuTP.js} +1 -1
  53. package/web/assets/{VersionBadge-lBKbqt98.js → VersionBadge-CXJ-2h5V.js} +1 -1
  54. package/web/assets/{WikiPage-RCU0T1IS.js → WikiPage-BT5a9d5Q.js} +1 -1
  55. package/web/assets/{WikiView-BV8MZ8qm.js → WikiView-BNReLsq5.js} +1 -1
  56. package/web/assets/{add-scalar-classes-CvUuKrM6.js → add-scalar-classes-C_lKfiMa.js} +2 -2
  57. package/web/assets/{agent.store-yrwtq9oP.js → agent.store-B6OM7edC.js} +1 -1
  58. package/web/assets/{auth-LbzJj0JF.js → auth-5bsz5kSC.js} +2 -2
  59. package/web/assets/{cloud-chat.store-y_9urSQZ.js → cloud-chat.store-Di38xQRJ.js} +1 -1
  60. package/web/assets/{cloud-client-DhjJyZHU.js → cloud-client-CEVx-bg6.js} +2 -2
  61. package/web/assets/{cloud-session-BlihYcy2.js → cloud-session-w6eNQX1X.js} +2 -2
  62. package/web/assets/composables-CCbqJqnd.js +1 -0
  63. package/web/assets/i18n-DbgjJIf8.js +1 -0
  64. package/web/assets/index-DB3-fmza.css +1 -0
  65. package/web/assets/{index-Ci2pSJuu.js → index-mvpLwxNT.js} +74 -74
  66. package/web/assets/modal-Bslt0NZi.js +5 -0
  67. package/web/assets/{notification.store-Bp37Xsss.js → notification.store-CAG1kWYq.js} +1 -1
  68. package/web/assets/{project-launch.store-AB257LRF.js → project-launch.store-Im5SMon9.js} +1 -1
  69. package/web/assets/{project.store-qBZ_R1td.js → project.store-CITAykC3.js} +1 -1
  70. package/web/assets/{scenario.store-B5ybSSzx.js → scenario.store-C9sSoRzb.js} +2 -2
  71. package/web/assets/{service.store-BrKRjCtb.js → service.store-CZJuuJiv.js} +1 -1
  72. package/web/assets/{settings-scope-BSdUKgjx.js → settings-scope-vcJEMNnD.js} +1 -1
  73. package/web/assets/{settings.store-C8guF9Fl.js → settings.store-CydW_2jh.js} +1 -1
  74. package/web/assets/src-CIOvFTGI.js +1960 -0
  75. package/web/assets/{src-Br-M9ln7.js → src-COZZWm-m.js} +1 -1
  76. package/web/assets/{src-DOcCcTQv.js → src-Ck_MutzB.js} +1 -1
  77. package/web/assets/src-DOziuDw8.css +1 -0
  78. package/web/assets/src-DrVR8mhy.css +1 -0
  79. package/web/assets/{trpc-CAKhySL_.js → trpc-BQ0mLnfI.js} +3 -3
  80. package/web/assets/{useCloudOrg-B_JPalTv.js → useCloudOrg-CQvEbrZv.js} +1 -1
  81. package/web/assets/{useEnvVarAutocomplete-DCZCMfLC.js → useEnvVarAutocomplete-hy3rO3SN.js} +1 -1
  82. package/web/assets/{useProjectCatalog-CCJAkQi_.js → useProjectCatalog-CWHpf3Yj.js} +1 -1
  83. package/web/assets/{useProjectCloudOrg-CG4CriU2.js → useProjectCloudOrg-B3rNPes5.js} +1 -1
  84. package/web/assets/{useServiceUrlHost-C1gL6Mjg.js → useServiceUrlHost-BFfie_6h.js} +1 -1
  85. package/web/index.html +19 -19
  86. package/chunk-EUTW2S4P.js.map +0 -1
  87. package/chunk-QJSIN26R.js.map +0 -1
  88. package/mcp-server/external.d.ts +0 -44
  89. package/mcp-server/index.d.ts +0 -1
  90. package/mcp-server/launch.d.ts +0 -89
  91. package/src-NWPED6TK.js.map +0 -1
  92. package/web/assets/AiChatModals-B_Cn5wNS.js +0 -4
  93. package/web/assets/AiChatModals-BmWxg-Sr.css +0 -1
  94. package/web/assets/AppLayout-BsFZ6nG3.js +0 -1
  95. package/web/assets/AppLayout-Djk3-XbN.css +0 -1
  96. package/web/assets/KanbanBoardPage-BOEK_sKt.js +0 -4
  97. package/web/assets/KanbanBoardPage-Bb_8xU-I.css +0 -1
  98. package/web/assets/SettingsPage-CsIXEQrW.js +0 -2086
  99. package/web/assets/composables-DguCUR3O.js +0 -1
  100. package/web/assets/i18n-DxVwqoxL.js +0 -1
  101. package/web/assets/index-Ck_3m_I9.css +0 -1
  102. package/web/assets/modal-KpWdqKSY.js +0 -5
  103. package/web/assets/src-Dbg3CMY2.css +0 -1
  104. package/web/assets/src-dQGrnDw0.css +0 -1
  105. package/web/assets/src-recq0B6o.js +0 -1960
  106. /package/{chat-Z7SFBEXS.js.map → chat-COP27XB3.js.map} +0 -0
  107. /package/{companion-ZDEGNZ2G.js.map → companion-3MW23ONE.js.map} +0 -0
  108. /package/{install-gate-TSCXUCYE.js.map → install-gate-3UVXPA57.js.map} +0 -0
@@ -55,7 +55,7 @@ import {
55
55
  syncConversationFromClaudeTranscript,
56
56
  verifyJwt,
57
57
  waitForPtyInput
58
- } from "./chunk-EUTW2S4P.js";
58
+ } from "./chunk-OLHL5NFP.js";
59
59
  import {
60
60
  conversationSectionStore,
61
61
  projectStore
@@ -134,7 +134,7 @@ import { z as z6 } from "zod";
134
134
  import Anthropic2 from "@anthropic-ai/sdk";
135
135
  import OpenAI2 from "openai";
136
136
  import { fileURLToPath as fileURLToPath2 } from "url";
137
- import { randomUUID as randomUUID5 } from "crypto";
137
+ import { randomUUID as randomUUID6 } from "crypto";
138
138
 
139
139
  // ../../packages/mcp-server/src/launch.ts
140
140
  import { fileURLToPath } from "url";
@@ -1557,6 +1557,105 @@ var serviceRouter = router({
1557
1557
  })
1558
1558
  });
1559
1559
 
1560
+ // ../server/src/services/conversation-secret-env-store.ts
1561
+ import { mkdir as mkdir2, readFile as readFile3, rm } from "fs/promises";
1562
+ import { dirname } from "path";
1563
+ var DIRNAME = "conversation-secret-env";
1564
+ var SECRET_ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
1565
+ var SECRET_ENV_MAX_ENTRIES = 50;
1566
+ var SECRET_ENV_MAX_KEY_LENGTH = 128;
1567
+ var SECRET_ENV_MAX_VALUE_LENGTH = 8192;
1568
+ var CONVERSATION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
1569
+ function dict(entries = []) {
1570
+ const out = /* @__PURE__ */ Object.create(null);
1571
+ for (const [k, v] of entries) out[k] = v;
1572
+ return out;
1573
+ }
1574
+ function isSecretFile(value) {
1575
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
1576
+ }
1577
+ var ConversationSecretEnvStore = class {
1578
+ /** File en mémoire : sérialise les écritures de ce process. */
1579
+ queue = Promise.resolve();
1580
+ pathOf(conversationId) {
1581
+ if (!CONVERSATION_ID_PATTERN.test(conversationId)) {
1582
+ throw new Error("Identifiant de conversation invalide");
1583
+ }
1584
+ return machineFilePath(`${DIRNAME}/${conversationId}.json`);
1585
+ }
1586
+ /**
1587
+ * Seul `ENOENT` veut dire « rien d'enregistré ». Toute autre erreur (lecture
1588
+ * impossible, contenu invalide) remonte : la rendre en `{}` ferait écraser
1589
+ * le fichier à l'écriture suivante.
1590
+ */
1591
+ async read(conversationId) {
1592
+ let raw;
1593
+ try {
1594
+ raw = await readFile3(this.pathOf(conversationId), "utf-8");
1595
+ } catch (err) {
1596
+ if (err.code === "ENOENT") return {};
1597
+ throw err;
1598
+ }
1599
+ const parsed = JSON.parse(raw);
1600
+ if (!isSecretFile(parsed)) throw new Error(`${DIRNAME}/${conversationId}.json : contenu inattendu, \xE9criture refus\xE9e`);
1601
+ return dict(Object.entries(parsed));
1602
+ }
1603
+ serialize(fn) {
1604
+ const next = this.queue.then(fn, fn);
1605
+ this.queue = next.catch(() => void 0);
1606
+ return next;
1607
+ }
1608
+ /** Les noms enregistrés pour une conversation, jamais les valeurs. */
1609
+ async keys(conversationId) {
1610
+ return Object.keys(await this.read(conversationId));
1611
+ }
1612
+ /** Nom → valeur en clair, prêt à entrer dans l'environnement d'un process. */
1613
+ async resolve(conversationId) {
1614
+ const stored = await this.read(conversationId);
1615
+ const result = dict();
1616
+ for (const [key, encrypted] of Object.entries(stored)) {
1617
+ try {
1618
+ result[key] = decryptValue(encrypted);
1619
+ } catch (err) {
1620
+ console.warn(`[conversation-secret-env] ${key} illisible pour ${conversationId} :`, err.message);
1621
+ }
1622
+ }
1623
+ return result;
1624
+ }
1625
+ /**
1626
+ * Remplace l'ensemble des variables de la conversation. Un nom absent de
1627
+ * `entries` est supprimé ; une ligne sans valeur garde celle de la base.
1628
+ */
1629
+ replace(conversationId, entries) {
1630
+ return this.serialize(async () => {
1631
+ const previous = await this.read(conversationId);
1632
+ const next = dict();
1633
+ for (const { key, value } of entries) {
1634
+ if (value !== void 0 && value !== "") {
1635
+ next[key] = encryptValue(value);
1636
+ } else if (Object.hasOwn(previous, key)) {
1637
+ next[key] = previous[key];
1638
+ } else {
1639
+ throw new Error(`Aucune valeur pour ${key}`);
1640
+ }
1641
+ }
1642
+ const path = this.pathOf(conversationId);
1643
+ if (Object.keys(next).length === 0) {
1644
+ await rm(path, { force: true });
1645
+ } else {
1646
+ await mkdir2(dirname(path), { recursive: true });
1647
+ await atomicWriteSecure(path, JSON.stringify(next, null, 2));
1648
+ }
1649
+ return Object.keys(next);
1650
+ });
1651
+ }
1652
+ /** La conversation disparaît : ses secrets partent avec elle. */
1653
+ remove(conversationId) {
1654
+ return this.serialize(() => rm(this.pathOf(conversationId), { force: true }));
1655
+ }
1656
+ };
1657
+ var conversationSecretEnvStore = new ConversationSecretEnvStore();
1658
+
1560
1659
  // ../server/src/services/scenario-store.ts
1561
1660
  function normalizeNode(raw) {
1562
1661
  const aiCli = raw.aiCli ?? "claude-code";
@@ -1718,7 +1817,7 @@ var ScenarioStore = class {
1718
1817
  var scenarioStore = new ScenarioStore();
1719
1818
 
1720
1819
  // ../server/src/services/scenario-run-store.ts
1721
- import { mkdir as mkdir2, rm, readFile as readFile3 } from "fs/promises";
1820
+ import { mkdir as mkdir3, rm as rm2, readFile as readFile4 } from "fs/promises";
1722
1821
  import { join as join3 } from "path";
1723
1822
  function sanitizeSlug(value) {
1724
1823
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "member";
@@ -1735,7 +1834,7 @@ var ScenarioRunStore = class _ScenarioRunStore {
1735
1834
  }
1736
1835
  async initializeConversationWorkspace(conversationId, scenario, userInput) {
1737
1836
  const dir = this.getRunDir(conversationId);
1738
- await mkdir2(dir, { recursive: true });
1837
+ await mkdir3(dir, { recursive: true });
1739
1838
  await this.saveScenarioSnapshot(conversationId, scenario);
1740
1839
  const nodesById = new Map(scenario.nodes.map((p) => [p.id, p]));
1741
1840
  const teamMap = new Map(scenario.groups.map((t2) => [t2.id, t2.name]));
@@ -1791,7 +1890,7 @@ ${userInput.trim() || "(empty)"}
1791
1890
  }
1792
1891
  }
1793
1892
  async deleteRunDir(conversationId) {
1794
- await rm(this.getRunDir(conversationId), { recursive: true, force: true });
1893
+ await rm2(this.getRunDir(conversationId), { recursive: true, force: true });
1795
1894
  this.activeRuns.delete(conversationId);
1796
1895
  }
1797
1896
  ensureRunState(conversationId) {
@@ -1847,7 +1946,7 @@ ${userInput.trim() || "(empty)"}
1847
1946
  if (!runState) return;
1848
1947
  try {
1849
1948
  const dir = this.getRunDir(conversationId);
1850
- await mkdir2(dir, { recursive: true });
1949
+ await mkdir3(dir, { recursive: true });
1851
1950
  const data = {
1852
1951
  nodeStates: { ...runState.nodeStates },
1853
1952
  nodeOutputs: { ...runState.nodeOutputs },
@@ -1866,7 +1965,7 @@ ${userInput.trim() || "(empty)"}
1866
1965
  async readUserRequest(conversationId) {
1867
1966
  try {
1868
1967
  const dir = this.getRunDir(conversationId);
1869
- const content = await readFile3(join3(dir, "USER_REQUEST.md"), "utf-8");
1968
+ const content = await readFile4(join3(dir, "USER_REQUEST.md"), "utf-8");
1870
1969
  return content.replace(/^#\s*User request\s*\n+/, "").trim() || null;
1871
1970
  } catch {
1872
1971
  return null;
@@ -1875,14 +1974,14 @@ ${userInput.trim() || "(empty)"}
1875
1974
  async readReadme(conversationId) {
1876
1975
  try {
1877
1976
  const dir = this.getRunDir(conversationId);
1878
- return await readFile3(join3(dir, "README.md"), "utf-8");
1977
+ return await readFile4(join3(dir, "README.md"), "utf-8");
1879
1978
  } catch {
1880
1979
  return null;
1881
1980
  }
1882
1981
  }
1883
1982
  async saveScenarioSnapshot(conversationId, scenario) {
1884
1983
  const dir = this.getRunDir(conversationId);
1885
- await mkdir2(dir, { recursive: true });
1984
+ await mkdir3(dir, { recursive: true });
1886
1985
  await atomicWriteJsonSecure(
1887
1986
  join3(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
1888
1987
  scenario,
@@ -1899,12 +1998,17 @@ var scenarioRunStore = new ScenarioRunStore();
1899
1998
  // ../server/src/services/ai-provider-utils.ts
1900
1999
  import Anthropic from "@anthropic-ai/sdk";
1901
2000
  import OpenAI from "openai";
2001
+ var OLLAMA_BASE_URL = "http://localhost:11434";
1902
2002
  var OPENAI_COMPAT_BASE_URLS = {
1903
2003
  openai: "https://api.openai.com/v1",
1904
2004
  groq: "https://api.groq.com/openai/v1",
1905
2005
  mistral: "https://api.mistral.ai/v1",
1906
- ollama: "http://localhost:11434/v1"
2006
+ ollama: `${OLLAMA_BASE_URL}/v1`
1907
2007
  };
2008
+ function truncationError(finishReason) {
2009
+ if (finishReason !== "length") return null;
2010
+ return new Error("The response was cut off: the model reached its context or output token limit (finish_reason: length).");
2011
+ }
1908
2012
  var DEFAULT_MODELS = {
1909
2013
  anthropic: "claude-opus-4-6",
1910
2014
  openai: "gpt-4o",
@@ -1935,6 +2039,159 @@ async function fetchProviderModels(provider, plaintextKey) {
1935
2039
  return [];
1936
2040
  }
1937
2041
 
2042
+ // ../server/src/services/ollama-chat.ts
2043
+ import { randomUUID as randomUUID3 } from "crypto";
2044
+ var OLLAMA_RESPONSE_MARGIN = 8192;
2045
+ var CHARS_PER_TOKEN = 4;
2046
+ var SHOW_TIMEOUT_MS = 5e3;
2047
+ var CHAT_TIMEOUT_MS = 10 * 6e4;
2048
+ var contextLimits = /* @__PURE__ */ new Map();
2049
+ async function modelContextLimit(model) {
2050
+ if (contextLimits.has(model)) return contextLimits.get(model);
2051
+ let limit = null;
2052
+ try {
2053
+ const res = await fetch(`${OLLAMA_BASE_URL}/api/show`, {
2054
+ method: "POST",
2055
+ headers: { "Content-Type": "application/json" },
2056
+ body: JSON.stringify({ model }),
2057
+ signal: AbortSignal.timeout(SHOW_TIMEOUT_MS)
2058
+ });
2059
+ if (res.ok) {
2060
+ const body = await res.json();
2061
+ for (const [key, value] of Object.entries(body.model_info ?? {})) {
2062
+ if (key.endsWith(".context_length") && typeof value === "number" && value > 0) limit = value;
2063
+ }
2064
+ }
2065
+ } catch {
2066
+ }
2067
+ if (limit !== null) contextLimits.set(model, limit);
2068
+ return limit;
2069
+ }
2070
+ function computeNumCtx(promptChars, modelLimit) {
2071
+ const wanted = Math.ceil(promptChars / CHARS_PER_TOKEN) + OLLAMA_RESPONSE_MARGIN;
2072
+ return modelLimit ? Math.min(wanted, modelLimit) : wanted;
2073
+ }
2074
+ function textOf(content) {
2075
+ if (typeof content === "string") return { text: content, images: [] };
2076
+ const images = [];
2077
+ const texts = [];
2078
+ for (const part of content ?? []) {
2079
+ if (part.type === "text") texts.push(part.text);
2080
+ else if (part.type === "image_url") {
2081
+ const base64 = part.image_url.url.split(",", 2)[1];
2082
+ if (base64) images.push(base64);
2083
+ }
2084
+ }
2085
+ return { text: texts.join("\n"), images };
2086
+ }
2087
+ function toOllamaMessages(messages) {
2088
+ const toolNames = /* @__PURE__ */ new Map();
2089
+ return messages.map((m) => {
2090
+ if (m.role === "tool") {
2091
+ return { role: "tool", content: textOf(m.content).text, tool_name: toolNames.get(m.tool_call_id) };
2092
+ }
2093
+ if (m.role === "assistant") {
2094
+ const toolCalls = (m.tool_calls ?? []).flatMap((tc) => {
2095
+ if (tc.type !== "function") return [];
2096
+ toolNames.set(tc.id, tc.function.name);
2097
+ let args = {};
2098
+ try {
2099
+ args = JSON.parse(tc.function.arguments || "{}");
2100
+ } catch {
2101
+ }
2102
+ return [{ function: { name: tc.function.name, arguments: args } }];
2103
+ });
2104
+ const msg2 = { role: "assistant", content: textOf(m.content ?? "").text };
2105
+ if (toolCalls.length > 0) msg2.tool_calls = toolCalls;
2106
+ return msg2;
2107
+ }
2108
+ const { text, images } = textOf(m.content);
2109
+ const role = m.role === "developer" ? "system" : m.role;
2110
+ const msg = { role, content: text };
2111
+ if (images.length > 0) msg.images = images;
2112
+ return msg;
2113
+ });
2114
+ }
2115
+ function chunk(delta, finishReason = null) {
2116
+ return {
2117
+ id: "",
2118
+ object: "chat.completion.chunk",
2119
+ created: 0,
2120
+ model: "",
2121
+ choices: [{ index: 0, delta, finish_reason: finishReason }]
2122
+ };
2123
+ }
2124
+ async function* streamOllamaChat(params) {
2125
+ const messages = toOllamaMessages(params.messages);
2126
+ const promptChars = JSON.stringify(messages).length + JSON.stringify(params.tools).length;
2127
+ const numCtx = computeNumCtx(promptChars, await modelContextLimit(params.model));
2128
+ const timeout = AbortSignal.timeout(CHAT_TIMEOUT_MS);
2129
+ const res = await fetch(`${OLLAMA_BASE_URL}/api/chat`, {
2130
+ method: "POST",
2131
+ headers: { "Content-Type": "application/json" },
2132
+ body: JSON.stringify({
2133
+ model: params.model,
2134
+ messages,
2135
+ tools: params.tools,
2136
+ stream: true,
2137
+ options: { num_ctx: numCtx, temperature: params.temperature ?? 0 }
2138
+ }),
2139
+ signal: params.signal ? AbortSignal.any([params.signal, timeout]) : timeout
2140
+ });
2141
+ if (!res.ok || !res.body) {
2142
+ throw new Error(`Ollama error ${res.status}: ${(await res.text().catch(() => "")).slice(0, 500)}`);
2143
+ }
2144
+ const decoder = new TextDecoder();
2145
+ let buffer = "";
2146
+ let toolIndex = 0;
2147
+ const handle = function* (line) {
2148
+ if (!line.trim()) return;
2149
+ const data = JSON.parse(line);
2150
+ if (data.error) throw new Error(`Ollama error: ${data.error}`);
2151
+ const content = data.message?.content;
2152
+ if (content) yield chunk({ content });
2153
+ for (const tc of data.message?.tool_calls ?? []) {
2154
+ yield chunk({
2155
+ tool_calls: [{
2156
+ index: toolIndex++,
2157
+ id: `call_${randomUUID3()}`,
2158
+ type: "function",
2159
+ function: { name: tc.function.name, arguments: JSON.stringify(tc.function.arguments ?? {}) }
2160
+ }]
2161
+ });
2162
+ }
2163
+ if (data.done) yield chunk({}, data.done_reason === "length" ? "length" : "stop");
2164
+ };
2165
+ for await (const bytes of res.body) {
2166
+ buffer += decoder.decode(bytes, { stream: true });
2167
+ let nl;
2168
+ while ((nl = buffer.indexOf("\n")) !== -1) {
2169
+ const line = buffer.slice(0, nl);
2170
+ buffer = buffer.slice(nl + 1);
2171
+ yield* handle(line);
2172
+ }
2173
+ }
2174
+ yield* handle(buffer + decoder.decode());
2175
+ }
2176
+ var OLLAMA_MODEL_PREFIX = "ollama:";
2177
+ async function listOllamaModels() {
2178
+ try {
2179
+ const res = await fetch(`${OLLAMA_BASE_URL}/api/tags`, { signal: AbortSignal.timeout(SHOW_TIMEOUT_MS) });
2180
+ if (!res.ok) return [];
2181
+ const body = await res.json();
2182
+ return (body.models ?? []).flatMap((m) => typeof m.name === "string" ? [m.name] : []);
2183
+ } catch {
2184
+ return [];
2185
+ }
2186
+ }
2187
+ function claudeOllamaTarget(model) {
2188
+ if (!model?.startsWith(OLLAMA_MODEL_PREFIX)) return null;
2189
+ return {
2190
+ model: model.slice(OLLAMA_MODEL_PREFIX.length),
2191
+ env: { ANTHROPIC_BASE_URL: OLLAMA_BASE_URL, ANTHROPIC_AUTH_TOKEN: "ollama", ANTHROPIC_API_KEY: "" }
2192
+ };
2193
+ }
2194
+
1938
2195
  // ../server/src/services/settings-scope.ts
1939
2196
  async function vaultOfProject(projectId) {
1940
2197
  if (!projectId) return void 0;
@@ -1945,8 +2202,8 @@ async function vaultOfProject(projectId) {
1945
2202
  }
1946
2203
 
1947
2204
  // ../server/src/services/image-storage.service.ts
1948
- import { randomUUID as randomUUID3 } from "crypto";
1949
- import { mkdir as mkdir3, writeFile, readFile as readFile4, unlink as unlink2, readdir as readdir3 } from "fs/promises";
2205
+ import { randomUUID as randomUUID4 } from "crypto";
2206
+ import { mkdir as mkdir4, writeFile, readFile as readFile5, unlink as unlink2, readdir as readdir3 } from "fs/promises";
1950
2207
  import { existsSync as existsSync2 } from "fs";
1951
2208
  import { join as join4, extname, resolve, sep } from "path";
1952
2209
  var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
@@ -1965,7 +2222,7 @@ var ImageStorageService = class {
1965
2222
  this.dir = dir;
1966
2223
  }
1967
2224
  async ensureTmpDir() {
1968
- await mkdir3(this.dir, { recursive: true, mode: 448 });
2225
+ await mkdir4(this.dir, { recursive: true, mode: 448 });
1969
2226
  }
1970
2227
  async saveImage(buffer, originalName, mimeType, contextId) {
1971
2228
  if (!ALLOWED_MIME_TYPES.has(mimeType)) {
@@ -1982,7 +2239,7 @@ var ImageStorageService = class {
1982
2239
  throw new Error(`Unsupported extension: ${ext}`);
1983
2240
  }
1984
2241
  await this.ensureTmpDir();
1985
- const imageId = randomUUID3();
2242
+ const imageId = randomUUID4();
1986
2243
  const safeExt = ext.replace(/[^a-z.]/g, "");
1987
2244
  const fileName = `${imageId}${safeExt}`;
1988
2245
  const filePath = join4(this.dir, fileName);
@@ -2011,7 +2268,7 @@ var ImageStorageService = class {
2011
2268
  const files = await readdir3(this.dir);
2012
2269
  const metaFile = files.find((f) => f.startsWith(imageId) && f.endsWith(".meta.json"));
2013
2270
  if (!metaFile) return null;
2014
- const raw = JSON.parse(await readFile4(join4(this.dir, metaFile), "utf-8"));
2271
+ const raw = JSON.parse(await readFile5(join4(this.dir, metaFile), "utf-8"));
2015
2272
  if (expectedContextId !== void 0 && raw.contextId !== expectedContextId) return null;
2016
2273
  if (!existsSync2(raw.filePath)) return null;
2017
2274
  return { filePath: raw.filePath, mimeType: raw.mimeType, contextId: raw.contextId };
@@ -2037,7 +2294,7 @@ var ImageStorageService = class {
2037
2294
  await Promise.all(
2038
2295
  files.filter((f) => f.endsWith(".meta.json")).map(async (file) => {
2039
2296
  try {
2040
- const raw = JSON.parse(await readFile4(join4(this.dir, file), "utf-8"));
2297
+ const raw = JSON.parse(await readFile5(join4(this.dir, file), "utf-8"));
2041
2298
  if (raw.contextId === contextId) {
2042
2299
  await unlink2(raw.filePath).catch(() => {
2043
2300
  });
@@ -2052,7 +2309,7 @@ var ImageStorageService = class {
2052
2309
  async readImageAsBase64(imageId, expectedContextId) {
2053
2310
  const result = await this.getImagePath(imageId, expectedContextId);
2054
2311
  if (!result) return null;
2055
- const buffer = await readFile4(result.filePath);
2312
+ const buffer = await readFile5(result.filePath);
2056
2313
  return { base64: buffer.toString("base64"), mimeType: result.mimeType };
2057
2314
  }
2058
2315
  async listExpiredImages(ttlMs = IMAGE_TTL_MS) {
@@ -2067,7 +2324,7 @@ var ImageStorageService = class {
2067
2324
  for (const file of files) {
2068
2325
  if (!file.endsWith(".meta.json")) continue;
2069
2326
  try {
2070
- const raw = JSON.parse(await readFile4(join4(this.dir, file), "utf-8"));
2327
+ const raw = JSON.parse(await readFile5(join4(this.dir, file), "utf-8"));
2071
2328
  if (now - raw.createdAt > ttlMs) expired.push(raw);
2072
2329
  } catch {
2073
2330
  }
@@ -2136,8 +2393,8 @@ import { basename } from "path";
2136
2393
  import { TRPCError as TRPCError3 } from "@trpc/server";
2137
2394
 
2138
2395
  // ../server/src/services/cloud-session-store.ts
2139
- import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir4, chmod, unlink as unlink3 } from "fs/promises";
2140
- import { dirname } from "path";
2396
+ import { readFile as readFile6, writeFile as writeFile2, mkdir as mkdir5, chmod, unlink as unlink3 } from "fs/promises";
2397
+ import { dirname as dirname2 } from "path";
2141
2398
  import { randomBytes as randomBytes2, createHash, timingSafeEqual as timingSafeEqual2 } from "crypto";
2142
2399
  var FILENAME = "cloud-session.json";
2143
2400
  var STATE_TTL_MS = 5 * 60 * 1e3;
@@ -2168,9 +2425,9 @@ var CloudSessionStore = class {
2168
2425
  */
2169
2426
  async readFromDisk() {
2170
2427
  try {
2171
- return await readFile5(await this.getFilePath(), "utf-8");
2428
+ return await readFile6(await this.getFilePath(), "utf-8");
2172
2429
  } catch {
2173
- return readFile5(this.getLegacyFilePath(), "utf-8");
2430
+ return readFile6(this.getLegacyFilePath(), "utf-8");
2174
2431
  }
2175
2432
  }
2176
2433
  /**
@@ -2244,7 +2501,7 @@ var CloudSessionStore = class {
2244
2501
  }
2245
2502
  async save() {
2246
2503
  const filePath = await this.getFilePath();
2247
- await mkdir4(dirname(filePath), { recursive: true });
2504
+ await mkdir5(dirname2(filePath), { recursive: true });
2248
2505
  const payload = { version: 2, accounts: this.accounts };
2249
2506
  const tmpPath = filePath + ".tmp";
2250
2507
  await writeFile2(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
@@ -2385,7 +2642,7 @@ var cloudSessionStore = new CloudSessionStore();
2385
2642
 
2386
2643
  // ../server/src/services/lan-mode.ts
2387
2644
  import { networkInterfaces } from "os";
2388
- import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir5 } from "fs/promises";
2645
+ import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir6 } from "fs/promises";
2389
2646
  import { join as join5 } from "path";
2390
2647
  import { z as z4 } from "zod";
2391
2648
  var LOOPBACK2 = "127.0.0.1";
@@ -2403,7 +2660,7 @@ function networkFilePath() {
2403
2660
  }
2404
2661
  async function readFileState() {
2405
2662
  try {
2406
- return NetworkFileSchema.parse(JSON.parse(await readFile6(networkFilePath(), "utf-8")));
2663
+ return NetworkFileSchema.parse(JSON.parse(await readFile7(networkFilePath(), "utf-8")));
2407
2664
  } catch {
2408
2665
  return { instances: {} };
2409
2666
  }
@@ -2414,7 +2671,7 @@ async function readPersisted() {
2414
2671
  }
2415
2672
  async function writePersisted(lanEnabled) {
2416
2673
  const root = machineRoot();
2417
- await mkdir5(root, { recursive: true });
2674
+ await mkdir6(root, { recursive: true });
2418
2675
  const state = await readFileState();
2419
2676
  state.instances[instanceKey()] = { lanEnabled };
2420
2677
  const filePath = networkFilePath();
@@ -2932,8 +3189,17 @@ async function resolveOrganizationId(explicit) {
2932
3189
  if (explicit) return explicit;
2933
3190
  const accounts = await cloudSessionStore.list();
2934
3191
  if (accounts.length > 1) {
3192
+ const perAccount = await Promise.all(accounts.map(async (account) => {
3193
+ const res = await callCloud(
3194
+ "/organization/list",
3195
+ { accountId: account.id }
3196
+ );
3197
+ return { account, orgs: res.ok ? res.data ?? [] : [], failure: res.ok ? null : res.reason };
3198
+ }));
3199
+ const listed2 = perAccount.flatMap((r) => r.orgs).map((o) => `${o.name ?? "(sans nom)"} \u2014 ${o.id}`).join(", ");
3200
+ const failures = perAccount.filter((r) => r.failure).map((r) => `${r.account.email} (${r.failure})`);
2935
3201
  throw new KanbanUnavailableError(
2936
- `Ce poste est li\xE9 \xE0 ${accounts.length} comptes cloud : pr\xE9cisez organizationId, sans quoi rien ne dit lequel interroger.`
3202
+ `Ce poste est li\xE9 \xE0 ${accounts.length} comptes cloud : relancez l'appel avec organizationId` + (listed2 ? `, \xE0 choisir parmi : ${listed2}.` : " (celui de la carte ou du tableau concern\xE9).") + (failures.length ? ` Liste ${listed2 ? "partielle" : "indisponible"}, organisations illisibles pour : ${failures.join(" ; ")}.` : "")
2937
3203
  );
2938
3204
  }
2939
3205
  const accountId = await soleAccountScope();
@@ -3559,7 +3825,7 @@ Ne jamais omettre ce tag. Il doit \xEAtre la toute derni\xE8re ligne de ta r\xE9
3559
3825
  `.trim();
3560
3826
 
3561
3827
  // ../server/src/services/ai-runners/claude-pty-runner.ts
3562
- import { randomUUID as randomUUID4 } from "crypto";
3828
+ import { randomUUID as randomUUID5 } from "crypto";
3563
3829
 
3564
3830
  // ../server/src/services/ai-runners/claude-pty-tail.ts
3565
3831
  import { open } from "fs/promises";
@@ -3759,9 +4025,9 @@ function dispatchEntry(entry, state, cbs) {
3759
4025
  async function runClaudeViaPty(params) {
3760
4026
  const { agentId, signal, skipTokenInstructions } = params;
3761
4027
  if (!agentId) throw new Error("runClaudeViaPty requires an agentId");
3762
- const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId : randomUUID4();
4028
+ const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId : randomUUID5();
3763
4029
  const deferPrompt = !!params.mcpConfig;
3764
- const mcpReadyId = deferPrompt ? randomUUID4() : void 0;
4030
+ const mcpReadyId = deferPrompt ? randomUUID5() : void 0;
3765
4031
  const args = buildPtyArgs(params, sessionId, { excludeMcp: true, deferPrompt });
3766
4032
  const agentMgr = await getAgentManager();
3767
4033
  const displayArgs = args.map((a) => {
@@ -4166,14 +4432,14 @@ var ClaudeCodeRunner = class {
4166
4432
  };
4167
4433
  signal.addEventListener("abort", onAbort, { once: true });
4168
4434
  let stderrBuffer = "";
4169
- proc.stdout.on("data", (chunk) => {
4170
- stdoutBuffer += chunk.toString("utf-8");
4435
+ proc.stdout.on("data", (chunk2) => {
4436
+ stdoutBuffer += chunk2.toString("utf-8");
4171
4437
  const lines = stdoutBuffer.split("\n");
4172
4438
  stdoutBuffer = lines.pop() ?? "";
4173
4439
  processClaudeStdoutLines(lines, streamState, params, ctx);
4174
4440
  });
4175
- proc.stderr.on("data", (chunk) => {
4176
- stderrBuffer += chunk.toString("utf-8");
4441
+ proc.stderr.on("data", (chunk2) => {
4442
+ stderrBuffer += chunk2.toString("utf-8");
4177
4443
  });
4178
4444
  proc.on("close", (code) => {
4179
4445
  if (ctx.earlyResult) {
@@ -4827,6 +5093,38 @@ var CodexRunner = class {
4827
5093
  }
4828
5094
  };
4829
5095
 
5096
+ // ../server/src/services/pty-session-stopper.ts
5097
+ var POLL_MS = 50;
5098
+ var EXIT_TIMEOUT_MS = 5e3;
5099
+ async function stopPtysOf(sessionId) {
5100
+ const results = await Promise.allSettled(agentManager.listAgents().map(async (agent) => {
5101
+ const agentId = agent.config.id;
5102
+ const query = () => agentManager.agentQuery(agentId, "ai.findPtyBySession", { sessionId });
5103
+ let pty;
5104
+ try {
5105
+ pty = await query();
5106
+ } catch {
5107
+ return;
5108
+ }
5109
+ if (pty?.status !== "running") return;
5110
+ await agentManager.agentMutation(agentId, "ai.kill", { spawnId: pty.spawnId });
5111
+ const deadline = Date.now() + EXIT_TIMEOUT_MS;
5112
+ while (Date.now() < deadline) {
5113
+ let state;
5114
+ try {
5115
+ state = await query();
5116
+ } catch (err) {
5117
+ throw new Error(`Terminal de la conversation : \xE9tat illisible apr\xE8s l'arr\xEAt (${err.message})`);
5118
+ }
5119
+ if (state?.status !== "running") return;
5120
+ await new Promise((r) => setTimeout(r, POLL_MS));
5121
+ }
5122
+ throw new Error(`Le terminal de la conversation tourne encore ${EXIT_TIMEOUT_MS / 1e3} s apr\xE8s la demande d'arr\xEAt`);
5123
+ }));
5124
+ const failure = results.find((r) => r.status === "rejected");
5125
+ if (failure) throw failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason));
5126
+ }
5127
+
4830
5128
  // ../server/src/services/cli-models.ts
4831
5129
  var CACHE_MS = 5 * 6e4;
4832
5130
  var MAX_PAGES = 10;
@@ -4980,23 +5278,18 @@ async function* runAnthropic(client, model, system, initialMessages, apiKey, aut
4980
5278
  }
4981
5279
  yield { type: "done", text: "" };
4982
5280
  }
4983
- async function* runOpenAI(client, model, initialMessages, apiKey, authToken, signal, anchor = {}) {
5281
+ async function* runOpenAI(createStream, initialMessages, apiKey, authToken, signal, anchor = {}) {
4984
5282
  const messages = [...initialMessages];
4985
5283
  const MAX_TOOL_ROUNDS = 10;
4986
5284
  for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
4987
5285
  const toolCallsAcc = {};
4988
5286
  if (signal?.aborted) return;
4989
- const stream = await client.chat.completions.create({
4990
- model,
4991
- messages,
4992
- tools: CHAT_TOOLS_OPENAI,
4993
- stream: true,
4994
- max_tokens: 16384,
4995
- temperature: 0
4996
- }, { signal });
4997
- for await (const chunk of stream) {
4998
- const choice = chunk.choices[0];
5287
+ const stream = await createStream(messages, signal);
5288
+ let finishReason = null;
5289
+ for await (const chunk2 of stream) {
5290
+ const choice = chunk2.choices[0];
4999
5291
  if (!choice) continue;
5292
+ if (choice.finish_reason) finishReason = choice.finish_reason;
5000
5293
  if (choice.delta?.content) {
5001
5294
  yield { type: "delta", text: choice.delta.content };
5002
5295
  }
@@ -5008,6 +5301,8 @@ async function* runOpenAI(client, model, initialMessages, apiKey, authToken, sig
5008
5301
  toolCallsAcc[tc.index] = acc;
5009
5302
  }
5010
5303
  }
5304
+ const truncated = truncationError(finishReason);
5305
+ if (truncated) throw truncated;
5011
5306
  if (Object.keys(toolCallsAcc).length === 0) {
5012
5307
  yield { type: "done", text: "" };
5013
5308
  return;
@@ -5142,15 +5437,23 @@ async function buildCliSystemContext(conv, jwt) {
5142
5437
  });
5143
5438
  return systemContext;
5144
5439
  }
5145
- async function resolveInjectedEnv(environmentId) {
5146
- if (!environmentId) return void 0;
5147
- try {
5148
- const injected = await environmentResolver.resolveAiInjectedEnv(environmentId);
5149
- return Object.keys(injected).length > 0 ? injected : void 0;
5150
- } catch (err) {
5151
- console.warn("[chat] resolveInjectedEnv failed:", err.message);
5152
- return void 0;
5440
+ async function resolveInjectedEnv(environmentId, secretOwnerId) {
5441
+ let injected = {};
5442
+ if (environmentId) {
5443
+ try {
5444
+ injected = await environmentResolver.resolveAiInjectedEnv(environmentId);
5445
+ } catch (err) {
5446
+ console.warn("[chat] resolveInjectedEnv failed:", err.message);
5447
+ }
5448
+ }
5449
+ if (secretOwnerId) {
5450
+ try {
5451
+ injected = { ...injected, ...await conversationSecretEnvStore.resolve(secretOwnerId) };
5452
+ } catch (err) {
5453
+ console.warn("[chat] secret env unreadable, started without it:", err.message);
5454
+ }
5153
5455
  }
5456
+ return Object.keys(injected).length > 0 ? injected : void 0;
5154
5457
  }
5155
5458
  async function startClaudeCodeSession(convId, prompt, opts = {}) {
5156
5459
  const conv = await conversationStore.get(convId);
@@ -5180,7 +5483,7 @@ async function startClaudeCodeSession(convId, prompt, opts = {}) {
5180
5483
  if (resolvedAgentId) {
5181
5484
  session.agentId = resolvedAgentId;
5182
5485
  }
5183
- const injectedEnv = await resolveInjectedEnv(opts.environmentId);
5486
+ const injectedEnv = await resolveInjectedEnv(opts.environmentId, opts.secretEnvOwnerId ?? convId);
5184
5487
  const done = new ClaudeCodeRunner().run({
5185
5488
  agentId: resolvedAgentId ?? void 0,
5186
5489
  // The conversation's project, not the server's launch directory.
@@ -5247,7 +5550,7 @@ async function startCodexSession(convId, prompt, opts = {}) {
5247
5550
  if (resolvedAgentId) {
5248
5551
  session.agentId = resolvedAgentId;
5249
5552
  }
5250
- const injectedEnv = await resolveInjectedEnv(opts.environmentId);
5553
+ const injectedEnv = await resolveInjectedEnv(opts.environmentId, opts.secretEnvOwnerId ?? convId);
5251
5554
  const done = new CodexRunner().run({
5252
5555
  agentId: resolvedAgentId ?? void 0,
5253
5556
  cwd: await conversationCwd(conv),
@@ -5423,7 +5726,9 @@ var chatRouter = router({
5423
5726
  projectId: Id128Schema.optional(),
5424
5727
  orgId: z6.string().max(128).nullable().optional(),
5425
5728
  // L'agent où la conversation travaille. Absent = agent par défaut.
5426
- agentId: Id128Schema.optional()
5729
+ agentId: Id128Schema.optional(),
5730
+ // Modèle du terminal Claude Code (`ollama:<nom>` : Claude Code sur Ollama).
5731
+ cliModel: z6.string().max(128).optional()
5427
5732
  })).mutation(async ({ input }) => {
5428
5733
  let scenario = null;
5429
5734
  if (input.scenarioId) {
@@ -5440,6 +5745,7 @@ var chatRouter = router({
5440
5745
  );
5441
5746
  const extra = {
5442
5747
  ...input.agentId ? { agentId: input.agentId } : {},
5748
+ ...input.cliModel ? { cliModel: input.cliModel } : {},
5443
5749
  ...companionDefaults
5444
5750
  };
5445
5751
  const conv = await conversationStore.create(
@@ -5510,6 +5816,52 @@ var chatRouter = router({
5510
5816
  forgetCompanionSkip(input.id);
5511
5817
  return conv;
5512
5818
  }),
5819
+ /**
5820
+ * Les noms des variables secrètes d'une conversation. Jamais les valeurs :
5821
+ * elles entrent dans l'environnement du CLI et n'en ressortent pas.
5822
+ */
5823
+ secretEnvKeys: protectedProcedure.input(z6.object({ id: Id128Schema })).query(async ({ input }) => {
5824
+ const existing = await conversationStore.get(input.id);
5825
+ if (!existing) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
5826
+ return conversationSecretEnvStore.keys(input.id);
5827
+ }),
5828
+ /**
5829
+ * Remplace les variables secrètes d'une conversation, puis arrête son
5830
+ * process et celui de son compagnon : un process ne reçoit son
5831
+ * environnement qu'à son lancement, la prochaine ouverture repart avec les
5832
+ * nouvelles valeurs. `value` absent = garder la valeur enregistrée.
5833
+ */
5834
+ setSecretEnv: protectedProcedure.input(z6.object({
5835
+ id: Id128Schema,
5836
+ entries: z6.array(z6.object({
5837
+ key: z6.string().min(1).max(SECRET_ENV_MAX_KEY_LENGTH).regex(SECRET_ENV_KEY_PATTERN),
5838
+ value: z6.string().max(SECRET_ENV_MAX_VALUE_LENGTH).optional()
5839
+ })).max(SECRET_ENV_MAX_ENTRIES).refine((list) => new Set(list.map((e) => e.key)).size === list.length, { message: "Duplicate variable name" })
5840
+ })).mutation(async ({ input }) => {
5841
+ const existing = await conversationStore.get(input.id);
5842
+ if (!existing) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
5843
+ if (existing.isCompanion) {
5844
+ throw new TRPCError4({ code: "BAD_REQUEST", message: "A companion uses the secrets of the conversation it reviews" });
5845
+ }
5846
+ let keys;
5847
+ try {
5848
+ keys = await conversationSecretEnvStore.replace(input.id, input.entries);
5849
+ } catch (err) {
5850
+ throw new TRPCError4({ code: "BAD_REQUEST", message: err.message });
5851
+ }
5852
+ activeSessionRegistry.kill(input.id);
5853
+ const stops = [stopPtysOf(input.id)];
5854
+ if (existing.companionConversationId) {
5855
+ activeSessionRegistry.kill(existing.companionConversationId);
5856
+ stops.push(stopPtysOf(existing.companionConversationId));
5857
+ }
5858
+ try {
5859
+ await Promise.all(stops);
5860
+ } catch (err) {
5861
+ throw new TRPCError4({ code: "INTERNAL_SERVER_ERROR", message: `Variables enregistr\xE9es, mais le process n'a pas pu \xEAtre arr\xEAt\xE9 : ${err.message}` });
5862
+ }
5863
+ return keys;
5864
+ }),
5513
5865
  /**
5514
5866
  * La dernière livraison du compagnon vers cette conversation.
5515
5867
  *
@@ -5614,19 +5966,9 @@ var chatRouter = router({
5614
5966
  forgetReviewProgress(input.id);
5615
5967
  forgetDelivery(input.id);
5616
5968
  forgetCompanionSkip(input.id);
5617
- for (const agent of agentManager.listAgents()) {
5618
- const agentId = agent.config.id;
5619
- agentManager.agentQuery(
5620
- agentId,
5621
- "ai.findPtyBySession",
5622
- { sessionId: input.id }
5623
- ).then((pty) => {
5624
- if (pty?.status === "running") {
5625
- return agentManager.agentMutation(agentId, "ai.kill", { spawnId: pty.spawnId });
5626
- }
5627
- }).catch(() => {
5628
- });
5629
- }
5969
+ await stopPtysOf(input.id).catch(() => {
5970
+ });
5971
+ await conversationSecretEnvStore.remove(input.id);
5630
5972
  await conversationStore.delete(input.id);
5631
5973
  await scenarioRunStore.deleteRunDir(input.id);
5632
5974
  imageStorageService.deleteByContextId(input.id).catch(
@@ -5705,6 +6047,11 @@ var chatRouter = router({
5705
6047
  return [];
5706
6048
  }
5707
6049
  }),
6050
+ /**
6051
+ * POC : les modèles Ollama installés, proposés dans « Choisir votre IA » pour
6052
+ * lancer le terminal Claude Code sur Ollama. Liste vide si Ollama ne répond pas.
6053
+ */
6054
+ listOllamaModels: protectedProcedure.query(async () => ({ models: await listOllamaModels() })),
5708
6055
  /**
5709
6056
  * Les modèles proposés pour un runner CLI : alias fixes pour Claude Code,
5710
6057
  * `model/list` de l'app-server pour Codex, sur l'agent visé (celui par
@@ -5851,7 +6198,7 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
5851
6198
  session.agentId = resolvedAgentId;
5852
6199
  }
5853
6200
  if (process.env.RUNEYA_DEBUG) console.log("[chat]: claude run...");
5854
- const injectedEnv = await resolveInjectedEnv(input.environmentId);
6201
+ const injectedEnv = await resolveInjectedEnv(input.environmentId, convId);
5855
6202
  new ClaudeCodeRunner().run({
5856
6203
  agentId: resolvedAgentId ?? void 0,
5857
6204
  cwd: await conversationCwd(conv),
@@ -5940,7 +6287,7 @@ ${input.prompt}` : input.prompt;
5940
6287
  if (resolvedAgentId) {
5941
6288
  session.agentId = resolvedAgentId;
5942
6289
  }
5943
- const injectedEnv = await resolveInjectedEnv(input.environmentId);
6290
+ const injectedEnv = await resolveInjectedEnv(input.environmentId, convId);
5944
6291
  new CodexRunner().run({
5945
6292
  agentId: resolvedAgentId ?? void 0,
5946
6293
  cwd: await conversationCwd(conv),
@@ -6048,8 +6395,6 @@ ${input.prompt}` : input.prompt;
6048
6395
  });
6049
6396
  gen = runAnthropic(client, model, system, msgs, apiKey, authToken, apiSignal, anchor);
6050
6397
  } else if (provider.provider in OPENAI_COMPAT_BASE_URLS) {
6051
- const baseURL = OPENAI_COMPAT_BASE_URLS[provider.provider];
6052
- const client = new OpenAI2({ apiKey: plaintextKey || "ollama", baseURL });
6053
6398
  const msgs = [
6054
6399
  { role: "system", content: system },
6055
6400
  ...input.messages.map((m, i) => {
@@ -6065,7 +6410,22 @@ ${input.prompt}` : input.prompt;
6065
6410
  return { role: m.role, content: m.content };
6066
6411
  })
6067
6412
  ];
6068
- gen = runOpenAI(client, model, msgs, apiKey, authToken, apiSignal, anchor);
6413
+ let createStream;
6414
+ if (provider.provider === "ollama") {
6415
+ createStream = (messages, signal2) => streamOllamaChat({ model, messages, tools: CHAT_TOOLS_OPENAI, temperature: 0, signal: signal2 });
6416
+ } else {
6417
+ const baseURL = OPENAI_COMPAT_BASE_URLS[provider.provider];
6418
+ const client = new OpenAI2({ apiKey: plaintextKey || "ollama", baseURL });
6419
+ createStream = (messages, signal2) => client.chat.completions.create({
6420
+ model,
6421
+ messages,
6422
+ tools: CHAT_TOOLS_OPENAI,
6423
+ stream: true,
6424
+ max_tokens: 16384,
6425
+ temperature: 0
6426
+ }, { signal: signal2 });
6427
+ }
6428
+ gen = runOpenAI(createStream, msgs, apiKey, authToken, apiSignal, anchor);
6069
6429
  } else {
6070
6430
  activeSessionRegistry.pushEvent(input.conversationId, { type: "error", message: `Provider "${provider.provider}" is not supported.` });
6071
6431
  activeSessionRegistry.pushEvent(input.conversationId, { type: "done", text: "" });
@@ -6154,6 +6514,7 @@ ${input.prompt}` : input.prompt;
6154
6514
  lap("findPtyBySession (no reattach)");
6155
6515
  const ptyConv = input.sessionId ? await conversationStore.get(input.sessionId).catch(() => null) : null;
6156
6516
  const isCodex = input.runner === "codex";
6517
+ let ollamaEnv = {};
6157
6518
  const cli = isCodex ? "codex" : "claude";
6158
6519
  const args = [];
6159
6520
  if (isCodex) {
@@ -6172,7 +6533,10 @@ ${input.prompt}` : input.prompt;
6172
6533
  args.push("--session-id", input.sessionId);
6173
6534
  }
6174
6535
  }
6175
- if (input.model) args.push("--model", input.model);
6536
+ const ollama = claudeOllamaTarget(input.model ?? ptyConv?.cliModel);
6537
+ if (ollama) ollamaEnv = ollama.env;
6538
+ const model = ollama?.model ?? input.model ?? ptyConv?.cliModel;
6539
+ if (model) args.push("--model", model);
6176
6540
  args.push("--dangerously-skip-permissions");
6177
6541
  }
6178
6542
  const ptyRef = (ptyConv ? await conversationStore.projectRefOf(ptyConv) : null) ?? (input.projectId ? { id: input.projectId, orgId: input.orgId ?? null } : null);
@@ -6192,9 +6556,9 @@ ${input.prompt}` : input.prompt;
6192
6556
  }
6193
6557
  lap("buildCliSystemContext");
6194
6558
  }
6195
- const injectedEnv = await resolveInjectedEnv(input.environmentId);
6559
+ const injectedEnv = { ...await resolveInjectedEnv(input.environmentId, ptyConv?.id), ...ollamaEnv };
6196
6560
  lap("resolveInjectedEnv");
6197
- const mcpReadyId = input.enableMcp && input.initialPrompt?.trim() ? randomUUID5() : void 0;
6561
+ const mcpReadyId = input.enableMcp && input.initialPrompt?.trim() ? randomUUID6() : void 0;
6198
6562
  const res = await agentManager.agentMutation(
6199
6563
  agentId,
6200
6564
  "ai.spawnPty",
@@ -6529,6 +6893,7 @@ export {
6529
6893
  accountForOrgScope,
6530
6894
  soleAccountScope,
6531
6895
  cloudAuthRouter,
6896
+ KanbanUnavailableError,
6532
6897
  kanbanStore,
6533
6898
  executeKanbanCommands,
6534
6899
  executeKanbanBoardCommands,
@@ -6543,4 +6908,4 @@ export {
6543
6908
  startCliSession,
6544
6909
  chatRouter
6545
6910
  };
6546
- //# sourceMappingURL=chunk-QJSIN26R.js.map
6911
+ //# sourceMappingURL=chunk-VKYSHTYT.js.map