@axiom-lattice/gateway 3.0.2 → 3.0.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.
- package/dist/index.js +31 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/.eslintrc.json +0 -22
- package/.turbo/turbo-build.log +0 -46
- package/AGENTS.md +0 -112
- package/CHANGELOG.md +0 -1190
- package/LOGGER_CONFIG.md +0 -135
- package/RESUME_STREAM_CONTENT_BASED.md +0 -326
- package/RESUME_STREAM_README.md +0 -388
- package/jest.config.js +0 -72
- package/public/sdk/README.md +0 -695
- package/public/sdk/dashboard-engine-skill.md +0 -1122
- package/public/sdk/dashboard-single-file-spec.md +0 -357
- package/public/sdk/data-query-sdk-skill.md +0 -307
- package/public/sdk/data-query-sdk.d.ts +0 -252
- package/public/sdk/data-query-sdk.js +0 -970
- package/public/sdk/occupancy-dashboard.html +0 -363
- package/public/sdk/test-dashboard.html +0 -690
- package/scripts/seed-lark-installation.ts +0 -54
- package/src/__tests__/__mocks__/e2b.ts +0 -1
- package/src/__tests__/a2a.test.ts +0 -428
- package/src/__tests__/channel-installations.test.ts +0 -199
- package/src/__tests__/data-query.test.ts +0 -77
- package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
- package/src/__tests__/setup.ts +0 -5
- package/src/__tests__/workspace.test.ts +0 -400
- package/src/channels/__tests__/routes.test.ts +0 -50
- package/src/channels/lark/LarkChannelAdapter.ts +0 -217
- package/src/channels/lark/README.md +0 -187
- package/src/channels/lark/__tests__/controller.test.ts +0 -136
- package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
- package/src/channels/lark/__tests__/parser.test.ts +0 -72
- package/src/channels/lark/__tests__/sender.test.ts +0 -37
- package/src/channels/lark/__tests__/verification.test.ts +0 -157
- package/src/channels/lark/controller.ts +0 -48
- package/src/channels/lark/mapping-service.ts +0 -138
- package/src/channels/lark/parser.ts +0 -68
- package/src/channels/lark/routes.ts +0 -22
- package/src/channels/lark/sender.ts +0 -58
- package/src/channels/lark/types.ts +0 -20
- package/src/channels/lark/verification.ts +0 -32
- package/src/channels/registry.ts +0 -20
- package/src/channels/routes.ts +0 -30
- package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
- package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
- package/src/channels/wechat/context-store.ts +0 -54
- package/src/channels/wechat/controller.ts +0 -59
- package/src/channels/wechat/routes.ts +0 -7
- package/src/channels/wechat/types.ts +0 -62
- package/src/channels/wechat/wechat-client.ts +0 -162
- package/src/config/local-a2a-templates.ts +0 -59
- package/src/config.ts +0 -124
- package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
- package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
- package/src/controllers/__tests__/mime.test.ts +0 -74
- package/src/controllers/__tests__/resources.test.ts +0 -122
- package/src/controllers/__tests__/run.test.ts +0 -115
- package/src/controllers/__tests__/tasks.test.ts +0 -215
- package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
- package/src/controllers/agent_task.ts +0 -72
- package/src/controllers/assistant.ts +0 -490
- package/src/controllers/auth.ts +0 -524
- package/src/controllers/channel-bindings.ts +0 -135
- package/src/controllers/channel-installations.ts +0 -409
- package/src/controllers/collections.ts +0 -176
- package/src/controllers/config.ts +0 -126
- package/src/controllers/connections.ts +0 -181
- package/src/controllers/data-query.ts +0 -236
- package/src/controllers/database-configs.ts +0 -440
- package/src/controllers/embeddings.ts +0 -22
- package/src/controllers/entries.ts +0 -167
- package/src/controllers/eval.ts +0 -513
- package/src/controllers/export-import.ts +0 -166
- package/src/controllers/health.ts +0 -36
- package/src/controllers/local-a2a-templates.ts +0 -18
- package/src/controllers/local-a2a.ts +0 -149
- package/src/controllers/mcp-configs.ts +0 -673
- package/src/controllers/memory.ts +0 -257
- package/src/controllers/menu-items.ts +0 -114
- package/src/controllers/metrics-configs.ts +0 -1061
- package/src/controllers/middleware-types.ts +0 -15
- package/src/controllers/models.ts +0 -199
- package/src/controllers/personal-assistant.ts +0 -191
- package/src/controllers/resources.ts +0 -477
- package/src/controllers/run.ts +0 -338
- package/src/controllers/sandbox.ts +0 -255
- package/src/controllers/schedules.ts +0 -336
- package/src/controllers/skills.ts +0 -256
- package/src/controllers/tasks.ts +0 -213
- package/src/controllers/tenants.ts +0 -121
- package/src/controllers/thread_status.ts +0 -66
- package/src/controllers/threads.ts +0 -260
- package/src/controllers/tools.ts +0 -139
- package/src/controllers/users.ts +0 -135
- package/src/controllers/workflow-tracking.ts +0 -693
- package/src/controllers/workspace.ts +0 -879
- package/src/export_registrations/a2a-api-key.registration.ts +0 -89
- package/src/export_registrations/agent.registration.ts +0 -80
- package/src/export_registrations/binding.registration.ts +0 -106
- package/src/export_registrations/channel-installation.registration.ts +0 -88
- package/src/export_registrations/collection.registration.ts +0 -85
- package/src/export_registrations/connection.registration.ts +0 -97
- package/src/export_registrations/database-config.registration.ts +0 -94
- package/src/export_registrations/eval.registration.ts +0 -334
- package/src/export_registrations/index.ts +0 -31
- package/src/export_registrations/mcp-config.registration.ts +0 -97
- package/src/export_registrations/menu.registration.ts +0 -91
- package/src/export_registrations/metrics-config.registration.ts +0 -94
- package/src/export_registrations/skill.registration.ts +0 -88
- package/src/export_registrations/task.registration.ts +0 -99
- package/src/index.ts +0 -497
- package/src/logger/Logger.ts +0 -186
- package/src/router/MessageContext.ts +0 -14
- package/src/router/MessageRouter.ts +0 -490
- package/src/router/middlewares/auditLogger.ts +0 -34
- package/src/router/middlewares/deduplication.ts +0 -38
- package/src/router/middlewares/index.ts +0 -3
- package/src/router/middlewares/rateLimit.ts +0 -46
- package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
- package/src/routes/a2a-bridge.ts +0 -266
- package/src/routes/a2a-test-connection.ts +0 -126
- package/src/routes/a2a.ts +0 -851
- package/src/routes/channel-bindings.ts +0 -11
- package/src/routes/channel-installations.ts +0 -33
- package/src/routes/index.ts +0 -640
- package/src/routes/menu-items.ts +0 -10
- package/src/routes/resource-routes.ts +0 -35
- package/src/routes/stt.ts +0 -102
- package/src/schemas/data-query.ts +0 -69
- package/src/schemas/index.ts +0 -852
- package/src/services/ExportImportService.ts +0 -296
- package/src/services/__tests__/ExportImportService.test.ts +0 -130
- package/src/services/__tests__/eval-runner.test.ts +0 -131
- package/src/services/__tests__/local-a2a.test.ts +0 -124
- package/src/services/agent_task_consumer.ts +0 -348
- package/src/services/collection_service.ts +0 -258
- package/src/services/eval-runner.ts +0 -283
- package/src/services/local-a2a.ts +0 -354
- package/src/services/queue_service.ts +0 -104
- package/src/services/sandbox_service.ts +0 -223
- package/src/services/skill_service.ts +0 -97
- package/src/services/supabase.ts +0 -60
- package/src/swagger.ts +0 -77
- package/src/types/index.ts +0 -118
- package/src/types/koa.d.ts +0 -7
- package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
- package/src/utils/__tests__/path-containment.test.ts +0 -35
- package/src/utils/a2a-url.ts +0 -20
- package/src/utils/local-a2a-runtime.ts +0 -107
- package/src/utils/mime.ts +0 -44
- package/src/utils/path-containment.ts +0 -38
- package/tsconfig.json +0 -26
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { ResourceController } from "../resources";
|
|
2
|
-
|
|
3
|
-
// Type for accessing private methods in tests
|
|
4
|
-
type Controller = ResourceController & {
|
|
5
|
-
_resolveSafeSubPath(entryFile: string, subPath: string): string;
|
|
6
|
-
_injectBaseTag(buf: Buffer, token: string): Buffer;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
function createController(): Controller {
|
|
10
|
-
const deps = {
|
|
11
|
-
store: {} as any,
|
|
12
|
-
sandboxManager: {} as any,
|
|
13
|
-
cache: {} as any,
|
|
14
|
-
logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
15
|
-
};
|
|
16
|
-
return new ResourceController(deps) as unknown as Controller;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe("ResourceController._resolveSafeSubPath", () => {
|
|
20
|
-
let ctrl: Controller;
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
ctrl = createController();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("returns entryFile when no subPath", () => {
|
|
27
|
-
expect(ctrl._resolveSafeSubPath("app/index.html", "")).toBe("app/index.html");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("appends subPath to entryFile directory", () => {
|
|
31
|
-
expect(ctrl._resolveSafeSubPath("app/index.html", "main.js")).toBe("app/main.js");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("handles nested subPaths", () => {
|
|
35
|
-
expect(ctrl._resolveSafeSubPath("app/index.html", "lib/x.js")).toBe("app/lib/x.js");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("handles deeply nested subPaths", () => {
|
|
39
|
-
expect(ctrl._resolveSafeSubPath("app/index.html", "a/b/c/d.js")).toBe("app/a/b/c/d.js");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("rejects path traversal with ..", () => {
|
|
43
|
-
expect(() => ctrl._resolveSafeSubPath("app/index.html", "../../etc/passwd")).toThrow("Path traversal");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("rejects path traversal with .. in middle", () => {
|
|
47
|
-
expect(() => ctrl._resolveSafeSubPath("app/index.html", "foo/../../etc")).toThrow("Path traversal");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("rejects path traversal with just ..", () => {
|
|
51
|
-
expect(() => ctrl._resolveSafeSubPath("app/index.html", "..")).toThrow("Path traversal");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("rejects path traversal with ../..", () => {
|
|
55
|
-
expect(() => ctrl._resolveSafeSubPath("app/index.html", "../..")).toThrow("Path traversal");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("strips the last component of entry to get directory", () => {
|
|
59
|
-
// entry "a/b/c/index.html" → dir "a/b/c/"
|
|
60
|
-
expect(ctrl._resolveSafeSubPath("a/b/c/index.html", "x.html")).toBe("a/b/c/x.html");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("deduplicates consecutive slashes", () => {
|
|
64
|
-
expect(ctrl._resolveSafeSubPath("app/index.html", "extra/slash")).toBe("app/extra/slash");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("does not double prefix when subPath already starts with directory entry", () => {
|
|
68
|
-
// Directory share: base tag /s/TOKEN/dir/
|
|
69
|
-
// Browser resolves style.css → subPath already includes the entry directory
|
|
70
|
-
expect(ctrl._resolveSafeSubPath("knowledge-graph-visualization", "knowledge-graph-visualization/style.css"))
|
|
71
|
-
.toBe("knowledge-graph-visualization/style.css");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("does not double prefix when subPath exactly matches directory entry", () => {
|
|
75
|
-
// Accessing /s/TOKEN/knowledge-graph-visualization (no trailing slash)
|
|
76
|
-
expect(ctrl._resolveSafeSubPath("knowledge-graph-visualization", "knowledge-graph-visualization"))
|
|
77
|
-
.toBe("knowledge-graph-visualization");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("does not double prefix for deeply nested assets", () => {
|
|
81
|
-
expect(ctrl._resolveSafeSubPath("dir", "dir/sub/file.js"))
|
|
82
|
-
.toBe("dir/sub/file.js");
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("still appends subPath when it does NOT start with entry", () => {
|
|
86
|
-
// File share: base tag /s/TOKEN/ → subPath is bare filename
|
|
87
|
-
expect(ctrl._resolveSafeSubPath("app/index.html", "style.css")).toBe("app/style.css");
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe("ResourceController._injectBaseTag", () => {
|
|
92
|
-
let ctrl: Controller;
|
|
93
|
-
|
|
94
|
-
beforeEach(() => {
|
|
95
|
-
ctrl = createController();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("injects base tag before </head>", () => {
|
|
99
|
-
const html = Buffer.from("<html><head></head><body></body></html>");
|
|
100
|
-
const result = ctrl._injectBaseTag(html, "abc123");
|
|
101
|
-
expect(result.toString()).toContain(`<base href="/s/abc123/">`);
|
|
102
|
-
expect(result.toString()).toContain(`<base href="/s/abc123/"></head>`);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("injects base tag inside <head> when no </head>", () => {
|
|
106
|
-
const html = Buffer.from("<html><head><title>Test</title></head><body></body></html>");
|
|
107
|
-
const result = ctrl._injectBaseTag(html, "abc123");
|
|
108
|
-
expect(result.toString()).toContain(`<base href="/s/abc123/"></head>`);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("does not inject if <base> already exists", () => {
|
|
112
|
-
const html = Buffer.from('<html><head><base href="/existing/"></head><body></body></html>');
|
|
113
|
-
const result = ctrl._injectBaseTag(html, "abc123");
|
|
114
|
-
expect(result.toString()).not.toContain('/s/abc123');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("does not inject if <base has extra attributes", () => {
|
|
118
|
-
const html = Buffer.from('<html><head><base href="/x" target="_blank"></head></html>');
|
|
119
|
-
const result = ctrl._injectBaseTag(html, "abc123");
|
|
120
|
-
expect(result.toString()).not.toContain('/s/abc123');
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, jest, beforeEach } from "@jest/globals";
|
|
2
|
-
|
|
3
|
-
const mockGetAgent = jest.fn();
|
|
4
|
-
const mockResumeTask = jest.fn();
|
|
5
|
-
const mockGetRunStatus = jest.fn();
|
|
6
|
-
|
|
7
|
-
jest.mock("@axiom-lattice/core", () => ({
|
|
8
|
-
agentInstanceManager: {
|
|
9
|
-
getAgent: mockGetAgent,
|
|
10
|
-
},
|
|
11
|
-
ThreadStatus: {
|
|
12
|
-
IDLE: "IDLE",
|
|
13
|
-
BUSY: "BUSY",
|
|
14
|
-
INTERRUPTED: "INTERRUPTED",
|
|
15
|
-
},
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
describe("recoverRun", () => {
|
|
19
|
-
let recoverRun: (...args: unknown[]) => unknown;
|
|
20
|
-
|
|
21
|
-
beforeEach(async () => {
|
|
22
|
-
jest.clearAllMocks();
|
|
23
|
-
const mod = await import("../../controllers/run");
|
|
24
|
-
recoverRun = mod.recoverRun;
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("should recover a thread and return its status", async () => {
|
|
28
|
-
const mockReply = {
|
|
29
|
-
status: jest.fn().mockReturnThis(),
|
|
30
|
-
send: jest.fn(),
|
|
31
|
-
};
|
|
32
|
-
const request = {
|
|
33
|
-
params: { assistantId: "a1", threadId: "t1" },
|
|
34
|
-
headers: {
|
|
35
|
-
"x-tenant-id": "tenant-x",
|
|
36
|
-
"x-workspace-id": "ws-1",
|
|
37
|
-
"x-project-id": "pj-1",
|
|
38
|
-
},
|
|
39
|
-
} as any;
|
|
40
|
-
|
|
41
|
-
const mockAgent = {
|
|
42
|
-
resumeTask: mockResumeTask.mockResolvedValue(undefined),
|
|
43
|
-
getRunStatus: mockGetRunStatus.mockResolvedValue("BUSY"),
|
|
44
|
-
};
|
|
45
|
-
mockGetAgent.mockReturnValue(mockAgent);
|
|
46
|
-
|
|
47
|
-
await recoverRun(request, mockReply);
|
|
48
|
-
|
|
49
|
-
expect(mockGetAgent).toHaveBeenCalledWith({
|
|
50
|
-
assistant_id: "a1",
|
|
51
|
-
thread_id: "t1",
|
|
52
|
-
tenant_id: "tenant-x",
|
|
53
|
-
workspace_id: "ws-1",
|
|
54
|
-
project_id: "pj-1",
|
|
55
|
-
});
|
|
56
|
-
expect(mockResumeTask).toHaveBeenCalled();
|
|
57
|
-
expect(mockGetRunStatus).toHaveBeenCalled();
|
|
58
|
-
expect(mockReply.status).toHaveBeenCalledWith(200);
|
|
59
|
-
expect(mockReply.send).toHaveBeenCalledWith({
|
|
60
|
-
success: true,
|
|
61
|
-
threadId: "t1",
|
|
62
|
-
status: "BUSY",
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("should default workspace/project to default when headers missing", async () => {
|
|
67
|
-
const mockReply = {
|
|
68
|
-
status: jest.fn().mockReturnThis(),
|
|
69
|
-
send: jest.fn(),
|
|
70
|
-
};
|
|
71
|
-
const request = {
|
|
72
|
-
params: { assistantId: "a1", threadId: "t1" },
|
|
73
|
-
headers: { "x-tenant-id": "tenant-x" },
|
|
74
|
-
} as any;
|
|
75
|
-
|
|
76
|
-
const mockAgent = {
|
|
77
|
-
resumeTask: mockResumeTask.mockResolvedValue(undefined),
|
|
78
|
-
getRunStatus: mockGetRunStatus.mockResolvedValue("IDLE"),
|
|
79
|
-
};
|
|
80
|
-
mockGetAgent.mockReturnValue(mockAgent);
|
|
81
|
-
|
|
82
|
-
await recoverRun(request, mockReply);
|
|
83
|
-
|
|
84
|
-
expect(mockGetAgent).toHaveBeenCalledWith({
|
|
85
|
-
assistant_id: "a1",
|
|
86
|
-
thread_id: "t1",
|
|
87
|
-
tenant_id: "tenant-x",
|
|
88
|
-
workspace_id: "default",
|
|
89
|
-
project_id: "default",
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("should return 500 on error", async () => {
|
|
94
|
-
const mockReply = {
|
|
95
|
-
status: jest.fn().mockReturnThis(),
|
|
96
|
-
send: jest.fn(),
|
|
97
|
-
};
|
|
98
|
-
const request = {
|
|
99
|
-
params: { assistantId: "a1", threadId: "t1" },
|
|
100
|
-
headers: { "x-tenant-id": "tenant-x" },
|
|
101
|
-
} as any;
|
|
102
|
-
|
|
103
|
-
mockGetAgent.mockImplementation(() => {
|
|
104
|
-
throw new Error("Agent not found");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
await recoverRun(request, mockReply);
|
|
108
|
-
|
|
109
|
-
expect(mockReply.status).toHaveBeenCalledWith(500);
|
|
110
|
-
expect(mockReply.send).toHaveBeenCalledWith({
|
|
111
|
-
success: false,
|
|
112
|
-
error: "Recover failed: Agent not found",
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tasks Controller 测试
|
|
3
|
-
*
|
|
4
|
-
* 测试任务 REST API 控制器的端点逻辑,包括权限控制和错误处理。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, expect, it, jest, beforeEach } from '@jest/globals';
|
|
8
|
-
|
|
9
|
-
const mockStore = {
|
|
10
|
-
create: jest.fn<(...args: unknown[]) => Promise<unknown>>(),
|
|
11
|
-
list: jest.fn<(...args: unknown[]) => Promise<unknown>>(),
|
|
12
|
-
update: jest.fn<(...args: unknown[]) => Promise<unknown>>(),
|
|
13
|
-
delete: jest.fn<(...args: unknown[]) => Promise<unknown>>(),
|
|
14
|
-
getById: jest.fn<(...args: unknown[]) => Promise<unknown>>(),
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
jest.mock('@axiom-lattice/core', () => ({
|
|
18
|
-
getStoreLattice: jest.fn(() => ({ store: mockStore })),
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
|
-
describe('Tasks Controller', () => {
|
|
22
|
-
let listTasks: (...args: unknown[]) => unknown;
|
|
23
|
-
let getTask: (...args: unknown[]) => unknown;
|
|
24
|
-
let createTask: (...args: unknown[]) => unknown;
|
|
25
|
-
let updateTask: (...args: unknown[]) => unknown;
|
|
26
|
-
let deleteTask: (...args: unknown[]) => unknown;
|
|
27
|
-
let completeTask: (...args: unknown[]) => unknown;
|
|
28
|
-
|
|
29
|
-
const mockReply = () =>
|
|
30
|
-
({
|
|
31
|
-
status: jest.fn().mockReturnThis(),
|
|
32
|
-
send: jest.fn(),
|
|
33
|
-
}) as any;
|
|
34
|
-
|
|
35
|
-
const makeRequest = (overrides: Record<string, any> = {}) =>
|
|
36
|
-
({
|
|
37
|
-
headers: { 'x-tenant-id': 'tenant-1', 'x-user-id': 'user-1' },
|
|
38
|
-
query: {},
|
|
39
|
-
...overrides,
|
|
40
|
-
}) as any;
|
|
41
|
-
|
|
42
|
-
beforeEach(async () => {
|
|
43
|
-
jest.clearAllMocks();
|
|
44
|
-
const mod = await import('../../controllers/tasks');
|
|
45
|
-
listTasks = mod.listTasks;
|
|
46
|
-
getTask = mod.getTask;
|
|
47
|
-
createTask = mod.createTask;
|
|
48
|
-
updateTask = mod.updateTask;
|
|
49
|
-
deleteTask = mod.deleteTask;
|
|
50
|
-
completeTask = mod.completeTask;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('listTasks', () => {
|
|
54
|
-
it('should return tasks for current user by default', async () => {
|
|
55
|
-
mockStore.list.mockResolvedValue([{ id: 't1', title: 'Test' }]);
|
|
56
|
-
const result = await listTasks(makeRequest(), mockReply());
|
|
57
|
-
expect(result.success).toBe(true);
|
|
58
|
-
expect(result.data).toHaveLength(1);
|
|
59
|
-
expect(mockStore.list).toHaveBeenCalledWith(
|
|
60
|
-
expect.objectContaining({ tenantId: 'tenant-1', ownerId: 'user-1' })
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should filter by ownerId from query params', async () => {
|
|
65
|
-
mockStore.list.mockResolvedValue([]);
|
|
66
|
-
await listTasks(
|
|
67
|
-
makeRequest({ query: { ownerId: 'other-user' } }),
|
|
68
|
-
mockReply()
|
|
69
|
-
);
|
|
70
|
-
expect(mockStore.list).toHaveBeenCalledWith(
|
|
71
|
-
expect.objectContaining({ ownerId: 'other-user' })
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should return error when something fails', async () => {
|
|
76
|
-
mockStore.list.mockRejectedValue(new Error('DB down'));
|
|
77
|
-
const result = await listTasks(makeRequest(), mockReply());
|
|
78
|
-
expect(result.success).toBe(false);
|
|
79
|
-
expect(result.error).toContain('DB down');
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('getTask', () => {
|
|
84
|
-
it('should return task by id', async () => {
|
|
85
|
-
const task = { id: 't1', tenantId: 'tenant-1', ownerType: 'user', ownerId: 'user-1', title: 'T' };
|
|
86
|
-
mockStore.getById.mockResolvedValue(task);
|
|
87
|
-
const result = await getTask(makeRequest({ params: { id: 't1' } }), mockReply());
|
|
88
|
-
expect(result.success).toBe(true);
|
|
89
|
-
expect(result.data).toEqual(task);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should return 404 for non-existent task', async () => {
|
|
93
|
-
mockStore.getById.mockResolvedValue(null);
|
|
94
|
-
const reply = mockReply();
|
|
95
|
-
await getTask(makeRequest({ params: { id: 'nope' } }), reply);
|
|
96
|
-
expect(reply.status).toHaveBeenCalledWith(404);
|
|
97
|
-
expect(reply.send).toHaveBeenCalledWith(
|
|
98
|
-
expect.objectContaining({ success: false, error: 'Task not found' })
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should return 403 for task owned by different user', async () => {
|
|
103
|
-
const task = { id: 't1', tenantId: 'tenant-1', ownerType: 'user', ownerId: 'other-user', title: 'T' };
|
|
104
|
-
mockStore.getById.mockResolvedValue(task);
|
|
105
|
-
const reply = mockReply();
|
|
106
|
-
await getTask(makeRequest({ params: { id: 't1' } }), reply);
|
|
107
|
-
expect(reply.status).toHaveBeenCalledWith(403);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('createTask', () => {
|
|
112
|
-
it('should create task with auto-filled owner', async () => {
|
|
113
|
-
const newTask = { id: 'new', title: 'Hello' };
|
|
114
|
-
mockStore.create.mockResolvedValue(newTask);
|
|
115
|
-
const reply = mockReply();
|
|
116
|
-
await createTask(makeRequest({ body: { title: 'Hello' } }), reply);
|
|
117
|
-
expect(mockStore.create).toHaveBeenCalledWith(
|
|
118
|
-
expect.objectContaining({
|
|
119
|
-
tenantId: 'tenant-1',
|
|
120
|
-
ownerType: 'user',
|
|
121
|
-
ownerId: 'user-1',
|
|
122
|
-
title: 'Hello',
|
|
123
|
-
})
|
|
124
|
-
);
|
|
125
|
-
expect(reply.status).toHaveBeenCalledWith(201);
|
|
126
|
-
expect(reply.send).toHaveBeenCalledWith(
|
|
127
|
-
expect.objectContaining({ success: true, data: newTask })
|
|
128
|
-
);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should return 400 when title is missing', async () => {
|
|
132
|
-
const reply = mockReply();
|
|
133
|
-
await createTask(makeRequest({ body: {} }), reply);
|
|
134
|
-
expect(reply.status).toHaveBeenCalledWith(400);
|
|
135
|
-
expect(reply.send).toHaveBeenCalledWith(
|
|
136
|
-
expect.objectContaining({ success: false, error: 'title is required' })
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should return 500 on store error', async () => {
|
|
141
|
-
mockStore.create.mockRejectedValue(new Error('Store error'));
|
|
142
|
-
const reply = mockReply();
|
|
143
|
-
await createTask(makeRequest({ body: { title: 'T' } }), reply);
|
|
144
|
-
expect(reply.status).toHaveBeenCalledWith(500);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe('updateTask', () => {
|
|
149
|
-
const existingTask = { id: 't1', tenantId: 'tenant-1', ownerType: 'user', ownerId: 'user-1' };
|
|
150
|
-
|
|
151
|
-
it('should update task fields', async () => {
|
|
152
|
-
mockStore.getById.mockResolvedValue(existingTask);
|
|
153
|
-
mockStore.update.mockResolvedValue({ ...existingTask, title: 'Updated' });
|
|
154
|
-
const result = await updateTask(
|
|
155
|
-
makeRequest({ params: { id: 't1' }, body: { title: 'Updated' } }),
|
|
156
|
-
mockReply()
|
|
157
|
-
);
|
|
158
|
-
expect(result.success).toBe(true);
|
|
159
|
-
expect(mockStore.update).toHaveBeenCalledWith('tenant-1', 't1', { title: 'Updated' });
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should return 404 for non-existent task', async () => {
|
|
163
|
-
mockStore.getById.mockResolvedValue(null);
|
|
164
|
-
const reply = mockReply();
|
|
165
|
-
await updateTask(makeRequest({ params: { id: 'nope' }, body: {} }), reply);
|
|
166
|
-
expect(reply.status).toHaveBeenCalledWith(404);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('should return 403 for task owned by different user', async () => {
|
|
170
|
-
mockStore.getById.mockResolvedValue({ ...existingTask, ownerId: 'other-user' });
|
|
171
|
-
const reply = mockReply();
|
|
172
|
-
await updateTask(makeRequest({ params: { id: 't1' }, body: {} }), reply);
|
|
173
|
-
expect(reply.status).toHaveBeenCalledWith(403);
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe('deleteTask', () => {
|
|
178
|
-
const existingTask = { id: 't1', tenantId: 'tenant-1', ownerType: 'user', ownerId: 'user-1' };
|
|
179
|
-
|
|
180
|
-
it('should delete task and return success', async () => {
|
|
181
|
-
mockStore.getById.mockResolvedValue(existingTask);
|
|
182
|
-
mockStore.delete.mockResolvedValue(true);
|
|
183
|
-
const result = await deleteTask(makeRequest({ params: { id: 't1' } }), mockReply());
|
|
184
|
-
expect(result.success).toBe(true);
|
|
185
|
-
expect(result.message).toBe('Task deleted');
|
|
186
|
-
expect(mockStore.delete).toHaveBeenCalledWith('tenant-1', 't1');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('should return 403 for task owned by different user', async () => {
|
|
190
|
-
mockStore.getById.mockResolvedValue({ ...existingTask, ownerId: 'other-user' });
|
|
191
|
-
const reply = mockReply();
|
|
192
|
-
await deleteTask(makeRequest({ params: { id: 't1' } }), reply);
|
|
193
|
-
expect(reply.status).toHaveBeenCalledWith(403);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('completeTask', () => {
|
|
198
|
-
const existingTask = { id: 't1', tenantId: 'tenant-1', ownerType: 'user', ownerId: 'user-1' };
|
|
199
|
-
|
|
200
|
-
it('should mark task as completed', async () => {
|
|
201
|
-
mockStore.getById.mockResolvedValue(existingTask);
|
|
202
|
-
mockStore.update.mockResolvedValue({ ...existingTask, status: 'completed' });
|
|
203
|
-
const result = await completeTask(makeRequest({ params: { id: 't1' } }), mockReply());
|
|
204
|
-
expect(result.success).toBe(true);
|
|
205
|
-
expect(mockStore.update).toHaveBeenCalledWith('tenant-1', 't1', { status: 'completed' });
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('should return 403 for task owned by different user', async () => {
|
|
209
|
-
mockStore.getById.mockResolvedValue({ ...existingTask, ownerId: 'other-user' });
|
|
210
|
-
const reply = mockReply();
|
|
211
|
-
await completeTask(makeRequest({ params: { id: 't1' } }), reply);
|
|
212
|
-
expect(reply.status).toHaveBeenCalledWith(403);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
});
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, jest, beforeEach } from "@jest/globals";
|
|
2
|
-
|
|
3
|
-
const mockQueryWorkflowRuns = jest.fn();
|
|
4
|
-
const mockGetRunSteps = jest.fn();
|
|
5
|
-
const mockGetAllAssistants = jest.fn();
|
|
6
|
-
const mockGetAgent = jest.fn();
|
|
7
|
-
|
|
8
|
-
jest.mock("@axiom-lattice/core", () => ({
|
|
9
|
-
getStoreLattice: (_key: string, type: string) =>
|
|
10
|
-
type === "workflowTracking"
|
|
11
|
-
? { store: { queryWorkflowRuns: mockQueryWorkflowRuns, getRunSteps: mockGetRunSteps } }
|
|
12
|
-
: { store: { getAllAssistants: mockGetAllAssistants } },
|
|
13
|
-
agentInstanceManager: { getAgent: mockGetAgent },
|
|
14
|
-
ThreadStatus: { IDLE: "IDLE", BUSY: "BUSY", INTERRUPTED: "INTERRUPTED" },
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
function makeRun(id: string, startedAt: string) {
|
|
18
|
-
return {
|
|
19
|
-
id,
|
|
20
|
-
tenantId: "t1",
|
|
21
|
-
assistantId: "a1",
|
|
22
|
-
threadId: `th_${id}`,
|
|
23
|
-
status: "interrupted" as const,
|
|
24
|
-
topologyEdges: [],
|
|
25
|
-
totalEdges: 2,
|
|
26
|
-
completedEdges: 1,
|
|
27
|
-
startedAt: new Date(startedAt),
|
|
28
|
-
createdAt: new Date(startedAt),
|
|
29
|
-
updatedAt: new Date(startedAt),
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function makeRequest(query: Record<string, string> = {}) {
|
|
34
|
-
return {
|
|
35
|
-
headers: { "x-tenant-id": "t1" },
|
|
36
|
-
query,
|
|
37
|
-
log: { error: jest.fn(), warn: jest.fn() },
|
|
38
|
-
} as never;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const mockReply = { status: jest.fn().mockReturnThis(), send: jest.fn() } as never;
|
|
42
|
-
|
|
43
|
-
describe("getInboxItems", () => {
|
|
44
|
-
let getInboxItems: (req: never, reply: never) => Promise<{ data?: Record<string, unknown> }>;
|
|
45
|
-
|
|
46
|
-
beforeEach(async () => {
|
|
47
|
-
jest.clearAllMocks();
|
|
48
|
-
mockGetAllAssistants.mockResolvedValue([{ id: "a1", name: "Assistant One" }] as never);
|
|
49
|
-
mockGetRunSteps.mockResolvedValue([] as never);
|
|
50
|
-
mockGetAgent.mockImplementation(() => { throw new Error("no agent"); }); // fallback path
|
|
51
|
-
const mod = await import("../../controllers/workflow-tracking");
|
|
52
|
-
getInboxItems = mod.getInboxItems as never;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("legacy mode: no query params returns all items without pagination fields", async () => {
|
|
56
|
-
mockQueryWorkflowRuns.mockResolvedValue({
|
|
57
|
-
records: [makeRun("r1", "2026-01-01"), makeRun("r2", "2026-01-02")],
|
|
58
|
-
total: 2,
|
|
59
|
-
} as never);
|
|
60
|
-
|
|
61
|
-
const res = await getInboxItems(makeRequest(), mockReply);
|
|
62
|
-
|
|
63
|
-
expect(mockQueryWorkflowRuns).toHaveBeenCalledWith("t1", { status: "interrupted" });
|
|
64
|
-
expect(res.data).toBeDefined();
|
|
65
|
-
const records = res.data!.records as unknown[];
|
|
66
|
-
expect(records).toHaveLength(2);
|
|
67
|
-
expect(res.data!.total).toBeUndefined();
|
|
68
|
-
expect(res.data!.page).toBeUndefined();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("paged mode: passes limit/offset to store and returns total/page/pageSize", async () => {
|
|
72
|
-
mockQueryWorkflowRuns.mockResolvedValue({
|
|
73
|
-
records: [makeRun("r3", "2026-01-03")],
|
|
74
|
-
total: 42,
|
|
75
|
-
} as never);
|
|
76
|
-
|
|
77
|
-
const res = await getInboxItems(makeRequest({ page: "2", pageSize: "20" }), mockReply);
|
|
78
|
-
|
|
79
|
-
expect(mockQueryWorkflowRuns).toHaveBeenCalledWith("t1", { status: "interrupted", limit: 20, offset: 20 });
|
|
80
|
-
expect(res.data!.total).toBe(42);
|
|
81
|
-
expect(res.data!.page).toBe(2);
|
|
82
|
-
expect(res.data!.pageSize).toBe(20);
|
|
83
|
-
expect(res.data!.records).toHaveLength(1);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("paged mode triggers when only pageSize is passed", async () => {
|
|
87
|
-
mockQueryWorkflowRuns.mockResolvedValue({ records: [], total: 0 } as never);
|
|
88
|
-
|
|
89
|
-
const res = await getInboxItems(makeRequest({ pageSize: "10" }), mockReply);
|
|
90
|
-
|
|
91
|
-
expect(mockQueryWorkflowRuns).toHaveBeenCalledWith("t1", { status: "interrupted", limit: 10, offset: 0 });
|
|
92
|
-
expect(res.data!.page).toBe(1);
|
|
93
|
-
expect(res.data!.pageSize).toBe(10);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("sanitizes invalid page and caps pageSize at 100", async () => {
|
|
97
|
-
mockQueryWorkflowRuns.mockResolvedValue({ records: [], total: 0 } as never);
|
|
98
|
-
|
|
99
|
-
const res = await getInboxItems(makeRequest({ page: "abc", pageSize: "500" }), mockReply);
|
|
100
|
-
|
|
101
|
-
expect(mockQueryWorkflowRuns).toHaveBeenCalledWith("t1", { status: "interrupted", limit: 100, offset: 0 });
|
|
102
|
-
expect(res.data!.page).toBe(1);
|
|
103
|
-
expect(res.data!.pageSize).toBe(100);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("returns empty records with pagination fields in paged mode", async () => {
|
|
107
|
-
mockQueryWorkflowRuns.mockResolvedValue({ records: [], total: 0 } as never);
|
|
108
|
-
|
|
109
|
-
const res = await getInboxItems(makeRequest({ page: "3", pageSize: "20" }), mockReply);
|
|
110
|
-
|
|
111
|
-
expect(res.data).toEqual({ records: [], total: 0, page: 3, pageSize: 20 });
|
|
112
|
-
});
|
|
113
|
-
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
-
import { AgentManager } from "@axiom-lattice/core";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Request body interface for triggering agent task
|
|
6
|
-
*/
|
|
7
|
-
interface TriggerAgentTaskRequest {
|
|
8
|
-
assistant_id: string;
|
|
9
|
-
thread_id: string;
|
|
10
|
-
input: any;
|
|
11
|
-
command?: any;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Trigger an agent task
|
|
16
|
-
* This endpoint triggers an agent task through the AgentManager,
|
|
17
|
-
* which publishes the task to the event bus for processing
|
|
18
|
-
*/
|
|
19
|
-
export const triggerAgentTask = async (
|
|
20
|
-
request: FastifyRequest,
|
|
21
|
-
reply: FastifyReply
|
|
22
|
-
): Promise<void> => {
|
|
23
|
-
try {
|
|
24
|
-
const { assistant_id, thread_id, input, command } =
|
|
25
|
-
request.body as TriggerAgentTaskRequest;
|
|
26
|
-
|
|
27
|
-
const tenant_id = request.headers["x-tenant-id"] as string;
|
|
28
|
-
const workspace_id = request.headers["x-workspace-id"] as string | undefined;
|
|
29
|
-
const project_id = request.headers["x-project-id"] as string | undefined;
|
|
30
|
-
|
|
31
|
-
// Validate required fields
|
|
32
|
-
if (!assistant_id) {
|
|
33
|
-
reply.status(400).send({
|
|
34
|
-
success: false,
|
|
35
|
-
error: "assistant_id is required",
|
|
36
|
-
});
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (!thread_id) {
|
|
41
|
-
reply.status(400).send({
|
|
42
|
-
success: false,
|
|
43
|
-
error: "thread_id is required",
|
|
44
|
-
});
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Get AgentManager instance and trigger the task
|
|
49
|
-
const agentManager = AgentManager.getInstance();
|
|
50
|
-
const result = await agentManager.callAgentInQueue({
|
|
51
|
-
assistant_id,
|
|
52
|
-
thread_id,
|
|
53
|
-
input,
|
|
54
|
-
command,
|
|
55
|
-
"x-tenant-id": tenant_id,
|
|
56
|
-
runConfig: {
|
|
57
|
-
workspaceId: workspace_id,
|
|
58
|
-
projectId: project_id,
|
|
59
|
-
user_id: (request as any).user?.id,
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
reply.status(200).send({
|
|
64
|
-
success: true,
|
|
65
|
-
});
|
|
66
|
-
} catch (error: any) {
|
|
67
|
-
reply.status(500).send({
|
|
68
|
-
success: false,
|
|
69
|
-
error: `Failed to trigger agent task: ${error.message}`,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
};
|