@vellumai/assistant 0.6.0 → 0.6.2
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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +42 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/openapi.yaml +539 -4
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/checker.test.ts +138 -172
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/context-overflow-approval.test.ts +5 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +257 -100
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +63 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +0 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +16 -29
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +4 -27
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/transport-hints-queue.test.ts +77 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/agent/loop.ts +6 -29
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/channels/types.ts +5 -0
- package/src/cli/__tests__/run-assistant-command.ts +56 -0
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
- package/src/cli/commands/oauth/connect.ts +25 -5
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +11 -2
- package/src/cli.ts +1 -120
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
- package/src/config/bundled-skills/gmail/SKILL.md +13 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +85 -3
- package/src/daemon/context-overflow-approval.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +179 -65
- package/src/daemon/conversation-attachments.ts +0 -1
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +30 -8
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +359 -308
- package/src/daemon/conversation-surfaces.ts +65 -0
- package/src/daemon/conversation-tool-setup.ts +44 -17
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +19 -3
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +5 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +70 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +220 -158
- package/src/daemon/message-types/conversations.ts +29 -6
- package/src/daemon/message-types/messages.ts +9 -2
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +18 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +87 -10
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/daemon/transport-hints.ts +33 -0
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +151 -117
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +66 -6
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/embedding-local.ts +1 -1
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -17
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/platform-connection.test.ts +2 -2
- package/src/oauth/seed-providers.ts +1 -0
- package/src/permissions/checker.ts +15 -4
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/prompter.ts +0 -2
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +76 -162
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +30 -9
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/assistant-event-hub.ts +22 -0
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +270 -44
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +41 -278
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -18
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/secret-detection-handler.ts +0 -1
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +3 -5
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -3
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +85 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the host-access gate in the permission checker.
|
|
3
|
+
*
|
|
4
|
+
* When the `permission-controls-v2` feature flag is enabled, the permission
|
|
5
|
+
* checker replaces the risk-classification path with a simple binary check:
|
|
6
|
+
* - Host tools + hostAccess=false → falls through to interactive prompter
|
|
7
|
+
* - Host tools + hostAccess=true → auto-allowed
|
|
8
|
+
* - Non-host tools → auto-allowed (no risk classification)
|
|
9
|
+
* - requireFreshApproval → falls through to interactive prompter
|
|
10
|
+
* - forcePromptSideEffects + side-effect tool → falls through to prompter
|
|
11
|
+
*
|
|
12
|
+
* When the flag is off, existing risk-level behavior is completely unchanged.
|
|
13
|
+
*/
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
|
+
|
|
16
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
17
|
+
import {
|
|
18
|
+
initPermissionModeStore,
|
|
19
|
+
resetForTesting as resetPermissionModeStore,
|
|
20
|
+
setHostAccess,
|
|
21
|
+
} from "../permissions/permission-mode-store.js";
|
|
22
|
+
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
23
|
+
import { RiskLevel } from "../permissions/types.js";
|
|
24
|
+
import { PermissionChecker } from "../tools/permission-checker.js";
|
|
25
|
+
import type {
|
|
26
|
+
ExecutionTarget,
|
|
27
|
+
Tool,
|
|
28
|
+
ToolContext,
|
|
29
|
+
ToolLifecycleEvent,
|
|
30
|
+
} from "../tools/types.js";
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Mocks — suppress the risk classification / trust-rule subsystem so we can
|
|
34
|
+
// test the v2 early-return gate in isolation.
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
mock.module("../permissions/checker.js", () => ({
|
|
38
|
+
classifyRisk: async () => RiskLevel.Low,
|
|
39
|
+
check: async () => ({ decision: "allow", reason: "" }),
|
|
40
|
+
generateAllowlistOptions: async () => [],
|
|
41
|
+
generateScopeOptions: () => [],
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
mock.module("../hooks/manager.js", () => ({
|
|
45
|
+
getHookManager: () => ({
|
|
46
|
+
trigger: async () => {},
|
|
47
|
+
}),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Helpers
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
function makePrompter(): PermissionPrompter {
|
|
55
|
+
return {
|
|
56
|
+
prompt: async () => ({ decision: "allow" as const }),
|
|
57
|
+
} as unknown as PermissionPrompter;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function makeTool(name: string): Tool {
|
|
61
|
+
return {
|
|
62
|
+
name,
|
|
63
|
+
description: "test tool",
|
|
64
|
+
category: "test",
|
|
65
|
+
defaultRiskLevel: RiskLevel.Low,
|
|
66
|
+
getDefinition: () => ({
|
|
67
|
+
name,
|
|
68
|
+
description: "test",
|
|
69
|
+
input_schema: { type: "object" as const, properties: {} },
|
|
70
|
+
}),
|
|
71
|
+
execute: async () => ({ content: "", isError: false }),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function makeContext(overrides?: Partial<ToolContext>): ToolContext {
|
|
76
|
+
return {
|
|
77
|
+
workingDir: "/tmp/test",
|
|
78
|
+
conversationId: "test-conv",
|
|
79
|
+
trustClass: "guardian",
|
|
80
|
+
isInteractive: true,
|
|
81
|
+
...overrides,
|
|
82
|
+
} as ToolContext;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const noopEmit = (_event: ToolLifecycleEvent): void => {};
|
|
86
|
+
const noopSanitize = (
|
|
87
|
+
_name: string,
|
|
88
|
+
input: Record<string, unknown>,
|
|
89
|
+
): Record<string, unknown> => input;
|
|
90
|
+
const noopDiff = () => undefined;
|
|
91
|
+
const executionTarget: ExecutionTarget = "host";
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Setup / teardown
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
_setOverridesForTesting({});
|
|
99
|
+
resetPermissionModeStore();
|
|
100
|
+
initPermissionModeStore();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
_setOverridesForTesting({});
|
|
105
|
+
resetPermissionModeStore();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Tests
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
describe("permission-checker host-access gate (v2)", () => {
|
|
113
|
+
describe("when permission-controls-v2 flag is ON", () => {
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
_setOverridesForTesting({ "permission-controls-v2": true });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const HOST_TOOL_NAMES = [
|
|
119
|
+
"host_bash",
|
|
120
|
+
"host_file_read",
|
|
121
|
+
"host_file_write",
|
|
122
|
+
"host_file_edit",
|
|
123
|
+
"computer_use_run_applescript",
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
describe("host tools with hostAccess=false", () => {
|
|
127
|
+
beforeEach(() => {
|
|
128
|
+
setHostAccess(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
for (const toolName of HOST_TOOL_NAMES) {
|
|
132
|
+
test(`${toolName} falls through to prompter (interactive prompt)`, async () => {
|
|
133
|
+
const promptSpy = mock(() =>
|
|
134
|
+
Promise.resolve({ decision: "allow" as const }),
|
|
135
|
+
);
|
|
136
|
+
const prompter = {
|
|
137
|
+
prompt: promptSpy,
|
|
138
|
+
} as unknown as PermissionPrompter;
|
|
139
|
+
const checker = new PermissionChecker(prompter);
|
|
140
|
+
const result = await checker.checkPermission(
|
|
141
|
+
toolName,
|
|
142
|
+
{},
|
|
143
|
+
makeTool(toolName),
|
|
144
|
+
makeContext(),
|
|
145
|
+
executionTarget,
|
|
146
|
+
noopEmit,
|
|
147
|
+
noopSanitize,
|
|
148
|
+
Date.now(),
|
|
149
|
+
noopDiff,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// The prompter should have been called (interactive dialog)
|
|
153
|
+
expect(promptSpy).toHaveBeenCalled();
|
|
154
|
+
// Since the mock prompter returns "allow", the result should be allowed
|
|
155
|
+
expect(result.allowed).toBe(true);
|
|
156
|
+
expect(result.decision).toBe("allow");
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
test("host tool denied by user through prompter returns allowed=false", async () => {
|
|
161
|
+
const promptSpy = mock(() =>
|
|
162
|
+
Promise.resolve({ decision: "deny" as const }),
|
|
163
|
+
);
|
|
164
|
+
const prompter = {
|
|
165
|
+
prompt: promptSpy,
|
|
166
|
+
} as unknown as PermissionPrompter;
|
|
167
|
+
const checker = new PermissionChecker(prompter);
|
|
168
|
+
const result = await checker.checkPermission(
|
|
169
|
+
"host_bash",
|
|
170
|
+
{},
|
|
171
|
+
makeTool("host_bash"),
|
|
172
|
+
makeContext(),
|
|
173
|
+
executionTarget,
|
|
174
|
+
noopEmit,
|
|
175
|
+
noopSanitize,
|
|
176
|
+
Date.now(),
|
|
177
|
+
noopDiff,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
expect(promptSpy).toHaveBeenCalled();
|
|
181
|
+
expect(result.allowed).toBe(false);
|
|
182
|
+
expect(result.decision).toBe("deny");
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("host tools with hostAccess=true", () => {
|
|
187
|
+
beforeEach(() => {
|
|
188
|
+
setHostAccess(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
for (const toolName of HOST_TOOL_NAMES) {
|
|
192
|
+
test(`${toolName} is auto-allowed`, async () => {
|
|
193
|
+
const checker = new PermissionChecker(makePrompter());
|
|
194
|
+
const result = await checker.checkPermission(
|
|
195
|
+
toolName,
|
|
196
|
+
{},
|
|
197
|
+
makeTool(toolName),
|
|
198
|
+
makeContext(),
|
|
199
|
+
executionTarget,
|
|
200
|
+
noopEmit,
|
|
201
|
+
noopSanitize,
|
|
202
|
+
Date.now(),
|
|
203
|
+
noopDiff,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
expect(result.allowed).toBe(true);
|
|
207
|
+
expect(result.decision).toBe("allow");
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("non-host tools are auto-allowed (no risk classification)", () => {
|
|
213
|
+
for (const toolName of [
|
|
214
|
+
"bash",
|
|
215
|
+
"file_read",
|
|
216
|
+
"file_write",
|
|
217
|
+
"web_search",
|
|
218
|
+
]) {
|
|
219
|
+
test(`${toolName} is auto-allowed regardless of hostAccess`, async () => {
|
|
220
|
+
setHostAccess(false);
|
|
221
|
+
const checker = new PermissionChecker(makePrompter());
|
|
222
|
+
const result = await checker.checkPermission(
|
|
223
|
+
toolName,
|
|
224
|
+
{},
|
|
225
|
+
makeTool(toolName),
|
|
226
|
+
makeContext(),
|
|
227
|
+
"sandbox",
|
|
228
|
+
noopEmit,
|
|
229
|
+
noopSanitize,
|
|
230
|
+
Date.now(),
|
|
231
|
+
noopDiff,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(result.allowed).toBe(true);
|
|
235
|
+
expect(result.decision).toBe("allow");
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("requireFreshApproval bypasses v2 auto-allow", () => {
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
setHostAccess(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("host tool with requireFreshApproval falls through to prompter", async () => {
|
|
246
|
+
const promptSpy = mock(() =>
|
|
247
|
+
Promise.resolve({ decision: "allow" as const }),
|
|
248
|
+
);
|
|
249
|
+
const prompter = {
|
|
250
|
+
prompt: promptSpy,
|
|
251
|
+
} as unknown as PermissionPrompter;
|
|
252
|
+
const checker = new PermissionChecker(prompter);
|
|
253
|
+
const result = await checker.checkPermission(
|
|
254
|
+
"host_bash",
|
|
255
|
+
{},
|
|
256
|
+
makeTool("host_bash"),
|
|
257
|
+
makeContext({ requireFreshApproval: true }),
|
|
258
|
+
executionTarget,
|
|
259
|
+
noopEmit,
|
|
260
|
+
noopSanitize,
|
|
261
|
+
Date.now(),
|
|
262
|
+
noopDiff,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(promptSpy).toHaveBeenCalled();
|
|
266
|
+
expect(result.allowed).toBe(true);
|
|
267
|
+
expect(result.decision).toBe("allow");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("non-host side-effect tool with requireFreshApproval falls through to prompter", async () => {
|
|
271
|
+
const promptSpy = mock(() =>
|
|
272
|
+
Promise.resolve({ decision: "allow" as const }),
|
|
273
|
+
);
|
|
274
|
+
const prompter = {
|
|
275
|
+
prompt: promptSpy,
|
|
276
|
+
} as unknown as PermissionPrompter;
|
|
277
|
+
const checker = new PermissionChecker(prompter);
|
|
278
|
+
const result = await checker.checkPermission(
|
|
279
|
+
"bash",
|
|
280
|
+
{ command: "echo hi" },
|
|
281
|
+
makeTool("bash"),
|
|
282
|
+
makeContext({ requireFreshApproval: true }),
|
|
283
|
+
"sandbox",
|
|
284
|
+
noopEmit,
|
|
285
|
+
noopSanitize,
|
|
286
|
+
Date.now(),
|
|
287
|
+
noopDiff,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
expect(promptSpy).toHaveBeenCalled();
|
|
291
|
+
expect(result.allowed).toBe(true);
|
|
292
|
+
expect(result.decision).toBe("allow");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("non-interactive guardian session with hostAccess=false", () => {
|
|
297
|
+
beforeEach(() => {
|
|
298
|
+
setHostAccess(false);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("host tool is NOT auto-approved (denies instead of guardian_auto_approve)", async () => {
|
|
302
|
+
const promptSpy = mock(() =>
|
|
303
|
+
Promise.resolve({ decision: "allow" as const }),
|
|
304
|
+
);
|
|
305
|
+
const prompter = {
|
|
306
|
+
prompt: promptSpy,
|
|
307
|
+
} as unknown as PermissionPrompter;
|
|
308
|
+
const checker = new PermissionChecker(prompter);
|
|
309
|
+
const result = await checker.checkPermission(
|
|
310
|
+
"host_bash",
|
|
311
|
+
{},
|
|
312
|
+
makeTool("host_bash"),
|
|
313
|
+
makeContext({ isInteractive: false, trustClass: "guardian" }),
|
|
314
|
+
executionTarget,
|
|
315
|
+
noopEmit,
|
|
316
|
+
noopSanitize,
|
|
317
|
+
Date.now(),
|
|
318
|
+
noopDiff,
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// v2ForcePrompt is true (hostAccess=false), so the non-interactive
|
|
322
|
+
// guardian auto-approve must NOT fire. Since there is no interactive
|
|
323
|
+
// client, the tool should be denied.
|
|
324
|
+
expect(promptSpy).not.toHaveBeenCalled();
|
|
325
|
+
expect(result.allowed).toBe(false);
|
|
326
|
+
expect(result.decision).toBe("denied");
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe("forcePromptSideEffects bypasses v2 auto-allow for side-effect tools", () => {
|
|
331
|
+
beforeEach(() => {
|
|
332
|
+
setHostAccess(true);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("host side-effect tool with forcePromptSideEffects falls through to prompter", async () => {
|
|
336
|
+
const promptSpy = mock(() =>
|
|
337
|
+
Promise.resolve({ decision: "allow" as const }),
|
|
338
|
+
);
|
|
339
|
+
const prompter = {
|
|
340
|
+
prompt: promptSpy,
|
|
341
|
+
} as unknown as PermissionPrompter;
|
|
342
|
+
const checker = new PermissionChecker(prompter);
|
|
343
|
+
const result = await checker.checkPermission(
|
|
344
|
+
"host_bash",
|
|
345
|
+
{},
|
|
346
|
+
makeTool("host_bash"),
|
|
347
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
348
|
+
executionTarget,
|
|
349
|
+
noopEmit,
|
|
350
|
+
noopSanitize,
|
|
351
|
+
Date.now(),
|
|
352
|
+
noopDiff,
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
expect(promptSpy).toHaveBeenCalled();
|
|
356
|
+
expect(result.allowed).toBe(true);
|
|
357
|
+
expect(result.decision).toBe("allow");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("non-host side-effect tool with forcePromptSideEffects falls through to prompter", async () => {
|
|
361
|
+
const promptSpy = mock(() =>
|
|
362
|
+
Promise.resolve({ decision: "allow" as const }),
|
|
363
|
+
);
|
|
364
|
+
const prompter = {
|
|
365
|
+
prompt: promptSpy,
|
|
366
|
+
} as unknown as PermissionPrompter;
|
|
367
|
+
const checker = new PermissionChecker(prompter);
|
|
368
|
+
const result = await checker.checkPermission(
|
|
369
|
+
"bash",
|
|
370
|
+
{ command: "echo hi" },
|
|
371
|
+
makeTool("bash"),
|
|
372
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
373
|
+
"sandbox",
|
|
374
|
+
noopEmit,
|
|
375
|
+
noopSanitize,
|
|
376
|
+
Date.now(),
|
|
377
|
+
noopDiff,
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
expect(promptSpy).toHaveBeenCalled();
|
|
381
|
+
expect(result.allowed).toBe(true);
|
|
382
|
+
expect(result.decision).toBe("allow");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("non-host read-only tool with forcePromptSideEffects is still auto-allowed", async () => {
|
|
386
|
+
const checker = new PermissionChecker(makePrompter());
|
|
387
|
+
const result = await checker.checkPermission(
|
|
388
|
+
"file_read",
|
|
389
|
+
{},
|
|
390
|
+
makeTool("file_read"),
|
|
391
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
392
|
+
"sandbox",
|
|
393
|
+
noopEmit,
|
|
394
|
+
noopSanitize,
|
|
395
|
+
Date.now(),
|
|
396
|
+
noopDiff,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// file_read is not a side-effect tool, so v2 auto-allows it
|
|
400
|
+
expect(result.allowed).toBe(true);
|
|
401
|
+
expect(result.decision).toBe("allow");
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
describe("when permission-controls-v2 flag is OFF", () => {
|
|
407
|
+
beforeEach(() => {
|
|
408
|
+
_setOverridesForTesting({ "permission-controls-v2": false });
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("host_bash falls through to existing risk classification (returns allow from mocked checker)", async () => {
|
|
412
|
+
const checker = new PermissionChecker(makePrompter());
|
|
413
|
+
const result = await checker.checkPermission(
|
|
414
|
+
"host_bash",
|
|
415
|
+
{},
|
|
416
|
+
makeTool("host_bash"),
|
|
417
|
+
makeContext(),
|
|
418
|
+
executionTarget,
|
|
419
|
+
noopEmit,
|
|
420
|
+
noopSanitize,
|
|
421
|
+
Date.now(),
|
|
422
|
+
noopDiff,
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
// The mocked checker returns 'allow', so the result should be
|
|
426
|
+
// allowed with the old risk-classification path's risk level.
|
|
427
|
+
expect(result.allowed).toBe(true);
|
|
428
|
+
expect(result.decision).toBe("allow");
|
|
429
|
+
// When flag is off, riskLevel comes from classifyRisk (mocked as "low")
|
|
430
|
+
expect(result.riskLevel).toBe(RiskLevel.Low);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("non-host tools also follow old path", async () => {
|
|
434
|
+
const checker = new PermissionChecker(makePrompter());
|
|
435
|
+
const result = await checker.checkPermission(
|
|
436
|
+
"bash",
|
|
437
|
+
{ command: "echo hello" },
|
|
438
|
+
makeTool("bash"),
|
|
439
|
+
makeContext(),
|
|
440
|
+
"sandbox",
|
|
441
|
+
noopEmit,
|
|
442
|
+
noopSanitize,
|
|
443
|
+
Date.now(),
|
|
444
|
+
noopDiff,
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
expect(result.allowed).toBe(true);
|
|
448
|
+
expect(result.decision).toBe("allow");
|
|
449
|
+
expect(result.riskLevel).toBe(RiskLevel.Low);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `permission-controls-v2` feature flag.
|
|
3
|
+
*
|
|
4
|
+
* Verifies:
|
|
5
|
+
* - The flag defaults to disabled (not enabled)
|
|
6
|
+
* - The flag can be enabled via `_setOverridesForTesting`
|
|
7
|
+
*/
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
_setOverridesForTesting,
|
|
12
|
+
isAssistantFeatureFlagEnabled,
|
|
13
|
+
} from "../config/assistant-feature-flags.js";
|
|
14
|
+
import type { AssistantConfig } from "../config/schema.js";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
function makeConfig(): AssistantConfig {
|
|
21
|
+
return {} as AssistantConfig;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Setup / teardown
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
_setOverridesForTesting({});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
_setOverridesForTesting({});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Tests
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
describe("permission-controls-v2 feature flag", () => {
|
|
41
|
+
test("defaults to disabled", () => {
|
|
42
|
+
const config = makeConfig();
|
|
43
|
+
expect(
|
|
44
|
+
isAssistantFeatureFlagEnabled("permission-controls-v2", config),
|
|
45
|
+
).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("can be enabled via overrides", () => {
|
|
49
|
+
_setOverridesForTesting({ "permission-controls-v2": true });
|
|
50
|
+
const config = makeConfig();
|
|
51
|
+
expect(
|
|
52
|
+
isAssistantFeatureFlagEnabled("permission-controls-v2", config),
|
|
53
|
+
).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|