@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
|
@@ -30,9 +30,11 @@ export const conversations = sqliteTable(
|
|
|
30
30
|
forkParentMessageId: text("fork_parent_message_id"),
|
|
31
31
|
isAutoTitle: integer("is_auto_title").notNull().default(1),
|
|
32
32
|
scheduleJobId: text("schedule_job_id"),
|
|
33
|
+
lastMessageAt: integer("last_message_at"),
|
|
33
34
|
},
|
|
34
35
|
(table) => [
|
|
35
36
|
index("idx_conversations_updated_at").on(table.updatedAt),
|
|
37
|
+
index("idx_conversations_last_message_at").on(table.lastMessageAt),
|
|
36
38
|
index("idx_conversations_conversation_type").on(table.conversationType),
|
|
37
39
|
index("idx_conversations_fork_parent_conversation_id").on(
|
|
38
40
|
table.forkParentConversationId,
|
|
@@ -109,6 +111,18 @@ export const messageAttachments = sqliteTable("message_attachments", {
|
|
|
109
111
|
createdAt: integer("created_at").notNull(),
|
|
110
112
|
});
|
|
111
113
|
|
|
114
|
+
export const conversationGraphMemoryState = sqliteTable(
|
|
115
|
+
"conversation_graph_memory_state",
|
|
116
|
+
{
|
|
117
|
+
conversationId: text("conversation_id")
|
|
118
|
+
.primaryKey()
|
|
119
|
+
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
120
|
+
stateJson: text("state_json").notNull(),
|
|
121
|
+
createdAt: integer("created_at").notNull(),
|
|
122
|
+
updatedAt: integer("updated_at").notNull(),
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
|
|
112
126
|
export const channelInboundEvents = sqliteTable("channel_inbound_events", {
|
|
113
127
|
id: text("id").primaryKey(),
|
|
114
128
|
sourceChannel: text("source_channel").notNull(),
|
|
@@ -25,6 +25,9 @@ export const cronJobs = sqliteTable("cron_jobs", {
|
|
|
25
25
|
routingHintsJson: text("routing_hints_json").notNull().default("{}"),
|
|
26
26
|
status: text("status").notNull().default("active"), // 'active' | 'firing' | 'fired' | 'cancelled'
|
|
27
27
|
quiet: integer("quiet", { mode: "boolean" }).notNull().default(false), // suppress completion notifications
|
|
28
|
+
reuseConversation: integer("reuse_conversation", { mode: "boolean" })
|
|
29
|
+
.notNull()
|
|
30
|
+
.default(false), // reuse the same conversation across runs
|
|
28
31
|
createdAt: integer("created_at").notNull(),
|
|
29
32
|
updatedAt: integer("updated_at").notNull(),
|
|
30
33
|
});
|
|
@@ -118,7 +121,10 @@ export const llmRequestLogs = sqliteTable(
|
|
|
118
121
|
responsePayload: text("response_payload").notNull(),
|
|
119
122
|
createdAt: integer("created_at").notNull(),
|
|
120
123
|
},
|
|
121
|
-
(table) => [
|
|
124
|
+
(table) => [
|
|
125
|
+
index("idx_llm_request_logs_message_id").on(table.messageId),
|
|
126
|
+
index("idx_llm_request_logs_created_at").on(table.createdAt),
|
|
127
|
+
],
|
|
122
128
|
);
|
|
123
129
|
|
|
124
130
|
export const memoryRecallLogs = sqliteTable(
|
|
@@ -144,6 +150,7 @@ export const memoryRecallLogs = sqliteTable(
|
|
|
144
150
|
topCandidatesJson: text("top_candidates_json").notNull(),
|
|
145
151
|
injectedText: text("injected_text"),
|
|
146
152
|
reason: text("reason"),
|
|
153
|
+
queryContext: text("query_context"),
|
|
147
154
|
createdAt: integer("created_at").notNull(),
|
|
148
155
|
},
|
|
149
156
|
(table) => [
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
blob,
|
|
3
3
|
index,
|
|
4
4
|
integer,
|
|
5
|
-
real,
|
|
6
5
|
sqliteTable,
|
|
7
6
|
text,
|
|
8
7
|
uniqueIndex,
|
|
@@ -32,56 +31,6 @@ export const memorySegments = sqliteTable(
|
|
|
32
31
|
(table) => [index("idx_memory_segments_scope_id").on(table.scopeId)],
|
|
33
32
|
);
|
|
34
33
|
|
|
35
|
-
export const memoryItems = sqliteTable(
|
|
36
|
-
"memory_items",
|
|
37
|
-
{
|
|
38
|
-
id: text("id").primaryKey(),
|
|
39
|
-
kind: text("kind").notNull(),
|
|
40
|
-
subject: text("subject").notNull(),
|
|
41
|
-
statement: text("statement").notNull(),
|
|
42
|
-
status: text("status").notNull(),
|
|
43
|
-
confidence: real("confidence").notNull(),
|
|
44
|
-
importance: real("importance"),
|
|
45
|
-
accessCount: integer("access_count").notNull().default(0),
|
|
46
|
-
fingerprint: text("fingerprint").notNull(),
|
|
47
|
-
verificationState: text("verification_state")
|
|
48
|
-
.notNull()
|
|
49
|
-
.default("assistant_inferred"),
|
|
50
|
-
scopeId: text("scope_id").notNull().default("default"),
|
|
51
|
-
firstSeenAt: integer("first_seen_at").notNull(),
|
|
52
|
-
lastSeenAt: integer("last_seen_at").notNull(),
|
|
53
|
-
lastUsedAt: integer("last_used_at"),
|
|
54
|
-
validFrom: integer("valid_from"),
|
|
55
|
-
invalidAt: integer("invalid_at"),
|
|
56
|
-
supersedes: text("supersedes"),
|
|
57
|
-
supersededBy: text("superseded_by"),
|
|
58
|
-
overrideConfidence: text("override_confidence").default("inferred"),
|
|
59
|
-
sourceType: text("source_type").notNull().default("extraction"),
|
|
60
|
-
sourceMessageRole: text("source_message_role"),
|
|
61
|
-
},
|
|
62
|
-
(table) => [
|
|
63
|
-
index("idx_memory_items_scope_id").on(table.scopeId),
|
|
64
|
-
index("idx_memory_items_fingerprint").on(table.fingerprint),
|
|
65
|
-
],
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
export const memoryItemSources = sqliteTable(
|
|
69
|
-
"memory_item_sources",
|
|
70
|
-
{
|
|
71
|
-
memoryItemId: text("memory_item_id")
|
|
72
|
-
.notNull()
|
|
73
|
-
.references(() => memoryItems.id, { onDelete: "cascade" }),
|
|
74
|
-
messageId: text("message_id")
|
|
75
|
-
.notNull()
|
|
76
|
-
.references(() => messages.id, { onDelete: "cascade" }),
|
|
77
|
-
evidence: text("evidence"),
|
|
78
|
-
createdAt: integer("created_at").notNull(),
|
|
79
|
-
},
|
|
80
|
-
(table) => [
|
|
81
|
-
index("idx_memory_item_sources_memory_item_id").on(table.memoryItemId),
|
|
82
|
-
],
|
|
83
|
-
);
|
|
84
|
-
|
|
85
34
|
export const memorySummaries = sqliteTable(
|
|
86
35
|
"memory_summaries",
|
|
87
36
|
{
|
|
@@ -137,3 +137,18 @@ export const memoryGraphTriggers = sqliteTable(
|
|
|
137
137
|
index("idx_graph_triggers_type").on(table.type),
|
|
138
138
|
],
|
|
139
139
|
);
|
|
140
|
+
|
|
141
|
+
export const memoryGraphNodeEdits = sqliteTable(
|
|
142
|
+
"memory_graph_node_edits",
|
|
143
|
+
{
|
|
144
|
+
id: text("id").primaryKey(),
|
|
145
|
+
nodeId: text("node_id")
|
|
146
|
+
.notNull()
|
|
147
|
+
.references(() => memoryGraphNodes.id, { onDelete: "cascade" }),
|
|
148
|
+
previousContent: text("previous_content").notNull(),
|
|
149
|
+
newContent: text("new_content").notNull(),
|
|
150
|
+
source: text("source").notNull(),
|
|
151
|
+
conversationId: text("conversation_id"),
|
|
152
|
+
created: integer("created").notNull(),
|
|
153
|
+
},
|
|
154
|
+
);
|
|
@@ -9,16 +9,29 @@ const log = getLogger("task-memory-cleanup");
|
|
|
9
9
|
* so the check survives daemon restarts.
|
|
10
10
|
*/
|
|
11
11
|
export function isConversationFailed(conversationId: string): boolean {
|
|
12
|
+
// For reused schedule conversations the same conversation_id appears in
|
|
13
|
+
// multiple cron_runs. A single failed run should NOT mark the conversation
|
|
14
|
+
// as permanently failed — only the *most recent* run for that conversation
|
|
15
|
+
// matters. We therefore check whether the latest cron_run (by created_at,
|
|
16
|
+
// which is a monotonically increasing epoch timestamp) has an error status.
|
|
17
|
+
// Note: cron_runs.id is a UUID v4 (random), so we cannot use MAX(id).
|
|
12
18
|
const row = rawGet<{ found: number }>(
|
|
13
19
|
`SELECT 1 AS found
|
|
14
20
|
FROM (
|
|
15
21
|
SELECT 1 FROM task_runs WHERE conversation_id = ? AND status = 'failed'
|
|
16
22
|
UNION ALL
|
|
17
|
-
SELECT 1 FROM cron_runs
|
|
23
|
+
SELECT 1 FROM cron_runs
|
|
24
|
+
WHERE conversation_id = ?
|
|
25
|
+
AND status = 'error'
|
|
26
|
+
AND id = (
|
|
27
|
+
SELECT id FROM cron_runs WHERE conversation_id = ?
|
|
28
|
+
ORDER BY created_at DESC LIMIT 1
|
|
29
|
+
)
|
|
18
30
|
)
|
|
19
31
|
LIMIT 1`,
|
|
20
32
|
conversationId,
|
|
21
33
|
conversationId,
|
|
34
|
+
conversationId,
|
|
22
35
|
);
|
|
23
36
|
return row != null;
|
|
24
37
|
}
|
|
@@ -57,9 +70,17 @@ export function invalidateAssistantInferredItemsForConversation(
|
|
|
57
70
|
AND tr.status = 'failed'
|
|
58
71
|
)
|
|
59
72
|
AND NOT EXISTS (
|
|
73
|
+
-- Check only the most recent cron_run for each conversation
|
|
74
|
+
-- so reused conversations with historical errors but recent
|
|
75
|
+
-- successes are still treated as valid corroborators.
|
|
60
76
|
SELECT 1 FROM cron_runs cr
|
|
61
77
|
WHERE cr.conversation_id = jc2.value
|
|
62
78
|
AND cr.status = 'error'
|
|
79
|
+
AND cr.id = (
|
|
80
|
+
SELECT cr2.id FROM cron_runs cr2
|
|
81
|
+
WHERE cr2.conversation_id = jc2.value
|
|
82
|
+
ORDER BY cr2.created_at DESC LIMIT 1
|
|
83
|
+
)
|
|
63
84
|
)
|
|
64
85
|
)`,
|
|
65
86
|
Date.now(),
|
|
@@ -79,9 +100,9 @@ export function invalidateAssistantInferredItemsForConversation(
|
|
|
79
100
|
|
|
80
101
|
/**
|
|
81
102
|
* Cancel all pending/running memory jobs referencing the given conversation.
|
|
82
|
-
* Covers every job type: `
|
|
103
|
+
* Covers every job type: `embed_attachment` (keyed by messageId),
|
|
83
104
|
* `embed_segment` (keyed by segmentId via memory_segments),
|
|
84
|
-
* `build_conversation_summary` (keyed by conversationId),
|
|
105
|
+
* `graph_extract`, `build_conversation_summary` (keyed by conversationId),
|
|
85
106
|
* and `embed_graph_node` (keyed by nodeId sourced from the conversation).
|
|
86
107
|
*/
|
|
87
108
|
export function cancelPendingJobsForConversation(
|
|
@@ -91,7 +112,7 @@ export function cancelPendingJobsForConversation(
|
|
|
91
112
|
const now = Date.now();
|
|
92
113
|
let total = 0;
|
|
93
114
|
|
|
94
|
-
// Jobs keyed by messageId:
|
|
115
|
+
// Jobs keyed by messageId: embed_attachment
|
|
95
116
|
total += rawRun(
|
|
96
117
|
`UPDATE memory_jobs
|
|
97
118
|
SET status = 'failed',
|
|
@@ -106,7 +127,7 @@ export function cancelPendingJobsForConversation(
|
|
|
106
127
|
conversationId,
|
|
107
128
|
);
|
|
108
129
|
|
|
109
|
-
// Jobs keyed by conversationId: build_conversation_summary
|
|
130
|
+
// Jobs keyed by conversationId: graph_extract, build_conversation_summary
|
|
110
131
|
total += rawRun(
|
|
111
132
|
`UPDATE memory_jobs
|
|
112
133
|
SET status = 'failed',
|
|
@@ -162,8 +183,8 @@ export function cancelPendingJobsForConversation(
|
|
|
162
183
|
}
|
|
163
184
|
|
|
164
185
|
/**
|
|
165
|
-
* Cancel only pending/running `
|
|
166
|
-
*
|
|
186
|
+
* Cancel only pending/running `graph_extract` jobs for the given
|
|
187
|
+
* conversation. Used by the task-failure path where we want to
|
|
167
188
|
* stop new extractions but must NOT cancel `embed_graph_node` jobs —
|
|
168
189
|
* those nodes may be multi-sourced and still valid.
|
|
169
190
|
*/
|
|
@@ -177,10 +198,8 @@ function cancelPendingExtractionJobsForConversation(
|
|
|
177
198
|
last_error = 'conversation_failed',
|
|
178
199
|
updated_at = ?
|
|
179
200
|
WHERE status IN ('pending', 'running')
|
|
180
|
-
AND type = '
|
|
181
|
-
AND json_extract(payload, '$.
|
|
182
|
-
SELECT id FROM messages WHERE conversation_id = ?
|
|
183
|
-
)`,
|
|
201
|
+
AND type = 'graph_extract'
|
|
202
|
+
AND json_extract(payload, '$.conversationId') = ?`,
|
|
184
203
|
now,
|
|
185
204
|
conversationId,
|
|
186
205
|
);
|
|
@@ -25,7 +25,7 @@ export interface MessagingProvider {
|
|
|
25
25
|
id: string;
|
|
26
26
|
/** Human-readable name (e.g. 'Slack', 'Gmail'). */
|
|
27
27
|
displayName: string;
|
|
28
|
-
/** Credential service name for token-manager (e.g. '
|
|
28
|
+
/** Credential service name for token-manager (e.g. 'slack'). */
|
|
29
29
|
credentialService: string;
|
|
30
30
|
|
|
31
31
|
// ── Universal operations (every platform must implement) ──────────
|
|
@@ -41,6 +41,10 @@ export interface ConversationCreatedInfo {
|
|
|
41
41
|
sourceEventName: string;
|
|
42
42
|
/** Present when the conversation is for a guardian-sensitive notification. */
|
|
43
43
|
targetGuardianPrincipalId?: string;
|
|
44
|
+
/** Conversation group identifier from the signal producer (e.g. "system:scheduled"). */
|
|
45
|
+
groupId?: string;
|
|
46
|
+
/** Semantic source from the signal producer (e.g. "schedule", "reminder"). */
|
|
47
|
+
source?: string;
|
|
44
48
|
}
|
|
45
49
|
export type OnConversationCreatedFn = (info: ConversationCreatedInfo) => void;
|
|
46
50
|
export interface BroadcastDecisionOptions {
|
|
@@ -238,6 +242,8 @@ export class NotificationBroadcaster {
|
|
|
238
242
|
title: conversationTitle,
|
|
239
243
|
sourceEventName: signal.sourceEventName,
|
|
240
244
|
targetGuardianPrincipalId,
|
|
245
|
+
groupId: signal.conversationMetadata?.groupId,
|
|
246
|
+
source: signal.conversationMetadata?.source,
|
|
241
247
|
};
|
|
242
248
|
|
|
243
249
|
// The per-dispatch onConversationCreated callback fires whenever a vellum
|
|
@@ -130,7 +130,9 @@ export async function pairDeliveryWithConversation(
|
|
|
130
130
|
const targetId = conversationAction.conversationId;
|
|
131
131
|
const existing = getConversation(targetId);
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
const effectiveSource =
|
|
134
|
+
signal.conversationMetadata?.source ?? "notification";
|
|
135
|
+
if (existing && existing.source === effectiveSource) {
|
|
134
136
|
// Append the seed message to the existing conversation
|
|
135
137
|
const message = await addMessage(
|
|
136
138
|
existing.id,
|
|
@@ -186,7 +188,9 @@ export async function pairDeliveryWithConversation(
|
|
|
186
188
|
const conversation = createConversation({
|
|
187
189
|
title,
|
|
188
190
|
conversationType,
|
|
189
|
-
source: "notification",
|
|
191
|
+
source: signal.conversationMetadata?.source ?? "notification",
|
|
192
|
+
groupId: signal.conversationMetadata?.groupId,
|
|
193
|
+
scheduleJobId: signal.conversationMetadata?.scheduleJobId,
|
|
190
194
|
});
|
|
191
195
|
|
|
192
196
|
const message = await addMessage(
|
|
@@ -241,7 +245,9 @@ export async function pairDeliveryWithConversation(
|
|
|
241
245
|
existingBinding.conversationId,
|
|
242
246
|
);
|
|
243
247
|
|
|
244
|
-
|
|
248
|
+
const effectiveSource =
|
|
249
|
+
signal.conversationMetadata?.source ?? "notification";
|
|
250
|
+
if (boundConversation && boundConversation.source === effectiveSource) {
|
|
245
251
|
const message = await addMessage(
|
|
246
252
|
boundConversation.id,
|
|
247
253
|
"assistant",
|
|
@@ -299,7 +305,9 @@ export async function pairDeliveryWithConversation(
|
|
|
299
305
|
const conversation = createConversation({
|
|
300
306
|
title,
|
|
301
307
|
conversationType,
|
|
302
|
-
source: "notification",
|
|
308
|
+
source: signal.conversationMetadata?.source ?? "notification",
|
|
309
|
+
groupId: signal.conversationMetadata?.groupId,
|
|
310
|
+
scheduleJobId: signal.conversationMetadata?.scheduleJobId,
|
|
303
311
|
});
|
|
304
312
|
|
|
305
313
|
// Skip memory indexing — notification audit messages are not conversational
|
|
@@ -408,6 +408,92 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
|
|
|
408
408
|
};
|
|
409
409
|
},
|
|
410
410
|
|
|
411
|
+
"ingress.trusted_contact.guardian_decision": (payload) => {
|
|
412
|
+
const decision = str(payload.decision, "decided on");
|
|
413
|
+
const sourceChannel =
|
|
414
|
+
typeof payload.sourceChannel === "string"
|
|
415
|
+
? payload.sourceChannel
|
|
416
|
+
: undefined;
|
|
417
|
+
|
|
418
|
+
const requesterDisplayName =
|
|
419
|
+
typeof payload.requesterDisplayName === "string" &&
|
|
420
|
+
payload.requesterDisplayName.length > 0
|
|
421
|
+
? payload.requesterDisplayName
|
|
422
|
+
: undefined;
|
|
423
|
+
const requesterExternalUserId =
|
|
424
|
+
typeof payload.requesterExternalUserId === "string" &&
|
|
425
|
+
payload.requesterExternalUserId.length > 0
|
|
426
|
+
? payload.requesterExternalUserId
|
|
427
|
+
: undefined;
|
|
428
|
+
const requesterLabel = sanitizeIdentityField(
|
|
429
|
+
requesterDisplayName ??
|
|
430
|
+
(sourceChannel === "slack" &&
|
|
431
|
+
requesterExternalUserId &&
|
|
432
|
+
/^U[A-Z0-9]+$/i.test(requesterExternalUserId)
|
|
433
|
+
? `<@${requesterExternalUserId}>`
|
|
434
|
+
: requesterExternalUserId) ??
|
|
435
|
+
"Someone",
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const decidedByDisplayName =
|
|
439
|
+
typeof payload.decidedByDisplayName === "string" &&
|
|
440
|
+
payload.decidedByDisplayName.length > 0
|
|
441
|
+
? payload.decidedByDisplayName
|
|
442
|
+
: undefined;
|
|
443
|
+
const decidedByExternalUserId =
|
|
444
|
+
typeof payload.decidedByExternalUserId === "string" &&
|
|
445
|
+
payload.decidedByExternalUserId.length > 0
|
|
446
|
+
? payload.decidedByExternalUserId
|
|
447
|
+
: undefined;
|
|
448
|
+
const decidedByLabel = sanitizeIdentityField(
|
|
449
|
+
decidedByDisplayName ??
|
|
450
|
+
(sourceChannel === "slack" &&
|
|
451
|
+
decidedByExternalUserId &&
|
|
452
|
+
/^U[A-Z0-9]+$/i.test(decidedByExternalUserId)
|
|
453
|
+
? `<@${decidedByExternalUserId}>`
|
|
454
|
+
: decidedByExternalUserId) ??
|
|
455
|
+
"a guardian",
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const verb = decision === "approved" ? "approved" : "denied";
|
|
459
|
+
return {
|
|
460
|
+
title: "Trusted Contact Decision",
|
|
461
|
+
body: `${requesterLabel}'s access request has been ${verb} by ${decidedByLabel}.`,
|
|
462
|
+
};
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
"ingress.trusted_contact.denied": (payload) => {
|
|
466
|
+
const sourceChannel =
|
|
467
|
+
typeof payload.sourceChannel === "string"
|
|
468
|
+
? payload.sourceChannel
|
|
469
|
+
: undefined;
|
|
470
|
+
|
|
471
|
+
const requesterDisplayName =
|
|
472
|
+
typeof payload.requesterDisplayName === "string" &&
|
|
473
|
+
payload.requesterDisplayName.length > 0
|
|
474
|
+
? payload.requesterDisplayName
|
|
475
|
+
: undefined;
|
|
476
|
+
const requesterExternalUserId =
|
|
477
|
+
typeof payload.requesterExternalUserId === "string" &&
|
|
478
|
+
payload.requesterExternalUserId.length > 0
|
|
479
|
+
? payload.requesterExternalUserId
|
|
480
|
+
: undefined;
|
|
481
|
+
const requesterLabel = sanitizeIdentityField(
|
|
482
|
+
requesterDisplayName ??
|
|
483
|
+
(sourceChannel === "slack" &&
|
|
484
|
+
requesterExternalUserId &&
|
|
485
|
+
/^U[A-Z0-9]+$/i.test(requesterExternalUserId)
|
|
486
|
+
? `<@${requesterExternalUserId}>`
|
|
487
|
+
: requesterExternalUserId) ??
|
|
488
|
+
"Someone",
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
title: "Trusted Contact Denied",
|
|
493
|
+
body: `A trusted contact request from ${requesterLabel} has been denied.`,
|
|
494
|
+
};
|
|
495
|
+
},
|
|
496
|
+
|
|
411
497
|
"ingress.escalation": (payload) => ({
|
|
412
498
|
title: "Escalation",
|
|
413
499
|
body:
|
|
@@ -13,6 +13,7 @@ import { v4 as uuid } from "uuid";
|
|
|
13
13
|
|
|
14
14
|
import { getDeliverableChannels } from "../channels/config.js";
|
|
15
15
|
import { getConfig } from "../config/loader.js";
|
|
16
|
+
import { listGuardianChannels } from "../contacts/contact-store.js";
|
|
16
17
|
import { resolveGuardianPersona } from "../prompts/persona-resolver.js";
|
|
17
18
|
import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
|
|
18
19
|
import {
|
|
@@ -73,6 +74,7 @@ function buildSystemPrompt(
|
|
|
73
74
|
preferenceContext?: string,
|
|
74
75
|
candidateContext?: string,
|
|
75
76
|
identityContext?: string,
|
|
77
|
+
recipientNotes?: string,
|
|
76
78
|
): string {
|
|
77
79
|
const sections: string[] = [
|
|
78
80
|
`You are a notification routing engine. Given a signal describing an event, decide whether the user should be notified, on which channel(s), and compose the notification copy.`,
|
|
@@ -89,6 +91,16 @@ function buildSystemPrompt(
|
|
|
89
91
|
);
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
if (recipientNotes) {
|
|
95
|
+
sections.push(
|
|
96
|
+
``,
|
|
97
|
+
`<recipient-context>`,
|
|
98
|
+
`The following are notes about the notification recipient. Use this context to tailor notification tone, formality, and content to the recipient's preferences.`,
|
|
99
|
+
recipientNotes,
|
|
100
|
+
`</recipient-context>`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
if (identityContext) {
|
|
93
105
|
sections.push(
|
|
94
106
|
``,
|
|
@@ -807,11 +819,34 @@ async function classifyWithLLM(
|
|
|
807
819
|
const identityContext = rawIdentityContext
|
|
808
820
|
? truncate(rawIdentityContext, MAX_IDENTITY_CONTEXT_CHARS, "\n…[truncated]")
|
|
809
821
|
: undefined;
|
|
822
|
+
|
|
823
|
+
// Resolve guardian contact notes for recipient context. Use the channel-
|
|
824
|
+
// agnostic guardian lookup so notes are available even when the only
|
|
825
|
+
// deliverable channel is "vellum" (which has no contact channel type).
|
|
826
|
+
let recipientNotes: string | undefined;
|
|
827
|
+
try {
|
|
828
|
+
const guardianResult = listGuardianChannels();
|
|
829
|
+
if (guardianResult?.contact.notes) {
|
|
830
|
+
recipientNotes = truncate(
|
|
831
|
+
guardianResult.contact.notes,
|
|
832
|
+
MAX_IDENTITY_CONTEXT_CHARS,
|
|
833
|
+
"\n…[truncated]",
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
} catch (err) {
|
|
837
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
838
|
+
log.warn(
|
|
839
|
+
{ err: errMsg },
|
|
840
|
+
"Failed to resolve guardian contact notes, proceeding without recipient context",
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
810
844
|
const systemPrompt = buildSystemPrompt(
|
|
811
845
|
availableChannels,
|
|
812
846
|
preferenceContext,
|
|
813
847
|
candidateContext,
|
|
814
848
|
identityContext,
|
|
849
|
+
recipientNotes,
|
|
815
850
|
);
|
|
816
851
|
const prompt = buildUserPrompt(signal);
|
|
817
852
|
const tool = buildDecisionTool(availableChannels);
|
|
@@ -79,6 +79,8 @@ function getBroadcaster(): NotificationBroadcaster {
|
|
|
79
79
|
title: info.title,
|
|
80
80
|
sourceEventName: info.sourceEventName,
|
|
81
81
|
targetGuardianPrincipalId: info.targetGuardianPrincipalId,
|
|
82
|
+
groupId: info.groupId,
|
|
83
|
+
source: info.source,
|
|
82
84
|
});
|
|
83
85
|
log.info(
|
|
84
86
|
{
|
|
@@ -176,6 +178,17 @@ export interface EmitSignalParams<TEventName extends string = string> {
|
|
|
176
178
|
* Useful for direct user-invoked actions that must fail closed.
|
|
177
179
|
*/
|
|
178
180
|
throwOnError?: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Optional metadata propagated to the conversation created by the notification
|
|
183
|
+
* pipeline. Allows signal producers (e.g. the scheduler) to set groupId,
|
|
184
|
+
* scheduleJobId, or override the default "notification" source on the
|
|
185
|
+
* resulting conversation so it appears in the correct folder on clients.
|
|
186
|
+
*/
|
|
187
|
+
conversationMetadata?: {
|
|
188
|
+
groupId?: string;
|
|
189
|
+
scheduleJobId?: string;
|
|
190
|
+
source?: string;
|
|
191
|
+
};
|
|
179
192
|
}
|
|
180
193
|
|
|
181
194
|
export interface EmitSignalResult {
|
|
@@ -210,6 +223,7 @@ export async function emitNotificationSignal<TEventName extends string>(
|
|
|
210
223
|
routingIntent: params.routingIntent,
|
|
211
224
|
routingHints: params.routingHints,
|
|
212
225
|
conversationAffinityHint: params.conversationAffinityHint,
|
|
226
|
+
conversationMetadata: params.conversationMetadata,
|
|
213
227
|
};
|
|
214
228
|
|
|
215
229
|
try {
|
|
@@ -200,4 +200,15 @@ export interface NotificationSignal<TEventName extends string = string> {
|
|
|
200
200
|
* affinity within a call session.
|
|
201
201
|
*/
|
|
202
202
|
conversationAffinityHint?: Partial<Record<string, string>>;
|
|
203
|
+
/**
|
|
204
|
+
* Optional metadata propagated to the conversation created by the notification
|
|
205
|
+
* pipeline. Allows signal producers (e.g. the scheduler) to set groupId,
|
|
206
|
+
* scheduleJobId, or override the default "notification" source on the
|
|
207
|
+
* resulting conversation so it appears in the correct folder on clients.
|
|
208
|
+
*/
|
|
209
|
+
conversationMetadata?: {
|
|
210
|
+
groupId?: string;
|
|
211
|
+
scheduleJobId?: string;
|
|
212
|
+
source?: string;
|
|
213
|
+
};
|
|
203
214
|
}
|
|
@@ -27,7 +27,7 @@ function makeMockClient(
|
|
|
27
27
|
fetch: mock(async (path: string, init?: RequestInit) => {
|
|
28
28
|
const url = `https://platform.example.com${path}`;
|
|
29
29
|
const headers = new Headers(init?.headers);
|
|
30
|
-
headers.set("Authorization", "
|
|
30
|
+
headers.set("Authorization", "Bearer test-api-key");
|
|
31
31
|
return mockFetchFn(url, { ...init, headers });
|
|
32
32
|
}),
|
|
33
33
|
} as unknown as VellumPlatformClient;
|
|
@@ -53,7 +53,7 @@ describe("PlatformOAuthConnection", () => {
|
|
|
53
53
|
);
|
|
54
54
|
expect(init?.method).toBe("POST");
|
|
55
55
|
const headers = new Headers(init?.headers);
|
|
56
|
-
expect(headers.get("Authorization")).toBe("
|
|
56
|
+
expect(headers.get("Authorization")).toBe("Bearer test-api-key");
|
|
57
57
|
expect(headers.get("Content-Type")).toBe("application/json");
|
|
58
58
|
|
|
59
59
|
const parsed = JSON.parse(init?.body as string);
|
|
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
6
|
+
import { getIsContainerized } from "../config/env-registry.js";
|
|
6
7
|
import { getConfig } from "../config/loader.js";
|
|
7
8
|
import { loadSkillCatalog, resolveSkillSelector } from "../config/skills.js";
|
|
8
9
|
import { indexCatalogById } from "../skills/include-graph.js";
|
|
@@ -677,7 +678,7 @@ export async function classifyRisk(
|
|
|
677
678
|
}
|
|
678
679
|
}
|
|
679
680
|
|
|
680
|
-
|
|
681
|
+
let result = await classifyRiskUncached(
|
|
681
682
|
toolName,
|
|
682
683
|
input,
|
|
683
684
|
workingDir,
|
|
@@ -685,6 +686,17 @@ export async function classifyRisk(
|
|
|
685
686
|
manifestOverride,
|
|
686
687
|
);
|
|
687
688
|
|
|
689
|
+
// Proxied bash commands route through the credential proxy which handles
|
|
690
|
+
// per-request approval separately. Cap the bash tool's own risk at Medium
|
|
691
|
+
// so trust rules can auto-allow the command execution.
|
|
692
|
+
if (
|
|
693
|
+
toolName === "bash" &&
|
|
694
|
+
input.network_mode === "proxied" &&
|
|
695
|
+
result === RiskLevel.High
|
|
696
|
+
) {
|
|
697
|
+
result = RiskLevel.Medium;
|
|
698
|
+
}
|
|
699
|
+
|
|
688
700
|
if (cacheKey) {
|
|
689
701
|
if (riskCache.size >= RISK_CACHE_MAX) {
|
|
690
702
|
const oldest = riskCache.keys().next().value;
|
|
@@ -1086,9 +1098,8 @@ export async function check(
|
|
|
1086
1098
|
!matchedRule &&
|
|
1087
1099
|
risk === RiskLevel.Low
|
|
1088
1100
|
) {
|
|
1089
|
-
//
|
|
1090
|
-
|
|
1091
|
-
if (toolName === "bash" && !sandboxEnabled) {
|
|
1101
|
+
// Outside a container, bash runs on the host — don't auto-allow
|
|
1102
|
+
if (toolName === "bash" && !getIsContainerized()) {
|
|
1092
1103
|
// Fall through to risk-based policy below
|
|
1093
1104
|
} else if (isWorkspaceScopedInvocation(toolName, input, workingDir)) {
|
|
1094
1105
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
|
|
3
|
+
import { getIsContainerized } from "../config/env-registry.js";
|
|
3
4
|
import { getConfig } from "../config/loader.js";
|
|
4
5
|
import { getBundledSkillsDir } from "../config/skills.js";
|
|
5
6
|
import { getWorkspaceDir } from "../util/platform.js";
|
|
@@ -42,7 +43,6 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
42
43
|
// Some test suites mock getConfig() with partial objects; treat missing
|
|
43
44
|
// branches as defaults so rule generation remains deterministic.
|
|
44
45
|
const config = getConfig() as {
|
|
45
|
-
sandbox?: { enabled?: boolean };
|
|
46
46
|
skills?: { load?: { extraDirs?: unknown } };
|
|
47
47
|
};
|
|
48
48
|
|
|
@@ -67,12 +67,11 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
67
67
|
priority: 50,
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// commands
|
|
74
|
-
const
|
|
75
|
-
const sandboxShellRule: DefaultRuleTemplate | null = sandboxEnabled
|
|
70
|
+
// When running inside a container (IS_CONTAINERIZED=true), bash commands
|
|
71
|
+
// execute in an isolated environment — auto-allow all of them (including
|
|
72
|
+
// high-risk) so the user is never prompted. Outside a container, bash
|
|
73
|
+
// commands run on the host and go through normal permission checks.
|
|
74
|
+
const bashShellRule: DefaultRuleTemplate | null = getIsContainerized()
|
|
76
75
|
? {
|
|
77
76
|
id: "default:allow-bash-global",
|
|
78
77
|
tool: "bash",
|
|
@@ -300,7 +299,7 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
300
299
|
return [
|
|
301
300
|
...hostFileRules,
|
|
302
301
|
hostShellRule,
|
|
303
|
-
...(
|
|
302
|
+
...(bashShellRule ? [bashShellRule] : []),
|
|
304
303
|
...computerUseRules,
|
|
305
304
|
...managedSkillRules,
|
|
306
305
|
...workspacePromptRules,
|