skyloom 1.14.4 → 1.14.6

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 (56) hide show
  1. package/dist/cli/loom.d.ts +2 -0
  2. package/dist/cli/loom.d.ts.map +1 -1
  3. package/dist/cli/loom.js +76 -7
  4. package/dist/cli/loom.js.map +1 -1
  5. package/dist/cli/main.js +12 -3
  6. package/dist/cli/main.js.map +1 -1
  7. package/dist/core/agent/guard.d.ts.map +1 -1
  8. package/dist/core/agent/guard.js +1 -2
  9. package/dist/core/agent/guard.js.map +1 -1
  10. package/dist/core/agent.d.ts.map +1 -1
  11. package/dist/core/agent.js +12 -9
  12. package/dist/core/agent.js.map +1 -1
  13. package/dist/core/agent_helpers.d.ts +3 -3
  14. package/dist/core/agent_helpers.d.ts.map +1 -1
  15. package/dist/core/agent_helpers.js +7 -3
  16. package/dist/core/agent_helpers.js.map +1 -1
  17. package/dist/core/config.d.ts.map +1 -1
  18. package/dist/core/config.js +8 -2
  19. package/dist/core/config.js.map +1 -1
  20. package/dist/core/memory.d.ts.map +1 -1
  21. package/dist/core/memory.js +12 -2
  22. package/dist/core/memory.js.map +1 -1
  23. package/dist/core/model_config.d.ts.map +1 -1
  24. package/dist/core/model_config.js +7 -2
  25. package/dist/core/model_config.js.map +1 -1
  26. package/dist/plugins/loader.d.ts +7 -0
  27. package/dist/plugins/loader.d.ts.map +1 -1
  28. package/dist/plugins/loader.js +27 -0
  29. package/dist/plugins/loader.js.map +1 -1
  30. package/dist/tools/builtin.d.ts +6 -0
  31. package/dist/tools/builtin.d.ts.map +1 -1
  32. package/dist/tools/builtin.js +160 -17
  33. package/dist/tools/builtin.js.map +1 -1
  34. package/dist/tools/computer.d.ts.map +1 -1
  35. package/dist/tools/computer.js +18 -7
  36. package/dist/tools/computer.js.map +1 -1
  37. package/dist/web/server.d.ts.map +1 -1
  38. package/dist/web/server.js +35 -2
  39. package/dist/web/server.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/cli/loom.ts +66 -7
  42. package/src/cli/main.ts +6 -3
  43. package/src/core/agent/guard.ts +1 -2
  44. package/src/core/agent.ts +12 -8
  45. package/src/core/agent_helpers.ts +7 -3
  46. package/src/core/config.ts +5 -2
  47. package/src/core/memory.ts +9 -2
  48. package/src/core/model_config.ts +4 -2
  49. package/src/plugins/loader.ts +91 -66
  50. package/src/tools/builtin.ts +119 -16
  51. package/src/tools/computer.ts +279 -269
  52. package/src/web/server.ts +35 -2
  53. package/tests/fence_plugin.test.ts +52 -0
  54. package/tests/loom.test.ts +89 -0
  55. package/tests/ssrf.test.ts +38 -0
  56. package/tsconfig.json +1 -0
@@ -246,3 +246,92 @@ describe("palette ↑↓ navigation + Enter execution", () => {
246
246
  expect(ui.inputGlyphs.length).toBe(0);
247
247
  });
248
248
  });
249
+
250
+ describe("mouse wheel scrolling", () => {
251
+ // Replay an SGR mouse sequence the way Node's keypress parser fragments it:
252
+ // ESC[< as one event, then every remaining char separately.
253
+ function wheel(ui: any, code: number) {
254
+ ui.onKey("", { sequence: "\x1b[<" });
255
+ for (const ch of `${code};10;5M`) ui.onKey(ch, { name: ch });
256
+ }
257
+ // Fill the viewport past one screen so there is something to scroll through.
258
+ function fillUI() {
259
+ const out = { columns: 80, rows: 24, isTTY: false, write: (_: string) => true };
260
+ const ui = new LoomUI({ out, inp: null, headless: true }) as any;
261
+ ui.start();
262
+ for (let i = 0; i < 60; i++) ui.line(`line ${i}`);
263
+ return ui;
264
+ }
265
+
266
+ it("wheel up scrolls into history, wheel down returns toward the tail", () => {
267
+ const ui = fillUI();
268
+ expect(ui.scrollOff).toBe(0);
269
+ wheel(ui, 64); // wheel up
270
+ expect(ui.scrollOff).toBeGreaterThan(0);
271
+ const up = ui.scrollOff;
272
+ wheel(ui, 65); // wheel down
273
+ expect(ui.scrollOff).toBeLessThan(up);
274
+ });
275
+
276
+ it("mouse fragments never leak into the input line", () => {
277
+ const ui = fillUI();
278
+ wheel(ui, 64);
279
+ wheel(ui, 65);
280
+ // A click (button 0) must also be swallowed whole, not typed.
281
+ ui.onKey("", { sequence: "\x1b[<" });
282
+ for (const ch of "0;3;4M") ui.onKey(ch, { name: ch });
283
+ expect(ui.inputGlyphs.join("")).toBe("");
284
+ });
285
+
286
+ it("does not yank a scrolled-up reader to the tail on new content", () => {
287
+ const ui = fillUI();
288
+ wheel(ui, 64);
289
+ wheel(ui, 64);
290
+ const before = ui.scrollOff;
291
+ expect(before).toBeGreaterThan(0);
292
+ ui.line("a fresh tool event arrives");
293
+ ui.blank();
294
+ expect(ui.scrollOff).toBe(before); // position preserved
295
+ });
296
+
297
+ it("submitting a turn snaps back to the tail", async () => {
298
+ const ui = fillUI();
299
+ wheel(ui, 64);
300
+ expect(ui.scrollOff).toBeGreaterThan(0);
301
+ const p = ui.readInput();
302
+ for (const ch of "hi") ui.onKey(ch, { name: ch });
303
+ ui.onKey("", { name: "return" });
304
+ await p;
305
+ expect(ui.scrollOff).toBe(0);
306
+ });
307
+
308
+ it("wheel navigates the slash palette while it is open", () => {
309
+ const ui = fillUI();
310
+ for (const ch of "/") ui.onKey(ch, { name: ch });
311
+ expect(ui.paletteMatches().length).toBeGreaterThan(1);
312
+ expect(ui.paletteIdx).toBe(0);
313
+ wheel(ui, 65); // wheel down → next command
314
+ expect(ui.paletteIdx).toBe(1);
315
+ wheel(ui, 64); // wheel up → previous command
316
+ expect(ui.paletteIdx).toBe(0);
317
+ expect(ui.scrollOff).toBe(0); // palette nav must not scroll the viewport
318
+ });
319
+ });
320
+
321
+ describe("input cursor — Home/End", () => {
322
+ function makeInput() {
323
+ const out = { columns: 80, rows: 24, isTTY: false, write: (_: string) => true };
324
+ const ui = new LoomUI({ out, inp: null, headless: true }) as any;
325
+ ui.start();
326
+ for (const ch of "hello") ui.onKey(ch, { name: ch });
327
+ return ui;
328
+ }
329
+ it("Home jumps to the start, End to the end", () => {
330
+ const ui = makeInput();
331
+ expect(ui.cursor).toBe(5);
332
+ ui.onKey("", { name: "home" });
333
+ expect(ui.cursor).toBe(0);
334
+ ui.onKey("", { name: "end" });
335
+ expect(ui.cursor).toBe(5);
336
+ });
337
+ });
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { isPrivateIp, assertFetchAllowed } from "../src/tools/builtin";
3
+
4
+ describe("SSRF guard — isPrivateIp", () => {
5
+ it("flags loopback, private, link-local and metadata addresses", () => {
6
+ for (const ip of ["127.0.0.1", "10.0.0.5", "172.16.3.4", "172.31.255.255",
7
+ "192.168.1.1", "169.254.169.254", "100.64.0.1", "0.0.0.0",
8
+ "::1", "::", "fc00::1", "fd12::3", "fe80::1", "::ffff:127.0.0.1"]) {
9
+ expect(isPrivateIp(ip), ip).toBe(true);
10
+ }
11
+ });
12
+ it("allows public addresses", () => {
13
+ for (const ip of ["8.8.8.8", "1.1.1.1", "172.32.0.1", "192.169.0.1", "2606:4700::1"]) {
14
+ expect(isPrivateIp(ip), ip).toBe(false);
15
+ }
16
+ });
17
+ });
18
+
19
+ describe("SSRF guard — assertFetchAllowed", () => {
20
+ afterEach(() => { delete process.env.SKYLOOM_ALLOW_PRIVATE_FETCH; });
21
+
22
+ it("rejects non-http(s) schemes", async () => {
23
+ await expect(assertFetchAllowed("file:///etc/passwd")).rejects.toThrow(/scheme/);
24
+ await expect(assertFetchAllowed("gopher://x")).rejects.toThrow(/scheme/);
25
+ });
26
+ it("rejects IP-literal private targets", async () => {
27
+ await expect(assertFetchAllowed("http://169.254.169.254/latest/meta-data/")).rejects.toThrow(/private/);
28
+ await expect(assertFetchAllowed("http://127.0.0.1:6379/")).rejects.toThrow(/private/);
29
+ await expect(assertFetchAllowed("http://[::1]/")).rejects.toThrow(/private/);
30
+ });
31
+ it("honors the opt-out env var", async () => {
32
+ process.env.SKYLOOM_ALLOW_PRIVATE_FETCH = "1";
33
+ await expect(assertFetchAllowed("http://127.0.0.1/")).resolves.toBeUndefined();
34
+ });
35
+ it("rejects an invalid URL", async () => {
36
+ await expect(assertFetchAllowed("not a url")).rejects.toThrow(/invalid URL/);
37
+ });
38
+ });
package/tsconfig.json CHANGED
@@ -26,6 +26,7 @@
26
26
  "noFallthroughCasesInSwitch": true,
27
27
  "allowSyntheticDefaultImports": true,
28
28
  "moduleResolution": "node",
29
+ "ignoreDeprecations": "5.0",
29
30
  "baseUrl": ".",
30
31
  "paths": {
31
32
  "@skyloom/*": ["src/*"],