@vellumai/assistant 0.4.10 → 0.4.12
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/ARCHITECTURE.md +418 -378
- package/Dockerfile +1 -1
- package/README.md +16 -9
- package/package.json +1 -1
- package/src/__tests__/account-registry.test.ts +1 -0
- package/src/__tests__/actor-token-service.test.ts +1 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/asset-materialize-tool.test.ts +7 -0
- package/src/__tests__/asset-search-tool.test.ts +7 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -0
- package/src/__tests__/call-start-guardian-guard.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +29 -0
- package/src/__tests__/channel-guardian.test.ts +2143 -1546
- package/src/__tests__/channel-retry-sweep.test.ts +169 -14
- package/src/__tests__/claude-code-tool-profiles.test.ts +1 -0
- package/src/__tests__/computer-use-tools.test.ts +1 -0
- package/src/__tests__/contacts-tools.test.ts +1 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +1 -0
- package/src/__tests__/credential-policy-validate.test.ts +97 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +1 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +1 -0
- package/src/__tests__/file-edit-tool.test.ts +1 -0
- package/src/__tests__/file-read-tool.test.ts +1 -0
- package/src/__tests__/file-write-tool.test.ts +1 -0
- package/src/__tests__/followup-tools.test.ts +1 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -1
- package/src/__tests__/guardian-control-plane-policy.test.ts +5 -4
- package/src/__tests__/guardian-grant-minting.test.ts +3 -0
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +4 -3
- package/src/__tests__/guardian-routing-state.test.ts +8 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +75 -61
- package/src/__tests__/headless-browser-interactions.test.ts +1 -0
- package/src/__tests__/headless-browser-navigate.test.ts +1 -0
- package/src/__tests__/headless-browser-read-tools.test.ts +1 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +1 -0
- package/src/__tests__/host-file-edit-tool.test.ts +1 -0
- package/src/__tests__/host-file-read-tool.test.ts +1 -0
- package/src/__tests__/host-file-write-tool.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -0
- package/src/__tests__/lifecycle-docs-guard.test.ts +207 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +8 -0
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/playbook-execution.test.ts +1 -0
- package/src/__tests__/playbook-tools.test.ts +1 -0
- package/src/__tests__/registry.test.ts +235 -187
- package/src/__tests__/relay-server.test.ts +4 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -0
- package/src/__tests__/schedule-tools.test.ts +1 -0
- package/src/__tests__/secret-onetime-send.test.ts +4 -0
- package/src/__tests__/secret-scanner-executor.test.ts +2 -0
- package/src/__tests__/secure-keys.test.ts +27 -0
- package/src/__tests__/send-notification-tool.test.ts +2 -0
- package/src/__tests__/session-agent-loop.test.ts +521 -256
- package/src/__tests__/session-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/shell-credential-ref.test.ts +1 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skill-load-tool.test.ts +1 -0
- package/src/__tests__/skill-script-runner-host.test.ts +1 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -0
- package/src/__tests__/skill-script-runner.test.ts +1 -0
- package/src/__tests__/skill-tool-factory.test.ts +1 -0
- package/src/__tests__/skills.test.ts +334 -276
- package/src/__tests__/starter-task-flow.test.ts +7 -17
- package/src/__tests__/subagent-tools.test.ts +1 -1
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-session-integration.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/task-management-tools.test.ts +1 -0
- package/src/__tests__/task-tools.test.ts +1 -0
- package/src/__tests__/terminal-tools.test.ts +1 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -2
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +1 -0
- package/src/__tests__/tool-executor.test.ts +1 -0
- package/src/__tests__/trust-context-guards.test.ts +218 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +6 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +6 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +1 -0
- package/src/__tests__/trusted-contact-verification.test.ts +1 -0
- package/src/__tests__/view-image-tool.test.ts +1 -0
- package/src/agent/loop.ts +9 -2
- package/src/calls/guardian-dispatch.ts +4 -4
- package/src/cli/mcp.ts +183 -3
- package/src/config/bundled-skills/agentmail/SKILL.md +4 -4
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +449 -0
- package/src/config/bundled-skills/doordash/SKILL.md +171 -0
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +203 -0
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +164 -0
- package/src/config/bundled-skills/doordash/doordash-cli.ts +1193 -0
- package/src/config/bundled-skills/doordash/doordash-entry.ts +22 -0
- package/src/config/bundled-skills/doordash/lib/cart-queries.ts +787 -0
- package/src/config/bundled-skills/doordash/lib/client.ts +1071 -0
- package/src/config/bundled-skills/doordash/lib/order-queries.ts +85 -0
- package/src/config/bundled-skills/doordash/lib/queries.ts +28 -0
- package/src/config/bundled-skills/doordash/lib/query-extractor.ts +94 -0
- package/src/config/bundled-skills/doordash/lib/search-queries.ts +203 -0
- package/src/config/bundled-skills/doordash/lib/session.ts +93 -0
- package/src/config/bundled-skills/doordash/lib/shared/errors.ts +61 -0
- package/src/config/bundled-skills/doordash/lib/shared/ipc.ts +32 -0
- package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +380 -0
- package/src/config/bundled-skills/doordash/lib/shared/platform.ts +35 -0
- package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +43 -0
- package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +49 -0
- package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +6 -0
- package/src/config/bundled-skills/doordash/lib/store-queries.ts +246 -0
- package/src/config/bundled-skills/doordash/lib/types.ts +367 -0
- package/src/config/bundled-skills/google-calendar/SKILL.md +4 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +42 -41
- package/src/config/bundled-skills/messaging/SKILL.md +59 -42
- package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
- package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +11 -2
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +10 -3
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -1
- package/src/config/bundled-skills/notion/SKILL.md +240 -0
- package/src/config/bundled-skills/notion-oauth-setup/SKILL.md +127 -0
- package/src/config/bundled-skills/oauth-setup/SKILL.md +144 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +91 -162
- package/src/config/bundled-skills/skills-catalog/SKILL.md +32 -29
- package/src/config/{vellum-skills → bundled-skills}/sms-setup/SKILL.md +29 -22
- package/src/config/{vellum-skills → bundled-skills}/telegram-setup/SKILL.md +17 -14
- package/src/config/{vellum-skills → bundled-skills}/twilio-setup/SKILL.md +21 -6
- package/src/config/bundled-tool-registry.ts +281 -267
- package/src/config/system-prompt.ts +4 -2
- package/src/daemon/computer-use-session.ts +1 -0
- package/src/daemon/handlers/skills.ts +334 -234
- package/src/daemon/ipc-contract/messages.ts +2 -0
- package/src/daemon/ipc-contract/surfaces.ts +2 -0
- package/src/daemon/lifecycle.ts +358 -221
- package/src/daemon/response-tier.ts +2 -0
- package/src/daemon/server.ts +453 -193
- package/src/daemon/session-agent-loop-handlers.ts +42 -2
- package/src/daemon/session-agent-loop.ts +4 -1
- package/src/daemon/session-lifecycle.ts +3 -0
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-process.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +2 -2
- package/src/daemon/session-surfaces.ts +22 -20
- package/src/daemon/session-tool-setup.ts +2 -1
- package/src/daemon/session.ts +5 -2
- package/src/mcp/client.ts +55 -6
- package/src/mcp/manager.ts +9 -0
- package/src/mcp/mcp-oauth-provider.ts +347 -0
- package/src/memory/channel-delivery-store.ts +1 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/delivery-status.ts +43 -0
- package/src/memory/guardian-bindings.ts +3 -3
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +108 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/schema.ts +1 -1
- package/src/messaging/outreach-classifier.ts +12 -5
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/gmail/adapter.ts +9 -3
- package/src/messaging/providers/gmail/client.ts +2 -0
- package/src/runtime/actor-trust-resolver.ts +13 -4
- package/src/runtime/channel-retry-sweep.ts +31 -14
- package/src/runtime/guardian-context-resolver.ts +25 -64
- package/src/runtime/guardian-outbound-actions.ts +399 -108
- package/src/runtime/guardian-vellum-migration.ts +1 -23
- package/src/runtime/guardian-verification-templates.ts +66 -30
- package/src/runtime/http-errors.ts +33 -20
- package/src/runtime/http-server.ts +706 -291
- package/src/runtime/http-types.ts +26 -16
- package/src/runtime/local-actor-identity.ts +4 -6
- package/src/runtime/middleware/actor-token.ts +2 -8
- package/src/runtime/routes/channel-route-shared.ts +0 -1
- package/src/runtime/routes/inbound-message-handler.ts +3 -4
- package/src/runtime/routes/secret-routes.ts +57 -2
- package/src/runtime/routes/surface-action-routes.ts +66 -0
- package/src/runtime/routes/trust-rules-routes.ts +140 -0
- package/src/runtime/tool-grant-request-helper.ts +1 -1
- package/src/security/keychain-to-encrypted-migration.ts +59 -0
- package/src/security/secure-keys.ts +17 -0
- package/src/skills/frontmatter.ts +9 -7
- package/src/tools/apps/executors.ts +2 -1
- package/src/tools/credentials/policy-validate.ts +22 -0
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/tool-manifest.ts +44 -42
- package/src/tools/types.ts +10 -1
- package/src/__tests__/skill-mirror-parity.test.ts +0 -176
- package/src/config/vellum-skills/catalog.json +0 -63
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +0 -295
- package/src/skills/vellum-catalog-remote.ts +0 -166
- package/src/tools/skills/vellum-catalog.ts +0 -168
- /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/TOOLS.json +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/deploy-fullstack-vercel/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/document-writer/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/guardian-verify-setup/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/slack-oauth-setup/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/trusted-contacts/SKILL.md +0 -0
|
@@ -5,113 +5,128 @@
|
|
|
5
5
|
* so the user does not have to manually ask whether verification succeeded.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readFileSync } from
|
|
9
|
-
import { resolve } from
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
10
|
|
|
11
|
-
import { describe, expect, test } from
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Locate the skill SKILL.md
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
|
|
17
|
-
const ASSISTANT_DIR = resolve(import.meta.dirname ?? __dirname,
|
|
17
|
+
const ASSISTANT_DIR = resolve(import.meta.dirname ?? __dirname, "..", "..");
|
|
18
18
|
const SKILL_PATH = resolve(
|
|
19
19
|
ASSISTANT_DIR,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
"src",
|
|
21
|
+
"config",
|
|
22
|
+
"bundled-skills",
|
|
23
|
+
"guardian-verify-setup",
|
|
24
|
+
"SKILL.md",
|
|
25
25
|
);
|
|
26
26
|
|
|
27
|
-
const skillContent = readFileSync(SKILL_PATH,
|
|
27
|
+
const skillContent = readFileSync(SKILL_PATH, "utf-8");
|
|
28
28
|
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
// Tests
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
|
|
33
|
-
describe(
|
|
34
|
-
test(
|
|
33
|
+
describe("guardian-verify-setup skill — voice auto-followup", () => {
|
|
34
|
+
test("voice path in Step 3 references the auto-check polling loop", () => {
|
|
35
35
|
// The voice success instruction in Step 3 must direct the assistant to
|
|
36
36
|
// begin the polling loop rather than waiting for the user to report back.
|
|
37
37
|
expect(skillContent).toContain(
|
|
38
|
-
|
|
38
|
+
"immediately begin the voice auto-check polling loop",
|
|
39
39
|
);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
test(
|
|
42
|
+
test("voice path in Step 4 (resend) references the auto-check polling loop", () => {
|
|
43
43
|
// After a voice resend, the same auto-check behavior must kick in.
|
|
44
|
-
const resendSection =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
44
|
+
const resendSection =
|
|
45
|
+
skillContent.split("## Step 4")[1]?.split("## Step 5")[0] ?? "";
|
|
46
|
+
expect(resendSection).toContain("voice auto-check polling loop");
|
|
48
47
|
});
|
|
49
48
|
|
|
50
|
-
test(
|
|
51
|
-
expect(skillContent).toContain(
|
|
49
|
+
test("contains a Voice Auto-Check Polling section", () => {
|
|
50
|
+
expect(skillContent).toContain("## Voice Auto-Check Polling");
|
|
52
51
|
});
|
|
53
52
|
|
|
54
|
-
test(
|
|
53
|
+
test("polling section specifies the correct status endpoint for voice", () => {
|
|
55
54
|
const pollingSection =
|
|
56
|
-
skillContent
|
|
55
|
+
skillContent
|
|
56
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
57
|
+
?.split("## Step 6")[0] ?? "";
|
|
57
58
|
expect(pollingSection).toContain(
|
|
58
|
-
|
|
59
|
+
"/v1/integrations/guardian/status?channel=voice",
|
|
59
60
|
);
|
|
60
61
|
});
|
|
61
62
|
|
|
62
|
-
test(
|
|
63
|
+
test("polling section includes ~15 second interval", () => {
|
|
63
64
|
const pollingSection =
|
|
64
|
-
skillContent
|
|
65
|
-
|
|
65
|
+
skillContent
|
|
66
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
67
|
+
?.split("## Step 6")[0] ?? "";
|
|
68
|
+
expect(pollingSection).toContain("~15 seconds");
|
|
66
69
|
});
|
|
67
70
|
|
|
68
|
-
test(
|
|
71
|
+
test("polling section includes 2-minute timeout", () => {
|
|
69
72
|
const pollingSection =
|
|
70
|
-
skillContent
|
|
71
|
-
|
|
73
|
+
skillContent
|
|
74
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
75
|
+
?.split("## Step 6")[0] ?? "";
|
|
76
|
+
expect(pollingSection).toContain("2 minutes");
|
|
72
77
|
});
|
|
73
78
|
|
|
74
|
-
test(
|
|
79
|
+
test("polling section checks for bound: true", () => {
|
|
75
80
|
const pollingSection =
|
|
76
|
-
skillContent
|
|
77
|
-
|
|
81
|
+
skillContent
|
|
82
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
83
|
+
?.split("## Step 6")[0] ?? "";
|
|
84
|
+
expect(pollingSection).toContain("bound: true");
|
|
78
85
|
});
|
|
79
86
|
|
|
80
|
-
test(
|
|
87
|
+
test("polling section includes proactive success confirmation", () => {
|
|
81
88
|
const pollingSection =
|
|
82
|
-
skillContent
|
|
83
|
-
|
|
89
|
+
skillContent
|
|
90
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
91
|
+
?.split("## Step 6")[0] ?? "";
|
|
92
|
+
expect(pollingSection).toContain("proactive success message");
|
|
84
93
|
});
|
|
85
94
|
|
|
86
|
-
test(
|
|
95
|
+
test("polling section includes timeout fallback with resend/restart offer", () => {
|
|
87
96
|
const pollingSection =
|
|
88
|
-
skillContent
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
skillContent
|
|
98
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
99
|
+
?.split("## Step 6")[0] ?? "";
|
|
100
|
+
expect(pollingSection).toContain("timeout");
|
|
101
|
+
expect(pollingSection).toContain("resend");
|
|
91
102
|
});
|
|
92
103
|
|
|
93
|
-
test(
|
|
104
|
+
test("polling section includes rebind guard against false-success from pre-existing binding", () => {
|
|
94
105
|
const pollingSection =
|
|
95
|
-
skillContent
|
|
106
|
+
skillContent
|
|
107
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
108
|
+
?.split("## Step 6")[0] ?? "";
|
|
96
109
|
// Must mention rebind guard concept
|
|
97
|
-
expect(pollingSection).toContain(
|
|
110
|
+
expect(pollingSection).toContain("Rebind guard");
|
|
98
111
|
// Must instruct not to trust the first bound: true in a rebind flow
|
|
99
112
|
expect(pollingSection).toContain(
|
|
100
|
-
|
|
113
|
+
"do NOT treat the first `bound: true` poll result as success",
|
|
101
114
|
);
|
|
102
115
|
// Must reference bound_at timestamp comparison as the primary mechanism
|
|
103
|
-
expect(pollingSection).toContain(
|
|
116
|
+
expect(pollingSection).toContain("bound_at");
|
|
104
117
|
// Must have a fallback for when bound_at is unavailable
|
|
105
|
-
expect(pollingSection).toContain(
|
|
118
|
+
expect(pollingSection).toContain("second poll onward");
|
|
106
119
|
// Must clarify non-rebind flows are unaffected
|
|
107
|
-
expect(pollingSection).toContain(
|
|
120
|
+
expect(pollingSection).toContain("Non-rebind flows");
|
|
108
121
|
});
|
|
109
122
|
|
|
110
|
-
test(
|
|
123
|
+
test("polling is voice-only — does not apply to SMS or Telegram", () => {
|
|
111
124
|
const pollingSection =
|
|
112
|
-
skillContent
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
skillContent
|
|
126
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
127
|
+
?.split("## Step 6")[0] ?? "";
|
|
128
|
+
expect(pollingSection).toContain("voice-only");
|
|
129
|
+
expect(pollingSection).toContain("Do NOT poll for SMS or Telegram");
|
|
115
130
|
});
|
|
116
131
|
|
|
117
132
|
test('no instruction requires waiting for user to ask "did it work?"', () => {
|
|
@@ -119,23 +134,22 @@ describe('guardian-verify-setup skill — voice auto-followup', () => {
|
|
|
119
134
|
// confirm that voice verification worked. The auto-check polling loop
|
|
120
135
|
// makes this unnecessary.
|
|
121
136
|
const voiceAutoCheckSection =
|
|
122
|
-
skillContent
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
);
|
|
137
|
+
skillContent
|
|
138
|
+
.split("## Voice Auto-Check Polling")[1]
|
|
139
|
+
?.split("## Step 6")[0] ?? "";
|
|
140
|
+
expect(voiceAutoCheckSection).toContain("Do NOT require the user to ask");
|
|
126
141
|
// The voice bullet in Step 3 should not instruct the assistant to wait
|
|
127
142
|
// for the user to confirm or ask if it worked. Narrow to just the voice
|
|
128
143
|
// bullet line to avoid false positives from Telegram's "wait for the
|
|
129
144
|
// user to confirm they clicked the link" which is unrelated to voice.
|
|
130
|
-
const step3Section =
|
|
131
|
-
.split(
|
|
132
|
-
?.split('## Step 4')[0] ?? '';
|
|
145
|
+
const step3Section =
|
|
146
|
+
skillContent.split("## Step 3")[1]?.split("## Step 4")[0] ?? "";
|
|
133
147
|
const voiceBullet = step3Section
|
|
134
|
-
.split(
|
|
148
|
+
.split("\n")
|
|
135
149
|
.filter((line) => /^\s*-\s+\*\*Voice\*\*/.test(line))
|
|
136
|
-
.join(
|
|
150
|
+
.join("\n");
|
|
137
151
|
expect(voiceBullet).not.toHaveLength(0);
|
|
138
|
-
expect(voiceBullet).not.toContain(
|
|
139
|
-
expect(voiceBullet).not.toContain(
|
|
152
|
+
expect(voiceBullet).not.toContain("wait for the user to confirm");
|
|
153
|
+
expect(voiceBullet).not.toContain("ask the user if it worked");
|
|
140
154
|
});
|
|
141
155
|
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'bun:test';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Guard test: prevent stale lifecycle instructions from being reintroduced
|
|
9
|
+
* into documentation. The canonical lifecycle commands are `vellum wake`,
|
|
10
|
+
* `vellum ps`, and `vellum sleep`. Repo-local slash commands live in
|
|
11
|
+
* `.claude/skills/`, not `.claude/commands/`.
|
|
12
|
+
*
|
|
13
|
+
* See AGENTS.md for the conventions these tests enforce.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const REPO_ROOT = join(import.meta.dir, '../../..');
|
|
17
|
+
|
|
18
|
+
describe('lifecycle docs guard', () => {
|
|
19
|
+
it('repo-local commands live in skills directory, not commands directory', () => {
|
|
20
|
+
const staleLocations = [
|
|
21
|
+
'.claude/commands/update.md',
|
|
22
|
+
'.claude/commands/release.md',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const violations = staleLocations.filter((p) =>
|
|
26
|
+
existsSync(join(REPO_ROOT, p)),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (violations.length > 0) {
|
|
30
|
+
const message = [
|
|
31
|
+
'Found repo-local commands in .claude/commands/ — they should live in .claude/skills/.',
|
|
32
|
+
'',
|
|
33
|
+
'Stale files:',
|
|
34
|
+
...violations.map((f) => ` - ${f}`),
|
|
35
|
+
'',
|
|
36
|
+
'Move them to .claude/skills/<name>/SKILL.md instead.',
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
expect(violations, message).toEqual([]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Verify the correct locations exist
|
|
43
|
+
const expectedLocations = [
|
|
44
|
+
'.claude/skills/update/SKILL.md',
|
|
45
|
+
'.claude/skills/release/SKILL.md',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const missing = expectedLocations.filter(
|
|
49
|
+
(p) => !existsSync(join(REPO_ROOT, p)),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (missing.length > 0) {
|
|
53
|
+
const message = [
|
|
54
|
+
'Expected repo-local skill files are missing:',
|
|
55
|
+
...missing.map((f) => ` - ${f}`),
|
|
56
|
+
].join('\n');
|
|
57
|
+
|
|
58
|
+
expect(missing, message).toEqual([]);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('key docs reference vellum lifecycle commands', () => {
|
|
63
|
+
const checks: Array<{
|
|
64
|
+
file: string;
|
|
65
|
+
pattern: string;
|
|
66
|
+
description: string;
|
|
67
|
+
}> = [
|
|
68
|
+
{
|
|
69
|
+
file: 'README.md',
|
|
70
|
+
pattern: 'vellum wake\\|vellum ps\\|vellum sleep',
|
|
71
|
+
description:
|
|
72
|
+
'README.md should mention vellum wake, vellum ps, or vellum sleep',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
file: 'assistant/README.md',
|
|
76
|
+
pattern: 'vellum wake\\|vellum ps',
|
|
77
|
+
description:
|
|
78
|
+
'assistant/README.md should mention vellum wake or vellum ps',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
file: 'AGENTS.md',
|
|
82
|
+
pattern: 'vellum ps\\|vellum sleep\\|vellum wake',
|
|
83
|
+
description:
|
|
84
|
+
'AGENTS.md should mention vellum ps, vellum sleep, or vellum wake in the /update command description',
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const failures: string[] = [];
|
|
89
|
+
|
|
90
|
+
for (const check of checks) {
|
|
91
|
+
try {
|
|
92
|
+
execSync(`git grep -q '${check.pattern}' -- '${check.file}'`, {
|
|
93
|
+
encoding: 'utf-8',
|
|
94
|
+
cwd: REPO_ROOT,
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
failures.push(check.description);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (failures.length > 0) {
|
|
102
|
+
const message = [
|
|
103
|
+
'Key docs are missing vellum lifecycle command references:',
|
|
104
|
+
'',
|
|
105
|
+
...failures.map((f) => ` - ${f}`),
|
|
106
|
+
'',
|
|
107
|
+
'These docs should reference vellum CLI lifecycle commands (wake/ps/sleep).',
|
|
108
|
+
].join('\n');
|
|
109
|
+
|
|
110
|
+
expect(failures, message).toEqual([]);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('no docs use stale daemon startup as primary instruction', () => {
|
|
115
|
+
// Files that are allowed to contain these patterns
|
|
116
|
+
const allowedPrefixes = [
|
|
117
|
+
'cli/', // CLI source code
|
|
118
|
+
'assistant/src/', // assistant runtime source
|
|
119
|
+
'CLAUDE.md', // project instructions
|
|
120
|
+
'AGENTS.md', // agent conventions (may reference patterns for context)
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const stalePatterns = [
|
|
124
|
+
{
|
|
125
|
+
pattern: 'bun run src/index.ts daemon start',
|
|
126
|
+
label: 'bun run src/index.ts daemon start',
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
const violations: string[] = [];
|
|
131
|
+
|
|
132
|
+
for (const { pattern, label } of stalePatterns) {
|
|
133
|
+
let grepOutput = '';
|
|
134
|
+
try {
|
|
135
|
+
grepOutput = execSync(
|
|
136
|
+
`git grep -n '${pattern}' -- '*.md'`,
|
|
137
|
+
{ encoding: 'utf-8', cwd: REPO_ROOT },
|
|
138
|
+
).trim();
|
|
139
|
+
} catch {
|
|
140
|
+
// No matches — happy path
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const lines = grepOutput.split('\n').filter((l) => l.length > 0);
|
|
145
|
+
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
const filePath = line.split(':')[0];
|
|
148
|
+
|
|
149
|
+
// Skip allowed file prefixes
|
|
150
|
+
if (allowedPrefixes.some((prefix) => filePath.startsWith(prefix))) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Skip test files
|
|
155
|
+
if (filePath.includes('__tests__') || filePath.endsWith('.test.ts')) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check if this specific occurrence is inside a <details> section or
|
|
160
|
+
// a "Development" / "raw bun commands" context by examining only the
|
|
161
|
+
// 10 lines before this match. We extract the line number from the
|
|
162
|
+
// grep output (format "filePath:lineNum:content") and use sed to
|
|
163
|
+
// get a targeted range, so each match is evaluated independently.
|
|
164
|
+
const parts = line.split(':');
|
|
165
|
+
const lineNum = parseInt(parts[1], 10);
|
|
166
|
+
|
|
167
|
+
if (!Number.isNaN(lineNum)) {
|
|
168
|
+
try {
|
|
169
|
+
const startLine = Math.max(1, lineNum - 10);
|
|
170
|
+
const context = execSync(
|
|
171
|
+
`sed -n '${startLine},${lineNum}p' '${filePath}'`,
|
|
172
|
+
{ encoding: 'utf-8', cwd: REPO_ROOT },
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const contextLower = context.toLowerCase();
|
|
176
|
+
const isInDetails = contextLower.includes('<details>');
|
|
177
|
+
const isDevContext =
|
|
178
|
+
contextLower.includes('development:') ||
|
|
179
|
+
contextLower.includes('low-level development') ||
|
|
180
|
+
contextLower.includes('raw bun commands');
|
|
181
|
+
|
|
182
|
+
if (isInDetails || isDevContext) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
// If context extraction fails, treat as a violation
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
violations.push(`${filePath}: contains "${label}" as primary instruction`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (violations.length > 0) {
|
|
195
|
+
const message = [
|
|
196
|
+
'Found docs using stale daemon startup patterns as primary instructions.',
|
|
197
|
+
'Use `vellum wake` / `vellum sleep` instead. Raw bun commands are acceptable',
|
|
198
|
+
'only in collapsed <details> sections or dev-only contexts.',
|
|
199
|
+
'',
|
|
200
|
+
'Violations:',
|
|
201
|
+
...violations.map((v) => ` - ${v}`),
|
|
202
|
+
].join('\n');
|
|
203
|
+
|
|
204
|
+
expect(violations, message).toEqual([]);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -235,6 +235,7 @@ describe('Story E2E: selfie yesterday -> generated image today', () => {
|
|
|
235
235
|
workingDir: sandboxDir,
|
|
236
236
|
sessionId: 'sess-story',
|
|
237
237
|
conversationId: threadB.id,
|
|
238
|
+
guardianTrustClass: 'guardian',
|
|
238
239
|
};
|
|
239
240
|
|
|
240
241
|
const result = await assetSearchTool.execute(
|
|
@@ -253,6 +254,7 @@ describe('Story E2E: selfie yesterday -> generated image today', () => {
|
|
|
253
254
|
workingDir: sandboxDir,
|
|
254
255
|
sessionId: 'sess-story',
|
|
255
256
|
conversationId: threadB.id,
|
|
257
|
+
guardianTrustClass: 'guardian',
|
|
256
258
|
};
|
|
257
259
|
|
|
258
260
|
const result = await assetSearchTool.execute(
|
|
@@ -269,6 +271,7 @@ describe('Story E2E: selfie yesterday -> generated image today', () => {
|
|
|
269
271
|
workingDir: sandboxDir,
|
|
270
272
|
sessionId: 'sess-story',
|
|
271
273
|
conversationId: threadB.id,
|
|
274
|
+
guardianTrustClass: 'guardian',
|
|
272
275
|
};
|
|
273
276
|
|
|
274
277
|
const result = await assetMaterializeTool.execute(
|
|
@@ -296,6 +299,7 @@ describe('Story E2E: selfie yesterday -> generated image today', () => {
|
|
|
296
299
|
workingDir: sandboxDir,
|
|
297
300
|
sessionId: 'sess-story',
|
|
298
301
|
conversationId: threadB.id,
|
|
302
|
+
guardianTrustClass: 'guardian',
|
|
299
303
|
};
|
|
300
304
|
|
|
301
305
|
// Step 3a: Search for the selfie
|
|
@@ -486,6 +490,7 @@ describe('Private-thread variant: cross-thread media blocking', () => {
|
|
|
486
490
|
workingDir: sandboxDir,
|
|
487
491
|
sessionId: 'sess-priv-test',
|
|
488
492
|
conversationId: standardThread.id,
|
|
493
|
+
guardianTrustClass: 'guardian',
|
|
489
494
|
};
|
|
490
495
|
|
|
491
496
|
const result = await assetSearchTool.execute(
|
|
@@ -510,6 +515,7 @@ describe('Private-thread variant: cross-thread media blocking', () => {
|
|
|
510
515
|
workingDir: sandboxDir,
|
|
511
516
|
sessionId: 'sess-priv-test',
|
|
512
517
|
conversationId: standardThread.id,
|
|
518
|
+
guardianTrustClass: 'guardian',
|
|
513
519
|
};
|
|
514
520
|
|
|
515
521
|
const result = await assetMaterializeTool.execute(
|
|
@@ -533,6 +539,7 @@ describe('Private-thread variant: cross-thread media blocking', () => {
|
|
|
533
539
|
workingDir: sandboxDir,
|
|
534
540
|
sessionId: 'sess-priv-test',
|
|
535
541
|
conversationId: privateThread.id,
|
|
542
|
+
guardianTrustClass: 'guardian',
|
|
536
543
|
};
|
|
537
544
|
|
|
538
545
|
const searchResult = await assetSearchTool.execute(
|
|
@@ -563,6 +570,7 @@ describe('Private-thread variant: cross-thread media blocking', () => {
|
|
|
563
570
|
workingDir: sandboxDir,
|
|
564
571
|
sessionId: 'sess-priv-test',
|
|
565
572
|
conversationId: privateThreadB.id,
|
|
573
|
+
guardianTrustClass: 'guardian',
|
|
566
574
|
};
|
|
567
575
|
|
|
568
576
|
const searchResult = await assetSearchTool.execute(
|