vellum 0.2.13 → 0.2.14
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/README.md +32 -0
- package/bun.lock +2 -2
- package/docs/skills.md +4 -4
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +213 -3
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +8 -8
- package/src/__tests__/browser-skill-endstate.test.ts +6 -6
- package/src/__tests__/call-bridge.test.ts +105 -13
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +113 -0
- package/src/__tests__/call-routes-http.test.ts +246 -6
- package/src/__tests__/channel-approval-routes.test.ts +438 -0
- package/src/__tests__/channel-approval.test.ts +266 -0
- package/src/__tests__/channel-approvals.test.ts +393 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/checker.test.ts +607 -1048
- package/src/__tests__/cli.test.ts +1 -56
- package/src/__tests__/config-schema.test.ts +137 -18
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +2 -0
- package/src/__tests__/contacts-tools.test.ts +3 -3
- package/src/__tests__/contradiction-checker.test.ts +99 -1
- package/src/__tests__/credential-security-invariants.test.ts +22 -6
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/elevenlabs-client.test.ts +62 -0
- package/src/__tests__/ephemeral-permissions.test.ts +73 -23
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +114 -4
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +2 -1
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +2 -1
- package/src/__tests__/handlers-slack-config.test.ts +2 -1
- package/src/__tests__/handlers-telegram-config.test.ts +855 -0
- package/src/__tests__/handlers-twitter-config.test.ts +141 -1
- package/src/__tests__/hooks-runner.test.ts +6 -2
- package/src/__tests__/host-file-edit-tool.test.ts +124 -0
- package/src/__tests__/host-file-read-tool.test.ts +62 -0
- package/src/__tests__/host-file-write-tool.test.ts +59 -0
- package/src/__tests__/host-shell-tool.test.ts +251 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ipc-snapshot.test.ts +100 -41
- package/src/__tests__/ipc-validate.test.ts +50 -0
- package/src/__tests__/key-migration.test.ts +23 -0
- package/src/__tests__/memory-regressions.test.ts +99 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/oauth-callback-registry.test.ts +11 -4
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +4 -6
- package/src/__tests__/public-ingress-urls.test.ts +34 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +97 -0
- package/src/__tests__/recurrence-engine.test.ts +9 -0
- package/src/__tests__/recurrence-types.test.ts +8 -0
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/runtime-runs.test.ts +1 -25
- package/src/__tests__/schedule-store.test.ts +16 -14
- package/src/__tests__/schedule-tools.test.ts +83 -0
- package/src/__tests__/scheduler-recurrence.test.ts +111 -10
- package/src/__tests__/secret-allowlist.test.ts +18 -17
- package/src/__tests__/secret-ingress-handler.test.ts +11 -0
- package/src/__tests__/secret-scanner.test.ts +43 -0
- package/src/__tests__/session-conflict-gate.test.ts +442 -6
- package/src/__tests__/session-init.benchmark.test.ts +3 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -1
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -1
- package/src/__tests__/subagent-tools.test.ts +637 -54
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +2 -2
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +85 -151
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/trust-store.test.ts +27 -453
- package/src/__tests__/twilio-provider.test.ts +153 -3
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +4 -4
- package/src/__tests__/twilio-routes.test.ts +17 -262
- package/src/__tests__/twitter-auth-handler.test.ts +2 -1
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/calls/call-bridge.ts +92 -19
- package/src/calls/call-domain.ts +157 -5
- package/src/calls/call-orchestrator.ts +93 -7
- package/src/calls/call-store.ts +6 -0
- package/src/calls/elevenlabs-client.ts +8 -0
- package/src/calls/elevenlabs-config.ts +7 -5
- package/src/calls/twilio-provider.ts +91 -0
- package/src/calls/twilio-routes.ts +32 -37
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-quality.ts +29 -7
- package/src/cli/twitter.ts +200 -21
- package/src/cli.ts +1 -20
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +52 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -4
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +61 -4
- package/src/config/bundled-skills/messaging/SKILL.md +17 -2
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +4 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +142 -34
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +95 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +51 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +73 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +110 -6
- package/src/config/bundled-skills/public-ingress/SKILL.md +22 -5
- package/src/config/bundled-skills/twitter/SKILL.md +103 -17
- package/src/config/defaults.ts +10 -4
- package/src/config/schema.ts +80 -21
- package/src/config/types.ts +1 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +56 -61
- package/src/daemon/assistant-attachments.ts +4 -2
- package/src/daemon/handlers/apps.ts +69 -0
- package/src/daemon/handlers/config.ts +543 -24
- package/src/daemon/handlers/index.ts +1 -0
- package/src/daemon/handlers/sessions.ts +22 -6
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +5 -20
- package/src/daemon/ipc-contract-inventory.json +28 -0
- package/src/daemon/ipc-contract.ts +168 -10
- package/src/daemon/ipc-validate.ts +17 -0
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/server.ts +78 -72
- package/src/daemon/session-attachments.ts +1 -1
- package/src/daemon/session-conflict-gate.ts +62 -6
- package/src/daemon/session-notifiers.ts +1 -1
- package/src/daemon/session-process.ts +62 -3
- package/src/daemon/session-tool-setup.ts +1 -2
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/video-thumbnail.ts +5 -3
- package/src/hooks/manager.ts +5 -9
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +21 -0
- package/src/memory/conflict-intent.ts +47 -4
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +9 -1
- package/src/memory/contradiction-checker.ts +28 -0
- package/src/memory/conversation-key-store.ts +15 -0
- package/src/memory/db.ts +81 -0
- package/src/memory/embedding-local.ts +3 -13
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/job-handlers/conflict.ts +22 -2
- package/src/memory/jobs-worker.ts +67 -28
- package/src/memory/runs-store.ts +54 -7
- package/src/memory/schema.ts +20 -0
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +1 -0
- package/src/permissions/checker.ts +48 -44
- package/src/permissions/prompter.ts +0 -4
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +76 -53
- package/src/permissions/types.ts +0 -19
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/providers/retry.ts +12 -37
- package/src/runtime/assistant-event-hub.ts +41 -4
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +71 -0
- package/src/runtime/channel-approvals.ts +145 -0
- package/src/runtime/gateway-client.ts +16 -0
- package/src/runtime/http-server.ts +29 -9
- package/src/runtime/routes/call-routes.ts +52 -2
- package/src/runtime/routes/channel-routes.ts +296 -16
- package/src/runtime/routes/events-routes.ts +97 -28
- package/src/runtime/routes/run-routes.ts +2 -7
- package/src/runtime/run-orchestrator.ts +0 -3
- package/src/schedule/recurrence-engine.ts +26 -2
- package/src/schedule/recurrence-types.ts +1 -1
- package/src/schedule/schedule-store.ts +12 -3
- package/src/security/secret-scanner.ts +7 -0
- package/src/tasks/ephemeral-permissions.ts +0 -2
- package/src/tasks/task-scheduler.ts +2 -1
- package/src/tools/calls/call-start.ts +8 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +6 -135
- package/src/tools/network/web-search.ts +9 -32
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/terminal/parser.ts +16 -18
- package/src/tools/types.ts +4 -11
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +29 -4
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/truncate.ts +1 -1
- package/src/workspace/git-service.ts +129 -112
- package/src/tools/contacts/contact-merge.ts +0 -55
- package/src/tools/contacts/contact-search.ts +0 -58
- package/src/tools/contacts/contact-upsert.ts +0 -64
- package/src/tools/playbooks/index.ts +0 -4
- package/src/tools/playbooks/playbook-create.ts +0 -96
- package/src/tools/playbooks/playbook-delete.ts +0 -52
- package/src/tools/playbooks/playbook-list.ts +0 -74
- package/src/tools/playbooks/playbook-update.ts +0 -111
package/README.md
CHANGED
|
@@ -120,6 +120,38 @@ assistant/
|
|
|
120
120
|
└── package.json
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
## Channel Approval Flow
|
|
124
|
+
|
|
125
|
+
When the assistant needs tool-use confirmation during a channel session (e.g., Telegram), the approval flow intercepts the run and surfaces an interactive prompt to the user. This is gated behind the `CHANNEL_APPROVALS_ENABLED=true` environment variable.
|
|
126
|
+
|
|
127
|
+
### How it works
|
|
128
|
+
|
|
129
|
+
1. **Detection** — When a channel inbound message triggers an agent loop, the runtime polls the run status. If the run transitions to `needs_confirmation`, the runtime sends an approval prompt to the gateway with inline keyboard metadata.
|
|
130
|
+
2. **Interception** — Subsequent inbound messages on the same conversation are intercepted before normal processing. The handler checks for a pending approval and attempts to extract a decision from either callback data (button clicks) or plain text.
|
|
131
|
+
3. **Decision** — The user's decision is mapped to the permission system (`allow` or `deny`) and applied to the pending run. For `approve_always`, a trust rule is persisted so future invocations of the same tool are auto-approved.
|
|
132
|
+
4. **Reminder** — If the user sends a non-decision message while an approval is pending, a reminder prompt is re-sent with the approval buttons.
|
|
133
|
+
|
|
134
|
+
### Key modules
|
|
135
|
+
|
|
136
|
+
| File | Purpose |
|
|
137
|
+
|------|---------|
|
|
138
|
+
| `src/runtime/channel-approvals.ts` | Orchestration: `getChannelApprovalPrompt`, `buildApprovalUIMetadata`, `handleChannelDecision`, `buildReminderPrompt` |
|
|
139
|
+
| `src/runtime/channel-approval-parser.ts` | Plain-text decision parser — matches phrases like `yes`, `approve`, `always`, `no`, `reject`, `deny`, `cancel` (case-insensitive) |
|
|
140
|
+
| `src/runtime/channel-approval-types.ts` | Shared types: `ApprovalAction`, `ChannelApprovalPrompt`, `ApprovalUIMetadata`, `ApprovalDecisionResult` |
|
|
141
|
+
| `src/runtime/routes/channel-routes.ts` | Integration point: `handleApprovalInterception` and `processChannelMessageWithApprovals` in the channel inbound handler |
|
|
142
|
+
| `src/runtime/gateway-client.ts` | `deliverApprovalPrompt()` — sends the approval payload (text + UI metadata) to the gateway for rendering |
|
|
143
|
+
| `src/memory/runs-store.ts` | `getPendingConfirmationsByConversation` — queries runs in `needs_confirmation` state |
|
|
144
|
+
|
|
145
|
+
### Enabling
|
|
146
|
+
|
|
147
|
+
Set the environment variable before starting the daemon:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
CHANNEL_APPROVALS_ENABLED=true
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
When disabled (the default), channel messages follow the standard fire-and-forget processing path without approval interception.
|
|
154
|
+
|
|
123
155
|
## Database
|
|
124
156
|
|
|
125
157
|
SQLite via Drizzle ORM, stored at `~/.vellum/workspace/data/db/assistant.db`. Key tables include conversations, messages, tool invocations, attachments, memory segments (with FTS5), memory items, entities, reminders, and recurrence schedules (cron + RRULE).
|
package/bun.lock
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"@huggingface/transformers": "^3.8.1",
|
|
12
12
|
"@qdrant/js-client-rest": "^1.16.2",
|
|
13
13
|
"@sentry/node": "^10.38.0",
|
|
14
|
-
"@vellumai/cli": "0.1.
|
|
14
|
+
"@vellumai/cli": "0.1.14",
|
|
15
15
|
"@vellumai/vellum-gateway": "0.1.10",
|
|
16
16
|
"agentmail": "^0.1.0",
|
|
17
17
|
"archiver": "^7.0.1",
|
|
@@ -542,7 +542,7 @@
|
|
|
542
542
|
|
|
543
543
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg=="],
|
|
544
544
|
|
|
545
|
-
"@vellumai/cli": ["@vellumai/cli@0.1.
|
|
545
|
+
"@vellumai/cli": ["@vellumai/cli@0.1.14", "", { "dependencies": { "ink": "^6.7.0", "react": "^19.2.4", "react-devtools-core": "^6.1.2" }, "bin": { "vellum-cli": "src/index.ts" } }, "sha512-SjPCBWhZIOsQaYdGswMlVpIqfFPvLEIwyBRQSMXcNayTxkMuj8nS6SyLiJau+8aD86EjrvAT1mp+Csd83wABvw=="],
|
|
546
546
|
|
|
547
547
|
"@vellumai/vellum-gateway": ["@vellumai/vellum-gateway@0.1.10", "", { "dependencies": { "file-type": "^21.3.0", "pino": "^9.6.0", "pino-pretty": "^13.1.3", "zod": "^4.3.6" } }, "sha512-a41fGexW8RpWL4RTfZ3EM+XJMvz7t26D1axu2xAtZioXW3ZWMLGuogHnIJsgglzESl49E6VmmUsUGeD+dseV2w=="],
|
|
548
548
|
|
package/docs/skills.md
CHANGED
|
@@ -6,7 +6,7 @@ This document describes the security model for the Vellum Assistant skill system
|
|
|
6
6
|
|
|
7
7
|
Skills extend the assistant's capabilities by providing instructions (via `SKILL.md`) and optional custom tools (via `TOOLS.json`). Skills can be **bundled** (shipped with the application), **managed** (user-installed via `scaffold_managed_skill`), **workspace** (project-local), or **extra** (additional directories configured by the user).
|
|
8
8
|
|
|
9
|
-
Because skills can introduce arbitrary tool behavior, they are subject to stricter permission defaults than core tools.
|
|
9
|
+
Because skills can introduce arbitrary tool behavior, they are subject to stricter permission defaults than core tools.
|
|
10
10
|
|
|
11
11
|
## Permission Defaults for Skill Tools
|
|
12
12
|
|
|
@@ -37,7 +37,7 @@ The allowlist options presented during a permission prompt include both version-
|
|
|
37
37
|
|
|
38
38
|
## Version-Bound Approvals
|
|
39
39
|
|
|
40
|
-
Trust rules can
|
|
40
|
+
Trust rules for `skill_load` can use version-specific patterns (e.g., `skill_load:my-skill@v1:abc123...`) to pin approval to a specific content hash of the skill's source files.
|
|
41
41
|
|
|
42
42
|
### How version hashing works
|
|
43
43
|
|
|
@@ -50,7 +50,7 @@ The `computeSkillVersionHash(directoryPath)` function computes a deterministic S
|
|
|
50
50
|
|
|
51
51
|
### Version invalidation
|
|
52
52
|
|
|
53
|
-
When a skill's source files change (any file added, removed, or modified), the hash changes.
|
|
53
|
+
When a skill's source files change (any file added, removed, or modified), the hash changes. Version-specific trust rules with the old hash no longer match, and the user is re-prompted. This protects against:
|
|
54
54
|
|
|
55
55
|
- **Supply-chain attacks**: A malicious update to a managed or workspace skill cannot silently inherit previous approvals.
|
|
56
56
|
- **Accidental drift**: Editing a skill's tool scripts invalidates stale approvals, ensuring the user reviews the new behavior.
|
|
@@ -155,4 +155,4 @@ Trust rules are stored in `~/.vellum/protected/trust.json`. You can inspect this
|
|
|
155
155
|
|
|
156
156
|
### "A skill tool keeps prompting even though I approved it."
|
|
157
157
|
|
|
158
|
-
Check whether the rule
|
|
158
|
+
Check whether the rule has the correct `executionTarget` — a rule scoped to `sandbox` will not match a tool running on `host`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vellum",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vellum": "./src/index.ts"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@huggingface/transformers": "^3.8.1",
|
|
30
30
|
"@qdrant/js-client-rest": "^1.16.2",
|
|
31
31
|
"@sentry/node": "^10.38.0",
|
|
32
|
-
"@vellumai/cli": "0.1.
|
|
32
|
+
"@vellumai/cli": "0.1.14",
|
|
33
33
|
"@vellumai/vellum-gateway": "0.1.10",
|
|
34
34
|
"agentmail": "^0.1.0",
|
|
35
35
|
"archiver": "^7.0.1",
|
|
@@ -318,7 +318,9 @@ exports[`IPC message snapshots ClientMessage types suggestion_request serializes
|
|
|
318
318
|
|
|
319
319
|
exports[`IPC message snapshots ClientMessage types add_trust_rule serializes to expected JSON 1`] = `
|
|
320
320
|
{
|
|
321
|
+
"allowHighRisk": true,
|
|
321
322
|
"decision": "allow",
|
|
323
|
+
"executionTarget": "host",
|
|
322
324
|
"pattern": "git *",
|
|
323
325
|
"scope": "/projects/my-app",
|
|
324
326
|
"toolName": "bash",
|
|
@@ -509,6 +511,40 @@ exports[`IPC message snapshots ClientMessage types app_preview_request serialize
|
|
|
509
511
|
}
|
|
510
512
|
`;
|
|
511
513
|
|
|
514
|
+
exports[`IPC message snapshots ClientMessage types app_history_request serializes to expected JSON 1`] = `
|
|
515
|
+
{
|
|
516
|
+
"appId": "app-001",
|
|
517
|
+
"limit": 25,
|
|
518
|
+
"type": "app_history_request",
|
|
519
|
+
}
|
|
520
|
+
`;
|
|
521
|
+
|
|
522
|
+
exports[`IPC message snapshots ClientMessage types app_diff_request serializes to expected JSON 1`] = `
|
|
523
|
+
{
|
|
524
|
+
"appId": "app-001",
|
|
525
|
+
"fromCommit": "abc123def456",
|
|
526
|
+
"toCommit": "789abc123def",
|
|
527
|
+
"type": "app_diff_request",
|
|
528
|
+
}
|
|
529
|
+
`;
|
|
530
|
+
|
|
531
|
+
exports[`IPC message snapshots ClientMessage types app_file_at_version_request serializes to expected JSON 1`] = `
|
|
532
|
+
{
|
|
533
|
+
"appId": "app-001",
|
|
534
|
+
"commitHash": "abc123def456",
|
|
535
|
+
"path": "index.html",
|
|
536
|
+
"type": "app_file_at_version_request",
|
|
537
|
+
}
|
|
538
|
+
`;
|
|
539
|
+
|
|
540
|
+
exports[`IPC message snapshots ClientMessage types app_restore_request serializes to expected JSON 1`] = `
|
|
541
|
+
{
|
|
542
|
+
"appId": "app-001",
|
|
543
|
+
"commitHash": "abc123def456",
|
|
544
|
+
"type": "app_restore_request",
|
|
545
|
+
}
|
|
546
|
+
`;
|
|
547
|
+
|
|
512
548
|
exports[`IPC message snapshots ClientMessage types share_app_cloud serializes to expected JSON 1`] = `
|
|
513
549
|
{
|
|
514
550
|
"appId": "app-001",
|
|
@@ -551,6 +587,13 @@ exports[`IPC message snapshots ClientMessage types twitter_integration_config se
|
|
|
551
587
|
}
|
|
552
588
|
`;
|
|
553
589
|
|
|
590
|
+
exports[`IPC message snapshots ClientMessage types telegram_config serializes to expected JSON 1`] = `
|
|
591
|
+
{
|
|
592
|
+
"action": "get",
|
|
593
|
+
"type": "telegram_config",
|
|
594
|
+
}
|
|
595
|
+
`;
|
|
596
|
+
|
|
554
597
|
exports[`IPC message snapshots ClientMessage types twitter_auth_start serializes to expected JSON 1`] = `
|
|
555
598
|
{
|
|
556
599
|
"type": "twitter_auth_start",
|
|
@@ -830,6 +873,19 @@ exports[`IPC message snapshots ClientMessage types identity_get serializes to ex
|
|
|
830
873
|
}
|
|
831
874
|
`;
|
|
832
875
|
|
|
876
|
+
exports[`IPC message snapshots ClientMessage types tool_permission_simulate serializes to expected JSON 1`] = `
|
|
877
|
+
{
|
|
878
|
+
"forcePromptSideEffects": false,
|
|
879
|
+
"input": {
|
|
880
|
+
"command": "rm -rf /tmp/test",
|
|
881
|
+
},
|
|
882
|
+
"isInteractive": true,
|
|
883
|
+
"toolName": "bash",
|
|
884
|
+
"type": "tool_permission_simulate",
|
|
885
|
+
"workingDir": "/projects/my-app",
|
|
886
|
+
}
|
|
887
|
+
`;
|
|
888
|
+
|
|
833
889
|
exports[`IPC message snapshots ServerMessage types auth_result serializes to expected JSON 1`] = `
|
|
834
890
|
{
|
|
835
891
|
"success": true,
|
|
@@ -946,9 +1002,6 @@ exports[`IPC message snapshots ServerMessage types confirmation_request serializ
|
|
|
946
1002
|
"input": {
|
|
947
1003
|
"command": "rm -rf /tmp/test",
|
|
948
1004
|
},
|
|
949
|
-
"principalId": "my-skill",
|
|
950
|
-
"principalKind": "skill",
|
|
951
|
-
"principalVersion": "sha256:abcdef1234567890",
|
|
952
1005
|
"requestId": "req-002",
|
|
953
1006
|
"riskLevel": "high",
|
|
954
1007
|
"sandboxed": false,
|
|
@@ -1841,6 +1894,17 @@ exports[`IPC message snapshots ServerMessage types twitter_integration_config_re
|
|
|
1841
1894
|
}
|
|
1842
1895
|
`;
|
|
1843
1896
|
|
|
1897
|
+
exports[`IPC message snapshots ServerMessage types telegram_config_response serializes to expected JSON 1`] = `
|
|
1898
|
+
{
|
|
1899
|
+
"botUsername": "my_test_bot",
|
|
1900
|
+
"connected": true,
|
|
1901
|
+
"hasBotToken": true,
|
|
1902
|
+
"hasWebhookSecret": true,
|
|
1903
|
+
"success": true,
|
|
1904
|
+
"type": "telegram_config_response",
|
|
1905
|
+
}
|
|
1906
|
+
`;
|
|
1907
|
+
|
|
1844
1908
|
exports[`IPC message snapshots ServerMessage types twitter_auth_result serializes to expected JSON 1`] = `
|
|
1845
1909
|
{
|
|
1846
1910
|
"accountInfo": "@vellum_test",
|
|
@@ -1882,6 +1946,49 @@ exports[`IPC message snapshots ServerMessage types app_preview_response serializ
|
|
|
1882
1946
|
}
|
|
1883
1947
|
`;
|
|
1884
1948
|
|
|
1949
|
+
exports[`IPC message snapshots ServerMessage types app_history_response serializes to expected JSON 1`] = `
|
|
1950
|
+
{
|
|
1951
|
+
"appId": "app-001",
|
|
1952
|
+
"type": "app_history_response",
|
|
1953
|
+
"versions": [
|
|
1954
|
+
{
|
|
1955
|
+
"commitHash": "abc123def456",
|
|
1956
|
+
"message": "Initial app commit",
|
|
1957
|
+
"timestamp": 1700000000,
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
"commitHash": "789abc123def",
|
|
1961
|
+
"message": "Update landing page",
|
|
1962
|
+
"timestamp": 1700001000,
|
|
1963
|
+
},
|
|
1964
|
+
],
|
|
1965
|
+
}
|
|
1966
|
+
`;
|
|
1967
|
+
|
|
1968
|
+
exports[`IPC message snapshots ServerMessage types app_diff_response serializes to expected JSON 1`] = `
|
|
1969
|
+
{
|
|
1970
|
+
"appId": "app-001",
|
|
1971
|
+
"diff": "diff --git a/index.html b/index.html",
|
|
1972
|
+
"type": "app_diff_response",
|
|
1973
|
+
}
|
|
1974
|
+
`;
|
|
1975
|
+
|
|
1976
|
+
exports[`IPC message snapshots ServerMessage types app_file_at_version_response serializes to expected JSON 1`] = `
|
|
1977
|
+
{
|
|
1978
|
+
"appId": "app-001",
|
|
1979
|
+
"content": "<html><body>Hello</body></html>",
|
|
1980
|
+
"path": "index.html",
|
|
1981
|
+
"type": "app_file_at_version_response",
|
|
1982
|
+
}
|
|
1983
|
+
`;
|
|
1984
|
+
|
|
1985
|
+
exports[`IPC message snapshots ServerMessage types app_restore_response serializes to expected JSON 1`] = `
|
|
1986
|
+
{
|
|
1987
|
+
"success": true,
|
|
1988
|
+
"type": "app_restore_response",
|
|
1989
|
+
}
|
|
1990
|
+
`;
|
|
1991
|
+
|
|
1885
1992
|
exports[`IPC message snapshots ServerMessage types ui_surface_undo_result serializes to expected JSON 1`] = `
|
|
1886
1993
|
{
|
|
1887
1994
|
"remainingUndos": 3,
|
|
@@ -2314,3 +2421,106 @@ exports[`IPC message snapshots ServerMessage types identity_get_response seriali
|
|
|
2314
2421
|
"type": "identity_get_response",
|
|
2315
2422
|
}
|
|
2316
2423
|
`;
|
|
2424
|
+
|
|
2425
|
+
exports[`IPC message snapshots ServerMessage types tool_permission_simulate_response serializes to expected JSON 1`] = `
|
|
2426
|
+
{
|
|
2427
|
+
"decision": "prompt",
|
|
2428
|
+
"executionTarget": "host",
|
|
2429
|
+
"promptPayload": {
|
|
2430
|
+
"allowlistOptions": [
|
|
2431
|
+
{
|
|
2432
|
+
"description": "Allow rm commands",
|
|
2433
|
+
"label": "Allow rm commands",
|
|
2434
|
+
"pattern": "bash:rm *",
|
|
2435
|
+
},
|
|
2436
|
+
],
|
|
2437
|
+
"persistentDecisionsAllowed": true,
|
|
2438
|
+
"scopeOptions": [
|
|
2439
|
+
{
|
|
2440
|
+
"label": "In /projects/my-app",
|
|
2441
|
+
"scope": "/projects/my-app",
|
|
2442
|
+
},
|
|
2443
|
+
],
|
|
2444
|
+
},
|
|
2445
|
+
"reason": "No matching trust rule; tool requires approval",
|
|
2446
|
+
"riskLevel": "high",
|
|
2447
|
+
"success": true,
|
|
2448
|
+
"type": "tool_permission_simulate_response",
|
|
2449
|
+
}
|
|
2450
|
+
`;
|
|
2451
|
+
|
|
2452
|
+
exports[`IPC message snapshots ClientMessage types app_history_request serializes to expected JSON 1`] = `
|
|
2453
|
+
{
|
|
2454
|
+
"appId": "app-001",
|
|
2455
|
+
"type": "app_history_request",
|
|
2456
|
+
}
|
|
2457
|
+
`;
|
|
2458
|
+
|
|
2459
|
+
exports[`IPC message snapshots ClientMessage types app_diff_request serializes to expected JSON 1`] = `
|
|
2460
|
+
{
|
|
2461
|
+
"appId": "app-001",
|
|
2462
|
+
"fromCommit": "abc123",
|
|
2463
|
+
"type": "app_diff_request",
|
|
2464
|
+
}
|
|
2465
|
+
`;
|
|
2466
|
+
|
|
2467
|
+
exports[`IPC message snapshots ClientMessage types app_file_at_version_request serializes to expected JSON 1`] = `
|
|
2468
|
+
{
|
|
2469
|
+
"appId": "app-001",
|
|
2470
|
+
"commitHash": "abc123",
|
|
2471
|
+
"path": "index.html",
|
|
2472
|
+
"type": "app_file_at_version_request",
|
|
2473
|
+
}
|
|
2474
|
+
`;
|
|
2475
|
+
|
|
2476
|
+
exports[`IPC message snapshots ClientMessage types app_restore_request serializes to expected JSON 1`] = `
|
|
2477
|
+
{
|
|
2478
|
+
"appId": "app-001",
|
|
2479
|
+
"commitHash": "abc123",
|
|
2480
|
+
"type": "app_restore_request",
|
|
2481
|
+
}
|
|
2482
|
+
`;
|
|
2483
|
+
|
|
2484
|
+
exports[`IPC message snapshots ServerMessage types app_history_response serializes to expected JSON 1`] = `
|
|
2485
|
+
{
|
|
2486
|
+
"appId": "app-001",
|
|
2487
|
+
"type": "app_history_response",
|
|
2488
|
+
"versions": [
|
|
2489
|
+
{
|
|
2490
|
+
"commitHash": "abc123",
|
|
2491
|
+
"message": "Initial commit",
|
|
2492
|
+
"timestamp": 1700000000,
|
|
2493
|
+
},
|
|
2494
|
+
],
|
|
2495
|
+
}
|
|
2496
|
+
`;
|
|
2497
|
+
|
|
2498
|
+
exports[`IPC message snapshots ServerMessage types app_diff_response serializes to expected JSON 1`] = `
|
|
2499
|
+
{
|
|
2500
|
+
"appId": "app-001",
|
|
2501
|
+
"diff":
|
|
2502
|
+
"--- a/index.html
|
|
2503
|
+
+++ b/index.html
|
|
2504
|
+
@@ -1 +1 @@
|
|
2505
|
+
-old
|
|
2506
|
+
+new"
|
|
2507
|
+
,
|
|
2508
|
+
"type": "app_diff_response",
|
|
2509
|
+
}
|
|
2510
|
+
`;
|
|
2511
|
+
|
|
2512
|
+
exports[`IPC message snapshots ServerMessage types app_file_at_version_response serializes to expected JSON 1`] = `
|
|
2513
|
+
{
|
|
2514
|
+
"appId": "app-001",
|
|
2515
|
+
"content": "<html></html>",
|
|
2516
|
+
"path": "index.html",
|
|
2517
|
+
"type": "app_file_at_version_response",
|
|
2518
|
+
}
|
|
2519
|
+
`;
|
|
2520
|
+
|
|
2521
|
+
exports[`IPC message snapshots ServerMessage types app_restore_response serializes to expected JSON 1`] = `
|
|
2522
|
+
{
|
|
2523
|
+
"success": true,
|
|
2524
|
+
"type": "app_restore_response",
|
|
2525
|
+
}
|
|
2526
|
+
`;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
2
|
+
import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { _resetGitServiceRegistry } from '../workspace/git-service.js';
|
|
6
|
+
import { _resetAppGitState } from '../memory/app-git-service.js';
|
|
7
|
+
|
|
8
|
+
// Mock getDataDir to use a temp directory
|
|
9
|
+
let testDataDir: string;
|
|
10
|
+
|
|
11
|
+
mock.module('../util/platform.js', () => ({
|
|
12
|
+
getDataDir: () => testDataDir,
|
|
13
|
+
getProjectDir: () => testDataDir,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Re-import after mocking so modules use our temp dir
|
|
17
|
+
const { createApp, updateApp, deleteApp: _deleteApp, writeAppFile: _writeAppFile, editAppFile: _editAppFile, getAppsDir } = await import('../memory/app-store.js');
|
|
18
|
+
const { getAppHistory, getAppDiff, getAppFileAtVersion, restoreAppVersion, commitAppChange: _commitAppChange } = await import('../memory/app-git-service.js');
|
|
19
|
+
|
|
20
|
+
describe('App Git History', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
testDataDir = join(tmpdir(), `vellum-app-git-history-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
23
|
+
mkdirSync(join(testDataDir, 'apps'), { recursive: true });
|
|
24
|
+
_resetGitServiceRegistry();
|
|
25
|
+
_resetAppGitState();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
if (existsSync(testDataDir)) {
|
|
30
|
+
rmSync(testDataDir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Wait for fire-and-forget commits to complete. */
|
|
35
|
+
async function waitForCommits(): Promise<void> {
|
|
36
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test('getAppHistory returns commits for a specific app', async () => {
|
|
40
|
+
const app = createApp({
|
|
41
|
+
name: 'History App',
|
|
42
|
+
schemaJson: '{}',
|
|
43
|
+
htmlDefinition: '<h1>v1</h1>',
|
|
44
|
+
});
|
|
45
|
+
await waitForCommits();
|
|
46
|
+
|
|
47
|
+
updateApp(app.id, { htmlDefinition: '<h1>v2</h1>' });
|
|
48
|
+
await waitForCommits();
|
|
49
|
+
|
|
50
|
+
const history = await getAppHistory(app.id);
|
|
51
|
+
expect(history.length).toBeGreaterThanOrEqual(2);
|
|
52
|
+
expect(history[0].message).toContain('Update app');
|
|
53
|
+
// The create commit may be absorbed into the "Initial commit" on a fresh repo
|
|
54
|
+
expect(history[history.length - 1].message).toMatch(/Create app|Initial commit/);
|
|
55
|
+
expect(history[0].commitHash).toMatch(/^[0-9a-f]+$/);
|
|
56
|
+
expect(history[0].timestamp).toBeGreaterThan(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('getAppHistory does not return commits for other apps', async () => {
|
|
60
|
+
const app1 = createApp({
|
|
61
|
+
name: 'App One',
|
|
62
|
+
schemaJson: '{}',
|
|
63
|
+
htmlDefinition: '<p>one</p>',
|
|
64
|
+
});
|
|
65
|
+
await waitForCommits();
|
|
66
|
+
|
|
67
|
+
const app2 = createApp({
|
|
68
|
+
name: 'App Two',
|
|
69
|
+
schemaJson: '{}',
|
|
70
|
+
htmlDefinition: '<p>two</p>',
|
|
71
|
+
});
|
|
72
|
+
await waitForCommits();
|
|
73
|
+
|
|
74
|
+
const history1 = await getAppHistory(app1.id);
|
|
75
|
+
const history2 = await getAppHistory(app2.id);
|
|
76
|
+
|
|
77
|
+
// App1's history should only contain its own commits
|
|
78
|
+
expect(history1.every(v => v.message.includes('App One') || v.message.includes('Initial commit'))).toBe(true);
|
|
79
|
+
// App2's history should only contain its own commits
|
|
80
|
+
expect(history2.every(v => v.message.includes('App Two') || v.message.includes('Initial commit'))).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('getAppHistory respects limit', async () => {
|
|
84
|
+
const app = createApp({
|
|
85
|
+
name: 'Limited App',
|
|
86
|
+
schemaJson: '{}',
|
|
87
|
+
htmlDefinition: '<p>v1</p>',
|
|
88
|
+
});
|
|
89
|
+
await waitForCommits();
|
|
90
|
+
|
|
91
|
+
updateApp(app.id, { htmlDefinition: '<p>v2</p>' });
|
|
92
|
+
await waitForCommits();
|
|
93
|
+
|
|
94
|
+
updateApp(app.id, { htmlDefinition: '<p>v3</p>' });
|
|
95
|
+
await waitForCommits();
|
|
96
|
+
|
|
97
|
+
const limited = await getAppHistory(app.id, 2);
|
|
98
|
+
expect(limited.length).toBe(2);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('getAppDiff shows changes between versions', async () => {
|
|
102
|
+
const app = createApp({
|
|
103
|
+
name: 'Diff App',
|
|
104
|
+
schemaJson: '{}',
|
|
105
|
+
htmlDefinition: '<p>original</p>',
|
|
106
|
+
});
|
|
107
|
+
await waitForCommits();
|
|
108
|
+
|
|
109
|
+
const history1 = await getAppHistory(app.id);
|
|
110
|
+
const createHash = history1[0].commitHash;
|
|
111
|
+
|
|
112
|
+
updateApp(app.id, { htmlDefinition: '<p>modified</p>' });
|
|
113
|
+
await waitForCommits();
|
|
114
|
+
|
|
115
|
+
const history2 = await getAppHistory(app.id);
|
|
116
|
+
const updateHash = history2[0].commitHash;
|
|
117
|
+
|
|
118
|
+
const diff = await getAppDiff(app.id, createHash, updateHash);
|
|
119
|
+
expect(diff).toContain('original');
|
|
120
|
+
expect(diff).toContain('modified');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('getAppFileAtVersion returns file content at a specific commit', async () => {
|
|
124
|
+
const app = createApp({
|
|
125
|
+
name: 'File Version App',
|
|
126
|
+
schemaJson: '{}',
|
|
127
|
+
htmlDefinition: '<p>version one</p>',
|
|
128
|
+
});
|
|
129
|
+
await waitForCommits();
|
|
130
|
+
|
|
131
|
+
const history1 = await getAppHistory(app.id);
|
|
132
|
+
const v1Hash = history1[0].commitHash;
|
|
133
|
+
|
|
134
|
+
updateApp(app.id, { htmlDefinition: '<p>version two</p>' });
|
|
135
|
+
await waitForCommits();
|
|
136
|
+
|
|
137
|
+
// Get the file at v1 — should show old content
|
|
138
|
+
const v1Content = await getAppFileAtVersion(app.id, 'index.html', v1Hash);
|
|
139
|
+
expect(v1Content).toContain('version one');
|
|
140
|
+
expect(v1Content).not.toContain('version two');
|
|
141
|
+
|
|
142
|
+
// Current file should show new content
|
|
143
|
+
const currentContent = readFileSync(join(getAppsDir(), app.id, 'index.html'), 'utf-8');
|
|
144
|
+
expect(currentContent).toContain('version two');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('restoreAppVersion restores files and creates a new commit', async () => {
|
|
148
|
+
const app = createApp({
|
|
149
|
+
name: 'Restore App',
|
|
150
|
+
schemaJson: '{}',
|
|
151
|
+
htmlDefinition: '<p>original content</p>',
|
|
152
|
+
});
|
|
153
|
+
await waitForCommits();
|
|
154
|
+
|
|
155
|
+
const history1 = await getAppHistory(app.id);
|
|
156
|
+
const originalHash = history1[0].commitHash;
|
|
157
|
+
|
|
158
|
+
updateApp(app.id, { htmlDefinition: '<p>new content</p>' });
|
|
159
|
+
await waitForCommits();
|
|
160
|
+
|
|
161
|
+
// Verify current content is "new content"
|
|
162
|
+
let current = readFileSync(join(getAppsDir(), app.id, 'index.html'), 'utf-8');
|
|
163
|
+
expect(current).toContain('new content');
|
|
164
|
+
|
|
165
|
+
// Restore to original
|
|
166
|
+
await restoreAppVersion(app.id, originalHash);
|
|
167
|
+
|
|
168
|
+
// Verify content is restored
|
|
169
|
+
current = readFileSync(join(getAppsDir(), app.id, 'index.html'), 'utf-8');
|
|
170
|
+
expect(current).toContain('original content');
|
|
171
|
+
|
|
172
|
+
// Verify a restore commit was created
|
|
173
|
+
const history2 = await getAppHistory(app.id);
|
|
174
|
+
expect(history2[0].message).toContain('Restore app');
|
|
175
|
+
});
|
|
176
|
+
});
|