vellum 0.2.1 → 0.2.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/README.md +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +4 -3
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +130 -4
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-store.test.ts +216 -1
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +299 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +62 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +5 -3
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
- package/src/__tests__/run-orchestrator.test.ts +4 -4
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
- package/src/__tests__/runtime-runs-http.test.ts +4 -4
- package/src/__tests__/runtime-runs.test.ts +4 -4
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +71 -48
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +218 -3
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +186 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +12 -8
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +43 -5
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +43 -17
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-store.ts +192 -5
- package/src/calls/relay-server.ts +41 -4
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-provider.ts +10 -6
- package/src/calls/twilio-routes.ts +90 -76
- package/src/calls/types.ts +1 -1
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +251 -1
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +82 -23
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +33 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +161 -1
- package/src/config/system-prompt.ts +61 -16
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +163 -5
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -277
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +384 -68
- package/src/daemon/ipc-contract-inventory.json +28 -4
- package/src/daemon/ipc-contract.ts +133 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +143 -26
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +216 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1404
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1153
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +96 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +23 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/http-server.ts +108 -20
- package/src/runtime/routes/attachment-routes.ts +2 -3
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +5 -10
- package/src/runtime/routes/conversation-routes.ts +5 -5
- package/src/runtime/routes/run-routes.ts +2 -2
- package/src/runtime/run-orchestrator.ts +9 -3
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +220 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +17 -67
- package/src/tools/calls/call-start.ts +24 -85
- package/src/tools/calls/call-status.ts +35 -51
- package/src/tools/claude-code/claude-code.ts +77 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +20 -4
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +60 -88
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +72 -108
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +183 -215
- package/src/tools/tasks/work-item-list.ts +33 -63
- package/src/tools/tasks/work-item-remove.ts +45 -97
- package/src/tools/tasks/work-item-update.ts +91 -163
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +0 -62
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +27 -2
- package/src/workspace/commit-message-enrichment-service.ts +31 -7
- package/src/workspace/git-service.ts +87 -22
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +62 -3
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { evaluateRequest, evaluateRequestWithApproval } from '../policy.js';
|
|
3
|
+
import type { CredentialInjectionTemplate } from '../../../credentials/policy-types.js';
|
|
4
|
+
|
|
5
|
+
function makeTemplate(overrides: Partial<CredentialInjectionTemplate> = {}): CredentialInjectionTemplate {
|
|
6
|
+
return {
|
|
7
|
+
hostPattern: '*.example.com',
|
|
8
|
+
injectionType: 'header',
|
|
9
|
+
headerName: 'Authorization',
|
|
10
|
+
valuePrefix: 'Bearer ',
|
|
11
|
+
...overrides,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// evaluateRequest
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
describe('evaluateRequest', () => {
|
|
20
|
+
test('returns unauthenticated when no credential IDs', () => {
|
|
21
|
+
const result = evaluateRequest('api.example.com', '/', [], new Map());
|
|
22
|
+
expect(result.kind).toBe('unauthenticated');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('returns missing when credential has no templates', () => {
|
|
26
|
+
const result = evaluateRequest('api.example.com', '/', ['cred-1'], new Map());
|
|
27
|
+
expect(result.kind).toBe('missing');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('returns missing when no template matches the host', () => {
|
|
31
|
+
const templates = new Map<string, CredentialInjectionTemplate[]>();
|
|
32
|
+
templates.set('cred-1', [makeTemplate({ hostPattern: '*.other.com' })]);
|
|
33
|
+
const result = evaluateRequest('api.example.com', '/', ['cred-1'], templates);
|
|
34
|
+
expect(result.kind).toBe('missing');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('returns matched for exact host match', () => {
|
|
38
|
+
const tpl = makeTemplate({ hostPattern: 'api.example.com' });
|
|
39
|
+
const templates = new Map<string, CredentialInjectionTemplate[]>();
|
|
40
|
+
templates.set('cred-1', [tpl]);
|
|
41
|
+
const result = evaluateRequest('api.example.com', '/', ['cred-1'], templates);
|
|
42
|
+
expect(result.kind).toBe('matched');
|
|
43
|
+
if (result.kind === 'matched') {
|
|
44
|
+
expect(result.credentialId).toBe('cred-1');
|
|
45
|
+
expect(result.template).toBe(tpl);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('returns matched for wildcard host match', () => {
|
|
50
|
+
const tpl = makeTemplate({ hostPattern: '*.example.com' });
|
|
51
|
+
const templates = new Map<string, CredentialInjectionTemplate[]>();
|
|
52
|
+
templates.set('cred-1', [tpl]);
|
|
53
|
+
const result = evaluateRequest('api.example.com', '/', ['cred-1'], templates);
|
|
54
|
+
expect(result.kind).toBe('matched');
|
|
55
|
+
if (result.kind === 'matched') {
|
|
56
|
+
expect(result.credentialId).toBe('cred-1');
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('prefers exact match over wildcard for same credential', () => {
|
|
61
|
+
const wildcard = makeTemplate({ hostPattern: '*.example.com', headerName: 'X-Wildcard' });
|
|
62
|
+
const exact = makeTemplate({ hostPattern: 'api.example.com', headerName: 'X-Exact' });
|
|
63
|
+
const templates = new Map<string, CredentialInjectionTemplate[]>();
|
|
64
|
+
templates.set('cred-1', [wildcard, exact]);
|
|
65
|
+
const result = evaluateRequest('api.example.com', '/', ['cred-1'], templates);
|
|
66
|
+
expect(result.kind).toBe('matched');
|
|
67
|
+
if (result.kind === 'matched') {
|
|
68
|
+
expect(result.template.headerName).toBe('X-Exact');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('returns ambiguous for same-specificity tie within one credential', () => {
|
|
73
|
+
const tpl1 = makeTemplate({ hostPattern: '*.example.com', headerName: 'X-One' });
|
|
74
|
+
const tpl2 = makeTemplate({ hostPattern: '*.example.com', headerName: 'X-Two' });
|
|
75
|
+
const templates = new Map<string, CredentialInjectionTemplate[]>();
|
|
76
|
+
templates.set('cred-1', [tpl1, tpl2]);
|
|
77
|
+
const result = evaluateRequest('api.example.com', '/', ['cred-1'], templates);
|
|
78
|
+
expect(result.kind).toBe('ambiguous');
|
|
79
|
+
if (result.kind === 'ambiguous') {
|
|
80
|
+
expect(result.candidates).toHaveLength(2);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('returns ambiguous for cross-credential match', () => {
|
|
85
|
+
const tpl1 = makeTemplate({ hostPattern: '*.example.com' });
|
|
86
|
+
const tpl2 = makeTemplate({ hostPattern: '*.example.com' });
|
|
87
|
+
const templates = new Map<string, CredentialInjectionTemplate[]>();
|
|
88
|
+
templates.set('cred-1', [tpl1]);
|
|
89
|
+
templates.set('cred-2', [tpl2]);
|
|
90
|
+
const result = evaluateRequest('api.example.com', '/', ['cred-1', 'cred-2'], templates);
|
|
91
|
+
expect(result.kind).toBe('ambiguous');
|
|
92
|
+
if (result.kind === 'ambiguous') {
|
|
93
|
+
expect(result.candidates).toHaveLength(2);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('skips query-type injection templates', () => {
|
|
98
|
+
const tpl = makeTemplate({
|
|
99
|
+
hostPattern: '*.example.com',
|
|
100
|
+
injectionType: 'query',
|
|
101
|
+
queryParamName: 'api_key',
|
|
102
|
+
headerName: undefined,
|
|
103
|
+
});
|
|
104
|
+
const templates = new Map<string, CredentialInjectionTemplate[]>();
|
|
105
|
+
templates.set('cred-1', [tpl]);
|
|
106
|
+
const result = evaluateRequest('api.example.com', '/', ['cred-1'], templates);
|
|
107
|
+
expect(result.kind).toBe('missing');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('wildcard with includeApexForWildcard matches bare domain', () => {
|
|
111
|
+
const tpl = makeTemplate({ hostPattern: '*.example.com' });
|
|
112
|
+
const templates = new Map<string, CredentialInjectionTemplate[]>();
|
|
113
|
+
templates.set('cred-1', [tpl]);
|
|
114
|
+
const result = evaluateRequest('example.com', '/', ['cred-1'], templates);
|
|
115
|
+
expect(result.kind).toBe('matched');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// evaluateRequestWithApproval
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
describe('evaluateRequestWithApproval', () => {
|
|
124
|
+
test('passes through matched decisions', () => {
|
|
125
|
+
const tpl = makeTemplate({ hostPattern: 'api.example.com' });
|
|
126
|
+
const sessionTemplates = new Map<string, CredentialInjectionTemplate[]>();
|
|
127
|
+
sessionTemplates.set('cred-1', [tpl]);
|
|
128
|
+
const result = evaluateRequestWithApproval(
|
|
129
|
+
'api.example.com', null, '/',
|
|
130
|
+
['cred-1'], sessionTemplates, [],
|
|
131
|
+
);
|
|
132
|
+
expect(result.kind).toBe('matched');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('passes through ambiguous decisions', () => {
|
|
136
|
+
const tpl1 = makeTemplate({ hostPattern: '*.example.com', headerName: 'X-One' });
|
|
137
|
+
const tpl2 = makeTemplate({ hostPattern: '*.example.com', headerName: 'X-Two' });
|
|
138
|
+
const sessionTemplates = new Map<string, CredentialInjectionTemplate[]>();
|
|
139
|
+
sessionTemplates.set('cred-1', [tpl1, tpl2]);
|
|
140
|
+
const result = evaluateRequestWithApproval(
|
|
141
|
+
'api.example.com', null, '/',
|
|
142
|
+
['cred-1'], sessionTemplates, [],
|
|
143
|
+
);
|
|
144
|
+
expect(result.kind).toBe('ambiguous');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('returns ask_missing_credential when known templates match', () => {
|
|
148
|
+
const sessionTemplates = new Map<string, CredentialInjectionTemplate[]>();
|
|
149
|
+
const allKnown = [makeTemplate({ hostPattern: '*.example.com' })];
|
|
150
|
+
const result = evaluateRequestWithApproval(
|
|
151
|
+
'api.example.com', 443, '/api',
|
|
152
|
+
['cred-1'], sessionTemplates, allKnown,
|
|
153
|
+
);
|
|
154
|
+
expect(result.kind).toBe('ask_missing_credential');
|
|
155
|
+
if (result.kind === 'ask_missing_credential') {
|
|
156
|
+
expect(result.target.hostname).toBe('api.example.com');
|
|
157
|
+
expect(result.target.port).toBe(443);
|
|
158
|
+
expect(result.target.path).toBe('/api');
|
|
159
|
+
expect(result.matchingPatterns).toContain('*.example.com');
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('deduplicates matching patterns', () => {
|
|
164
|
+
const sessionTemplates = new Map<string, CredentialInjectionTemplate[]>();
|
|
165
|
+
const allKnown = [
|
|
166
|
+
makeTemplate({ hostPattern: '*.example.com', headerName: 'X-One' }),
|
|
167
|
+
makeTemplate({ hostPattern: '*.example.com', headerName: 'X-Two' }),
|
|
168
|
+
];
|
|
169
|
+
const result = evaluateRequestWithApproval(
|
|
170
|
+
'api.example.com', null, '/',
|
|
171
|
+
['cred-1'], sessionTemplates, allKnown,
|
|
172
|
+
);
|
|
173
|
+
expect(result.kind).toBe('ask_missing_credential');
|
|
174
|
+
if (result.kind === 'ask_missing_credential') {
|
|
175
|
+
expect(result.matchingPatterns).toEqual(['*.example.com']);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('returns ask_unauthenticated when no known templates match and no credentials', () => {
|
|
180
|
+
const sessionTemplates = new Map<string, CredentialInjectionTemplate[]>();
|
|
181
|
+
const result = evaluateRequestWithApproval(
|
|
182
|
+
'unknown.example.com', null, '/',
|
|
183
|
+
[], sessionTemplates, [],
|
|
184
|
+
);
|
|
185
|
+
expect(result.kind).toBe('ask_unauthenticated');
|
|
186
|
+
if (result.kind === 'ask_unauthenticated') {
|
|
187
|
+
expect(result.target.hostname).toBe('unknown.example.com');
|
|
188
|
+
expect(result.target.scheme).toBe('https');
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('returns ask_unauthenticated for unknown host even with known templates for other hosts', () => {
|
|
193
|
+
const sessionTemplates = new Map<string, CredentialInjectionTemplate[]>();
|
|
194
|
+
const allKnown = [makeTemplate({ hostPattern: '*.other.com' })];
|
|
195
|
+
const result = evaluateRequestWithApproval(
|
|
196
|
+
'unknown.example.com', null, '/',
|
|
197
|
+
[], sessionTemplates, allKnown,
|
|
198
|
+
);
|
|
199
|
+
expect(result.kind).toBe('ask_unauthenticated');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('skips query templates when scanning allKnownTemplates', () => {
|
|
203
|
+
const sessionTemplates = new Map<string, CredentialInjectionTemplate[]>();
|
|
204
|
+
const allKnown = [makeTemplate({
|
|
205
|
+
hostPattern: '*.example.com',
|
|
206
|
+
injectionType: 'query',
|
|
207
|
+
queryParamName: 'key',
|
|
208
|
+
headerName: undefined,
|
|
209
|
+
})];
|
|
210
|
+
const result = evaluateRequestWithApproval(
|
|
211
|
+
'api.example.com', null, '/',
|
|
212
|
+
['cred-1'], sessionTemplates, allKnown,
|
|
213
|
+
);
|
|
214
|
+
// Query templates are skipped, so no pattern matches → ask_unauthenticated
|
|
215
|
+
// But credentialIds is non-empty so base is 'missing' → then no known header templates → ask_unauthenticated
|
|
216
|
+
// Actually: credentialIds=['cred-1'] but sessionTemplates is empty → base='missing'
|
|
217
|
+
// allKnown only has query type → no header matches → falls through to ask_unauthenticated
|
|
218
|
+
// Wait: base is 'missing' (not unauthenticated), and uniquePatterns.length===0
|
|
219
|
+
// For 'missing' with no matching patterns, the function returns ask_unauthenticated
|
|
220
|
+
expect(result.kind).toBe('ask_unauthenticated');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('uses provided scheme parameter', () => {
|
|
224
|
+
const sessionTemplates = new Map<string, CredentialInjectionTemplate[]>();
|
|
225
|
+
const result = evaluateRequestWithApproval(
|
|
226
|
+
'unknown.example.com', 80, '/',
|
|
227
|
+
[], sessionTemplates, [], 'http',
|
|
228
|
+
);
|
|
229
|
+
expect(result.kind).toBe('ask_unauthenticated');
|
|
230
|
+
if (result.kind === 'ask_unauthenticated') {
|
|
231
|
+
expect(result.target.scheme).toBe('http');
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { routeConnection } from '../router.js';
|
|
3
|
+
import type { CredentialInjectionTemplate } from '../../../credentials/policy-types.js';
|
|
4
|
+
|
|
5
|
+
function makeTemplate(overrides: Partial<CredentialInjectionTemplate> = {}): CredentialInjectionTemplate {
|
|
6
|
+
return {
|
|
7
|
+
hostPattern: '*.example.com',
|
|
8
|
+
injectionType: 'header',
|
|
9
|
+
headerName: 'Authorization',
|
|
10
|
+
valuePrefix: 'Bearer ',
|
|
11
|
+
...overrides,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('routeConnection', () => {
|
|
16
|
+
test('tunnels when no credentials', () => {
|
|
17
|
+
const result = routeConnection('api.example.com', 443, [], new Map());
|
|
18
|
+
expect(result.action).toBe('tunnel');
|
|
19
|
+
expect(result.reason).toBe('tunnel:no_credentials');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('tunnels when credentials exist but no template matches', () => {
|
|
23
|
+
const templates = new Map<string, readonly CredentialInjectionTemplate[]>();
|
|
24
|
+
templates.set('cred-1', [makeTemplate({ hostPattern: '*.other.com' })]);
|
|
25
|
+
const result = routeConnection('api.example.com', 443, ['cred-1'], templates);
|
|
26
|
+
expect(result.action).toBe('tunnel');
|
|
27
|
+
expect(result.reason).toBe('tunnel:no_rewrite');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('tunnels when credential has no templates in map', () => {
|
|
31
|
+
const templates = new Map<string, readonly CredentialInjectionTemplate[]>();
|
|
32
|
+
const result = routeConnection('api.example.com', 443, ['cred-1'], templates);
|
|
33
|
+
expect(result.action).toBe('tunnel');
|
|
34
|
+
expect(result.reason).toBe('tunnel:no_rewrite');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('MITMs when wildcard template matches', () => {
|
|
38
|
+
const templates = new Map<string, readonly CredentialInjectionTemplate[]>();
|
|
39
|
+
templates.set('cred-1', [makeTemplate({ hostPattern: '*.example.com' })]);
|
|
40
|
+
const result = routeConnection('api.example.com', 443, ['cred-1'], templates);
|
|
41
|
+
expect(result.action).toBe('mitm');
|
|
42
|
+
expect(result.reason).toBe('mitm:credential_injection');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('MITMs when exact template matches', () => {
|
|
46
|
+
const templates = new Map<string, readonly CredentialInjectionTemplate[]>();
|
|
47
|
+
templates.set('cred-1', [makeTemplate({ hostPattern: 'api.example.com' })]);
|
|
48
|
+
const result = routeConnection('api.example.com', 443, ['cred-1'], templates);
|
|
49
|
+
expect(result.action).toBe('mitm');
|
|
50
|
+
expect(result.reason).toBe('mitm:credential_injection');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('MITMs when any credential matches (first wins)', () => {
|
|
54
|
+
const templates = new Map<string, readonly CredentialInjectionTemplate[]>();
|
|
55
|
+
templates.set('cred-1', [makeTemplate({ hostPattern: '*.other.com' })]);
|
|
56
|
+
templates.set('cred-2', [makeTemplate({ hostPattern: '*.example.com' })]);
|
|
57
|
+
const result = routeConnection('api.example.com', 443, ['cred-1', 'cred-2'], templates);
|
|
58
|
+
expect(result.action).toBe('mitm');
|
|
59
|
+
expect(result.reason).toBe('mitm:credential_injection');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('wildcard matches bare apex domain with includeApexForWildcard', () => {
|
|
63
|
+
const templates = new Map<string, readonly CredentialInjectionTemplate[]>();
|
|
64
|
+
templates.set('cred-1', [makeTemplate({ hostPattern: '*.example.com' })]);
|
|
65
|
+
const result = routeConnection('example.com', 443, ['cred-1'], templates);
|
|
66
|
+
expect(result.action).toBe('mitm');
|
|
67
|
+
expect(result.reason).toBe('mitm:credential_injection');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('case-insensitive matching', () => {
|
|
71
|
+
const templates = new Map<string, readonly CredentialInjectionTemplate[]>();
|
|
72
|
+
templates.set('cred-1', [makeTemplate({ hostPattern: '*.EXAMPLE.COM' })]);
|
|
73
|
+
const result = routeConnection('API.example.com', 443, ['cred-1'], templates);
|
|
74
|
+
expect(result.action).toBe('mitm');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -298,12 +298,21 @@ function extractFirstMatch(text: string, regex: RegExp, captureGroup = 1): strin
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
function extractHtmlMetadata(html: string): { title?: string; description?: string } {
|
|
301
|
-
|
|
301
|
+
// Only search the <head> section (or first 50KB) to avoid catastrophic
|
|
302
|
+
// regex backtracking on large HTML documents.
|
|
303
|
+
// Strip <script> blocks first so that a literal "</head>" inside a script
|
|
304
|
+
// doesn't cause a false match that truncates the search region prematurely.
|
|
305
|
+
const candidate = html.slice(0, 200_000);
|
|
306
|
+
const stripped = candidate.replace(/<script[\s>][\s\S]*?<\/script>/gi, '');
|
|
307
|
+
const headEnd = stripped.search(/<\/head[\s>]/i);
|
|
308
|
+
const searchRegion = headEnd > 0 ? stripped.slice(0, headEnd + 10) : stripped.slice(0, 50_000);
|
|
309
|
+
|
|
310
|
+
const title = extractFirstMatch(searchRegion, /<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
302
311
|
const description =
|
|
303
|
-
extractFirstMatch(
|
|
304
|
-
?? extractFirstMatch(
|
|
305
|
-
?? extractFirstMatch(
|
|
306
|
-
?? extractFirstMatch(
|
|
312
|
+
extractFirstMatch(searchRegion, /<meta\s+[^>]*name=(['"])description\1[^>]*content=(['"])([\s\S]*?)\2[^>]*>/i, 3)
|
|
313
|
+
?? extractFirstMatch(searchRegion, /<meta\s+[^>]*content=(['"])([\s\S]*?)\1[^>]*name=(['"])description\3[^>]*>/i, 2)
|
|
314
|
+
?? extractFirstMatch(searchRegion, /<meta\s+[^>]*property=(['"])og:description\1[^>]*content=(['"])([\s\S]*?)\2[^>]*>/i, 3)
|
|
315
|
+
?? extractFirstMatch(searchRegion, /<meta\s+[^>]*content=(['"])([\s\S]*?)\1[^>]*property=(['"])og:description\3[^>]*>/i, 2);
|
|
307
316
|
|
|
308
317
|
return { title, description };
|
|
309
318
|
}
|
|
@@ -437,7 +446,10 @@ export async function executeWebFetch(
|
|
|
437
446
|
const safeRequestedUrl = sanitizeUrlForOutput(parsedUrl);
|
|
438
447
|
|
|
439
448
|
const controller = new AbortController();
|
|
440
|
-
const timeoutHandle = setTimeout(() =>
|
|
449
|
+
const timeoutHandle = setTimeout(() => {
|
|
450
|
+
log.warn({ url: safeRequestedUrl, timeoutSeconds }, 'Web fetch timeout fired, aborting');
|
|
451
|
+
controller.abort();
|
|
452
|
+
}, timeoutSeconds * 1000);
|
|
441
453
|
|
|
442
454
|
try {
|
|
443
455
|
log.debug({ url: safeRequestedUrl, timeoutSeconds, maxChars, startIndex, rawMode }, 'Fetching webpage');
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import './playbook-delete.js';
|
|
1
|
+
export { executePlaybookCreate } from './playbook-create.js';
|
|
2
|
+
export { executePlaybookList } from './playbook-list.js';
|
|
3
|
+
export { executePlaybookUpdate } from './playbook-update.js';
|
|
4
|
+
export { executePlaybookDelete } from './playbook-delete.js';
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { and, eq } from 'drizzle-orm';
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
|
-
import { RiskLevel } from '../../permissions/types.js';
|
|
4
3
|
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
5
|
-
import { registerTool } from '../registry.js';
|
|
6
4
|
import { getDb } from '../../memory/db.js';
|
|
7
5
|
import { computeMemoryFingerprint } from '../../memory/fingerprint.js';
|
|
8
6
|
import { memoryItems } from '../../memory/schema.js';
|
|
9
7
|
import { enqueueMemoryJob } from '../../memory/jobs-store.js';
|
|
10
8
|
import type { Playbook, PlaybookAutonomyLevel } from '../../playbooks/types.js';
|
|
9
|
+
import { truncate } from '../../util/truncate.js';
|
|
11
10
|
|
|
12
11
|
const VALID_AUTONOMY_LEVELS = new Set<string>(['auto', 'draft', 'notify']);
|
|
13
12
|
|
|
14
|
-
async function
|
|
13
|
+
export async function executePlaybookCreate(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
15
14
|
const trigger = input.trigger as string;
|
|
16
15
|
const action = input.action as string;
|
|
17
16
|
|
|
@@ -32,7 +31,7 @@ async function execute(input: Record<string, unknown>, context: ToolContext): Pr
|
|
|
32
31
|
|
|
33
32
|
const playbook: Playbook = { trigger, channel, category, action, autonomyLevel, priority };
|
|
34
33
|
const statement = JSON.stringify(playbook);
|
|
35
|
-
const subject = `Playbook: ${trigger}
|
|
34
|
+
const subject = truncate(`Playbook: ${trigger}`, 80, '');
|
|
36
35
|
const scopeId = context.memoryScopeId ?? 'default';
|
|
37
36
|
|
|
38
37
|
const fingerprint = computeMemoryFingerprint(scopeId, 'playbook', subject, statement);
|
|
@@ -95,46 +94,3 @@ async function execute(input: Record<string, unknown>, context: ToolContext): Pr
|
|
|
95
94
|
return { content: `Error creating playbook: ${msg}`, isError: true };
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
|
-
|
|
99
|
-
registerTool({
|
|
100
|
-
name: 'playbook_create',
|
|
101
|
-
description: 'Create an action playbook — a trigger→action rule that tells the assistant how to handle incoming messages matching a pattern',
|
|
102
|
-
category: 'playbook',
|
|
103
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
104
|
-
getDefinition: () => ({
|
|
105
|
-
name: 'playbook_create',
|
|
106
|
-
description: 'Create an action playbook — a trigger→action rule that tells the assistant how to handle incoming messages matching a pattern',
|
|
107
|
-
input_schema: {
|
|
108
|
-
type: 'object',
|
|
109
|
-
properties: {
|
|
110
|
-
trigger: {
|
|
111
|
-
type: 'string',
|
|
112
|
-
description: 'Pattern or description that triggers this playbook (e.g. "meeting request", "from:ceo@*", "newsletter")',
|
|
113
|
-
},
|
|
114
|
-
action: {
|
|
115
|
-
type: 'string',
|
|
116
|
-
description: 'What to do when the trigger matches — natural language action description (e.g. "check calendar, propose 3 times")',
|
|
117
|
-
},
|
|
118
|
-
channel: {
|
|
119
|
-
type: 'string',
|
|
120
|
-
description: 'Channel this rule applies to (e.g. "email", "slack"), or "*" for all channels. Defaults to "*".',
|
|
121
|
-
},
|
|
122
|
-
category: {
|
|
123
|
-
type: 'string',
|
|
124
|
-
description: 'Free-form category for grouping (e.g. "scheduling", "triage"). Defaults to "general".',
|
|
125
|
-
},
|
|
126
|
-
autonomy_level: {
|
|
127
|
-
type: 'string',
|
|
128
|
-
enum: ['auto', 'draft', 'notify'],
|
|
129
|
-
description: 'How much autonomy: "auto" = execute automatically, "draft" = draft for review, "notify" = notify only. Defaults to "draft".',
|
|
130
|
-
},
|
|
131
|
-
priority: {
|
|
132
|
-
type: 'number',
|
|
133
|
-
description: 'Relative priority — higher numbers take precedence. Defaults to 0.',
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
required: ['trigger', 'action'],
|
|
137
|
-
},
|
|
138
|
-
}),
|
|
139
|
-
execute,
|
|
140
|
-
});
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { and, eq } from 'drizzle-orm';
|
|
2
|
-
import { RiskLevel } from '../../permissions/types.js';
|
|
3
2
|
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
4
|
-
import { registerTool } from '../registry.js';
|
|
5
3
|
import { getDb } from '../../memory/db.js';
|
|
6
4
|
import { memoryItems } from '../../memory/schema.js';
|
|
7
5
|
import { parsePlaybookStatement } from '../../playbooks/types.js';
|
|
8
6
|
|
|
9
|
-
async function
|
|
7
|
+
export async function executePlaybookDelete(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
10
8
|
const playbookId = input.playbook_id as string;
|
|
11
9
|
if (!playbookId || typeof playbookId !== 'string') {
|
|
12
10
|
return { content: 'Error: playbook_id is required and must be a string', isError: true };
|
|
@@ -52,25 +50,3 @@ async function execute(input: Record<string, unknown>, context: ToolContext): Pr
|
|
|
52
50
|
return { content: `Error deleting playbook: ${msg}`, isError: true };
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
|
-
|
|
56
|
-
registerTool({
|
|
57
|
-
name: 'playbook_delete',
|
|
58
|
-
description: 'Delete an action playbook rule',
|
|
59
|
-
category: 'playbook',
|
|
60
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
61
|
-
getDefinition: () => ({
|
|
62
|
-
name: 'playbook_delete',
|
|
63
|
-
description: 'Delete an action playbook rule',
|
|
64
|
-
input_schema: {
|
|
65
|
-
type: 'object',
|
|
66
|
-
properties: {
|
|
67
|
-
playbook_id: {
|
|
68
|
-
type: 'string',
|
|
69
|
-
description: 'ID of the playbook to delete (from playbook_list results)',
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
required: ['playbook_id'],
|
|
73
|
-
},
|
|
74
|
-
}),
|
|
75
|
-
execute,
|
|
76
|
-
});
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { and, desc, eq, isNull } from 'drizzle-orm';
|
|
2
|
-
import { RiskLevel } from '../../permissions/types.js';
|
|
3
2
|
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
4
|
-
import { registerTool } from '../registry.js';
|
|
5
3
|
import { getDb } from '../../memory/db.js';
|
|
6
4
|
import { memoryItems } from '../../memory/schema.js';
|
|
7
5
|
import { parsePlaybookStatement } from '../../playbooks/types.js';
|
|
8
6
|
|
|
9
|
-
async function
|
|
7
|
+
export async function executePlaybookList(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
10
8
|
const scopeId = context.memoryScopeId ?? 'default';
|
|
11
9
|
const channelFilter = typeof input.channel === 'string' ? input.channel : null;
|
|
12
10
|
const categoryFilter = typeof input.category === 'string' ? input.category : null;
|
|
@@ -74,28 +72,3 @@ async function execute(input: Record<string, unknown>, context: ToolContext): Pr
|
|
|
74
72
|
return { content: `Error listing playbooks: ${msg}`, isError: true };
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
|
-
|
|
78
|
-
registerTool({
|
|
79
|
-
name: 'playbook_list',
|
|
80
|
-
description: 'List action playbooks, optionally filtered by channel or category',
|
|
81
|
-
category: 'playbook',
|
|
82
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
83
|
-
getDefinition: () => ({
|
|
84
|
-
name: 'playbook_list',
|
|
85
|
-
description: 'List action playbooks, optionally filtered by channel or category',
|
|
86
|
-
input_schema: {
|
|
87
|
-
type: 'object',
|
|
88
|
-
properties: {
|
|
89
|
-
channel: {
|
|
90
|
-
type: 'string',
|
|
91
|
-
description: 'Filter by channel (e.g. "email", "slack"). Omit to show all.',
|
|
92
|
-
},
|
|
93
|
-
category: {
|
|
94
|
-
type: 'string',
|
|
95
|
-
description: 'Filter by category (e.g. "scheduling", "triage"). Omit to show all.',
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
}),
|
|
100
|
-
execute,
|
|
101
|
-
});
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { and, eq } from 'drizzle-orm';
|
|
2
|
-
import { RiskLevel } from '../../permissions/types.js';
|
|
3
2
|
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
4
|
-
import { registerTool } from '../registry.js';
|
|
5
3
|
import { getDb } from '../../memory/db.js';
|
|
6
4
|
import { computeMemoryFingerprint } from '../../memory/fingerprint.js';
|
|
7
5
|
import { memoryItems } from '../../memory/schema.js';
|
|
8
6
|
import { enqueueMemoryJob } from '../../memory/jobs-store.js';
|
|
9
7
|
import { parsePlaybookStatement } from '../../playbooks/types.js';
|
|
10
8
|
import type { Playbook, PlaybookAutonomyLevel } from '../../playbooks/types.js';
|
|
9
|
+
import { truncate } from '../../util/truncate.js';
|
|
11
10
|
|
|
12
11
|
const VALID_AUTONOMY_LEVELS = new Set<string>(['auto', 'draft', 'notify']);
|
|
13
12
|
|
|
14
|
-
async function
|
|
13
|
+
export async function executePlaybookUpdate(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
15
14
|
const playbookId = input.playbook_id as string;
|
|
16
15
|
if (!playbookId || typeof playbookId !== 'string') {
|
|
17
16
|
return { content: 'Error: playbook_id is required and must be a string', isError: true };
|
|
@@ -55,7 +54,7 @@ async function execute(input: Record<string, unknown>, context: ToolContext): Pr
|
|
|
55
54
|
};
|
|
56
55
|
|
|
57
56
|
const statement = JSON.stringify(updated);
|
|
58
|
-
const subject = `Playbook: ${updated.trigger}
|
|
57
|
+
const subject = truncate(`Playbook: ${updated.trigger}`, 80, '');
|
|
59
58
|
const now = Date.now();
|
|
60
59
|
|
|
61
60
|
const fingerprint = computeMemoryFingerprint(scopeId, 'playbook', subject, statement);
|
|
@@ -110,50 +109,3 @@ async function execute(input: Record<string, unknown>, context: ToolContext): Pr
|
|
|
110
109
|
return { content: `Error updating playbook: ${msg}`, isError: true };
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
|
-
|
|
114
|
-
registerTool({
|
|
115
|
-
name: 'playbook_update',
|
|
116
|
-
description: 'Update an existing action playbook rule',
|
|
117
|
-
category: 'playbook',
|
|
118
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
119
|
-
getDefinition: () => ({
|
|
120
|
-
name: 'playbook_update',
|
|
121
|
-
description: 'Update an existing action playbook rule',
|
|
122
|
-
input_schema: {
|
|
123
|
-
type: 'object',
|
|
124
|
-
properties: {
|
|
125
|
-
playbook_id: {
|
|
126
|
-
type: 'string',
|
|
127
|
-
description: 'ID of the playbook to update (from playbook_list results)',
|
|
128
|
-
},
|
|
129
|
-
trigger: {
|
|
130
|
-
type: 'string',
|
|
131
|
-
description: 'Updated trigger pattern',
|
|
132
|
-
},
|
|
133
|
-
action: {
|
|
134
|
-
type: 'string',
|
|
135
|
-
description: 'Updated action description',
|
|
136
|
-
},
|
|
137
|
-
channel: {
|
|
138
|
-
type: 'string',
|
|
139
|
-
description: 'Updated channel ("*" for all)',
|
|
140
|
-
},
|
|
141
|
-
category: {
|
|
142
|
-
type: 'string',
|
|
143
|
-
description: 'Updated category',
|
|
144
|
-
},
|
|
145
|
-
autonomy_level: {
|
|
146
|
-
type: 'string',
|
|
147
|
-
enum: ['auto', 'draft', 'notify'],
|
|
148
|
-
description: 'Updated autonomy level',
|
|
149
|
-
},
|
|
150
|
-
priority: {
|
|
151
|
-
type: 'number',
|
|
152
|
-
description: 'Updated priority',
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
required: ['playbook_id'],
|
|
156
|
-
},
|
|
157
|
-
}),
|
|
158
|
-
execute,
|
|
159
|
-
});
|