@vellumai/cli 0.11.3-dev.202608172108.14b9345 → 0.11.3-dev.202608172253.65bf2a2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.11.3-dev.202608172108.14b9345",
3
+ "version": "0.11.3-dev.202608172253.65bf2a2",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -120,11 +120,11 @@ describe("up", () => {
120
120
  logSpy.mockRestore();
121
121
  });
122
122
 
123
- test("starts the webhooks-only edge when the flag is off and states the mode", async () => {
123
+ test("starts the edge and states the remote web mode", async () => {
124
124
  ensureTunnelEdgeMock.mockResolvedValue({
125
- port: 7845,
125
+ port: 7840,
126
126
  started: true,
127
- includesWebApp: false,
127
+ includesWebApp: true,
128
128
  });
129
129
 
130
130
  await up({
@@ -139,28 +139,9 @@ describe("up", () => {
139
139
  workspaceDir,
140
140
  gatewayPort: 7830,
141
141
  });
142
- const output = logs.join("\n");
143
- expect(output).toContain("http://127.0.0.1:7845 (webhooks only)");
144
- expect(output).toContain("web-remote-ingress");
145
- expect(output).toContain("vellum tunnel --provider ngrok");
146
- });
147
-
148
- test("states the remote web mode when the flag is on", async () => {
149
- ensureTunnelEdgeMock.mockResolvedValue({
150
- port: 7840,
151
- started: true,
152
- includesWebApp: true,
153
- });
154
-
155
- await up({
156
- assistantId: "assistant-1",
157
- workspaceDir,
158
- gatewayPort: 7830,
159
- });
160
-
161
142
  const output = logs.join("\n");
162
143
  expect(output).toContain("http://127.0.0.1:7840 (remote web + webhooks)");
163
- expect(output).not.toContain("Enable the web-remote-ingress feature flag");
144
+ expect(output).toContain("vellum tunnel --provider ngrok");
164
145
  });
165
146
  });
166
147
 
@@ -23,12 +23,10 @@ import {
23
23
  test,
24
24
  } from "bun:test";
25
25
 
26
- import * as featureFlags from "../lib/feature-flags.js";
27
26
  import * as httpClient from "../lib/http-client.js";
28
27
 
29
28
  const realChildProcess = { ...childProcess };
30
29
  const realFs = { ...fsModule };
31
- const realFeatureFlags = { ...featureFlags };
32
30
  const realHttpClient = { ...httpClient };
33
31
 
34
32
  const execFileSyncMock = mock(childProcess.execFileSync);
@@ -60,21 +58,11 @@ mock.module("../lib/http-client.js", () => ({
60
58
  waitForDaemonReady: waitForDaemonReadyMock,
61
59
  }));
62
60
 
63
- const isFeatureFlagEnabledMock = mock<
64
- typeof featureFlags.isAssistantFeatureFlagEnabled
65
- >(async () => true);
66
-
67
- mock.module("../lib/feature-flags.js", () => ({
68
- ...featureFlags,
69
- isAssistantFeatureFlagEnabled: isFeatureFlagEnabledMock,
70
- }));
71
-
72
61
  // Restore the real modules once this file finishes so the mocks do not leak
73
62
  // into sibling test files in the same `bun test` run.
74
63
  afterAll(() => {
75
64
  mock.module("node:child_process", () => realChildProcess);
76
65
  mock.module("node:fs", () => realFs);
77
- mock.module("../lib/feature-flags.js", () => realFeatureFlags);
78
66
  mock.module("../lib/http-client.js", () => realHttpClient);
79
67
  });
80
68
 
@@ -132,8 +120,6 @@ afterEach(() => {
132
120
  readFileSyncMock.mockImplementation(realFs.readFileSync);
133
121
  waitForDaemonReadyMock.mockReset();
134
122
  waitForDaemonReadyMock.mockImplementation(async () => true);
135
- isFeatureFlagEnabledMock.mockReset();
136
- isFeatureFlagEnabledMock.mockImplementation(async () => true);
137
123
  for (const dir of workspaces.splice(0)) {
138
124
  rmSync(dir, { recursive: true, force: true });
139
125
  }
@@ -1617,7 +1603,7 @@ describe("ensureTunnelEdge", () => {
1617
1603
  expect(indexHtml).toContain(`"hubUrl":"${PRODUCTION_HUB_URL}"`);
1618
1604
  });
1619
1605
 
1620
- test("reuses a running edge that matches the flag-resolved mode", async () => {
1606
+ test("reuses a running edge that already serves the SPA mode", async () => {
1621
1607
  const ws = makeWorkspace();
1622
1608
  mockNginxInstalled();
1623
1609
  mockNginxSpawn();
@@ -1644,34 +1630,6 @@ describe("ensureTunnelEdge", () => {
1644
1630
  expect(spawnMock).not.toHaveBeenCalled();
1645
1631
  });
1646
1632
 
1647
- test("reuses a running webhooks-only edge and reports the recorded mode", async () => {
1648
- const ws = makeWorkspace();
1649
- mockNginxInstalled();
1650
- mockNginxSpawn();
1651
- mockWebDistMissing();
1652
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1653
- const pid = mockRunningEdge(ws, {
1654
- listenPort: 7845,
1655
- includeWebApp: false,
1656
- gatewayPort: 7830,
1657
- });
1658
- const edge = mockKillableNginx(pid);
1659
-
1660
- const result = await ensureTunnelEdge({
1661
- assistantId: ASSISTANT_ID,
1662
- workspaceDir: ws,
1663
- gatewayPort: 7830,
1664
- });
1665
-
1666
- expect(result).toEqual({
1667
- port: 7845,
1668
- started: false,
1669
- includesWebApp: false,
1670
- });
1671
- expect(edge.killed()).toBe(false);
1672
- expect(spawnMock).not.toHaveBeenCalled();
1673
- });
1674
-
1675
1633
  test("restarts the edge when the lockfile display name changes", async () => {
1676
1634
  const ws = makeWorkspace();
1677
1635
  mockNginxInstalled();
@@ -1751,9 +1709,8 @@ describe("ensureTunnelEdge", () => {
1751
1709
  const ws = makeWorkspace();
1752
1710
  mockNginxInstalled();
1753
1711
  mockNginxSpawn();
1754
- mockWebDistMissing();
1755
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1756
- const pid = mockRunningEdge(ws, { listenPort: 7845, includeWebApp: true });
1712
+ mockWebDistPresent();
1713
+ const pid = mockRunningEdge(ws, { listenPort: 7845, includeWebApp: false });
1757
1714
  mockUnkillableNginx(pid);
1758
1715
 
1759
1716
  const promise = ensureTunnelEdge({
@@ -1763,7 +1720,7 @@ describe("ensureTunnelEdge", () => {
1763
1720
  });
1764
1721
 
1765
1722
  await expect(promise).rejects.toThrow(
1766
- "still running in web app mode and could not be restarted in webhooks-only mode",
1723
+ "still running in webhooks-only mode and could not be restarted in web app mode",
1767
1724
  );
1768
1725
  await expect(promise).rejects.toThrow("vellum nginx-ingress down");
1769
1726
  expect(spawnMock).not.toHaveBeenCalled();
@@ -1773,12 +1730,12 @@ describe("ensureTunnelEdge", () => {
1773
1730
  const ws = makeWorkspace();
1774
1731
  mockNginxInstalled();
1775
1732
  mockNginxSpawn();
1776
- mockWebDistMissing();
1777
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1733
+ mockWebDistPresent();
1778
1734
  const pid = mockRunningEdge(ws, {
1779
1735
  listenPort: 7845,
1780
- includeWebApp: false,
1736
+ includeWebApp: true,
1781
1737
  gatewayPort: 7900,
1738
+ remoteWebConfigHash: spaConfigHash(),
1782
1739
  });
1783
1740
  mockUnkillableNginx(pid);
1784
1741
 
@@ -1799,12 +1756,12 @@ describe("ensureTunnelEdge", () => {
1799
1756
  const ws = makeWorkspace();
1800
1757
  mockNginxInstalled();
1801
1758
  mockNginxSpawn();
1802
- mockWebDistMissing();
1803
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1759
+ mockWebDistPresent();
1804
1760
  const pid = mockRunningEdge(ws, {
1805
1761
  listenPort: 7845,
1806
- includeWebApp: false,
1762
+ includeWebApp: true,
1807
1763
  gatewayPort: 7900,
1764
+ remoteWebConfigHash: spaConfigHash(),
1808
1765
  });
1809
1766
  const edge = mockKillableNginx(pid);
1810
1767
 
@@ -1817,26 +1774,19 @@ describe("ensureTunnelEdge", () => {
1817
1774
  expect(result).toEqual({
1818
1775
  port: REQUESTED_PORT,
1819
1776
  started: true,
1820
- includesWebApp: false,
1777
+ includesWebApp: true,
1821
1778
  });
1822
1779
  expect(edge.killed()).toBe(true);
1823
1780
  const conf = realFs.readFileSync(ingressConfPath(ws), "utf-8");
1824
- expect(conf).toBe(
1825
- buildIngressNginxConfig({
1826
- gatewayPort: 7830,
1827
- listenPort: REQUESTED_PORT,
1828
- ipv6Loopback: hasIpv6Loopback(),
1829
- }),
1830
- );
1781
+ expect(conf).toContain("location ^~ /assistant/ {");
1831
1782
  });
1832
1783
 
1833
- test("restarts a mode-drifted edge into the flag-resolved mode", async () => {
1784
+ test("restarts a webhooks-only edge into the SPA mode", async () => {
1834
1785
  const ws = makeWorkspace();
1835
1786
  mockNginxInstalled();
1836
1787
  mockNginxSpawn();
1837
- mockWebDistMissing();
1838
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1839
- const pid = mockRunningEdge(ws, { listenPort: 7845, includeWebApp: true });
1788
+ mockWebDistPresent();
1789
+ const pid = mockRunningEdge(ws, { listenPort: 7845, includeWebApp: false });
1840
1790
  const edge = mockKillableNginx(pid);
1841
1791
 
1842
1792
  const result = await ensureTunnelEdge({
@@ -1848,20 +1798,14 @@ describe("ensureTunnelEdge", () => {
1848
1798
  expect(result).toEqual({
1849
1799
  port: REQUESTED_PORT,
1850
1800
  started: true,
1851
- includesWebApp: false,
1801
+ includesWebApp: true,
1852
1802
  });
1853
1803
  expect(edge.killed()).toBe(true);
1854
1804
  const conf = realFs.readFileSync(ingressConfPath(ws), "utf-8");
1855
- expect(conf).toBe(
1856
- buildIngressNginxConfig({
1857
- gatewayPort: 7830,
1858
- listenPort: REQUESTED_PORT,
1859
- ipv6Loopback: hasIpv6Loopback(),
1860
- }),
1861
- );
1805
+ expect(conf).toContain("location ^~ /assistant/ {");
1862
1806
  });
1863
1807
 
1864
- test("flag enabled starts the SPA edge", async () => {
1808
+ test("starts the SPA edge", async () => {
1865
1809
  const ws = makeWorkspace();
1866
1810
  mockNginxInstalled();
1867
1811
  mockNginxSpawn();
@@ -1878,11 +1822,6 @@ describe("ensureTunnelEdge", () => {
1878
1822
  started: true,
1879
1823
  includesWebApp: true,
1880
1824
  });
1881
- expect(isFeatureFlagEnabledMock).toHaveBeenCalledWith(
1882
- ASSISTANT_ID,
1883
- featureFlags.WEB_REMOTE_INGRESS_FLAG,
1884
- { runtimeUrl: "http://127.0.0.1:7830" },
1885
- );
1886
1825
  const conf = realFs.readFileSync(ingressConfPath(ws), "utf-8");
1887
1826
  expect(conf).toContain("location ^~ /assistant/ {");
1888
1827
  expect(conf).toContain("location ^~ /webhooks/ {");
@@ -1894,40 +1833,11 @@ describe("ensureTunnelEdge", () => {
1894
1833
  });
1895
1834
  });
1896
1835
 
1897
- test("flag disabled starts the webhooks-only edge", async () => {
1898
- const ws = makeWorkspace();
1899
- mockNginxInstalled();
1900
- mockNginxSpawn();
1901
- mockWebDistMissing();
1902
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1903
-
1904
- const result = await ensureTunnelEdge({
1905
- assistantId: ASSISTANT_ID,
1906
- workspaceDir: ws,
1907
- gatewayPort: 7830,
1908
- });
1909
-
1910
- expect(result).toEqual({
1911
- port: REQUESTED_PORT,
1912
- started: true,
1913
- includesWebApp: false,
1914
- });
1915
- const conf = realFs.readFileSync(ingressConfPath(ws), "utf-8");
1916
- expect(conf).toContain("location = /v1/pair { return 404; }");
1917
- expect(conf).not.toContain("location ^~ /assistant/ {");
1918
- expect((readConfig(ws).ingress as Record<string, unknown>).nginx).toEqual({
1919
- listenPort: REQUESTED_PORT,
1920
- includeWebApp: false,
1921
- gatewayPort: 7830,
1922
- });
1923
- });
1924
-
1925
1836
  test("forwards onStarting so callers can print progress", async () => {
1926
1837
  const ws = makeWorkspace();
1927
1838
  mockNginxInstalled();
1928
1839
  mockNginxSpawn();
1929
- mockWebDistMissing();
1930
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1840
+ mockWebDistPresent();
1931
1841
  const onStarting = mock(
1932
1842
  (_info: {
1933
1843
  version: string;
@@ -1943,18 +1853,18 @@ describe("ensureTunnelEdge", () => {
1943
1853
  onStarting,
1944
1854
  });
1945
1855
 
1946
- expect(onStarting).toHaveBeenCalledWith({
1947
- version: NGINX_VERSION,
1948
- webDistDir: null,
1949
- listenPort: REQUESTED_PORT,
1950
- });
1856
+ expect(onStarting).toHaveBeenCalledTimes(1);
1857
+ const info = onStarting.mock.calls[0][0];
1858
+ expect(info.version).toBe(NGINX_VERSION);
1859
+ expect(info.listenPort).toBe(REQUESTED_PORT);
1860
+ expect(typeof info.webDistDir).toBe("string");
1951
1861
  });
1952
1862
 
1953
- test("an entry without an assistant id gets the webhooks-only edge", async () => {
1863
+ test("an entry without an assistant id still gets the SPA edge", async () => {
1954
1864
  const ws = makeWorkspace();
1955
1865
  mockNginxInstalled();
1956
1866
  mockNginxSpawn();
1957
- mockWebDistMissing();
1867
+ mockWebDistPresent();
1958
1868
 
1959
1869
  const result = await ensureTunnelEdge({
1960
1870
  assistantId: undefined,
@@ -1965,11 +1875,10 @@ describe("ensureTunnelEdge", () => {
1965
1875
  expect(result).toEqual({
1966
1876
  port: REQUESTED_PORT,
1967
1877
  started: true,
1968
- includesWebApp: false,
1878
+ includesWebApp: true,
1969
1879
  });
1970
- expect(isFeatureFlagEnabledMock).not.toHaveBeenCalled();
1971
1880
  const conf = realFs.readFileSync(ingressConfPath(ws), "utf-8");
1972
- expect(conf).not.toContain("location ^~ /assistant/ {");
1881
+ expect(conf).toContain("location ^~ /assistant/ {");
1973
1882
  });
1974
1883
 
1975
1884
  test("missing nginx throws with install instructions", async () => {
@@ -1988,97 +1897,7 @@ describe("ensureTunnelEdge", () => {
1988
1897
  expect(spawnMock).not.toHaveBeenCalled();
1989
1898
  });
1990
1899
 
1991
- test("flag lookup failure throws the wake hint", async () => {
1992
- const ws = makeWorkspace();
1993
- mockNginxInstalled();
1994
- isFeatureFlagEnabledMock.mockImplementation(async () => {
1995
- throw new Error("connect ECONNREFUSED");
1996
- });
1997
-
1998
- const promise = ensureTunnelEdge({
1999
- assistantId: ASSISTANT_ID,
2000
- workspaceDir: ws,
2001
- gatewayPort: 7830,
2002
- });
2003
-
2004
- await expect(promise).rejects.toThrow(
2005
- "Could not verify the `web-remote-ingress` feature flag",
2006
- );
2007
- await expect(promise).rejects.toThrow("Try `vellum wake` and retry");
2008
- await expect(promise).rejects.toThrow("connect ECONNREFUSED");
2009
- expect(spawnMock).not.toHaveBeenCalled();
2010
- });
2011
-
2012
- test("flagRetry retries a thrown flag lookup and then starts the edge", async () => {
2013
- const ws = makeWorkspace();
2014
- mockNginxInstalled();
2015
- mockNginxSpawn();
2016
- mockWebDistMissing();
2017
- isFeatureFlagEnabledMock
2018
- .mockImplementationOnce(async () => {
2019
- throw new Error('HTTP 503 {"status":"starting"}');
2020
- })
2021
- .mockImplementationOnce(async () => {
2022
- throw new Error('HTTP 503 {"status":"starting"}');
2023
- })
2024
- .mockImplementationOnce(async () => false);
2025
-
2026
- const result = await ensureTunnelEdge({
2027
- assistantId: ASSISTANT_ID,
2028
- workspaceDir: ws,
2029
- gatewayPort: 7830,
2030
- flagRetry: { attempts: 3, intervalMs: 1 },
2031
- });
2032
-
2033
- expect(isFeatureFlagEnabledMock).toHaveBeenCalledTimes(3);
2034
- expect(result).toEqual({
2035
- port: REQUESTED_PORT,
2036
- started: true,
2037
- includesWebApp: false,
2038
- });
2039
- });
2040
-
2041
- test("flagRetry does not retry a resolved false: it is a real answer", async () => {
2042
- const ws = makeWorkspace();
2043
- mockNginxInstalled();
2044
- mockNginxSpawn();
2045
- mockWebDistMissing();
2046
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
2047
-
2048
- const result = await ensureTunnelEdge({
2049
- assistantId: ASSISTANT_ID,
2050
- workspaceDir: ws,
2051
- gatewayPort: 7830,
2052
- flagRetry: { attempts: 3, intervalMs: 1 },
2053
- });
2054
-
2055
- expect(isFeatureFlagEnabledMock).toHaveBeenCalledTimes(1);
2056
- expect(result.includesWebApp).toBe(false);
2057
- });
2058
-
2059
- test("an exhausted flagRetry throws the wake hint with the last error", async () => {
2060
- const ws = makeWorkspace();
2061
- mockNginxInstalled();
2062
- isFeatureFlagEnabledMock.mockImplementation(async () => {
2063
- throw new Error("connect ECONNREFUSED");
2064
- });
2065
-
2066
- const promise = ensureTunnelEdge({
2067
- assistantId: ASSISTANT_ID,
2068
- workspaceDir: ws,
2069
- gatewayPort: 7830,
2070
- flagRetry: { attempts: 3, intervalMs: 1 },
2071
- });
2072
-
2073
- await expect(promise).rejects.toThrow(
2074
- "Could not verify the `web-remote-ingress` feature flag",
2075
- );
2076
- await expect(promise).rejects.toThrow("connect ECONNREFUSED");
2077
- expect(isFeatureFlagEnabledMock).toHaveBeenCalledTimes(3);
2078
- expect(spawnMock).not.toHaveBeenCalled();
2079
- });
2080
-
2081
- test("missing web dist with the flag enabled throws build guidance", async () => {
1900
+ test("missing web dist throws build guidance", async () => {
2082
1901
  const ws = makeWorkspace();
2083
1902
  mockNginxInstalled();
2084
1903
  mockWebDistMissing();
@@ -2096,8 +1915,7 @@ describe("ensureTunnelEdge", () => {
2096
1915
  const ws = makeWorkspace();
2097
1916
  mockNginxInstalled();
2098
1917
  mockNginxSpawn();
2099
- mockWebDistMissing();
2100
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1918
+ mockWebDistPresent();
2101
1919
  waitForDaemonReadyMock.mockImplementation(async () => false);
2102
1920
 
2103
1921
  await expect(
@@ -2113,8 +1931,7 @@ describe("ensureTunnelEdge", () => {
2113
1931
  const ws = makeWorkspace();
2114
1932
  mockNginxInstalled();
2115
1933
  mockNginxSpawnExitsOnStartup();
2116
- mockWebDistMissing();
2117
- isFeatureFlagEnabledMock.mockImplementation(async () => false);
1934
+ mockWebDistPresent();
2118
1935
  waitForDaemonReadyMock.mockImplementation(async () => true);
2119
1936
 
2120
1937
  const promise = ensureTunnelEdge({