@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
|
@@ -14,7 +14,7 @@ const CANONICAL_TABS = [
|
|
|
14
14
|
"Sounds",
|
|
15
15
|
"Permissions & Privacy",
|
|
16
16
|
"Billing",
|
|
17
|
-
"
|
|
17
|
+
"Archive",
|
|
18
18
|
"Schedules",
|
|
19
19
|
"Developer",
|
|
20
20
|
];
|
|
@@ -85,4 +85,17 @@ describe("navigate-settings-tab", () => {
|
|
|
85
85
|
expect(result.isError).toBe(false);
|
|
86
86
|
expect(result.content).toContain("Developer");
|
|
87
87
|
});
|
|
88
|
+
|
|
89
|
+
test("normalizes legacy 'Archived Conversations' alias to 'Archive'", async () => {
|
|
90
|
+
const messages: unknown[] = [];
|
|
91
|
+
const result = await run(
|
|
92
|
+
{ tab: "Archived Conversations" },
|
|
93
|
+
makeContext((msg) => messages.push(msg)),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(result.isError).toBe(false);
|
|
97
|
+
expect(result.content).toContain("Archive");
|
|
98
|
+
expect(messages).toHaveLength(1);
|
|
99
|
+
expect(messages[0]).toEqual({ type: "navigate_settings", tab: "Archive" });
|
|
100
|
+
});
|
|
88
101
|
});
|
|
@@ -583,4 +583,69 @@ describe("notification broadcaster", () => {
|
|
|
583
583
|
expect(vellumCall).toBeDefined();
|
|
584
584
|
expect(vellumCall!.options?.bindingContext).toBeUndefined();
|
|
585
585
|
});
|
|
586
|
+
|
|
587
|
+
// ── conversationMetadata propagation ──────────────────────────────
|
|
588
|
+
|
|
589
|
+
test("onConversationCreated includes groupId and source from conversationMetadata", async () => {
|
|
590
|
+
const vellumAdapter = new MockAdapter("vellum");
|
|
591
|
+
const broadcaster = new NotificationBroadcaster([vellumAdapter]);
|
|
592
|
+
const createdCalls: ConversationCreatedInfo[] = [];
|
|
593
|
+
broadcaster.setOnConversationCreated((info) => createdCalls.push(info));
|
|
594
|
+
|
|
595
|
+
const signal = makeSignal({
|
|
596
|
+
sourceEventName: "schedule.complete",
|
|
597
|
+
conversationMetadata: {
|
|
598
|
+
groupId: "system:scheduled",
|
|
599
|
+
source: "schedule",
|
|
600
|
+
scheduleJobId: "job-abc-123",
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
const decision = makeDecision();
|
|
604
|
+
|
|
605
|
+
await broadcaster.broadcastDecision(signal, decision);
|
|
606
|
+
|
|
607
|
+
expect(createdCalls).toHaveLength(1);
|
|
608
|
+
expect(createdCalls[0].groupId).toBe("system:scheduled");
|
|
609
|
+
expect(createdCalls[0].source).toBe("schedule");
|
|
610
|
+
expect(createdCalls[0].sourceEventName).toBe("schedule.complete");
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
test("onConversationCreated omits groupId and source when conversationMetadata is absent", async () => {
|
|
614
|
+
const vellumAdapter = new MockAdapter("vellum");
|
|
615
|
+
const broadcaster = new NotificationBroadcaster([vellumAdapter]);
|
|
616
|
+
const createdCalls: ConversationCreatedInfo[] = [];
|
|
617
|
+
broadcaster.setOnConversationCreated((info) => createdCalls.push(info));
|
|
618
|
+
|
|
619
|
+
const signal = makeSignal(); // no conversationMetadata
|
|
620
|
+
const decision = makeDecision();
|
|
621
|
+
|
|
622
|
+
await broadcaster.broadcastDecision(signal, decision);
|
|
623
|
+
|
|
624
|
+
expect(createdCalls).toHaveLength(1);
|
|
625
|
+
expect(createdCalls[0].groupId).toBeUndefined();
|
|
626
|
+
expect(createdCalls[0].source).toBeUndefined();
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
test("per-dispatch callback receives conversationMetadata fields", async () => {
|
|
630
|
+
const vellumAdapter = new MockAdapter("vellum");
|
|
631
|
+
const broadcaster = new NotificationBroadcaster([vellumAdapter]);
|
|
632
|
+
const dispatchCalls: ConversationCreatedInfo[] = [];
|
|
633
|
+
|
|
634
|
+
const signal = makeSignal({
|
|
635
|
+
sourceEventName: "schedule.complete",
|
|
636
|
+
conversationMetadata: {
|
|
637
|
+
groupId: "system:scheduled",
|
|
638
|
+
source: "schedule",
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
const decision = makeDecision();
|
|
642
|
+
|
|
643
|
+
await broadcaster.broadcastDecision(signal, decision, {
|
|
644
|
+
onConversationCreated: (info) => dispatchCalls.push(info),
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
expect(dispatchCalls).toHaveLength(1);
|
|
648
|
+
expect(dispatchCalls[0].groupId).toBe("system:scheduled");
|
|
649
|
+
expect(dispatchCalls[0].source).toBe("schedule");
|
|
650
|
+
});
|
|
586
651
|
});
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for recipient context (guardian contact notes) injection in the
|
|
3
|
+
* notification decision engine.
|
|
4
|
+
*
|
|
5
|
+
* Validates that guardian contact notes appear in the LLM system prompt as
|
|
6
|
+
* a <recipient-context> block when available, are omitted when absent or
|
|
7
|
+
* empty, and are truncated when large.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
// ── Mocks (must precede imports from mocked modules) ──────────────────
|
|
13
|
+
|
|
14
|
+
mock.module("../channels/config.js", () => ({
|
|
15
|
+
getDeliverableChannels: () => ["vellum"],
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
mock.module("../config/loader.js", () => ({
|
|
19
|
+
getConfig: () => ({
|
|
20
|
+
ui: {},
|
|
21
|
+
notifications: {
|
|
22
|
+
decisionModelIntent: "latency-optimized",
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
mock.module("../notifications/decisions-store.js", () => ({
|
|
28
|
+
createDecision: () => {},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
mock.module("../notifications/preference-summary.js", () => ({
|
|
32
|
+
getPreferenceSummary: () => undefined,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
mock.module("../notifications/conversation-candidates.js", () => ({
|
|
36
|
+
buildConversationCandidates: () => undefined,
|
|
37
|
+
serializeCandidatesForPrompt: () => undefined,
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
mock.module("../prompts/persona-resolver.js", () => ({
|
|
41
|
+
resolveGuardianPersona: () => null,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
mock.module("../prompts/system-prompt.js", () => ({
|
|
45
|
+
buildCoreIdentityContext: () => null,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// ── Guardian contact mock ────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
let mockGuardianResult: {
|
|
51
|
+
contact: { notes: string | null };
|
|
52
|
+
channels: Record<string, unknown>[];
|
|
53
|
+
} | null = null;
|
|
54
|
+
|
|
55
|
+
mock.module("../contacts/contact-store.js", () => ({
|
|
56
|
+
listGuardianChannels: () => mockGuardianResult,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
// ── Provider mock with system prompt capture ──────────────────────────
|
|
60
|
+
|
|
61
|
+
let configuredProvider: {
|
|
62
|
+
sendMessage: (...args: unknown[]) => Promise<unknown>;
|
|
63
|
+
} | null = null;
|
|
64
|
+
let extractedToolUse: unknown = null;
|
|
65
|
+
let capturedSystemPrompt: string | undefined;
|
|
66
|
+
|
|
67
|
+
mock.module("../providers/provider-send-message.js", () => ({
|
|
68
|
+
getConfiguredProvider: async () => configuredProvider,
|
|
69
|
+
createTimeout: () => ({
|
|
70
|
+
signal: new AbortController().signal,
|
|
71
|
+
cleanup: () => {},
|
|
72
|
+
}),
|
|
73
|
+
extractToolUse: () => extractedToolUse,
|
|
74
|
+
userMessage: (text: string) => ({ role: "user", content: text }),
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
mock.module("../util/logger.js", () => ({
|
|
78
|
+
getLogger: () =>
|
|
79
|
+
new Proxy({} as Record<string, unknown>, {
|
|
80
|
+
get: () => () => {},
|
|
81
|
+
}),
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
// ── Imports (after all mocks) ─────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
import { evaluateSignal } from "../notifications/decision-engine.js";
|
|
87
|
+
import type { NotificationSignal } from "../notifications/signal.js";
|
|
88
|
+
import type { NotificationChannel } from "../notifications/types.js";
|
|
89
|
+
|
|
90
|
+
// ── Helpers ───────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
function makeSignal(
|
|
93
|
+
overrides?: Partial<NotificationSignal>,
|
|
94
|
+
): NotificationSignal {
|
|
95
|
+
return {
|
|
96
|
+
signalId: "sig-recipient-ctx-test-1",
|
|
97
|
+
createdAt: Date.now(),
|
|
98
|
+
sourceChannel: "phone",
|
|
99
|
+
sourceContextId: "call-session-1",
|
|
100
|
+
sourceEventName: "guardian.question",
|
|
101
|
+
contextPayload: {
|
|
102
|
+
questionText: "What is the gate code?",
|
|
103
|
+
},
|
|
104
|
+
attentionHints: {
|
|
105
|
+
requiresAction: true,
|
|
106
|
+
urgency: "high",
|
|
107
|
+
isAsyncBackground: false,
|
|
108
|
+
visibleInSourceNow: false,
|
|
109
|
+
},
|
|
110
|
+
...overrides,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function setupLLMProvider() {
|
|
115
|
+
configuredProvider = {
|
|
116
|
+
sendMessage: async (
|
|
117
|
+
_messages: unknown,
|
|
118
|
+
_tools: unknown,
|
|
119
|
+
systemPrompt: unknown,
|
|
120
|
+
) => {
|
|
121
|
+
capturedSystemPrompt = systemPrompt as string;
|
|
122
|
+
return { content: [] };
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
extractedToolUse = {
|
|
126
|
+
name: "record_notification_decision",
|
|
127
|
+
input: {
|
|
128
|
+
shouldNotify: true,
|
|
129
|
+
selectedChannels: ["vellum"],
|
|
130
|
+
reasoningSummary: "LLM decision with recipient context",
|
|
131
|
+
renderedCopy: {
|
|
132
|
+
vellum: {
|
|
133
|
+
title: "Guardian Question",
|
|
134
|
+
body: "What is the gate code?",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
dedupeKey: "recipient-ctx-test",
|
|
138
|
+
confidence: 0.9,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Tests ─────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
describe("recipient context in notification decision engine", () => {
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
configuredProvider = null;
|
|
148
|
+
extractedToolUse = null;
|
|
149
|
+
mockGuardianResult = null;
|
|
150
|
+
capturedSystemPrompt = undefined;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("guardian contact notes appear in system prompt as <recipient-context>", async () => {
|
|
154
|
+
mockGuardianResult = {
|
|
155
|
+
contact: { notes: "Prefers formal tone. Address as Dr. Smith." },
|
|
156
|
+
channels: [{ type: "vellum" }],
|
|
157
|
+
};
|
|
158
|
+
setupLLMProvider();
|
|
159
|
+
|
|
160
|
+
const signal = makeSignal();
|
|
161
|
+
await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
|
|
162
|
+
|
|
163
|
+
expect(capturedSystemPrompt).toBeDefined();
|
|
164
|
+
expect(capturedSystemPrompt).toContain("<recipient-context>");
|
|
165
|
+
expect(capturedSystemPrompt).toContain(
|
|
166
|
+
"Prefers formal tone. Address as Dr. Smith.",
|
|
167
|
+
);
|
|
168
|
+
expect(capturedSystemPrompt).toContain("</recipient-context>");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("recipient-context is omitted when no guardian exists", async () => {
|
|
172
|
+
mockGuardianResult = null;
|
|
173
|
+
setupLLMProvider();
|
|
174
|
+
|
|
175
|
+
const signal = makeSignal();
|
|
176
|
+
await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
|
|
177
|
+
|
|
178
|
+
expect(capturedSystemPrompt).toBeDefined();
|
|
179
|
+
expect(capturedSystemPrompt).not.toContain("<recipient-context>");
|
|
180
|
+
expect(capturedSystemPrompt).not.toContain("</recipient-context>");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("recipient-context is omitted when guardian notes are null", async () => {
|
|
184
|
+
mockGuardianResult = {
|
|
185
|
+
contact: { notes: null },
|
|
186
|
+
channels: [{ type: "vellum" }],
|
|
187
|
+
};
|
|
188
|
+
setupLLMProvider();
|
|
189
|
+
|
|
190
|
+
const signal = makeSignal();
|
|
191
|
+
await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
|
|
192
|
+
|
|
193
|
+
expect(capturedSystemPrompt).toBeDefined();
|
|
194
|
+
expect(capturedSystemPrompt).not.toContain("<recipient-context>");
|
|
195
|
+
expect(capturedSystemPrompt).not.toContain("</recipient-context>");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("recipient-context is omitted when guardian notes are empty string", async () => {
|
|
199
|
+
mockGuardianResult = {
|
|
200
|
+
contact: { notes: "" },
|
|
201
|
+
channels: [{ type: "vellum" }],
|
|
202
|
+
};
|
|
203
|
+
setupLLMProvider();
|
|
204
|
+
|
|
205
|
+
const signal = makeSignal();
|
|
206
|
+
await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
|
|
207
|
+
|
|
208
|
+
expect(capturedSystemPrompt).toBeDefined();
|
|
209
|
+
expect(capturedSystemPrompt).not.toContain("<recipient-context>");
|
|
210
|
+
expect(capturedSystemPrompt).not.toContain("</recipient-context>");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("large guardian notes are truncated to prevent oversized prompts", async () => {
|
|
214
|
+
mockGuardianResult = {
|
|
215
|
+
contact: { notes: "N".repeat(3000) },
|
|
216
|
+
channels: [{ type: "vellum" }],
|
|
217
|
+
};
|
|
218
|
+
setupLLMProvider();
|
|
219
|
+
|
|
220
|
+
const signal = makeSignal();
|
|
221
|
+
await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
|
|
222
|
+
|
|
223
|
+
expect(capturedSystemPrompt).toBeDefined();
|
|
224
|
+
expect(capturedSystemPrompt).toContain("<recipient-context>");
|
|
225
|
+
// Full 3000-char string should NOT appear
|
|
226
|
+
expect(capturedSystemPrompt).not.toContain("N".repeat(3000));
|
|
227
|
+
// Truncation marker should be present
|
|
228
|
+
expect(capturedSystemPrompt).toContain("\u2026[truncated]");
|
|
229
|
+
|
|
230
|
+
const match = capturedSystemPrompt!.match(
|
|
231
|
+
/<recipient-context>([\s\S]*?)<\/recipient-context>/,
|
|
232
|
+
);
|
|
233
|
+
expect(match).toBeTruthy();
|
|
234
|
+
const block = match![1];
|
|
235
|
+
expect(block).toContain("\u2026[truncated]");
|
|
236
|
+
// The notes portion within the block should not exceed 2000 chars
|
|
237
|
+
expect(block).not.toContain("N".repeat(2001));
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("fallback path works correctly without recipient context", async () => {
|
|
241
|
+
mockGuardianResult = {
|
|
242
|
+
contact: { notes: "Prefers formal tone." },
|
|
243
|
+
channels: [{ type: "vellum" }],
|
|
244
|
+
};
|
|
245
|
+
// null provider forces fallback path
|
|
246
|
+
configuredProvider = null;
|
|
247
|
+
|
|
248
|
+
const signal = makeSignal();
|
|
249
|
+
const decision = await evaluateSignal(signal, [
|
|
250
|
+
"vellum",
|
|
251
|
+
] as NotificationChannel[]);
|
|
252
|
+
|
|
253
|
+
expect(decision.fallbackUsed).toBe(true);
|
|
254
|
+
expect(decision.shouldNotify).toBe(true);
|
|
255
|
+
expect(decision.renderedCopy.vellum?.title).toBeDefined();
|
|
256
|
+
expect(decision.renderedCopy.vellum?.body).toBeDefined();
|
|
257
|
+
// No LLM call, so no system prompt captured
|
|
258
|
+
expect(capturedSystemPrompt).toBeUndefined();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("recipient-context appears after user-preferences in prompt", async () => {
|
|
262
|
+
mockGuardianResult = {
|
|
263
|
+
contact: { notes: "Prefers brief updates." },
|
|
264
|
+
channels: [{ type: "vellum" }],
|
|
265
|
+
};
|
|
266
|
+
setupLLMProvider();
|
|
267
|
+
|
|
268
|
+
const signal = makeSignal();
|
|
269
|
+
await evaluateSignal(
|
|
270
|
+
signal,
|
|
271
|
+
["vellum"] as NotificationChannel[],
|
|
272
|
+
"Notify only for urgent items",
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
expect(capturedSystemPrompt).toBeDefined();
|
|
276
|
+
const prefsIdx = capturedSystemPrompt!.indexOf("</user-preferences>");
|
|
277
|
+
const recipientIdx = capturedSystemPrompt!.indexOf("<recipient-context>");
|
|
278
|
+
expect(prefsIdx).toBeGreaterThan(-1);
|
|
279
|
+
expect(recipientIdx).toBeGreaterThan(-1);
|
|
280
|
+
expect(recipientIdx).toBeGreaterThan(prefsIdx);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -4,6 +4,10 @@ import { describe, expect, test } from "bun:test";
|
|
|
4
4
|
|
|
5
5
|
const templatesDir = join(import.meta.dirname, "..", "prompts", "templates");
|
|
6
6
|
const bootstrap = readFileSync(join(templatesDir, "BOOTSTRAP.md"), "utf-8");
|
|
7
|
+
const bootstrapRef = readFileSync(
|
|
8
|
+
join(templatesDir, "BOOTSTRAP-REFERENCE.md"),
|
|
9
|
+
"utf-8",
|
|
10
|
+
);
|
|
7
11
|
const identity = readFileSync(join(templatesDir, "IDENTITY.md"), "utf-8");
|
|
8
12
|
const user = readFileSync(join(templatesDir, "USER.md"), "utf-8");
|
|
9
13
|
|
|
@@ -17,28 +21,32 @@ describe("onboarding template contracts", () => {
|
|
|
17
21
|
const lower = bootstrap.toLowerCase();
|
|
18
22
|
expect(lower).toContain("your name");
|
|
19
23
|
expect(lower).toContain("personality");
|
|
20
|
-
expect(lower).toContain("avatar");
|
|
21
24
|
});
|
|
22
25
|
|
|
23
|
-
test("
|
|
26
|
+
test("leads with personality-first emotional arc", () => {
|
|
24
27
|
const lower = bootstrap.toLowerCase();
|
|
25
28
|
expect(lower).toContain("personality");
|
|
26
|
-
expect(lower).toContain("emerge");
|
|
27
29
|
expect(lower).toContain("vibe");
|
|
30
|
+
// Personality arc should come before usefulness arc
|
|
31
|
+
const personalityIdx = lower.indexOf("oh, this has personality");
|
|
32
|
+
const usefulIdx = lower.indexOf("oh, this is useful");
|
|
33
|
+
expect(personalityIdx).toBeGreaterThan(-1);
|
|
34
|
+
expect(usefulIdx).toBeGreaterThan(-1);
|
|
35
|
+
expect(personalityIdx).toBeLessThan(usefulIdx);
|
|
28
36
|
});
|
|
29
37
|
|
|
30
38
|
test("contains name selection with change-later instruction", () => {
|
|
31
39
|
const lower = bootstrap.toLowerCase();
|
|
32
|
-
expect(lower).toContain("what
|
|
40
|
+
expect(lower).toContain("what they want to call you");
|
|
33
41
|
expect(lower).toContain("change it later");
|
|
34
42
|
});
|
|
35
43
|
|
|
36
|
-
test("
|
|
37
|
-
const nameIdx = bootstrap.indexOf("
|
|
38
|
-
const
|
|
44
|
+
test("name exchange happens before personality quiz", () => {
|
|
45
|
+
const nameIdx = bootstrap.indexOf("Step 1: Name Exchange");
|
|
46
|
+
const quizIdx = bootstrap.indexOf("Step 2: Personality Quiz");
|
|
39
47
|
expect(nameIdx).toBeGreaterThan(-1);
|
|
40
|
-
expect(
|
|
41
|
-
expect(nameIdx).toBeLessThan(
|
|
48
|
+
expect(quizIdx).toBeGreaterThan(-1);
|
|
49
|
+
expect(nameIdx).toBeLessThan(quizIdx);
|
|
42
50
|
});
|
|
43
51
|
|
|
44
52
|
test("gathers user context: work role, hobbies, daily tools", () => {
|
|
@@ -48,18 +56,16 @@ describe("onboarding template contracts", () => {
|
|
|
48
56
|
expect(lower).toContain("tools");
|
|
49
57
|
});
|
|
50
58
|
|
|
51
|
-
test("
|
|
59
|
+
test("references ui_show payloads from BOOTSTRAP-REFERENCE.md", () => {
|
|
52
60
|
expect(bootstrap).toContain("ui_show");
|
|
53
|
-
expect(bootstrap).toContain("
|
|
54
|
-
expect(bootstrap).toContain("relay_prompt");
|
|
61
|
+
expect(bootstrap).toContain("BOOTSTRAP-REFERENCE.md");
|
|
55
62
|
});
|
|
56
63
|
|
|
57
|
-
test("contains wrapping-up criteria with
|
|
64
|
+
test("contains wrapping-up criteria with deletion instructions", () => {
|
|
58
65
|
const lower = bootstrap.toLowerCase();
|
|
59
66
|
expect(lower).toContain("wrapping up");
|
|
60
67
|
expect(lower).toContain("delete");
|
|
61
68
|
expect(lower).toContain("bootstrap.md");
|
|
62
|
-
expect(lower).toContain("two suggestions");
|
|
63
69
|
});
|
|
64
70
|
|
|
65
71
|
test("contains refusal policy", () => {
|
|
@@ -83,6 +89,49 @@ describe("onboarding template contracts", () => {
|
|
|
83
89
|
expect(bootstrap).toContain("SOUL.md");
|
|
84
90
|
expect(bootstrap).toContain("file_edit");
|
|
85
91
|
});
|
|
92
|
+
|
|
93
|
+
test("includes budget constraint", () => {
|
|
94
|
+
expect(bootstrap).toContain("$5");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("includes new colleague framing", () => {
|
|
98
|
+
expect(bootstrap).toContain("new colleague");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("instructs checking Connected Services for email task variant", () => {
|
|
102
|
+
expect(bootstrap).toContain("Connected Services");
|
|
103
|
+
expect(bootstrap).toContain("Connect my email");
|
|
104
|
+
expect(bootstrap).toContain("Check my email");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("keeps momentum by chaining off the first task", () => {
|
|
108
|
+
const lower = bootstrap.toLowerCase();
|
|
109
|
+
expect(lower).toContain("keep the momentum");
|
|
110
|
+
expect(lower).toContain("don't pivot to setup");
|
|
111
|
+
expect(lower).toContain("chain off the task");
|
|
112
|
+
expect(lower).toContain("while we're at it");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("BOOTSTRAP-REFERENCE.md", () => {
|
|
117
|
+
test("contains personality form with 4 dropdowns", () => {
|
|
118
|
+
expect(bootstrapRef).toContain('surface_type: "form"');
|
|
119
|
+
expect(bootstrapRef).toContain("communication_style");
|
|
120
|
+
expect(bootstrapRef).toContain("task_style");
|
|
121
|
+
expect(bootstrapRef).toContain("humor");
|
|
122
|
+
expect(bootstrapRef).toContain("depth");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("contains email-not-connected task card variant", () => {
|
|
126
|
+
expect(bootstrapRef).toContain("Email Not Connected");
|
|
127
|
+
expect(bootstrapRef).toContain("Connect my email");
|
|
128
|
+
expect(bootstrapRef).toContain("relay_prompt");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("contains email-already-connected task card variant", () => {
|
|
132
|
+
expect(bootstrapRef).toContain("Email Already Connected");
|
|
133
|
+
expect(bootstrapRef).toContain("Check my email");
|
|
134
|
+
});
|
|
86
135
|
});
|
|
87
136
|
|
|
88
137
|
describe("IDENTITY.md", () => {
|
|
@@ -177,6 +177,38 @@ describe("Shell Parser", () => {
|
|
|
177
177
|
result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
|
|
178
178
|
).toBe(false);
|
|
179
179
|
});
|
|
180
|
+
|
|
181
|
+
test("pipe to python3 -c is not flagged (inline code, not stdin exec)", async () => {
|
|
182
|
+
const result = await parse(
|
|
183
|
+
'cat data.json | python3 -c "import sys; print(sys.stdin.read())"',
|
|
184
|
+
);
|
|
185
|
+
expect(
|
|
186
|
+
result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
|
|
187
|
+
).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("pipe to node -e is not flagged (inline code)", async () => {
|
|
191
|
+
const result = await parse(
|
|
192
|
+
"cat data.json | node -e \"process.stdin.resume()\"",
|
|
193
|
+
);
|
|
194
|
+
expect(
|
|
195
|
+
result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
|
|
196
|
+
).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("pipe to python3 without flags is flagged (stdin exec)", async () => {
|
|
200
|
+
const result = await parse("cat exploit.py | python3");
|
|
201
|
+
expect(
|
|
202
|
+
result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
|
|
203
|
+
).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("pipe to python3 - is flagged (explicit stdin exec)", async () => {
|
|
207
|
+
const result = await parse("cat exploit.py | python3 -");
|
|
208
|
+
expect(
|
|
209
|
+
result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
|
|
210
|
+
).toBe(true);
|
|
211
|
+
});
|
|
180
212
|
});
|
|
181
213
|
|
|
182
214
|
// base64_execute
|