skyloom 1.23.0 → 1.25.0

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 (66) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/README.md +6 -1
  3. package/dist/cli/main.js +71 -22
  4. package/dist/cli/main.js.map +1 -1
  5. package/dist/cli/tui.d.ts.map +1 -1
  6. package/dist/cli/tui.js +0 -6
  7. package/dist/cli/tui.js.map +1 -1
  8. package/dist/core/commands.d.ts.map +1 -1
  9. package/dist/core/commands.js +10 -0
  10. package/dist/core/commands.js.map +1 -1
  11. package/dist/core/diff.d.ts.map +1 -1
  12. package/dist/core/diff.js +0 -1
  13. package/dist/core/diff.js.map +1 -1
  14. package/dist/core/evolve.d.ts.map +1 -1
  15. package/dist/core/evolve.js +0 -9
  16. package/dist/core/evolve.js.map +1 -1
  17. package/dist/core/graph.d.ts +1 -1
  18. package/dist/core/graph.d.ts.map +1 -1
  19. package/dist/core/graph.js +1 -1
  20. package/dist/core/graph.js.map +1 -1
  21. package/dist/core/llm.d.ts +3 -0
  22. package/dist/core/llm.d.ts.map +1 -1
  23. package/dist/core/llm.js +4 -27
  24. package/dist/core/llm.js.map +1 -1
  25. package/dist/core/sandbox.d.ts.map +1 -1
  26. package/dist/core/sandbox.js +0 -2
  27. package/dist/core/sandbox.js.map +1 -1
  28. package/dist/core/schemas.d.ts +1 -1
  29. package/dist/core/schemas.d.ts.map +1 -1
  30. package/dist/core/schemas.js +1 -23
  31. package/dist/core/schemas.js.map +1 -1
  32. package/dist/core/skill.d.ts.map +1 -1
  33. package/dist/core/skill.js +0 -1
  34. package/dist/core/skill.js.map +1 -1
  35. package/dist/gateway/qr.d.ts +8 -0
  36. package/dist/gateway/qr.d.ts.map +1 -0
  37. package/dist/gateway/qr.js +22 -0
  38. package/dist/gateway/qr.js.map +1 -0
  39. package/dist/gateway/setup.d.ts +57 -0
  40. package/dist/gateway/setup.d.ts.map +1 -0
  41. package/dist/gateway/setup.js +127 -0
  42. package/dist/gateway/setup.js.map +1 -0
  43. package/dist/tools/computer.d.ts.map +1 -1
  44. package/dist/tools/computer.js.map +1 -1
  45. package/dist/web/server.d.ts.map +1 -1
  46. package/dist/web/server.js +0 -2
  47. package/dist/web/server.js.map +1 -1
  48. package/eslint.config.js +62 -0
  49. package/package.json +2 -1
  50. package/src/cli/main.ts +65 -22
  51. package/src/cli/tui.ts +0 -2
  52. package/src/core/commands.ts +10 -0
  53. package/src/core/diff.ts +0 -1
  54. package/src/core/evolve.ts +0 -6
  55. package/src/core/graph.ts +1 -1
  56. package/src/core/llm.ts +4 -33
  57. package/src/core/sandbox.ts +1 -4
  58. package/src/core/schemas.ts +1 -25
  59. package/src/core/skill.ts +0 -1
  60. package/src/gateway/qr.ts +20 -0
  61. package/src/gateway/setup.ts +145 -0
  62. package/src/tools/computer.ts +2 -2
  63. package/src/web/server.ts +0 -3
  64. package/tests/channel_setup.test.ts +88 -0
  65. package/tests/factory.test.ts +56 -0
  66. package/tests/pipelines.test.ts +118 -0
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { SystemContext, TaskExecutionResult } from "../src/core/factory";
3
+ import { MessageBus } from "../src/core/bus";
4
+
5
+ describe("factory · TaskExecutionResult", () => {
6
+ it("holds the provided fields", () => {
7
+ const r = new TaskExecutionResult({ id: "t1", agent: "fog", description: "do x", success: true, content: "done" });
8
+ expect(r).toMatchObject({ id: "t1", agent: "fog", success: true, content: "done" });
9
+ });
10
+ });
11
+
12
+ describe("factory · SystemContext", () => {
13
+ function ctx(overrides: Partial<ConstructorParameters<typeof SystemContext>[0]> = {}) {
14
+ return new SystemContext({
15
+ config: { agents: {} } as any,
16
+ bus: new MessageBus(),
17
+ llm: {} as any,
18
+ agentMap: new Map(),
19
+ toolRegistry: {} as any,
20
+ ...overrides,
21
+ });
22
+ }
23
+
24
+ it("stores constructor options with sensible defaults", () => {
25
+ const c = ctx({ workspacePath: "/ws" });
26
+ expect(c.workspacePath).toBe("/ws");
27
+ expect(c.mcp).toBeNull();
28
+ expect(c.mcpStatus).toEqual([]);
29
+ expect(c.agentMap.size).toBe(0);
30
+ });
31
+
32
+ it("initAll fires the plugin init hook before agents come up", async () => {
33
+ const emit = vi.fn().mockResolvedValue(undefined);
34
+ const c = ctx({ plugins: { emit } as any });
35
+ await c.initAll();
36
+ expect(emit).toHaveBeenCalledWith("init", expect.objectContaining({ config: expect.anything() }));
37
+ });
38
+
39
+ it("closeAll closes every agent and tolerates a missing mcp", async () => {
40
+ const close = vi.fn().mockResolvedValue(undefined);
41
+ const agents = new Map<string, any>([
42
+ ["fog", { close }],
43
+ ["rain", { close }],
44
+ ]);
45
+ const c = ctx({ agentMap: agents as any });
46
+ await c.closeAll();
47
+ expect(close).toHaveBeenCalledTimes(2);
48
+ });
49
+
50
+ it("closeAll also closes mcp when present", async () => {
51
+ const closeAll = vi.fn().mockResolvedValue(undefined);
52
+ const c = ctx({ mcp: { closeAll } as any });
53
+ await c.closeAll();
54
+ expect(closeAll).toHaveBeenCalled();
55
+ });
56
+ });
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ matchPipeline,
4
+ matchAllPipelines,
5
+ buildTasksFromPipeline,
6
+ listPipelines,
7
+ getPipelineByName,
8
+ validateDAG,
9
+ topologicalSort,
10
+ type Task,
11
+ } from "../src/core/pipelines";
12
+
13
+ function task(id: string, dependsOn: string[] = []): Task {
14
+ return { id, description: id, assignedTo: "fog", parentId: dependsOn[0] ?? null, dependsOn };
15
+ }
16
+
17
+ describe("pipelines · matching", () => {
18
+ it("matches a code-review goal to the code_review pipeline", () => {
19
+ const p = matchPipeline("帮我审查代码");
20
+ expect(p?.name).toBe("code_review");
21
+ });
22
+
23
+ it("matches an English review goal", () => {
24
+ expect(matchPipeline("please review my code")?.name).toBe("code_review");
25
+ });
26
+
27
+ it("returns null for an unmatched / empty goal", () => {
28
+ expect(matchPipeline("今天天气怎么样")).toBeNull();
29
+ expect(matchPipeline("")).toBeNull();
30
+ });
31
+
32
+ it("matchAllPipelines returns an array (possibly empty)", () => {
33
+ expect(Array.isArray(matchAllPipelines("审查代码"))).toBe(true);
34
+ expect(matchAllPipelines("xyzzy nonsense")).toEqual([]);
35
+ });
36
+ });
37
+
38
+ describe("pipelines · introspection", () => {
39
+ it("lists pipelines with names, triggers and steps", () => {
40
+ const list = listPipelines();
41
+ expect(list.length).toBeGreaterThan(0);
42
+ for (const p of list) {
43
+ expect(typeof p.name).toBe("string");
44
+ expect(Array.isArray(p.triggers)).toBe(true);
45
+ expect(Array.isArray(p.steps)).toBe(true);
46
+ }
47
+ });
48
+
49
+ it("getPipelineByName round-trips a listed name; unknown → null", () => {
50
+ const name = listPipelines()[0].name as string;
51
+ expect(getPipelineByName(name)?.name).toBe(name);
52
+ expect(getPipelineByName("___nope___")).toBeNull();
53
+ });
54
+ });
55
+
56
+ describe("pipelines · materialization", () => {
57
+ it("builds tasks from a pipeline, substituting {goal}", () => {
58
+ const p = getPipelineByName("code_review")!;
59
+ const tasks = buildTasksFromPipeline(p, "登录模块");
60
+ expect(tasks.length).toBe(p.steps.length);
61
+ expect(tasks[0].description).toContain("登录模块");
62
+ expect(tasks[0].metadata?.goal).toBe("登录模块");
63
+ expect(tasks[0].metadata?.pipeline).toBe("code_review");
64
+ });
65
+
66
+ it("a materialized pipeline is a valid DAG", () => {
67
+ for (const meta of listPipelines()) {
68
+ const p = getPipelineByName(meta.name as string)!;
69
+ const tasks = buildTasksFromPipeline(p, "x");
70
+ const v = validateDAG(tasks);
71
+ expect(v.valid, `${meta.name}: ${v.errors.join("; ")}`).toBe(true);
72
+ }
73
+ });
74
+ });
75
+
76
+ describe("pipelines · validateDAG", () => {
77
+ it("accepts a linear chain", () => {
78
+ const v = validateDAG([task("1"), task("2", ["1"]), task("3", ["2"])]);
79
+ expect(v.valid).toBe(true);
80
+ expect(v.errors).toEqual([]);
81
+ });
82
+
83
+ it("flags a missing dependency", () => {
84
+ const v = validateDAG([task("2", ["1"])]); // 1 doesn't exist
85
+ expect(v.valid).toBe(false);
86
+ expect(v.errors.join(" ")).toMatch(/non-existent/);
87
+ });
88
+
89
+ it("detects a cycle", () => {
90
+ const v = validateDAG([task("a", ["b"]), task("b", ["a"])]);
91
+ expect(v.valid).toBe(false);
92
+ expect(v.errors.join(" ")).toMatch(/[Cc]ycle/);
93
+ });
94
+ });
95
+
96
+ describe("pipelines · topologicalSort", () => {
97
+ it("orders dependencies before dependents", () => {
98
+ const sorted = topologicalSort([task("3", ["2"]), task("1"), task("2", ["1"])]);
99
+ const order = sorted.map((t) => t.id);
100
+ expect(order.indexOf("1")).toBeLessThan(order.indexOf("2"));
101
+ expect(order.indexOf("2")).toBeLessThan(order.indexOf("3"));
102
+ });
103
+
104
+ it("handles independent tasks (all in-degree 0)", () => {
105
+ const sorted = topologicalSort([task("a"), task("b"), task("c")]);
106
+ expect(sorted.map((t) => t.id).sort()).toEqual(["a", "b", "c"]);
107
+ });
108
+
109
+ it("a diamond DAG keeps the root first and the join last", () => {
110
+ // a → b, a → c, b → d, c → d
111
+ const sorted = topologicalSort([
112
+ task("d", ["b", "c"]), task("b", ["a"]), task("c", ["a"]), task("a"),
113
+ ]);
114
+ const order = sorted.map((t) => t.id);
115
+ expect(order[0]).toBe("a");
116
+ expect(order[order.length - 1]).toBe("d");
117
+ });
118
+ });