alvin-bot 4.15.2 → 4.16.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.
- package/CHANGELOG.md +51 -18
- package/README.md +13 -13
- package/bin/cli.js +124 -0
- package/dist/handlers/platform-message.js +2 -2
- package/dist/paths.js +12 -2
- package/dist/services/alvin-mcp-tools.js +1 -1
- package/dist/services/asset-index.js +5 -11
- package/dist/services/browser-manager.js +19 -6
- package/dist/services/cdp-bootstrap.js +351 -0
- package/dist/services/memory-layers.js +1 -1
- package/dist/services/personality.js +1 -1
- package/dist/services/session.js +1 -1
- package/dist/services/skills.js +4 -7
- package/dist/services/workspaces.js +4 -4
- package/docs/security.md +4 -4
- package/package.json +1 -1
- package/skills/browse/SKILL.md +77 -70
- package/skills/social-fetch/SKILL.md +3 -3
- package/skills/webcheck/SKILL.md +1 -1
- package/test/async-agent-chunk-flow.test.ts +1 -1
- package/test/claude-sdk-tool-use-id.test.ts +1 -1
- package/test/memory-extractor.test.ts +10 -10
- package/test/memory-layers.test.ts +15 -15
- package/test/memory-sdk-injection.test.ts +4 -4
- package/test/memory-stress-restart.test.ts +2 -2
- package/test/multi-session-stress.test.ts +21 -21
- package/test/platform-session-key.test.ts +2 -2
- package/test/slack-test-connection.test.ts +3 -3
- package/test/subagent-delivery-platform-routing.test.ts +2 -2
- package/test/telegram-workspace-command.test.ts +5 -5
- package/test/workspaces.test.ts +32 -32
|
@@ -91,8 +91,8 @@ describe("POST /api/platforms/test-connection — slack (v4.13.1)", () => {
|
|
|
91
91
|
ok: true,
|
|
92
92
|
json: async () => ({
|
|
93
93
|
ok: true,
|
|
94
|
-
url: "https://
|
|
95
|
-
team: "
|
|
94
|
+
url: "https://my-project.slack.com/",
|
|
95
|
+
team: "my-project Workspace",
|
|
96
96
|
user: "alvinbot",
|
|
97
97
|
team_id: "T123",
|
|
98
98
|
user_id: "U456",
|
|
@@ -109,7 +109,7 @@ describe("POST /api/platforms/test-connection — slack (v4.13.1)", () => {
|
|
|
109
109
|
|
|
110
110
|
const parsed = JSON.parse(res.body);
|
|
111
111
|
expect(parsed.ok).toBe(true);
|
|
112
|
-
expect(parsed.info).toMatch(/alvinbot|
|
|
112
|
+
expect(parsed.info).toMatch(/alvinbot|my-project/i);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
it("returns {ok:false} when Slack's auth.test rejects the token", async () => {
|
|
@@ -107,7 +107,7 @@ describe("subagent-delivery platform routing (v4.14)", () => {
|
|
|
107
107
|
startedAt: Date.now() - 3000,
|
|
108
108
|
source: "cron",
|
|
109
109
|
depth: 0,
|
|
110
|
-
parentChatId:
|
|
110
|
+
parentChatId: 1234567890,
|
|
111
111
|
// platform undefined → defaults to telegram
|
|
112
112
|
},
|
|
113
113
|
{
|
|
@@ -121,7 +121,7 @@ describe("subagent-delivery platform routing (v4.14)", () => {
|
|
|
121
121
|
);
|
|
122
122
|
|
|
123
123
|
expect(grammyCalls).toHaveLength(1);
|
|
124
|
-
expect(grammyCalls[0].chatId).toBe(
|
|
124
|
+
expect(grammyCalls[0].chatId).toBe(1234567890);
|
|
125
125
|
expect(grammyCalls[0].text).toContain("Telegram body");
|
|
126
126
|
// Slack adapter must NOT have been touched
|
|
127
127
|
expect(slackCalls).toHaveLength(0);
|
|
@@ -23,13 +23,13 @@ describe("Telegram workspace state (v4.12.0)", () => {
|
|
|
23
23
|
|
|
24
24
|
it("setTelegramWorkspace stores the name", async () => {
|
|
25
25
|
const { getTelegramWorkspace, setTelegramWorkspace } = await import("../src/services/session.js");
|
|
26
|
-
setTelegramWorkspace("42", "
|
|
27
|
-
expect(getTelegramWorkspace("42")).toBe("
|
|
26
|
+
setTelegramWorkspace("42", "my-project");
|
|
27
|
+
expect(getTelegramWorkspace("42")).toBe("my-project");
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
it("setTelegramWorkspace(userId, null) clears the mapping", async () => {
|
|
31
31
|
const { getTelegramWorkspace, setTelegramWorkspace } = await import("../src/services/session.js");
|
|
32
|
-
setTelegramWorkspace("42", "
|
|
32
|
+
setTelegramWorkspace("42", "my-project");
|
|
33
33
|
setTelegramWorkspace("42", null);
|
|
34
34
|
expect(getTelegramWorkspace("42")).toBeNull();
|
|
35
35
|
});
|
|
@@ -39,7 +39,7 @@ describe("Telegram workspace state (v4.12.0)", () => {
|
|
|
39
39
|
const { flushSessions, schedulePersist } = await import("../src/services/session-persistence.js");
|
|
40
40
|
attachPersistHook(schedulePersist);
|
|
41
41
|
|
|
42
|
-
setTelegramWorkspace("42", "
|
|
42
|
+
setTelegramWorkspace("42", "my-project");
|
|
43
43
|
setTelegramWorkspace("99", "homes");
|
|
44
44
|
await flushSessions();
|
|
45
45
|
|
|
@@ -48,7 +48,7 @@ describe("Telegram workspace state (v4.12.0)", () => {
|
|
|
48
48
|
const p2 = await import("../src/services/session-persistence.js");
|
|
49
49
|
p2.loadPersistedSessions();
|
|
50
50
|
|
|
51
|
-
expect(s2.getTelegramWorkspace("42")).toBe("
|
|
51
|
+
expect(s2.getTelegramWorkspace("42")).toBe("my-project");
|
|
52
52
|
expect(s2.getTelegramWorkspace("99")).toBe("homes");
|
|
53
53
|
});
|
|
54
54
|
|
package/test/workspaces.test.ts
CHANGED
|
@@ -46,30 +46,30 @@ describe("workspace registry (v4.12.0)", () => {
|
|
|
46
46
|
|
|
47
47
|
it("loads a workspace from a markdown file with frontmatter", async () => {
|
|
48
48
|
writeWorkspace(
|
|
49
|
-
"
|
|
50
|
-
{ purpose: "
|
|
51
|
-
"You are the
|
|
49
|
+
"my-project",
|
|
50
|
+
{ purpose: "my-project consulting website dev", cwd: "~/Projects/my-project-website", emoji: "🏢", color: "#6366f1" },
|
|
51
|
+
"You are the my-project dev assistant. Stack: React + Express + Drizzle.",
|
|
52
52
|
);
|
|
53
53
|
const { loadWorkspaces, getWorkspace } = await import("../src/services/workspaces.js");
|
|
54
54
|
loadWorkspaces();
|
|
55
|
-
const ws = getWorkspace("
|
|
55
|
+
const ws = getWorkspace("my-project");
|
|
56
56
|
expect(ws).not.toBeNull();
|
|
57
|
-
expect(ws!.name).toBe("
|
|
58
|
-
expect(ws!.purpose).toBe("
|
|
59
|
-
expect(ws!.cwd).toContain("
|
|
57
|
+
expect(ws!.name).toBe("my-project");
|
|
58
|
+
expect(ws!.purpose).toBe("my-project consulting website dev");
|
|
59
|
+
expect(ws!.cwd).toContain("my-project-website");
|
|
60
60
|
expect(ws!.emoji).toBe("🏢");
|
|
61
61
|
expect(ws!.color).toBe("#6366f1");
|
|
62
|
-
expect(ws!.systemPromptOverride).toContain("
|
|
62
|
+
expect(ws!.systemPromptOverride).toContain("my-project dev assistant");
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it("loads multiple workspaces and listWorkspaces returns all of them", async () => {
|
|
66
|
-
writeWorkspace("
|
|
66
|
+
writeWorkspace("my-project", { purpose: "p1" }, "body1");
|
|
67
67
|
writeWorkspace("homes", { purpose: "p2" }, "body2");
|
|
68
|
-
writeWorkspace("
|
|
68
|
+
writeWorkspace("my-landing", { purpose: "p3" }, "body3");
|
|
69
69
|
const { loadWorkspaces, listWorkspaces } = await import("../src/services/workspaces.js");
|
|
70
70
|
loadWorkspaces();
|
|
71
71
|
const names = listWorkspaces().map(w => w.name).sort();
|
|
72
|
-
expect(names).toEqual(["
|
|
72
|
+
expect(names).toEqual(["homes", "my-landing", "my-project"]);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
it("expands ~ in cwd to the user's home directory", async () => {
|
|
@@ -83,28 +83,28 @@ describe("workspace registry (v4.12.0)", () => {
|
|
|
83
83
|
|
|
84
84
|
it("matchWorkspaceForChannel matches by explicit channel ID in frontmatter", async () => {
|
|
85
85
|
writeWorkspace(
|
|
86
|
-
"
|
|
86
|
+
"my-project",
|
|
87
87
|
{ purpose: "p", channels: ["C01ALEVABC", "C01ALEVXYZ"] },
|
|
88
88
|
"",
|
|
89
89
|
);
|
|
90
90
|
const { loadWorkspaces, matchWorkspaceForChannel } = await import("../src/services/workspaces.js");
|
|
91
91
|
loadWorkspaces();
|
|
92
92
|
const ws = matchWorkspaceForChannel("slack", "C01ALEVABC", undefined);
|
|
93
|
-
expect(ws?.name).toBe("
|
|
93
|
+
expect(ws?.name).toBe("my-project");
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
it("matchWorkspaceForChannel falls back to channel name match (case-insensitive, # stripped)", async () => {
|
|
97
|
-
writeWorkspace("
|
|
97
|
+
writeWorkspace("my-project", { purpose: "p" }, "");
|
|
98
98
|
const { loadWorkspaces, matchWorkspaceForChannel } = await import("../src/services/workspaces.js");
|
|
99
99
|
loadWorkspaces();
|
|
100
|
-
const byHash = matchWorkspaceForChannel("slack", "C_UNKNOWN", "#
|
|
101
|
-
const noHash = matchWorkspaceForChannel("slack", "C_UNKNOWN", "
|
|
102
|
-
expect(byHash?.name).toBe("
|
|
103
|
-
expect(noHash?.name).toBe("
|
|
100
|
+
const byHash = matchWorkspaceForChannel("slack", "C_UNKNOWN", "#my-project");
|
|
101
|
+
const noHash = matchWorkspaceForChannel("slack", "C_UNKNOWN", "MY-PROJECT");
|
|
102
|
+
expect(byHash?.name).toBe("my-project");
|
|
103
|
+
expect(noHash?.name).toBe("my-project");
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
it("matchWorkspaceForChannel returns null for unknown channel with no name match", async () => {
|
|
107
|
-
writeWorkspace("
|
|
107
|
+
writeWorkspace("my-project", { purpose: "p" }, "");
|
|
108
108
|
const { loadWorkspaces, matchWorkspaceForChannel } = await import("../src/services/workspaces.js");
|
|
109
109
|
loadWorkspaces();
|
|
110
110
|
const ws = matchWorkspaceForChannel("slack", "C_MYSTERY", "#unmapped");
|
|
@@ -112,11 +112,11 @@ describe("workspace registry (v4.12.0)", () => {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
it("resolveWorkspaceOrDefault returns the matched workspace when one is found", async () => {
|
|
115
|
-
writeWorkspace("
|
|
115
|
+
writeWorkspace("my-project", { purpose: "p" }, "persona body");
|
|
116
116
|
const { loadWorkspaces, resolveWorkspaceOrDefault } = await import("../src/services/workspaces.js");
|
|
117
117
|
loadWorkspaces();
|
|
118
|
-
const ws = resolveWorkspaceOrDefault("slack", "C_UNKNOWN", "#
|
|
119
|
-
expect(ws.name).toBe("
|
|
118
|
+
const ws = resolveWorkspaceOrDefault("slack", "C_UNKNOWN", "#my-project");
|
|
119
|
+
expect(ws.name).toBe("my-project");
|
|
120
120
|
expect(ws.systemPromptOverride).toContain("persona body");
|
|
121
121
|
});
|
|
122
122
|
|
|
@@ -170,26 +170,26 @@ describe("workspace registry (v4.12.0)", () => {
|
|
|
170
170
|
describe("workspace resolver integration with session (v4.12.0)", () => {
|
|
171
171
|
it("two channels resolve to two different workspaces", async () => {
|
|
172
172
|
fs.writeFileSync(
|
|
173
|
-
resolve(TEST_DATA_DIR, "workspaces", "
|
|
174
|
-
`---\npurpose:
|
|
173
|
+
resolve(TEST_DATA_DIR, "workspaces", "my-project.md"),
|
|
174
|
+
`---\npurpose: project-a\ncwd: ~/tmp/project-a\nchannels: ["C_PROJECT_A"]\n---\nproject-a persona`,
|
|
175
175
|
);
|
|
176
176
|
fs.writeFileSync(
|
|
177
|
-
resolve(TEST_DATA_DIR, "workspaces", "
|
|
178
|
-
`---\npurpose:
|
|
177
|
+
resolve(TEST_DATA_DIR, "workspaces", "project-b.md"),
|
|
178
|
+
`---\npurpose: project-b\ncwd: ~/tmp/project-b\nchannels: ["C_PROJECT_B"]\n---\nproject-b persona`,
|
|
179
179
|
);
|
|
180
180
|
|
|
181
181
|
vi.resetModules();
|
|
182
182
|
const { initWorkspaces, resolveWorkspaceOrDefault } = await import("../src/services/workspaces.js");
|
|
183
183
|
initWorkspaces();
|
|
184
184
|
|
|
185
|
-
const
|
|
186
|
-
const
|
|
185
|
+
const a = resolveWorkspaceOrDefault("slack", "C_PROJECT_A", undefined);
|
|
186
|
+
const b = resolveWorkspaceOrDefault("slack", "C_PROJECT_B", undefined);
|
|
187
187
|
const unknown = resolveWorkspaceOrDefault("slack", "C_MYSTERY", undefined);
|
|
188
188
|
|
|
189
|
-
expect(
|
|
190
|
-
expect(
|
|
191
|
-
expect(
|
|
192
|
-
expect(
|
|
189
|
+
expect(a.name).toBe("my-project");
|
|
190
|
+
expect(a.systemPromptOverride).toBe("project-a persona");
|
|
191
|
+
expect(b.name).toBe("project-b");
|
|
192
|
+
expect(b.systemPromptOverride).toBe("project-b persona");
|
|
193
193
|
expect(unknown.name).toBe("default");
|
|
194
194
|
expect(unknown.systemPromptOverride).toBe("");
|
|
195
195
|
});
|