@vellumai/assistant 0.5.9 → 0.5.11
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 +9 -1
- package/ARCHITECTURE.md +48 -48
- package/Dockerfile +2 -0
- package/README.md +1 -1
- package/docs/architecture/integrations.md +6 -13
- package/docs/architecture/memory.md +7 -12
- package/docs/architecture/security.md +5 -5
- package/docs/credential-execution-service.md +9 -9
- package/docs/skills.md +1 -1
- package/node_modules/@vellumai/credential-storage/src/index.ts +2 -2
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
- package/openapi.yaml +7130 -0
- package/package.json +2 -1
- package/scripts/generate-openapi.ts +562 -0
- package/src/__tests__/acp-session.test.ts +239 -44
- package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
- package/src/__tests__/browser-skill-endstate.test.ts +1 -1
- package/src/__tests__/btw-routes.test.ts +8 -0
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
- package/src/__tests__/channel-approvals.test.ts +7 -7
- package/src/__tests__/channel-readiness-service.test.ts +41 -0
- package/src/__tests__/config-schema.test.ts +10 -2
- package/src/__tests__/context-memory-e2e.test.ts +2 -6
- package/src/__tests__/conversation-skill-tools.test.ts +1 -3
- package/src/__tests__/conversation-title-service.test.ts +2 -15
- package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
- package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
- package/src/__tests__/credential-security-e2e.test.ts +4 -4
- package/src/__tests__/credential-security-invariants.test.ts +3 -3
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/heartbeat-service.test.ts +35 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
- package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
- package/src/__tests__/log-export-workspace.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
- package/src/__tests__/memory-recall-log-store.test.ts +182 -0
- package/src/__tests__/memory-recall-quality.test.ts +6 -8
- package/src/__tests__/memory-regressions.test.ts +53 -42
- package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
- package/src/__tests__/messaging-skill-split.test.ts +2 -17
- package/src/__tests__/oauth-cli.test.ts +98 -551
- package/src/__tests__/platform-callback-registration.test.ts +119 -0
- package/src/__tests__/secret-ingress-channel.test.ts +261 -0
- package/src/__tests__/secret-ingress-cli.test.ts +201 -0
- package/src/__tests__/secret-ingress-http.test.ts +312 -0
- package/src/__tests__/secret-ingress.test.ts +283 -0
- package/src/__tests__/secret-onetime-send.test.ts +4 -4
- package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
- package/src/__tests__/skill-feature-flags.test.ts +11 -19
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
- package/src/__tests__/skill-load-inline-command.test.ts +3 -3
- package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
- package/src/__tests__/skill-memory.test.ts +2 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
- package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
- package/src/__tests__/skills.test.ts +16 -2
- package/src/__tests__/slack-channel-config.test.ts +1 -1
- package/src/__tests__/slack-skill.test.ts +5 -69
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +5 -238
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -206
- package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +15 -7
- package/src/acp/client-handler.ts +113 -31
- package/src/acp/session-manager.ts +29 -27
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/cli/AGENTS.md +73 -0
- package/src/cli/commands/autonomy.ts +3 -5
- package/src/cli/commands/credential-execution.ts +1 -2
- package/src/cli/commands/credentials.ts +4 -4
- package/src/cli/commands/memory.ts +2 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +785 -0
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +760 -0
- package/src/cli/commands/oauth/__tests__/mode.test.ts +672 -0
- package/src/cli/commands/oauth/__tests__/ping.test.ts +690 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +579 -0
- package/src/cli/commands/oauth/__tests__/token.test.ts +467 -0
- package/src/cli/commands/oauth/apps.ts +29 -11
- package/src/cli/commands/oauth/connect.ts +373 -0
- package/src/cli/commands/oauth/connections.ts +14 -493
- package/src/cli/commands/oauth/disconnect.ts +333 -0
- package/src/cli/commands/oauth/index.ts +62 -10
- package/src/cli/commands/oauth/mode.ts +263 -0
- package/src/cli/commands/oauth/ping.ts +222 -0
- package/src/cli/commands/oauth/providers.ts +30 -3
- package/src/cli/commands/oauth/request.ts +576 -0
- package/src/cli/commands/oauth/shared.ts +132 -0
- package/src/cli/commands/oauth/status.ts +202 -0
- package/src/cli/commands/oauth/token.ts +159 -0
- package/src/cli/commands/platform.ts +20 -14
- package/src/cli.ts +82 -17
- package/src/config/assistant-feature-flags.ts +74 -11
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +13 -36
- package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +2 -2
- package/src/config/bundled-skills/settings/SKILL.md +5 -3
- package/src/config/bundled-skills/settings/TOOLS.json +17 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
- package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
- package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
- package/src/config/bundled-skills/slack/SKILL.md +58 -44
- package/src/config/bundled-tool-registry.ts +2 -19
- package/src/config/env.ts +5 -1
- package/src/config/feature-flag-registry.json +57 -41
- package/src/config/loader.ts +4 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/security.ts +9 -1
- package/src/config/schemas/services.ts +1 -1
- package/src/config/skill-state.ts +1 -3
- package/src/config/skills.ts +2 -4
- package/src/credential-execution/feature-gates.ts +9 -16
- package/src/credential-execution/process-manager.ts +12 -0
- package/src/daemon/config-watcher.ts +4 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
- package/src/daemon/conversation-agent-loop.ts +49 -2
- package/src/daemon/conversation-memory.ts +0 -1
- package/src/daemon/handlers/config-slack-channel.ts +43 -1
- package/src/daemon/handlers/conversations.ts +41 -33
- package/src/daemon/lifecycle.ts +28 -5
- package/src/daemon/message-types/acp.ts +0 -15
- package/src/daemon/message-types/memory.ts +0 -1
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +9 -0
- package/src/daemon/server.ts +19 -7
- package/src/email/feature-gate.ts +3 -3
- package/src/heartbeat/heartbeat-service.ts +48 -0
- package/src/inbound/platform-callback-registration.ts +61 -7
- package/src/mcp/mcp-oauth-provider.ts +3 -3
- package/src/memory/app-store.ts +3 -3
- package/src/memory/conversation-crud.ts +124 -0
- package/src/memory/conversation-title-service.ts +7 -17
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-local.ts +47 -2
- package/src/memory/indexer.ts +13 -10
- package/src/memory/items-extractor.ts +12 -4
- package/src/memory/job-utils.ts +5 -0
- package/src/memory/jobs-store.ts +10 -2
- package/src/memory/journal-memory.ts +6 -2
- package/src/memory/llm-request-log-store.ts +88 -21
- package/src/memory/memory-recall-log-store.ts +128 -0
- package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
- package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/migrations/validate-migration-state.ts +14 -1
- package/src/memory/retriever.test.ts +4 -5
- package/src/memory/schema/infrastructure.ts +31 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -1
- package/src/oauth/connect-orchestrator.ts +54 -0
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +26 -5
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -2
- package/src/permissions/trust-client.ts +2 -2
- package/src/platform/client.ts +2 -2
- package/src/prompts/journal-context.ts +6 -1
- package/src/providers/anthropic/client.ts +143 -1
- package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
- package/src/runtime/auth/route-policy.ts +0 -1
- package/src/runtime/btw-sidechain.ts +7 -1
- package/src/runtime/channel-approvals.ts +2 -2
- package/src/runtime/channel-readiness-service.ts +30 -7
- package/src/runtime/http-router.ts +31 -0
- package/src/runtime/http-server.ts +21 -4
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/pending-interactions.ts +21 -3
- package/src/runtime/routes/acp-routes.ts +46 -28
- package/src/runtime/routes/app-management-routes.ts +123 -0
- package/src/runtime/routes/app-routes.ts +31 -0
- package/src/runtime/routes/approval-routes.ts +108 -3
- package/src/runtime/routes/attachment-routes.ts +45 -0
- package/src/runtime/routes/avatar-routes.ts +16 -0
- package/src/runtime/routes/brain-graph-routes.ts +18 -0
- package/src/runtime/routes/btw-routes.ts +20 -0
- package/src/runtime/routes/call-routes.ts +81 -0
- package/src/runtime/routes/channel-readiness-routes.ts +48 -7
- package/src/runtime/routes/channel-routes.ts +18 -0
- package/src/runtime/routes/channel-verification-routes.ts +49 -1
- package/src/runtime/routes/contact-routes.ts +77 -0
- package/src/runtime/routes/conversation-attention-routes.ts +37 -0
- package/src/runtime/routes/conversation-management-routes.ts +94 -0
- package/src/runtime/routes/conversation-query-routes.ts +78 -0
- package/src/runtime/routes/conversation-routes.ts +115 -38
- package/src/runtime/routes/conversation-starter-routes.ts +29 -0
- package/src/runtime/routes/debug-routes.ts +23 -0
- package/src/runtime/routes/diagnostics-routes.ts +30 -0
- package/src/runtime/routes/documents-routes.ts +42 -0
- package/src/runtime/routes/events-routes.ts +10 -0
- package/src/runtime/routes/global-search-routes.ts +35 -0
- package/src/runtime/routes/guardian-action-routes.ts +47 -2
- package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
- package/src/runtime/routes/heartbeat-routes.ts +278 -0
- package/src/runtime/routes/host-bash-routes.ts +16 -1
- package/src/runtime/routes/host-cu-routes.ts +23 -1
- package/src/runtime/routes/host-file-routes.ts +18 -1
- package/src/runtime/routes/identity-routes.ts +35 -0
- package/src/runtime/routes/inbound-message-handler.ts +46 -25
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
- package/src/runtime/routes/integrations/twilio.ts +32 -22
- package/src/runtime/routes/invite-routes.ts +83 -0
- package/src/runtime/routes/log-export-routes.ts +14 -0
- package/src/runtime/routes/memory-item-routes.ts +99 -1
- package/src/runtime/routes/migration-rollback-routes.ts +25 -0
- package/src/runtime/routes/migration-routes.ts +40 -0
- package/src/runtime/routes/notification-routes.ts +20 -0
- package/src/runtime/routes/oauth-apps.ts +11 -3
- package/src/runtime/routes/pairing-routes.ts +15 -0
- package/src/runtime/routes/recording-routes.ts +72 -0
- package/src/runtime/routes/schedule-routes.ts +77 -5
- package/src/runtime/routes/secret-routes.ts +63 -1
- package/src/runtime/routes/settings-routes.ts +91 -1
- package/src/runtime/routes/skills-routes.ts +98 -16
- package/src/runtime/routes/subagents-routes.ts +38 -3
- package/src/runtime/routes/surface-action-routes.ts +66 -24
- package/src/runtime/routes/surface-content-routes.ts +20 -0
- package/src/runtime/routes/telemetry-routes.ts +12 -0
- package/src/runtime/routes/trace-event-routes.ts +25 -0
- package/src/runtime/routes/trust-rules-routes.ts +46 -0
- package/src/runtime/routes/tts-routes.ts +15 -4
- package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
- package/src/runtime/routes/usage-routes.ts +59 -0
- package/src/runtime/routes/watch-routes.ts +28 -0
- package/src/runtime/routes/work-items-routes.ts +59 -0
- package/src/runtime/routes/workspace-commit-routes.ts +12 -0
- package/src/runtime/routes/workspace-routes.ts +102 -0
- package/src/schedule/scheduler.ts +7 -1
- package/src/security/AGENTS.md +7 -0
- package/src/security/credential-backend.ts +1 -1
- package/src/security/encrypted-store.ts +3 -3
- package/src/security/oauth2.ts +55 -0
- package/src/security/secret-ingress.ts +174 -0
- package/src/security/secret-patterns.ts +133 -0
- package/src/security/secret-scanner.ts +28 -117
- package/src/signals/confirm.ts +12 -8
- package/src/signals/user-message.ts +18 -3
- package/src/skills/skill-memory.ts +1 -2
- package/src/tasks/task-runner.ts +7 -1
- package/src/tools/credentials/broker.ts +1 -1
- package/src/tools/credentials/metadata-store.ts +1 -1
- package/src/tools/credentials/vault.ts +2 -3
- package/src/tools/memory/definitions.ts +1 -1
- package/src/tools/memory/handlers.test.ts +2 -4
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/tool-manifest.ts +1 -1
- package/src/util/log-redact.ts +9 -34
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +13 -148
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +7 -145
- package/src/workspace/migrations/AGENTS.md +11 -0
- package/src/workspace/migrations/runner.ts +16 -6
- package/src/workspace/migrations/types.ts +7 -0
- package/docs/architecture/keychain-broker.md +0 -69
- package/src/__tests__/keychain-broker-client.test.ts +0 -800
- package/src/cli/commands/oauth/platform.ts +0 -525
- package/src/config/bundled-skills/slack/TOOLS.json +0 -272
- package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
- package/src/security/keychain-broker-client.ts +0 -446
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { statSync } from "node:fs";
|
|
6
6
|
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
7
9
|
import { getConfig } from "../../config/loader.js";
|
|
8
10
|
import { countConversations } from "../../memory/conversation-queries.js";
|
|
9
11
|
import { rawAll } from "../../memory/db.js";
|
|
@@ -93,7 +95,28 @@ export function debugRouteDefinitions(): RouteDefinition[] {
|
|
|
93
95
|
{
|
|
94
96
|
endpoint: "debug",
|
|
95
97
|
method: "GET",
|
|
98
|
+
summary: "Debug introspection",
|
|
99
|
+
description:
|
|
100
|
+
"Return runtime diagnostics: uptime, provider info, memory stats, job counts, and schedule counts.",
|
|
101
|
+
tags: ["debug"],
|
|
96
102
|
handler: () => handleDebug(),
|
|
103
|
+
responseBody: z.object({
|
|
104
|
+
session: z.object({}).passthrough().describe("Uptime and start time"),
|
|
105
|
+
provider: z
|
|
106
|
+
.object({})
|
|
107
|
+
.passthrough()
|
|
108
|
+
.describe("Inference provider configuration"),
|
|
109
|
+
memory: z
|
|
110
|
+
.object({})
|
|
111
|
+
.passthrough()
|
|
112
|
+
.describe("Conversation and memory item counts"),
|
|
113
|
+
jobs: z.object({}).passthrough().describe("Background job counts"),
|
|
114
|
+
schedules: z
|
|
115
|
+
.object({})
|
|
116
|
+
.passthrough()
|
|
117
|
+
.describe("Schedule counts (total, enabled)"),
|
|
118
|
+
timestamp: z.string().describe("Current server timestamp (ISO 8601)"),
|
|
119
|
+
}),
|
|
97
120
|
},
|
|
98
121
|
];
|
|
99
122
|
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* HTTP route handlers for dictation processing.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
5
7
|
import {
|
|
6
8
|
type ProfileResolution,
|
|
7
9
|
resolveProfile,
|
|
@@ -435,6 +437,34 @@ export function diagnosticsRouteDefinitions(): RouteDefinition[] {
|
|
|
435
437
|
endpoint: "dictation",
|
|
436
438
|
method: "POST",
|
|
437
439
|
policyKey: "dictation",
|
|
440
|
+
summary: "Process dictation",
|
|
441
|
+
description:
|
|
442
|
+
"Classify voice input as dictation or action, clean up text, and apply user style preferences.",
|
|
443
|
+
tags: ["diagnostics"],
|
|
444
|
+
requestBody: z.object({
|
|
445
|
+
transcription: z.string().describe("Raw speech transcription"),
|
|
446
|
+
context: z
|
|
447
|
+
.object({})
|
|
448
|
+
.passthrough()
|
|
449
|
+
.describe(
|
|
450
|
+
"Dictation context (app name, window title, bundle ID, cursor state, selected text)",
|
|
451
|
+
),
|
|
452
|
+
profileId: z
|
|
453
|
+
.string()
|
|
454
|
+
.describe("Optional dictation profile ID")
|
|
455
|
+
.optional(),
|
|
456
|
+
}),
|
|
457
|
+
responseBody: z.object({
|
|
458
|
+
text: z.string().describe("Processed text output"),
|
|
459
|
+
mode: z
|
|
460
|
+
.string()
|
|
461
|
+
.describe("Detected mode: dictation, command, or action"),
|
|
462
|
+
actionPlan: z
|
|
463
|
+
.string()
|
|
464
|
+
.describe("Action plan (only when mode is action)"),
|
|
465
|
+
resolvedProfileId: z.string().describe("Resolved dictation profile ID"),
|
|
466
|
+
profileSource: z.string().describe("How the profile was resolved"),
|
|
467
|
+
}),
|
|
438
468
|
handler: async ({ req }) => {
|
|
439
469
|
const body = (await req.json()) as DictationBody;
|
|
440
470
|
if (!body.transcription) {
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Exposes document CRUD over HTTP, sharing business logic with the
|
|
5
5
|
* handlers in `daemon/handlers/documents.ts`.
|
|
6
6
|
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
7
9
|
import { rawAll, rawGet, rawRun } from "../../memory/db.js";
|
|
8
10
|
import { getLogger } from "../../util/logger.js";
|
|
9
11
|
import { httpError } from "../http-errors.js";
|
|
@@ -161,6 +163,19 @@ export function documentRouteDefinitions(): RouteDefinition[] {
|
|
|
161
163
|
endpoint: "documents",
|
|
162
164
|
method: "GET",
|
|
163
165
|
policyKey: "documents",
|
|
166
|
+
summary: "List documents",
|
|
167
|
+
description: "Return all documents, optionally filtered by conversation.",
|
|
168
|
+
tags: ["documents"],
|
|
169
|
+
queryParams: [
|
|
170
|
+
{
|
|
171
|
+
name: "conversationId",
|
|
172
|
+
schema: { type: "string" },
|
|
173
|
+
description: "Filter by conversation ID",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
responseBody: z.object({
|
|
177
|
+
documents: z.array(z.unknown()).describe("Document summary objects"),
|
|
178
|
+
}),
|
|
164
179
|
handler: ({ url }) => {
|
|
165
180
|
const conversationId =
|
|
166
181
|
url.searchParams.get("conversationId") ?? undefined;
|
|
@@ -172,6 +187,19 @@ export function documentRouteDefinitions(): RouteDefinition[] {
|
|
|
172
187
|
endpoint: "documents/:id",
|
|
173
188
|
method: "GET",
|
|
174
189
|
policyKey: "documents",
|
|
190
|
+
summary: "Get a document",
|
|
191
|
+
description: "Return a single document by surface ID.",
|
|
192
|
+
tags: ["documents"],
|
|
193
|
+
responseBody: z.object({
|
|
194
|
+
success: z.boolean(),
|
|
195
|
+
surfaceId: z.string(),
|
|
196
|
+
conversationId: z.string(),
|
|
197
|
+
title: z.string(),
|
|
198
|
+
content: z.string(),
|
|
199
|
+
wordCount: z.number(),
|
|
200
|
+
createdAt: z.number(),
|
|
201
|
+
updatedAt: z.number(),
|
|
202
|
+
}),
|
|
175
203
|
handler: ({ params }) => {
|
|
176
204
|
const result = loadDocument(params.id);
|
|
177
205
|
if (!result.success) {
|
|
@@ -184,6 +212,20 @@ export function documentRouteDefinitions(): RouteDefinition[] {
|
|
|
184
212
|
endpoint: "documents",
|
|
185
213
|
method: "POST",
|
|
186
214
|
policyKey: "documents",
|
|
215
|
+
summary: "Save a document",
|
|
216
|
+
description: "Create or upsert a document (by surfaceId).",
|
|
217
|
+
tags: ["documents"],
|
|
218
|
+
requestBody: z.object({
|
|
219
|
+
surfaceId: z.string().describe("Surface ID (unique key)"),
|
|
220
|
+
conversationId: z.string().describe("Owning conversation"),
|
|
221
|
+
title: z.string().describe("Document title"),
|
|
222
|
+
content: z.string().describe("Document content"),
|
|
223
|
+
wordCount: z.number().describe("Word count"),
|
|
224
|
+
}),
|
|
225
|
+
responseBody: z.object({
|
|
226
|
+
success: z.boolean(),
|
|
227
|
+
surfaceId: z.string(),
|
|
228
|
+
}),
|
|
187
229
|
handler: async ({ req }) => {
|
|
188
230
|
const body = (await req.json()) as {
|
|
189
231
|
surfaceId?: string;
|
|
@@ -215,6 +215,16 @@ export function eventsRouteDefinitions(): RouteDefinition[] {
|
|
|
215
215
|
{
|
|
216
216
|
endpoint: "events",
|
|
217
217
|
method: "GET",
|
|
218
|
+
summary: "Subscribe to assistant events",
|
|
219
|
+
description: "Stream assistant events as Server-Sent Events (SSE).",
|
|
220
|
+
tags: ["events"],
|
|
221
|
+
queryParams: [
|
|
222
|
+
{
|
|
223
|
+
name: "conversationKey",
|
|
224
|
+
schema: { type: "string" },
|
|
225
|
+
description: "Scope to a single conversation",
|
|
226
|
+
},
|
|
227
|
+
],
|
|
218
228
|
handler: ({ req, url, authContext }) =>
|
|
219
229
|
handleSubscribeAssistantEvents(req, url, { authContext }),
|
|
220
230
|
},
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* and merges results with lexical matches.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
11
13
|
import { getConfig } from "../../config/loader.js";
|
|
12
14
|
import { searchContacts } from "../../contacts/contact-store.js";
|
|
13
15
|
import { searchConversations } from "../../memory/conversation-queries.js";
|
|
@@ -273,6 +275,39 @@ export function globalSearchRouteDefinitions(): RouteDefinition[] {
|
|
|
273
275
|
{
|
|
274
276
|
endpoint: "search/global",
|
|
275
277
|
method: "GET",
|
|
278
|
+
summary: "Global search",
|
|
279
|
+
description:
|
|
280
|
+
"Federated search across conversations, memories, schedules, and contacts.",
|
|
281
|
+
tags: ["search"],
|
|
282
|
+
queryParams: [
|
|
283
|
+
{
|
|
284
|
+
name: "q",
|
|
285
|
+
schema: { type: "string" },
|
|
286
|
+
description: "Search query (required)",
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "limit",
|
|
290
|
+
schema: { type: "integer" },
|
|
291
|
+
description: "Max results per category (1–100, default 20)",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "categories",
|
|
295
|
+
schema: { type: "string" },
|
|
296
|
+
description: "Comma-separated categories to search",
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "deep",
|
|
300
|
+
schema: { type: "string" },
|
|
301
|
+
description: "Enable semantic search for memories (true/false)",
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
responseBody: z.object({
|
|
305
|
+
query: z.string(),
|
|
306
|
+
results: z
|
|
307
|
+
.object({})
|
|
308
|
+
.passthrough()
|
|
309
|
+
.describe("Results grouped by category"),
|
|
310
|
+
}),
|
|
276
311
|
handler: async ({ url }) => handleGlobalSearch(url),
|
|
277
312
|
},
|
|
278
313
|
];
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* Guardian decisions additionally verify the actor is the bound guardian
|
|
12
12
|
* via the AuthContext's actorPrincipalId.
|
|
13
13
|
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
|
|
16
|
+
import { isHttpAuthDisabled } from "../../config/env.js";
|
|
17
|
+
import { findGuardianForChannel } from "../../contacts/contact-store.js";
|
|
14
18
|
import {
|
|
15
19
|
type CanonicalGuardianRequest,
|
|
16
20
|
listPendingRequestsByConversationScope,
|
|
@@ -91,14 +95,25 @@ export async function handleGuardianActionDecision(
|
|
|
91
95
|
return httpError("BAD_REQUEST", "action is required", 400);
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
// Resolve the actor's guardian principal ID. For JWT-verified actors this
|
|
99
|
+
// comes from the token claims. For dev bypass (HTTP auth disabled) the
|
|
100
|
+
// synthetic "dev-bypass" principal won't match the real guardian binding,
|
|
101
|
+
// so fall back to the local guardian binding to avoid identity_mismatch.
|
|
102
|
+
let guardianPrincipalId: string | undefined =
|
|
103
|
+
authContext.actorPrincipalId ?? undefined;
|
|
104
|
+
if (isHttpAuthDisabled() && authContext.actorPrincipalId === "dev-bypass") {
|
|
105
|
+
const binding = findGuardianForChannel("vellum");
|
|
106
|
+
guardianPrincipalId = binding?.contact.principalId ?? undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
const result = await processGuardianDecision({
|
|
95
110
|
requestId,
|
|
96
111
|
action,
|
|
97
112
|
conversationId,
|
|
98
113
|
channel: "vellum",
|
|
99
114
|
actorContext: {
|
|
100
|
-
actorPrincipalId:
|
|
101
|
-
guardianPrincipalId
|
|
115
|
+
actorPrincipalId: guardianPrincipalId,
|
|
116
|
+
guardianPrincipalId,
|
|
102
117
|
},
|
|
103
118
|
});
|
|
104
119
|
|
|
@@ -248,12 +263,42 @@ export function guardianActionRouteDefinitions(): RouteDefinition[] {
|
|
|
248
263
|
{
|
|
249
264
|
endpoint: "guardian-actions/pending",
|
|
250
265
|
method: "GET",
|
|
266
|
+
summary: "List pending guardian actions",
|
|
267
|
+
description:
|
|
268
|
+
"Return pending guardian decision prompts for a conversation.",
|
|
269
|
+
tags: ["guardian"],
|
|
270
|
+
queryParams: [
|
|
271
|
+
{
|
|
272
|
+
name: "conversationId",
|
|
273
|
+
schema: { type: "string" },
|
|
274
|
+
description: "Conversation ID (required)",
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
responseBody: z.object({
|
|
278
|
+
conversationId: z.string(),
|
|
279
|
+
prompts: z
|
|
280
|
+
.array(z.unknown())
|
|
281
|
+
.describe("Guardian decision prompt objects"),
|
|
282
|
+
}),
|
|
251
283
|
handler: ({ url, authContext }) =>
|
|
252
284
|
handleGuardianActionsPending(url, authContext),
|
|
253
285
|
},
|
|
254
286
|
{
|
|
255
287
|
endpoint: "guardian-actions/decision",
|
|
256
288
|
method: "POST",
|
|
289
|
+
summary: "Submit guardian decision",
|
|
290
|
+
description: "Submit a guardian action decision (approve/reject).",
|
|
291
|
+
tags: ["guardian"],
|
|
292
|
+
requestBody: z.object({
|
|
293
|
+
requestId: z.string().describe("Guardian request ID"),
|
|
294
|
+
action: z.string().describe("Decision action"),
|
|
295
|
+
conversationId: z.string().describe("Conversation ID").optional(),
|
|
296
|
+
}),
|
|
297
|
+
responseBody: z.object({
|
|
298
|
+
applied: z.boolean(),
|
|
299
|
+
requestId: z.string(),
|
|
300
|
+
reason: z.string(),
|
|
301
|
+
}),
|
|
257
302
|
handler: async ({ req, authContext }) =>
|
|
258
303
|
handleGuardianActionDecision(req, authContext),
|
|
259
304
|
},
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Approval prompt delivery: rich UI (buttons) with plain-text fallback.
|
|
3
3
|
*/
|
|
4
4
|
import type { ChannelId } from "../../channels/types.js";
|
|
5
|
+
import { redactSecrets } from "../../security/secret-scanner.js";
|
|
5
6
|
import { getLogger } from "../../util/logger.js";
|
|
6
7
|
import type { ApprovalMessageContext } from "../approval-message-composer.js";
|
|
7
8
|
import { composeApprovalMessageGenerative } from "../approval-message-composer.js";
|
|
@@ -20,6 +21,66 @@ import { requiredDecisionKeywords } from "./channel-route-shared.js";
|
|
|
20
21
|
|
|
21
22
|
const log = getLogger("runtime-http");
|
|
22
23
|
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Tool input summary for rich-UI approval prompts
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/** Max characters for the tool input preview line. */
|
|
29
|
+
const INPUT_PREVIEW_MAX_LENGTH = 200;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extract a concise, human-readable preview of the tool input so that
|
|
33
|
+
* sequential approval prompts for the same tool are distinguishable.
|
|
34
|
+
*
|
|
35
|
+
* Returns `null` when no meaningful preview can be produced.
|
|
36
|
+
*/
|
|
37
|
+
/** Escape backticks in user-controlled input so they don't break inline code spans. */
|
|
38
|
+
function escapeBackticks(value: string): string {
|
|
39
|
+
return value.replace(/`/g, "'");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Redact potential secrets from tool input before previewing. */
|
|
43
|
+
function sanitizePreviewValue(value: string): string {
|
|
44
|
+
return escapeBackticks(redactSecrets(value));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatToolInputPreview(
|
|
48
|
+
toolName: string,
|
|
49
|
+
toolInput: Record<string, unknown>,
|
|
50
|
+
): string | null {
|
|
51
|
+
// Pick the most relevant field based on tool type
|
|
52
|
+
const command = toolInput.command ?? toolInput.cmd;
|
|
53
|
+
if (typeof command === "string" && command.length > 0) {
|
|
54
|
+
return truncatePreview(`\`${sanitizePreviewValue(command)}\``);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const path = toolInput.path ?? toolInput.file_path ?? toolInput.filePath;
|
|
58
|
+
if (typeof path === "string" && path.length > 0) {
|
|
59
|
+
const verb =
|
|
60
|
+
toolName.includes("write") || toolName.includes("edit")
|
|
61
|
+
? "writing to"
|
|
62
|
+
: toolName.includes("read")
|
|
63
|
+
? "reading"
|
|
64
|
+
: "on";
|
|
65
|
+
return truncatePreview(`${verb} \`${sanitizePreviewValue(path)}\``);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const url = toolInput.url;
|
|
69
|
+
if (typeof url === "string" && url.length > 0) {
|
|
70
|
+
return truncatePreview(`fetching \`${sanitizePreviewValue(url)}\``);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function truncatePreview(text: string): string {
|
|
77
|
+
if (text.length <= INPUT_PREVIEW_MAX_LENGTH) return text;
|
|
78
|
+
const truncated = text.slice(0, INPUT_PREVIEW_MAX_LENGTH - 1) + "…";
|
|
79
|
+
// Preserve backtick pairing so markdown renders correctly.
|
|
80
|
+
const openBackticks = (truncated.match(/`/g) || []).length;
|
|
81
|
+
return openBackticks % 2 !== 0 ? truncated + "`" : truncated;
|
|
82
|
+
}
|
|
83
|
+
|
|
23
84
|
export interface DeliverGeneratedApprovalPromptParams {
|
|
24
85
|
replyCallbackUrl: string;
|
|
25
86
|
chatId: string;
|
|
@@ -61,15 +122,29 @@ export async function deliverGeneratedApprovalPrompt(
|
|
|
61
122
|
approvalCopyGenerator,
|
|
62
123
|
);
|
|
63
124
|
|
|
125
|
+
// Append a tool input preview so sequential approvals are distinguishable
|
|
126
|
+
let enrichedText = richText;
|
|
127
|
+
if (uiMetadata.permissionDetails) {
|
|
128
|
+
const preview = formatToolInputPreview(
|
|
129
|
+
uiMetadata.permissionDetails.toolName,
|
|
130
|
+
uiMetadata.permissionDetails.toolInput,
|
|
131
|
+
);
|
|
132
|
+
if (preview) {
|
|
133
|
+
enrichedText = `${enrichedText}\n\n${preview}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
64
137
|
// Append a legend explaining what each button does
|
|
65
138
|
const legend = buildActionLegend(uiMetadata.actions);
|
|
66
|
-
|
|
139
|
+
if (legend) {
|
|
140
|
+
enrichedText = `${enrichedText}\n\n${legend}`;
|
|
141
|
+
}
|
|
67
142
|
|
|
68
143
|
try {
|
|
69
144
|
await deliverApprovalPrompt(
|
|
70
145
|
replyCallbackUrl,
|
|
71
146
|
chatId,
|
|
72
|
-
|
|
147
|
+
enrichedText,
|
|
73
148
|
uiMetadata,
|
|
74
149
|
assistantId,
|
|
75
150
|
bearerToken,
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP route handlers for heartbeat management.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
|
|
8
|
+
import { desc, eq } from "drizzle-orm";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
import { getConfig, saveConfig } from "../../config/loader.js";
|
|
12
|
+
import type { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
|
|
13
|
+
import { getDb } from "../../memory/db.js";
|
|
14
|
+
import { conversations } from "../../memory/schema/conversations.js";
|
|
15
|
+
import { readTextFileSync } from "../../util/fs.js";
|
|
16
|
+
import { getLogger } from "../../util/logger.js";
|
|
17
|
+
import { getWorkspacePromptPath } from "../../util/platform.js";
|
|
18
|
+
import { httpError } from "../http-errors.js";
|
|
19
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
20
|
+
|
|
21
|
+
const log = getLogger("heartbeat-routes");
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Handlers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function handleGetConfig(heartbeatService?: HeartbeatService): Response {
|
|
28
|
+
const config = getConfig().heartbeat;
|
|
29
|
+
return Response.json({
|
|
30
|
+
enabled: config.enabled,
|
|
31
|
+
intervalMs: config.intervalMs,
|
|
32
|
+
activeHoursStart: config.activeHoursStart ?? null,
|
|
33
|
+
activeHoursEnd: config.activeHoursEnd ?? null,
|
|
34
|
+
nextRunAt: heartbeatService?.nextRunAt ?? null,
|
|
35
|
+
lastRunAt: heartbeatService?.lastRunAt ?? null,
|
|
36
|
+
success: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleUpdateConfig(
|
|
41
|
+
body: Record<string, unknown>,
|
|
42
|
+
heartbeatService?: HeartbeatService,
|
|
43
|
+
): Response {
|
|
44
|
+
const config = getConfig();
|
|
45
|
+
const heartbeat = { ...config.heartbeat };
|
|
46
|
+
|
|
47
|
+
if (typeof body.enabled === "boolean") heartbeat.enabled = body.enabled;
|
|
48
|
+
if (typeof body.intervalMs === "number")
|
|
49
|
+
heartbeat.intervalMs = body.intervalMs;
|
|
50
|
+
if ("activeHoursStart" in body) {
|
|
51
|
+
heartbeat.activeHoursStart =
|
|
52
|
+
typeof body.activeHoursStart === "number"
|
|
53
|
+
? body.activeHoursStart
|
|
54
|
+
: undefined;
|
|
55
|
+
}
|
|
56
|
+
if ("activeHoursEnd" in body) {
|
|
57
|
+
heartbeat.activeHoursEnd =
|
|
58
|
+
typeof body.activeHoursEnd === "number" ? body.activeHoursEnd : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
saveConfig({ ...config, heartbeat });
|
|
63
|
+
log.info({ heartbeat }, "Heartbeat config updated via HTTP");
|
|
64
|
+
} catch (err) {
|
|
65
|
+
log.error({ err }, "Failed to save heartbeat config");
|
|
66
|
+
return httpError("INTERNAL_ERROR", "Failed to save config", 500);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return Response.json({
|
|
70
|
+
enabled: heartbeat.enabled,
|
|
71
|
+
intervalMs: heartbeat.intervalMs,
|
|
72
|
+
activeHoursStart: heartbeat.activeHoursStart ?? null,
|
|
73
|
+
activeHoursEnd: heartbeat.activeHoursEnd ?? null,
|
|
74
|
+
nextRunAt: heartbeatService?.nextRunAt ?? null,
|
|
75
|
+
lastRunAt: heartbeatService?.lastRunAt ?? null,
|
|
76
|
+
success: true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleListRuns(limit: number): Response {
|
|
81
|
+
const db = getDb();
|
|
82
|
+
const rows = db
|
|
83
|
+
.select({
|
|
84
|
+
id: conversations.id,
|
|
85
|
+
title: conversations.title,
|
|
86
|
+
createdAt: conversations.createdAt,
|
|
87
|
+
})
|
|
88
|
+
.from(conversations)
|
|
89
|
+
.where(eq(conversations.source, "heartbeat"))
|
|
90
|
+
.orderBy(desc(conversations.createdAt))
|
|
91
|
+
.limit(limit)
|
|
92
|
+
.all();
|
|
93
|
+
|
|
94
|
+
return Response.json({
|
|
95
|
+
runs: rows.map((r) => ({
|
|
96
|
+
id: r.id,
|
|
97
|
+
title: r.title ?? "Heartbeat",
|
|
98
|
+
createdAt: r.createdAt,
|
|
99
|
+
result: "ok",
|
|
100
|
+
})),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function handleRunNow(
|
|
105
|
+
heartbeatService?: HeartbeatService,
|
|
106
|
+
): Promise<Response> {
|
|
107
|
+
if (!heartbeatService) {
|
|
108
|
+
return httpError(
|
|
109
|
+
"SERVICE_UNAVAILABLE",
|
|
110
|
+
"Heartbeat service not available",
|
|
111
|
+
503,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const ran = await heartbeatService.runOnce({ force: true });
|
|
117
|
+
return Response.json({ success: true, ran });
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
+
log.error({ err }, "Heartbeat run-now failed");
|
|
121
|
+
return Response.json({ success: false, error: message });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function handleGetChecklist(): Response {
|
|
126
|
+
const path = getWorkspacePromptPath("HEARTBEAT.md");
|
|
127
|
+
const content = readTextFileSync(path);
|
|
128
|
+
return Response.json({
|
|
129
|
+
content: content ?? "",
|
|
130
|
+
isDefault: content == null,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function handleWriteChecklist(content: string): Response {
|
|
135
|
+
const path = getWorkspacePromptPath("HEARTBEAT.md");
|
|
136
|
+
try {
|
|
137
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
138
|
+
writeFileSync(path, content, "utf-8");
|
|
139
|
+
log.info("Heartbeat checklist updated via HTTP");
|
|
140
|
+
return Response.json({ success: true });
|
|
141
|
+
} catch (err) {
|
|
142
|
+
log.error({ err }, "Failed to write heartbeat checklist");
|
|
143
|
+
return httpError("INTERNAL_ERROR", "Failed to write checklist", 500);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Route definitions
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
export function heartbeatRouteDefinitions(deps: {
|
|
152
|
+
getHeartbeatService?: () => HeartbeatService | undefined;
|
|
153
|
+
}): RouteDefinition[] {
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
endpoint: "heartbeat/config",
|
|
157
|
+
method: "GET",
|
|
158
|
+
policyKey: "heartbeat",
|
|
159
|
+
summary: "Get heartbeat config",
|
|
160
|
+
description: "Return the current heartbeat schedule configuration.",
|
|
161
|
+
tags: ["heartbeat"],
|
|
162
|
+
responseBody: z.object({
|
|
163
|
+
enabled: z.boolean(),
|
|
164
|
+
intervalMs: z.number(),
|
|
165
|
+
activeHoursStart: z.number(),
|
|
166
|
+
activeHoursEnd: z.number(),
|
|
167
|
+
nextRunAt: z.string(),
|
|
168
|
+
success: z.boolean(),
|
|
169
|
+
}),
|
|
170
|
+
handler: () => handleGetConfig(deps.getHeartbeatService?.()),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
endpoint: "heartbeat/config",
|
|
174
|
+
method: "PUT",
|
|
175
|
+
policyKey: "heartbeat",
|
|
176
|
+
summary: "Update heartbeat config",
|
|
177
|
+
description: "Update the heartbeat schedule configuration.",
|
|
178
|
+
tags: ["heartbeat"],
|
|
179
|
+
requestBody: z.object({
|
|
180
|
+
enabled: z.boolean().describe("Enable or disable heartbeat"),
|
|
181
|
+
intervalMs: z.number().describe("Heartbeat interval in ms"),
|
|
182
|
+
activeHoursStart: z.number().describe("Active hours start (0–23)"),
|
|
183
|
+
activeHoursEnd: z.number().describe("Active hours end (0–23)"),
|
|
184
|
+
}),
|
|
185
|
+
responseBody: z.object({
|
|
186
|
+
enabled: z.boolean(),
|
|
187
|
+
intervalMs: z.number(),
|
|
188
|
+
activeHoursStart: z.number(),
|
|
189
|
+
activeHoursEnd: z.number(),
|
|
190
|
+
nextRunAt: z.string(),
|
|
191
|
+
success: z.boolean(),
|
|
192
|
+
}),
|
|
193
|
+
handler: async ({ req }) => {
|
|
194
|
+
const body: unknown = await req.json();
|
|
195
|
+
if (typeof body !== "object" || !body || Array.isArray(body)) {
|
|
196
|
+
return httpError(
|
|
197
|
+
"BAD_REQUEST",
|
|
198
|
+
"Request body must be a JSON object",
|
|
199
|
+
400,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return handleUpdateConfig(
|
|
203
|
+
body as Record<string, unknown>,
|
|
204
|
+
deps.getHeartbeatService?.(),
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
endpoint: "heartbeat/runs",
|
|
210
|
+
method: "GET",
|
|
211
|
+
policyKey: "heartbeat",
|
|
212
|
+
summary: "List heartbeat runs",
|
|
213
|
+
description: "Return recent heartbeat conversation runs.",
|
|
214
|
+
tags: ["heartbeat"],
|
|
215
|
+
queryParams: [
|
|
216
|
+
{
|
|
217
|
+
name: "limit",
|
|
218
|
+
schema: { type: "integer" },
|
|
219
|
+
description: "Max runs to return (default 20)",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
responseBody: z.object({
|
|
223
|
+
runs: z.array(z.unknown()).describe("Heartbeat run records"),
|
|
224
|
+
}),
|
|
225
|
+
handler: ({ url }) => {
|
|
226
|
+
const limit = Number(url.searchParams.get("limit") ?? 20);
|
|
227
|
+
return handleListRuns(limit);
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
endpoint: "heartbeat/run-now",
|
|
232
|
+
method: "POST",
|
|
233
|
+
policyKey: "heartbeat",
|
|
234
|
+
summary: "Run heartbeat now",
|
|
235
|
+
description: "Trigger an immediate heartbeat run.",
|
|
236
|
+
tags: ["heartbeat"],
|
|
237
|
+
responseBody: z.object({
|
|
238
|
+
success: z.boolean(),
|
|
239
|
+
ran: z.boolean().describe("Whether the heartbeat actually ran"),
|
|
240
|
+
}),
|
|
241
|
+
handler: () => handleRunNow(deps.getHeartbeatService?.()),
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
endpoint: "heartbeat/checklist",
|
|
245
|
+
method: "GET",
|
|
246
|
+
policyKey: "heartbeat",
|
|
247
|
+
summary: "Get heartbeat checklist",
|
|
248
|
+
description: "Return the HEARTBEAT.md checklist content.",
|
|
249
|
+
tags: ["heartbeat"],
|
|
250
|
+
responseBody: z.object({
|
|
251
|
+
content: z.string().describe("Checklist markdown content"),
|
|
252
|
+
isDefault: z.boolean().describe("True when no custom checklist exists"),
|
|
253
|
+
}),
|
|
254
|
+
handler: () => handleGetChecklist(),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
endpoint: "heartbeat/checklist",
|
|
258
|
+
method: "PUT",
|
|
259
|
+
policyKey: "heartbeat",
|
|
260
|
+
summary: "Write heartbeat checklist",
|
|
261
|
+
description: "Overwrite the HEARTBEAT.md checklist content.",
|
|
262
|
+
tags: ["heartbeat"],
|
|
263
|
+
requestBody: z.object({
|
|
264
|
+
content: z.string().describe("Checklist markdown content"),
|
|
265
|
+
}),
|
|
266
|
+
responseBody: z.object({
|
|
267
|
+
success: z.boolean(),
|
|
268
|
+
}),
|
|
269
|
+
handler: async ({ req }) => {
|
|
270
|
+
const body = (await req.json()) as { content?: string };
|
|
271
|
+
if (typeof body.content !== "string") {
|
|
272
|
+
return httpError("BAD_REQUEST", "content is required", 400);
|
|
273
|
+
}
|
|
274
|
+
return handleWriteChecklist(body.content);
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
}
|