@vellumai/assistant 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +42 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/openapi.yaml +539 -4
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/checker.test.ts +138 -172
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/context-overflow-approval.test.ts +5 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +257 -100
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +63 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +0 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +16 -29
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +4 -27
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/transport-hints-queue.test.ts +77 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/agent/loop.ts +6 -29
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/channels/types.ts +5 -0
- package/src/cli/__tests__/run-assistant-command.ts +56 -0
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
- package/src/cli/commands/oauth/connect.ts +25 -5
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +11 -2
- package/src/cli.ts +1 -120
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
- package/src/config/bundled-skills/gmail/SKILL.md +13 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +85 -3
- package/src/daemon/context-overflow-approval.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +179 -65
- package/src/daemon/conversation-attachments.ts +0 -1
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +30 -8
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +359 -308
- package/src/daemon/conversation-surfaces.ts +65 -0
- package/src/daemon/conversation-tool-setup.ts +44 -17
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +19 -3
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +5 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +70 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +220 -158
- package/src/daemon/message-types/conversations.ts +29 -6
- package/src/daemon/message-types/messages.ts +9 -2
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +18 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +87 -10
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/daemon/transport-hints.ts +33 -0
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +151 -117
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +66 -6
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/embedding-local.ts +1 -1
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -17
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/platform-connection.test.ts +2 -2
- package/src/oauth/seed-providers.ts +1 -0
- package/src/permissions/checker.ts +15 -4
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/prompter.ts +0 -2
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +76 -162
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +30 -9
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/assistant-event-hub.ts +22 -0
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +270 -44
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +41 -278
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -18
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/secret-detection-handler.ts +0 -1
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +3 -5
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -3
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +85 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
isInteractiveInterface,
|
|
18
18
|
parseChannelId,
|
|
19
19
|
parseInterfaceId,
|
|
20
|
+
supportsHostProxy,
|
|
20
21
|
} from "../../channels/types.js";
|
|
21
22
|
import { isHttpAuthDisabled } from "../../config/env.js";
|
|
22
23
|
import { getConfig } from "../../config/loader.js";
|
|
@@ -38,6 +39,10 @@ import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
|
|
|
38
39
|
import { HostCuProxy } from "../../daemon/host-cu-proxy.js";
|
|
39
40
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
40
41
|
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
42
|
+
import type {
|
|
43
|
+
MacosTransportMetadata,
|
|
44
|
+
NonMacosTransportMetadata,
|
|
45
|
+
} from "../../daemon/message-types/conversations.js";
|
|
41
46
|
import type { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
|
|
42
47
|
import * as attachmentsStore from "../../memory/attachments-store.js";
|
|
43
48
|
import {
|
|
@@ -410,8 +415,15 @@ export function handleListMessages(
|
|
|
410
415
|
rawMessages = getMessages(resolvedConversationId);
|
|
411
416
|
}
|
|
412
417
|
|
|
418
|
+
// During streaming, tool_use (assistant) and tool_result (user) events are
|
|
419
|
+
// assembled client-side into a single assistant ChatMessage. On reload, they
|
|
420
|
+
// are separate DB rows. Merge tool_result blocks from user messages into the
|
|
421
|
+
// preceding assistant message so renderHistoryContent can pair them via its
|
|
422
|
+
// pendingToolUses map — otherwise they render as "Unknown" tool calls.
|
|
423
|
+
const mergedMessages = mergeToolResultsIntoAssistantMessages(rawMessages);
|
|
424
|
+
|
|
413
425
|
// Parse content blocks and extract text + tool calls
|
|
414
|
-
const parsed =
|
|
426
|
+
const parsed = mergedMessages.map((msg) => {
|
|
415
427
|
let content: unknown;
|
|
416
428
|
try {
|
|
417
429
|
content = JSON.parse(msg.content);
|
|
@@ -424,10 +436,33 @@ export function handleListMessages(
|
|
|
424
436
|
// was queued or its persistence was delayed (long assistant generation),
|
|
425
437
|
// sentAt captures the actual event time. Falls back to createdAt.
|
|
426
438
|
let sentAt: number | undefined;
|
|
439
|
+
let subagentNotification:
|
|
440
|
+
| {
|
|
441
|
+
subagentId: string;
|
|
442
|
+
label: string;
|
|
443
|
+
status: string;
|
|
444
|
+
error?: string;
|
|
445
|
+
conversationId?: string;
|
|
446
|
+
}
|
|
447
|
+
| undefined;
|
|
427
448
|
if (msg.metadata) {
|
|
428
449
|
try {
|
|
429
450
|
const meta = JSON.parse(msg.metadata);
|
|
430
451
|
if (typeof meta.sentAt === "number") sentAt = meta.sentAt;
|
|
452
|
+
if (meta.subagentNotification) {
|
|
453
|
+
const n = meta.subagentNotification;
|
|
454
|
+
if (typeof n.subagentId === "string" && typeof n.label === "string") {
|
|
455
|
+
subagentNotification = {
|
|
456
|
+
subagentId: n.subagentId,
|
|
457
|
+
label: n.label,
|
|
458
|
+
status: typeof n.status === "string" ? n.status : "completed",
|
|
459
|
+
...(typeof n.error === "string" ? { error: n.error } : {}),
|
|
460
|
+
...(typeof n.conversationId === "string"
|
|
461
|
+
? { conversationId: n.conversationId }
|
|
462
|
+
: {}),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
431
466
|
} catch {
|
|
432
467
|
// Ignore malformed metadata
|
|
433
468
|
}
|
|
@@ -475,6 +510,7 @@ export function handleListMessages(
|
|
|
475
510
|
? { thinkingSegments: rendered.thinkingSegments }
|
|
476
511
|
: {}),
|
|
477
512
|
id: msg.id,
|
|
513
|
+
subagentNotification,
|
|
478
514
|
};
|
|
479
515
|
}
|
|
480
516
|
|
|
@@ -492,6 +528,7 @@ export function handleListMessages(
|
|
|
492
528
|
? { thinkingSegments: rendered.thinkingSegments }
|
|
493
529
|
: {}),
|
|
494
530
|
id: msg.id,
|
|
531
|
+
subagentNotification,
|
|
495
532
|
};
|
|
496
533
|
});
|
|
497
534
|
|
|
@@ -580,6 +617,9 @@ export function handleListMessages(
|
|
|
580
617
|
? { thinkingSegments: m.thinkingSegments }
|
|
581
618
|
: {}),
|
|
582
619
|
...(m.contentOrder.length > 0 ? { contentOrder: m.contentOrder } : {}),
|
|
620
|
+
...(m.subagentNotification
|
|
621
|
+
? { subagentNotification: m.subagentNotification }
|
|
622
|
+
: {}),
|
|
583
623
|
};
|
|
584
624
|
});
|
|
585
625
|
|
|
@@ -599,6 +639,160 @@ export function handleListMessages(
|
|
|
599
639
|
return Response.json({ messages });
|
|
600
640
|
}
|
|
601
641
|
|
|
642
|
+
// ── Tool-result merging ─────────────────────────────────────────────
|
|
643
|
+
|
|
644
|
+
function isToolResultType(type: string): boolean {
|
|
645
|
+
return type === "tool_result" || type === "web_search_tool_result";
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function isSystemNoticeText(block: Record<string, unknown>): boolean {
|
|
649
|
+
if (block.type !== "text") return false;
|
|
650
|
+
const text = typeof block.text === "string" ? block.text : "";
|
|
651
|
+
return (
|
|
652
|
+
text.startsWith("<system_notice>") && text.endsWith("</system_notice>")
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Merge tool_result blocks from user messages into the preceding assistant
|
|
658
|
+
* message's content array. This lets renderHistoryContent's pendingToolUses
|
|
659
|
+
* map pair tool_use and tool_result blocks, preventing "unknown" tool names.
|
|
660
|
+
*
|
|
661
|
+
* User messages that consist entirely of tool_result blocks (and optional
|
|
662
|
+
* system_notice text) are removed from the output. Mixed messages (tool_result
|
|
663
|
+
* + real user text) keep only the non-tool-result blocks.
|
|
664
|
+
*/
|
|
665
|
+
function mergeToolResultsIntoAssistantMessages(
|
|
666
|
+
messages: MessageRow[],
|
|
667
|
+
): MessageRow[] {
|
|
668
|
+
// Index of the most recent assistant message in the output array.
|
|
669
|
+
let lastAssistantIdx = -1;
|
|
670
|
+
// Parsed content caches — lazily populated per assistant message.
|
|
671
|
+
const parsedAssistantContent = new Map<number, unknown[]>();
|
|
672
|
+
|
|
673
|
+
const result: MessageRow[] = [];
|
|
674
|
+
|
|
675
|
+
for (const msg of messages) {
|
|
676
|
+
if (msg.role === "assistant") {
|
|
677
|
+
lastAssistantIdx = result.length;
|
|
678
|
+
result.push(msg);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Only process user messages — other roles pass through.
|
|
683
|
+
if (msg.role !== "user") {
|
|
684
|
+
result.push(msg);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
let blocks: unknown[];
|
|
689
|
+
try {
|
|
690
|
+
const parsed = JSON.parse(msg.content);
|
|
691
|
+
if (!Array.isArray(parsed)) {
|
|
692
|
+
result.push(msg);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
blocks = parsed;
|
|
696
|
+
} catch {
|
|
697
|
+
result.push(msg);
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Separate tool-result blocks from real user content.
|
|
702
|
+
const toolResultBlocks: unknown[] = [];
|
|
703
|
+
const otherBlocks: unknown[] = [];
|
|
704
|
+
for (const block of blocks) {
|
|
705
|
+
if (
|
|
706
|
+
typeof block === "object" &&
|
|
707
|
+
block !== null &&
|
|
708
|
+
typeof (block as Record<string, unknown>).type === "string"
|
|
709
|
+
) {
|
|
710
|
+
const rec = block as Record<string, unknown>;
|
|
711
|
+
if (isToolResultType(rec.type as string)) {
|
|
712
|
+
toolResultBlocks.push(block);
|
|
713
|
+
} else if (isSystemNoticeText(rec)) {
|
|
714
|
+
// System notices don't count as user content — drop them when
|
|
715
|
+
// the message is otherwise tool-result-only.
|
|
716
|
+
otherBlocks.push(block);
|
|
717
|
+
} else {
|
|
718
|
+
otherBlocks.push(block);
|
|
719
|
+
}
|
|
720
|
+
} else {
|
|
721
|
+
otherBlocks.push(block);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// No tool results → pass through unchanged. System notices are only
|
|
726
|
+
// injected alongside tool results in the agent loop, so a pure user
|
|
727
|
+
// message (no tool_result blocks) should never be filtered — even if
|
|
728
|
+
// the user's text happens to look like a system_notice tag.
|
|
729
|
+
if (toolResultBlocks.length === 0) {
|
|
730
|
+
result.push(msg);
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Append tool_result blocks to the preceding assistant message's content.
|
|
735
|
+
if (lastAssistantIdx >= 0) {
|
|
736
|
+
const assistant = result[lastAssistantIdx];
|
|
737
|
+
let assistantContent = parsedAssistantContent.get(lastAssistantIdx);
|
|
738
|
+
if (!assistantContent) {
|
|
739
|
+
try {
|
|
740
|
+
const parsed = JSON.parse(assistant.content);
|
|
741
|
+
assistantContent = Array.isArray(parsed) ? parsed : [parsed];
|
|
742
|
+
} catch {
|
|
743
|
+
assistantContent = [];
|
|
744
|
+
}
|
|
745
|
+
parsedAssistantContent.set(lastAssistantIdx, assistantContent);
|
|
746
|
+
}
|
|
747
|
+
assistantContent.push(...toolResultBlocks);
|
|
748
|
+
} else {
|
|
749
|
+
// No preceding assistant message (pagination boundary) — keep the
|
|
750
|
+
// original message as-is to avoid permanent data loss. The preceding
|
|
751
|
+
// assistant tool_use lives in the previous page; dropping the result
|
|
752
|
+
// here would be unrecoverable.
|
|
753
|
+
// Still strip system notices so internal prompt text isn't exposed.
|
|
754
|
+
const filteredBlocks = blocks.filter(
|
|
755
|
+
(b) =>
|
|
756
|
+
!(
|
|
757
|
+
typeof b === "object" &&
|
|
758
|
+
b !== null &&
|
|
759
|
+
isSystemNoticeText(b as Record<string, unknown>)
|
|
760
|
+
),
|
|
761
|
+
);
|
|
762
|
+
result.push({
|
|
763
|
+
...msg,
|
|
764
|
+
content:
|
|
765
|
+
filteredBlocks.length === blocks.length
|
|
766
|
+
? msg.content
|
|
767
|
+
: JSON.stringify(filteredBlocks),
|
|
768
|
+
});
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// If the user message had only tool_result (+ system_notice) blocks,
|
|
773
|
+
// suppress it entirely. Otherwise keep the non-tool-result content.
|
|
774
|
+
const realUserContent = otherBlocks.filter(
|
|
775
|
+
(b) =>
|
|
776
|
+
!(
|
|
777
|
+
typeof b === "object" &&
|
|
778
|
+
b !== null &&
|
|
779
|
+
isSystemNoticeText(b as Record<string, unknown>)
|
|
780
|
+
),
|
|
781
|
+
);
|
|
782
|
+
if (realUserContent.length > 0) {
|
|
783
|
+
result.push({ ...msg, content: JSON.stringify(otherBlocks) });
|
|
784
|
+
}
|
|
785
|
+
// else: tool-result-only → suppressed (results already merged above)
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Write back any modified assistant message content.
|
|
789
|
+
for (const [idx, content] of parsedAssistantContent) {
|
|
790
|
+
result[idx] = { ...result[idx], content: JSON.stringify(content) };
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return result;
|
|
794
|
+
}
|
|
795
|
+
|
|
602
796
|
/**
|
|
603
797
|
* Build an `onEvent` callback that publishes every outbound event to the
|
|
604
798
|
* assistant event hub, maintaining ordered delivery through a serial chain.
|
|
@@ -658,13 +852,10 @@ function makeHubPublisher(
|
|
|
658
852
|
guardianPrincipalId: trustContext?.guardianPrincipalId ?? undefined,
|
|
659
853
|
toolName: msg.toolName,
|
|
660
854
|
commandPreview:
|
|
661
|
-
redactSecrets(
|
|
662
|
-
|
|
663
|
-
) || undefined,
|
|
855
|
+
redactSecrets(summarizeToolInput(msg.toolName, inputRecord)) ||
|
|
856
|
+
undefined,
|
|
664
857
|
riskLevel: msg.riskLevel,
|
|
665
|
-
activityText: activityRaw
|
|
666
|
-
? redactSecrets(activityRaw)
|
|
667
|
-
: undefined,
|
|
858
|
+
activityText: activityRaw ? redactSecrets(activityRaw) : undefined,
|
|
668
859
|
executionTarget: msg.executionTarget,
|
|
669
860
|
status: "pending",
|
|
670
861
|
requestCode: generateCanonicalRequestCode(),
|
|
@@ -759,6 +950,8 @@ export async function handleSendMessage(
|
|
|
759
950
|
conversationType?: string;
|
|
760
951
|
automated?: boolean;
|
|
761
952
|
bypassSecretCheck?: boolean;
|
|
953
|
+
hostHomeDir?: string;
|
|
954
|
+
hostUsername?: string;
|
|
762
955
|
};
|
|
763
956
|
|
|
764
957
|
const { conversationKey, content, attachmentIds } = body;
|
|
@@ -791,9 +984,11 @@ export async function handleSendMessage(
|
|
|
791
984
|
);
|
|
792
985
|
}
|
|
793
986
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
987
|
+
// When conversationKey is omitted, derive a stable default from
|
|
988
|
+
// sourceChannel + sourceInterface so that repeated calls from the same
|
|
989
|
+
// channel/interface pair share a single conversation thread.
|
|
990
|
+
const resolvedConversationKey =
|
|
991
|
+
conversationKey ?? `default:${sourceChannel}:${sourceInterface}`;
|
|
797
992
|
|
|
798
993
|
// Reject non-string content values (numbers, objects, etc.)
|
|
799
994
|
if (content != null && typeof content !== "string") {
|
|
@@ -856,12 +1051,29 @@ export async function handleSendMessage(
|
|
|
856
1051
|
|
|
857
1052
|
const conversationType =
|
|
858
1053
|
body.conversationType === "private" ? ("private" as const) : undefined;
|
|
859
|
-
const mapping = getOrCreateConversation(
|
|
1054
|
+
const mapping = getOrCreateConversation(resolvedConversationKey, {
|
|
860
1055
|
conversationType,
|
|
861
1056
|
});
|
|
862
1057
|
const smDeps = deps.sendMessageDeps;
|
|
1058
|
+
|
|
1059
|
+
// Build transport metadata from the request so the daemon can inject
|
|
1060
|
+
// host environment hints (home directory, username) into the LLM context.
|
|
1061
|
+
const transport =
|
|
1062
|
+
sourceInterface === "macos"
|
|
1063
|
+
? ({
|
|
1064
|
+
channelId: sourceChannel,
|
|
1065
|
+
interfaceId: "macos" as const,
|
|
1066
|
+
hostHomeDir: body.hostHomeDir,
|
|
1067
|
+
hostUsername: body.hostUsername,
|
|
1068
|
+
} satisfies MacosTransportMetadata)
|
|
1069
|
+
: ({
|
|
1070
|
+
channelId: sourceChannel,
|
|
1071
|
+
interfaceId: sourceInterface,
|
|
1072
|
+
} satisfies NonMacosTransportMetadata);
|
|
1073
|
+
|
|
863
1074
|
const conversation = await smDeps.getOrCreateConversation(
|
|
864
1075
|
mapping.conversationId,
|
|
1076
|
+
{ transport },
|
|
865
1077
|
);
|
|
866
1078
|
|
|
867
1079
|
// Resolve guardian context from the AuthContext's actorPrincipalId.
|
|
@@ -932,7 +1144,7 @@ export async function handleSendMessage(
|
|
|
932
1144
|
// channels, headless) fall back to local execution.
|
|
933
1145
|
// Set the proxy BEFORE updateClient so updateClient's call to
|
|
934
1146
|
// hostBashProxy.updateSender targets the correct (new) proxy.
|
|
935
|
-
if (sourceInterface
|
|
1147
|
+
if (supportsHostProxy(sourceInterface)) {
|
|
936
1148
|
// Reuse the existing proxy if the conversation is actively processing a
|
|
937
1149
|
// host bash request to avoid orphaning in-flight requests.
|
|
938
1150
|
if (!conversation.isProcessing() || !conversation.hostBashProxy) {
|
|
@@ -969,9 +1181,7 @@ export async function handleSendMessage(
|
|
|
969
1181
|
// When proxies are preserved during an active turn (non-desktop request while
|
|
970
1182
|
// processing), skip updating proxy senders to avoid degrading them.
|
|
971
1183
|
const preservingProxies =
|
|
972
|
-
conversation.isProcessing() &&
|
|
973
|
-
sourceInterface !== "macos" &&
|
|
974
|
-
sourceInterface !== "ios";
|
|
1184
|
+
conversation.isProcessing() && !supportsHostProxy(sourceInterface);
|
|
975
1185
|
conversation.updateClient(onEvent, !isInteractive, {
|
|
976
1186
|
skipProxySenderUpdate: preservingProxies,
|
|
977
1187
|
});
|
|
@@ -1132,6 +1342,8 @@ export async function handleSendMessage(
|
|
|
1132
1342
|
...(body.automated === true ? { automated: true } : {}),
|
|
1133
1343
|
},
|
|
1134
1344
|
{ isInteractive },
|
|
1345
|
+
undefined, // displayContent
|
|
1346
|
+
transport,
|
|
1135
1347
|
);
|
|
1136
1348
|
if (enqueueResult.rejected) {
|
|
1137
1349
|
return Response.json(
|
|
@@ -1143,36 +1355,47 @@ export async function handleSendMessage(
|
|
|
1143
1355
|
// Auto-deny pending confirmations only after enqueue succeeds, so we
|
|
1144
1356
|
// don't cancel approval-gated workflows when the replacement message
|
|
1145
1357
|
// is itself rejected by the queue budget.
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
)) {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1358
|
+
// Wrapped in try-catch: the message is already enqueued, so a failure
|
|
1359
|
+
// here must not turn the 202 response into a 500 — that would leave
|
|
1360
|
+
// the client showing "Failed to send" for a message the daemon will
|
|
1361
|
+
// process from the queue.
|
|
1362
|
+
try {
|
|
1363
|
+
if (conversation.hasAnyPendingConfirmation()) {
|
|
1364
|
+
// Emit authoritative denial state for each pending request.
|
|
1365
|
+
// sendToClient (wired to the SSE hub) delivers these to the client.
|
|
1366
|
+
for (const interaction of pendingInteractions.getByConversation(
|
|
1367
|
+
mapping.conversationId,
|
|
1368
|
+
)) {
|
|
1369
|
+
if (
|
|
1370
|
+
interaction.conversation === conversation &&
|
|
1371
|
+
interaction.kind === "confirmation"
|
|
1372
|
+
) {
|
|
1373
|
+
conversation.emitConfirmationStateChanged({
|
|
1374
|
+
conversationId: mapping.conversationId,
|
|
1375
|
+
requestId: interaction.requestId,
|
|
1376
|
+
state: "denied" as const,
|
|
1377
|
+
source: "auto_deny" as const,
|
|
1378
|
+
});
|
|
1379
|
+
// Sync canonical guardian request status so stale "pending" DB
|
|
1380
|
+
// records don't get matched by later guardian reply routing.
|
|
1381
|
+
resolveCanonicalGuardianRequest(interaction.requestId, "pending", {
|
|
1382
|
+
status: "denied",
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1167
1385
|
}
|
|
1386
|
+
conversation.denyAllPendingConfirmations();
|
|
1387
|
+
pendingInteractions.removeByConversation(conversation);
|
|
1168
1388
|
}
|
|
1169
|
-
conversation.denyAllPendingConfirmations();
|
|
1170
|
-
pendingInteractions.removeByConversation(conversation);
|
|
1171
|
-
}
|
|
1172
1389
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1390
|
+
// Expire any orphaned canonical requests that survived without a
|
|
1391
|
+
// matching in-memory pending interaction (e.g. prompter timeouts).
|
|
1392
|
+
expireOrphanedCanonicalRequests(mapping.conversationId);
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
log.warn(
|
|
1395
|
+
{ err, conversationId: mapping.conversationId },
|
|
1396
|
+
"Post-enqueue auto-deny failed — queued message unaffected",
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1176
1399
|
|
|
1177
1400
|
return Response.json(
|
|
1178
1401
|
{ accepted: true, queued: true, conversationId: mapping.conversationId },
|
|
@@ -1397,7 +1620,10 @@ export async function handleSendMessage(
|
|
|
1397
1620
|
conversationId,
|
|
1398
1621
|
});
|
|
1399
1622
|
conversation.processing = false;
|
|
1400
|
-
silentlyWithLog(
|
|
1623
|
+
silentlyWithLog(
|
|
1624
|
+
conversation.drainQueue(),
|
|
1625
|
+
"compact-command queue drain",
|
|
1626
|
+
);
|
|
1401
1627
|
}, 0);
|
|
1402
1628
|
|
|
1403
1629
|
cleanupDeferred = true;
|
|
@@ -1713,7 +1939,7 @@ export function conversationRouteDefinitions(deps: {
|
|
|
1713
1939
|
"Send a user message to a conversation and trigger the assistant response.",
|
|
1714
1940
|
tags: ["messages"],
|
|
1715
1941
|
requestBody: z.object({
|
|
1716
|
-
conversationKey: z.string(),
|
|
1942
|
+
conversationKey: z.string().optional(),
|
|
1717
1943
|
content: z.string().describe("Message text content"),
|
|
1718
1944
|
attachments: z
|
|
1719
1945
|
.array(z.unknown())
|
|
@@ -63,8 +63,22 @@ export function groupRouteDefinitions(): RouteDefinition[] {
|
|
|
63
63
|
if (!body.name || typeof body.name !== "string") {
|
|
64
64
|
return httpError("BAD_REQUEST", "Missing or invalid name", 400);
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
try {
|
|
67
|
+
const group = createGroup(body.name);
|
|
68
|
+
return Response.json(serializeGroup(group), { status: 201 });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (
|
|
71
|
+
err instanceof Error &&
|
|
72
|
+
err.message.includes("sort_position must be >= 4")
|
|
73
|
+
) {
|
|
74
|
+
return httpError(
|
|
75
|
+
"BAD_REQUEST",
|
|
76
|
+
"Too many custom groups — sort_position ceiling reached",
|
|
77
|
+
400,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
68
82
|
},
|
|
69
83
|
},
|
|
70
84
|
{
|
|
@@ -105,16 +119,16 @@ export function groupRouteDefinitions(): RouteDefinition[] {
|
|
|
105
119
|
403,
|
|
106
120
|
);
|
|
107
121
|
}
|
|
108
|
-
// Custom group sort_position must be >= 3
|
|
122
|
+
// Custom group sort_position must be >= 4 (0–3 reserved for system groups)
|
|
109
123
|
if (
|
|
110
124
|
body.sortPosition !== undefined &&
|
|
111
125
|
(typeof body.sortPosition !== "number" ||
|
|
112
126
|
!isFinite(body.sortPosition) ||
|
|
113
|
-
body.sortPosition <
|
|
127
|
+
body.sortPosition < 4)
|
|
114
128
|
) {
|
|
115
129
|
return httpError(
|
|
116
130
|
"BAD_REQUEST",
|
|
117
|
-
"Custom group sort_position must be >=
|
|
131
|
+
"Custom group sort_position must be >= 4",
|
|
118
132
|
400,
|
|
119
133
|
);
|
|
120
134
|
}
|
|
@@ -176,7 +190,7 @@ export function groupRouteDefinitions(): RouteDefinition[] {
|
|
|
176
190
|
if (!Array.isArray(body.updates)) {
|
|
177
191
|
return httpError("BAD_REQUEST", "Missing updates array", 400);
|
|
178
192
|
}
|
|
179
|
-
// Validate: no system group reordering,
|
|
193
|
+
// Validate: no system group reordering, sort_position >= 4 for custom groups
|
|
180
194
|
for (const update of body.updates) {
|
|
181
195
|
const group = getGroup(update.groupId);
|
|
182
196
|
if (!group) continue;
|
|
@@ -190,11 +204,11 @@ export function groupRouteDefinitions(): RouteDefinition[] {
|
|
|
190
204
|
if (
|
|
191
205
|
typeof update.sortPosition !== "number" ||
|
|
192
206
|
!isFinite(update.sortPosition) ||
|
|
193
|
-
update.sortPosition <
|
|
207
|
+
update.sortPosition < 4
|
|
194
208
|
) {
|
|
195
209
|
return httpError(
|
|
196
210
|
"BAD_REQUEST",
|
|
197
|
-
`Custom group sort_position must be >=
|
|
211
|
+
`Custom group sort_position must be >= 4 (got ${update.sortPosition} for ${update.groupId})`,
|
|
198
212
|
400,
|
|
199
213
|
);
|
|
200
214
|
}
|
|
@@ -47,16 +47,10 @@ function handleUpdateConfig(
|
|
|
47
47
|
if (typeof body.enabled === "boolean") heartbeat.enabled = body.enabled;
|
|
48
48
|
if (typeof body.intervalMs === "number")
|
|
49
49
|
heartbeat.intervalMs = body.intervalMs;
|
|
50
|
-
if (
|
|
51
|
-
heartbeat.activeHoursStart =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
: undefined;
|
|
55
|
-
}
|
|
56
|
-
if ("activeHoursEnd" in body) {
|
|
57
|
-
heartbeat.activeHoursEnd =
|
|
58
|
-
typeof body.activeHoursEnd === "number" ? body.activeHoursEnd : undefined;
|
|
59
|
-
}
|
|
50
|
+
if (typeof body.activeHoursStart === "number")
|
|
51
|
+
heartbeat.activeHoursStart = body.activeHoursStart;
|
|
52
|
+
if (typeof body.activeHoursEnd === "number")
|
|
53
|
+
heartbeat.activeHoursEnd = body.activeHoursEnd;
|
|
60
54
|
|
|
61
55
|
try {
|
|
62
56
|
saveConfig({ ...config, heartbeat });
|
|
@@ -10,6 +10,7 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
import { parseIdentityFields } from "../../daemon/handlers/identity.js";
|
|
13
|
+
import { getProfilerRuntimeStatus } from "../../daemon/profiler-run-store.js";
|
|
13
14
|
import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
|
|
14
15
|
import {
|
|
15
16
|
getWorkspaceDir,
|
|
@@ -144,6 +145,13 @@ export function handleHealth(): Response {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
export function handleDetailedHealth(): Response {
|
|
148
|
+
let profiler: ReturnType<typeof getProfilerRuntimeStatus> | undefined;
|
|
149
|
+
try {
|
|
150
|
+
profiler = getProfilerRuntimeStatus();
|
|
151
|
+
} catch {
|
|
152
|
+
// Profiler status is non-critical — omit on error
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
return Response.json({
|
|
148
156
|
status: "healthy",
|
|
149
157
|
timestamp: new Date().toISOString(),
|
|
@@ -156,6 +164,7 @@ export function handleDetailedHealth(): Response {
|
|
|
156
164
|
lastWorkspaceMigrationId:
|
|
157
165
|
getLastWorkspaceMigrationId(WORKSPACE_MIGRATIONS),
|
|
158
166
|
},
|
|
167
|
+
...(profiler ? { profiler } : {}),
|
|
159
168
|
});
|
|
160
169
|
}
|
|
161
170
|
|
|
@@ -239,6 +248,48 @@ export function handleGetIdentityIntro(): Response {
|
|
|
239
248
|
return Response.json({ text: cached.text });
|
|
240
249
|
}
|
|
241
250
|
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
// Zod schemas for profiler health metadata
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
const profilerBudgetSchema = z.object({
|
|
256
|
+
maxBytes: z.number(),
|
|
257
|
+
remainingBytes: z.number(),
|
|
258
|
+
minFreeMb: z.number(),
|
|
259
|
+
freeMb: z.number(),
|
|
260
|
+
overBudget: z.boolean(),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const profilerLastCompletedRunSchema = z.object({
|
|
264
|
+
runId: z.string(),
|
|
265
|
+
totalBytes: z.number(),
|
|
266
|
+
artifactCount: z.number(),
|
|
267
|
+
hasSummaries: z.boolean(),
|
|
268
|
+
completedAt: z.string(),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const profilerStatusSchema = z.object({
|
|
272
|
+
enabled: z.boolean(),
|
|
273
|
+
mode: z.string().nullable(),
|
|
274
|
+
runId: z.string().nullable(),
|
|
275
|
+
runDir: z.string().nullable(),
|
|
276
|
+
totalBytes: z.number(),
|
|
277
|
+
artifactCount: z.number(),
|
|
278
|
+
budget: profilerBudgetSchema.nullable(),
|
|
279
|
+
lastCompletedRun: profilerLastCompletedRunSchema.nullable(),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const detailedHealthSchema = z.object({
|
|
283
|
+
status: z.string(),
|
|
284
|
+
timestamp: z.string(),
|
|
285
|
+
version: z.string(),
|
|
286
|
+
disk: z.object({}).passthrough(),
|
|
287
|
+
memory: z.object({}).passthrough(),
|
|
288
|
+
cpu: z.object({}).passthrough(),
|
|
289
|
+
migrations: z.object({}).passthrough(),
|
|
290
|
+
profiler: profilerStatusSchema.optional(),
|
|
291
|
+
});
|
|
292
|
+
|
|
242
293
|
// ---------------------------------------------------------------------------
|
|
243
294
|
// Route definitions
|
|
244
295
|
// ---------------------------------------------------------------------------
|
|
@@ -253,15 +304,7 @@ export function identityRouteDefinitions(): RouteDefinition[] {
|
|
|
253
304
|
description:
|
|
254
305
|
"Returns runtime health including version, disk, memory, CPU, and migration status.",
|
|
255
306
|
tags: ["system"],
|
|
256
|
-
responseBody:
|
|
257
|
-
status: z.string(),
|
|
258
|
-
timestamp: z.string(),
|
|
259
|
-
version: z.string(),
|
|
260
|
-
disk: z.object({}).passthrough(),
|
|
261
|
-
memory: z.object({}).passthrough(),
|
|
262
|
-
cpu: z.object({}).passthrough(),
|
|
263
|
-
migrations: z.object({}).passthrough(),
|
|
264
|
-
}),
|
|
307
|
+
responseBody: detailedHealthSchema,
|
|
265
308
|
},
|
|
266
309
|
{
|
|
267
310
|
endpoint: "healthz",
|
|
@@ -272,15 +315,7 @@ export function identityRouteDefinitions(): RouteDefinition[] {
|
|
|
272
315
|
description:
|
|
273
316
|
"Alias for /v1/health. Returns runtime health including version, disk, memory, CPU, and migration status.",
|
|
274
317
|
tags: ["system"],
|
|
275
|
-
responseBody:
|
|
276
|
-
status: z.string(),
|
|
277
|
-
timestamp: z.string(),
|
|
278
|
-
version: z.string(),
|
|
279
|
-
disk: z.object({}).passthrough(),
|
|
280
|
-
memory: z.object({}).passthrough(),
|
|
281
|
-
cpu: z.object({}).passthrough(),
|
|
282
|
-
migrations: z.object({}).passthrough(),
|
|
283
|
-
}),
|
|
318
|
+
responseBody: detailedHealthSchema,
|
|
284
319
|
},
|
|
285
320
|
{
|
|
286
321
|
endpoint: "identity",
|