@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
package/src/daemon/server.ts
CHANGED
|
@@ -11,17 +11,20 @@ import {
|
|
|
11
11
|
createAssistantMessage,
|
|
12
12
|
createUserMessage,
|
|
13
13
|
} from "../agent/message-types.js";
|
|
14
|
+
import { compileApp } from "../bundler/app-compiler.js";
|
|
14
15
|
import {
|
|
15
16
|
type ChannelId,
|
|
16
17
|
type InterfaceId,
|
|
17
18
|
parseChannelId,
|
|
18
19
|
parseInterfaceId,
|
|
20
|
+
supportsHostProxy,
|
|
19
21
|
} from "../channels/types.js";
|
|
20
22
|
import { getConfig } from "../config/loader.js";
|
|
21
23
|
import { onContactChange } from "../contacts/contact-events.js";
|
|
22
24
|
import type { CesClient } from "../credential-execution/client.js";
|
|
23
25
|
import type { CesProcessManager } from "../credential-execution/process-manager.js";
|
|
24
26
|
import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
27
|
+
import { getApp, getAppDirPath, isMultifileApp } from "../memory/app-store.js";
|
|
25
28
|
import * as attachmentsStore from "../memory/attachments-store.js";
|
|
26
29
|
import {
|
|
27
30
|
createCanonicalGuardianRequest,
|
|
@@ -49,6 +52,7 @@ import { bridgeConfirmationRequestToGuardian } from "../runtime/confirmation-req
|
|
|
49
52
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
50
53
|
import { checkIngressForSecrets } from "../security/secret-ingress.js";
|
|
51
54
|
import { redactSecrets } from "../security/secret-scanner.js";
|
|
55
|
+
import { updatePublishedAppDeployment } from "../services/published-app-updater.js";
|
|
52
56
|
import { registerCancelCallback } from "../signals/cancel.js";
|
|
53
57
|
import { registerConversationUndoCallback } from "../signals/conversation-undo.js";
|
|
54
58
|
import { appendEventToStream } from "../signals/event-stream.js";
|
|
@@ -57,10 +61,12 @@ import { getSubagentManager } from "../subagent/index.js";
|
|
|
57
61
|
import { summarizeToolInput } from "../tools/tool-input-summary.js";
|
|
58
62
|
import { getLogger } from "../util/logger.js";
|
|
59
63
|
import {
|
|
64
|
+
getAvatarImagePath,
|
|
60
65
|
getSandboxWorkingDir,
|
|
61
66
|
getWorkspacePromptPath,
|
|
62
67
|
} from "../util/platform.js";
|
|
63
68
|
import { registerDaemonCallbacks } from "../work-items/work-item-runner.js";
|
|
69
|
+
import { AppSourceWatcher } from "./app-source-watcher.js";
|
|
64
70
|
import { ConfigWatcher } from "./config-watcher.js";
|
|
65
71
|
import {
|
|
66
72
|
Conversation,
|
|
@@ -71,6 +77,7 @@ import { ConversationEvictor } from "./conversation-evictor.js";
|
|
|
71
77
|
import { formatCompactResult } from "./conversation-process.js";
|
|
72
78
|
import { resolveChannelCapabilities } from "./conversation-runtime-assembly.js";
|
|
73
79
|
import { resolveSlash, type SlashContext } from "./conversation-slash.js";
|
|
80
|
+
import { refreshSurfacesForApp } from "./conversation-surfaces.js";
|
|
74
81
|
import { undoLastMessage } from "./handlers/conversations.js";
|
|
75
82
|
import { parseIdentityFields } from "./handlers/identity.js";
|
|
76
83
|
import type {
|
|
@@ -85,6 +92,7 @@ import type {
|
|
|
85
92
|
ServerMessage,
|
|
86
93
|
UserMessageAttachment,
|
|
87
94
|
} from "./message-protocol.js";
|
|
95
|
+
import { buildTransportHints } from "./transport-hints.js";
|
|
88
96
|
|
|
89
97
|
const log = getLogger("server");
|
|
90
98
|
|
|
@@ -201,13 +209,10 @@ function makePendingInteractionRegistrar(
|
|
|
201
209
|
guardianPrincipalId: trustContext?.guardianPrincipalId ?? undefined,
|
|
202
210
|
toolName: msg.toolName,
|
|
203
211
|
commandPreview:
|
|
204
|
-
redactSecrets(
|
|
205
|
-
|
|
206
|
-
) || undefined,
|
|
212
|
+
redactSecrets(summarizeToolInput(msg.toolName, inputRecord)) ||
|
|
213
|
+
undefined,
|
|
207
214
|
riskLevel: msg.riskLevel,
|
|
208
|
-
activityText: activityRaw
|
|
209
|
-
? redactSecrets(activityRaw)
|
|
210
|
-
: undefined,
|
|
215
|
+
activityText: activityRaw ? redactSecrets(activityRaw) : undefined,
|
|
211
216
|
executionTarget: msg.executionTarget,
|
|
212
217
|
status: "pending",
|
|
213
218
|
requestCode: generateCanonicalRequestCode(),
|
|
@@ -271,6 +276,7 @@ export class DaemonServer {
|
|
|
271
276
|
|
|
272
277
|
// Composed subsystems
|
|
273
278
|
private configWatcher = new ConfigWatcher();
|
|
279
|
+
private appSourceWatcher = new AppSourceWatcher();
|
|
274
280
|
|
|
275
281
|
// CES (Credential Execution Service) — process-level singleton.
|
|
276
282
|
// Lifecycle is managed by startCesProcess() in lifecycle.ts; the server
|
|
@@ -369,7 +375,7 @@ export class DaemonServer {
|
|
|
369
375
|
{ channelId: transport.channelId },
|
|
370
376
|
"Transport metadata received",
|
|
371
377
|
);
|
|
372
|
-
conversation.setTransportHints(transport
|
|
378
|
+
conversation.setTransportHints(buildTransportHints(transport));
|
|
373
379
|
}
|
|
374
380
|
|
|
375
381
|
constructor() {
|
|
@@ -523,6 +529,63 @@ export class DaemonServer {
|
|
|
523
529
|
}
|
|
524
530
|
}
|
|
525
531
|
|
|
532
|
+
private broadcastConfigChanged(): void {
|
|
533
|
+
this.broadcast({ type: "config_changed" });
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private broadcastSoundsConfigUpdated(): void {
|
|
537
|
+
this.broadcast({ type: "sounds_config_updated" });
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private broadcastFeatureFlagsChanged(): void {
|
|
541
|
+
this.broadcast({ type: "feature_flags_changed" });
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private broadcastAvatarUpdated(): void {
|
|
545
|
+
this.broadcast({
|
|
546
|
+
type: "avatar_updated",
|
|
547
|
+
avatarPath: getAvatarImagePath(),
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Handle a detected app source file change from the filesystem watcher.
|
|
553
|
+
* Recompiles multifile apps and refreshes surfaces across ALL conversations.
|
|
554
|
+
*/
|
|
555
|
+
private handleAppSourceChange(appId: string): void {
|
|
556
|
+
const app = getApp(appId);
|
|
557
|
+
if (!app) return;
|
|
558
|
+
|
|
559
|
+
const doRefresh = () => {
|
|
560
|
+
for (const conversation of this.conversations.values()) {
|
|
561
|
+
refreshSurfacesForApp(conversation, appId, { fileChange: true });
|
|
562
|
+
}
|
|
563
|
+
this.broadcast({ type: "app_files_changed", appId });
|
|
564
|
+
void updatePublishedAppDeployment(appId);
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
if (isMultifileApp(app)) {
|
|
568
|
+
const appDir = getAppDirPath(appId);
|
|
569
|
+
void compileApp(appDir)
|
|
570
|
+
.then((result) => {
|
|
571
|
+
if (!result.ok) {
|
|
572
|
+
log.warn(
|
|
573
|
+
{ appId, errors: result.errors },
|
|
574
|
+
"Recompile failed on app source change",
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
doRefresh();
|
|
578
|
+
})
|
|
579
|
+
.catch((err) => {
|
|
580
|
+
log.warn({ appId, err }, "Recompile threw on app source change");
|
|
581
|
+
doRefresh();
|
|
582
|
+
});
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
doRefresh();
|
|
587
|
+
}
|
|
588
|
+
|
|
526
589
|
// ── Server lifecycle ────────────────────────────────────────────────
|
|
527
590
|
|
|
528
591
|
async start(): Promise<void> {
|
|
@@ -672,8 +735,14 @@ export class DaemonServer {
|
|
|
672
735
|
this.configWatcher.start(
|
|
673
736
|
() => this.evictConversationsForReload(),
|
|
674
737
|
() => this.broadcastIdentityChanged(),
|
|
738
|
+
() => this.broadcastSoundsConfigUpdated(),
|
|
739
|
+
() => this.broadcastAvatarUpdated(),
|
|
740
|
+
() => this.broadcastConfigChanged(),
|
|
741
|
+
() => this.broadcastFeatureFlagsChanged(),
|
|
675
742
|
);
|
|
676
743
|
|
|
744
|
+
this.appSourceWatcher.start((appId) => this.handleAppSourceChange(appId));
|
|
745
|
+
|
|
677
746
|
// Broadcast contacts_changed to all clients when any contact mutation occurs.
|
|
678
747
|
this.unsubscribeContactChange = onContactChange(() => {
|
|
679
748
|
this.broadcast({ type: "contacts_changed" });
|
|
@@ -687,6 +756,7 @@ export class DaemonServer {
|
|
|
687
756
|
disposeAcpSessionManager();
|
|
688
757
|
this.evictor.stop();
|
|
689
758
|
this.configWatcher.stop();
|
|
759
|
+
this.appSourceWatcher.stop();
|
|
690
760
|
if (this.unsubscribeContactChange) {
|
|
691
761
|
this.unsubscribeContactChange();
|
|
692
762
|
this.unsubscribeContactChange = null;
|
|
@@ -899,7 +969,13 @@ export class DaemonServer {
|
|
|
899
969
|
}
|
|
900
970
|
this.evictor.touch(conversationId);
|
|
901
971
|
} else {
|
|
902
|
-
|
|
972
|
+
// Only apply transport metadata when the conversation is idle.
|
|
973
|
+
// When processing, the hints are stored on the queued message and
|
|
974
|
+
// will be applied at dequeue time — applying them here would
|
|
975
|
+
// overwrite the in-flight conversation's transportHints.
|
|
976
|
+
if (!conversation.isProcessing()) {
|
|
977
|
+
this.applyTransportMetadata(conversation, options);
|
|
978
|
+
}
|
|
903
979
|
this.evictor.touch(conversationId);
|
|
904
980
|
}
|
|
905
981
|
return conversation;
|
|
@@ -1012,7 +1088,7 @@ export class DaemonServer {
|
|
|
1012
1088
|
// Guard: don't replace an active proxy during concurrent turn races —
|
|
1013
1089
|
// another request may have started processing between the isProcessing()
|
|
1014
1090
|
// check above and the await on ensureActorScopedHistory().
|
|
1015
|
-
if (resolvedInterface
|
|
1091
|
+
if (supportsHostProxy(resolvedInterface)) {
|
|
1016
1092
|
if (!conversation.isProcessing() || !conversation.hostBashProxy) {
|
|
1017
1093
|
conversation.setHostBashProxy(
|
|
1018
1094
|
new HostBashProxy(conversation.getCurrentSender(), (requestId) => {
|
|
@@ -1369,8 +1445,9 @@ export class DaemonServer {
|
|
|
1369
1445
|
*/
|
|
1370
1446
|
async getConversationForMessages(
|
|
1371
1447
|
conversationId: string,
|
|
1448
|
+
options?: ConversationCreateOptions,
|
|
1372
1449
|
): Promise<Conversation> {
|
|
1373
|
-
return this.getOrCreateConversation(conversationId);
|
|
1450
|
+
return this.getOrCreateConversation(conversationId, options);
|
|
1374
1451
|
}
|
|
1375
1452
|
|
|
1376
1453
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Sentry from "@sentry/node";
|
|
2
2
|
|
|
3
|
+
import type { FilingService } from "../filing/filing-service.js";
|
|
3
4
|
import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
4
5
|
import type { HookManager } from "../hooks/manager.js";
|
|
5
6
|
import type { McpServerManager } from "../mcp/manager.js";
|
|
@@ -7,6 +8,7 @@ import { getSqlite, resetDb } from "../memory/db.js";
|
|
|
7
8
|
import type { QdrantManager } from "../memory/qdrant-manager.js";
|
|
8
9
|
import type { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
9
10
|
import { browserManager } from "../tools/browser/browser-manager.js";
|
|
11
|
+
import { cleanupShellOutputTempFiles } from "../tools/shared/shell-output.js";
|
|
10
12
|
import { getLogger } from "../util/logger.js";
|
|
11
13
|
import { getEnrichmentService } from "../workspace/commit-message-enrichment-service.js";
|
|
12
14
|
import type { WorkspaceHeartbeatService } from "../workspace/heartbeat-service.js";
|
|
@@ -18,6 +20,7 @@ export interface ShutdownDeps {
|
|
|
18
20
|
server: DaemonServer;
|
|
19
21
|
workspaceHeartbeat: WorkspaceHeartbeatService;
|
|
20
22
|
heartbeat: HeartbeatService;
|
|
23
|
+
filing: FilingService;
|
|
21
24
|
hookManager: HookManager;
|
|
22
25
|
runtimeHttp: RuntimeHttpServer | null;
|
|
23
26
|
scheduler: { stop(): void };
|
|
@@ -51,6 +54,7 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
|
|
|
51
54
|
|
|
52
55
|
await deps.workspaceHeartbeat.stop();
|
|
53
56
|
await deps.heartbeat.stop();
|
|
57
|
+
await deps.filing.stop();
|
|
54
58
|
|
|
55
59
|
try {
|
|
56
60
|
await deps.hookManager.trigger("daemon-stop", { pid: process.pid });
|
|
@@ -105,6 +109,7 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
|
|
|
105
109
|
|
|
106
110
|
if (deps.runtimeHttp) await deps.runtimeHttp.stop();
|
|
107
111
|
await browserManager.closeAllPages();
|
|
112
|
+
cleanupShellOutputTempFiles();
|
|
108
113
|
deps.scheduler.stop();
|
|
109
114
|
deps.getMemoryWorker()?.stop();
|
|
110
115
|
|
|
@@ -158,12 +158,32 @@ registerHook(
|
|
|
158
158
|
},
|
|
159
159
|
);
|
|
160
160
|
|
|
161
|
-
// Trigger
|
|
161
|
+
// Trigger surface refresh + broadcast when an app is refreshed.
|
|
162
|
+
// If the executor already compiled (multifile path), skip the redundant
|
|
163
|
+
// recompile and just refresh surfaces / broadcast / deploy directly.
|
|
162
164
|
registerHook(
|
|
163
165
|
"app_refresh",
|
|
164
|
-
(_name, input,
|
|
166
|
+
(_name, input, result, { ctx, broadcastToAllClients }) => {
|
|
165
167
|
const appId = input.app_id as string | undefined;
|
|
166
|
-
if (appId)
|
|
168
|
+
if (!appId) return;
|
|
169
|
+
|
|
170
|
+
// executeAppRefresh already compiled multifile apps and included a
|
|
171
|
+
// "compiled" field in the result. Skip the expensive recompile and
|
|
172
|
+
// go straight to surface refresh + broadcast + deploy.
|
|
173
|
+
let alreadyCompiled = false;
|
|
174
|
+
try {
|
|
175
|
+
const parsed = JSON.parse(result.content) as { compiled?: boolean };
|
|
176
|
+
alreadyCompiled = parsed.compiled !== undefined;
|
|
177
|
+
} catch {
|
|
178
|
+
// Result wasn't valid JSON — fall through to handleAppChange.
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (alreadyCompiled) {
|
|
182
|
+
const opts = { fileChange: true };
|
|
183
|
+
refreshSurfacesForApp(ctx, appId, opts);
|
|
184
|
+
broadcastToAllClients?.({ type: "app_files_changed", appId });
|
|
185
|
+
void updatePublishedAppDeployment(appId);
|
|
186
|
+
} else {
|
|
167
187
|
handleAppChange(ctx, appId, broadcastToAllClients, { fileChange: true });
|
|
168
188
|
}
|
|
169
189
|
},
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parseInterfaceId } from "../channels/types.js";
|
|
2
|
+
import type { ConversationTransportMetadata } from "./message-types/conversations.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build enriched transport hints from conversation transport metadata.
|
|
6
|
+
*
|
|
7
|
+
* Interface ID first, then host environment (macOS only), then any
|
|
8
|
+
* client-provided hints. Shared between the conversation creation path
|
|
9
|
+
* (server.ts) and the queue drain path (conversation-process.ts).
|
|
10
|
+
*/
|
|
11
|
+
export function buildTransportHints(
|
|
12
|
+
transport: ConversationTransportMetadata,
|
|
13
|
+
): string[] {
|
|
14
|
+
const hints: string[] = [];
|
|
15
|
+
|
|
16
|
+
const interfaceLabel = parseInterfaceId(transport.interfaceId) ?? "vellum";
|
|
17
|
+
hints.push(`User is messaging from interface: ${interfaceLabel}`);
|
|
18
|
+
|
|
19
|
+
if (transport.interfaceId === "macos") {
|
|
20
|
+
if (transport.hostHomeDir) {
|
|
21
|
+
hints.push(`Host home directory: ${transport.hostHomeDir}`);
|
|
22
|
+
}
|
|
23
|
+
if (transport.hostUsername) {
|
|
24
|
+
hints.push(`Host username: ${transport.hostUsername}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (transport.hints) {
|
|
29
|
+
hints.push(...transport.hints);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return hints;
|
|
33
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript formatter for conversation analysis.
|
|
3
|
+
*
|
|
4
|
+
* Builds a markdown transcript of a conversation, including inline
|
|
5
|
+
* subagent conversation sections when present in message metadata.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
getConversation,
|
|
10
|
+
getMessages,
|
|
11
|
+
messageMetadataSchema,
|
|
12
|
+
} from "../memory/conversation-crud.js";
|
|
13
|
+
import { truncate } from "../util/truncate.js";
|
|
14
|
+
|
|
15
|
+
interface ContentBlock {
|
|
16
|
+
type: string;
|
|
17
|
+
text?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
input?: Record<string, unknown>;
|
|
20
|
+
content?: string;
|
|
21
|
+
tool_use_id?: string;
|
|
22
|
+
is_error?: boolean;
|
|
23
|
+
source?: { media_type?: string; filename?: string };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatTimestamp(ms: number): string {
|
|
27
|
+
return new Date(ms).toISOString().replace("T", " ").slice(0, 19);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function extractAnalysisText(blocks: ContentBlock[]): string {
|
|
31
|
+
const parts: string[] = [];
|
|
32
|
+
for (const block of blocks) {
|
|
33
|
+
switch (block.type) {
|
|
34
|
+
case "text":
|
|
35
|
+
if (block.text) parts.push(block.text);
|
|
36
|
+
break;
|
|
37
|
+
case "tool_use":
|
|
38
|
+
parts.push(
|
|
39
|
+
`[Tool: ${block.name}] ${JSON.stringify(block.input ?? {})}`,
|
|
40
|
+
);
|
|
41
|
+
break;
|
|
42
|
+
case "tool_result":
|
|
43
|
+
if (block.is_error) {
|
|
44
|
+
parts.push(`[Error: ${block.content ?? ""}]`);
|
|
45
|
+
} else {
|
|
46
|
+
parts.push(`[Result: ${truncate(block.content ?? "", 500)}]`);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case "server_tool_use":
|
|
50
|
+
parts.push(`[Web search: ${block.name ?? "web_search"}]`);
|
|
51
|
+
break;
|
|
52
|
+
case "web_search_tool_result":
|
|
53
|
+
parts.push("[Web search results]");
|
|
54
|
+
break;
|
|
55
|
+
case "image":
|
|
56
|
+
parts.push("[Image attachment]");
|
|
57
|
+
break;
|
|
58
|
+
case "file":
|
|
59
|
+
parts.push(`[File: ${block.source?.filename ?? "unknown"}]`);
|
|
60
|
+
break;
|
|
61
|
+
case "thinking":
|
|
62
|
+
case "redacted_thinking":
|
|
63
|
+
// Skip internal model reasoning blocks
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return parts.join("\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatRole(role: string): string {
|
|
71
|
+
return role === "user" ? "User" : "Assistant";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function formatSubagentMessages(msgs: ReturnType<typeof getMessages>): string {
|
|
75
|
+
const lines: string[] = [];
|
|
76
|
+
for (const msg of msgs) {
|
|
77
|
+
const role = formatRole(msg.role);
|
|
78
|
+
const time = formatTimestamp(msg.createdAt);
|
|
79
|
+
const content = parseContent(msg.content);
|
|
80
|
+
const text = extractAnalysisText(content);
|
|
81
|
+
if (text) {
|
|
82
|
+
lines.push(`> **${role}** (${time})`);
|
|
83
|
+
for (const line of text.split("\n")) {
|
|
84
|
+
lines.push(`> ${line}`);
|
|
85
|
+
}
|
|
86
|
+
lines.push(">");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseContent(raw: string): ContentBlock[] {
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(raw) as ContentBlock[];
|
|
95
|
+
} catch {
|
|
96
|
+
return [{ type: "text", text: raw }];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function buildAnalysisTranscript(conversationId: string): string {
|
|
101
|
+
const conversation = getConversation(conversationId);
|
|
102
|
+
if (!conversation) {
|
|
103
|
+
return `# Conversation not found: ${conversationId}\n`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const allMessages = getMessages(conversationId);
|
|
107
|
+
const title = conversation.title ?? "Untitled";
|
|
108
|
+
const lines: string[] = [];
|
|
109
|
+
|
|
110
|
+
lines.push(`# Conversation: ${title}`);
|
|
111
|
+
lines.push(`Created: ${formatTimestamp(conversation.createdAt)}`);
|
|
112
|
+
lines.push("");
|
|
113
|
+
|
|
114
|
+
for (const msg of allMessages) {
|
|
115
|
+
const role = formatRole(msg.role);
|
|
116
|
+
const time = formatTimestamp(msg.createdAt);
|
|
117
|
+
const content = parseContent(msg.content);
|
|
118
|
+
const text = extractAnalysisText(content);
|
|
119
|
+
|
|
120
|
+
lines.push(`## ${role} (${time})`);
|
|
121
|
+
lines.push(text);
|
|
122
|
+
lines.push("");
|
|
123
|
+
|
|
124
|
+
// Check for subagent notifications in metadata
|
|
125
|
+
if (msg.metadata) {
|
|
126
|
+
try {
|
|
127
|
+
const parsed = messageMetadataSchema.safeParse(JSON.parse(msg.metadata));
|
|
128
|
+
if (parsed.success && parsed.data.subagentNotification) {
|
|
129
|
+
const notif = parsed.data.subagentNotification;
|
|
130
|
+
if (
|
|
131
|
+
(notif.status === "completed" || notif.status === "failed" || notif.status === "aborted") &&
|
|
132
|
+
notif.conversationId
|
|
133
|
+
) {
|
|
134
|
+
const subMessages = getMessages(notif.conversationId);
|
|
135
|
+
lines.push(`### Subagent: ${notif.label} (${notif.status})`);
|
|
136
|
+
lines.push("");
|
|
137
|
+
lines.push(formatSubagentMessages(subMessages));
|
|
138
|
+
lines.push("");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// Skip unparseable metadata
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return lines.join("\n");
|
|
148
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getConfig } from "../config/loader.js";
|
|
5
|
+
import type { Speed } from "../config/schemas/inference.js";
|
|
6
|
+
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
7
|
+
import { getLogger } from "../util/logger.js";
|
|
8
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
9
|
+
import { stripCommentLines } from "../util/strip-comment-lines.js";
|
|
10
|
+
|
|
11
|
+
const log = getLogger("filing-service");
|
|
12
|
+
|
|
13
|
+
const FILING_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
|
|
14
|
+
|
|
15
|
+
const FILING_PROMPT_TEMPLATE = `You are running a periodic knowledge base filing job. This is a background maintenance task.
|
|
16
|
+
|
|
17
|
+
## Part 1: File the buffer
|
|
18
|
+
|
|
19
|
+
Read \`pkb/buffer.md\`. For each item in the buffer:
|
|
20
|
+
1. Determine which topic file it belongs in. Check \`pkb/INDEX.md\` to see what topic files exist.
|
|
21
|
+
2. Read the target topic file, then append or integrate the new fact.
|
|
22
|
+
3. If the fact is important enough to always be in context, add it to \`pkb/essentials.md\` instead.
|
|
23
|
+
4. If the fact is a commitment, follow-up, or active project, add it to \`pkb/threads.md\`.
|
|
24
|
+
5. If no existing topic file fits, create a new one and update \`pkb/INDEX.md\`.
|
|
25
|
+
|
|
26
|
+
After all items are filed, clear the processed items from \`pkb/buffer.md\` (leave the file empty, don't delete it).
|
|
27
|
+
|
|
28
|
+
## Part 2: Nest
|
|
29
|
+
|
|
30
|
+
Pick 1-2 topic files from your knowledge base and review them:
|
|
31
|
+
- Is the information still accurate and up to date?
|
|
32
|
+
- Are there duplicates that should be consolidated?
|
|
33
|
+
- Is anything important enough to promote to \`pkb/essentials.md\`?
|
|
34
|
+
- Is anything in \`pkb/essentials.md\` that's no longer essential? Demote it to a topic file.
|
|
35
|
+
- Are any threads in \`pkb/threads.md\` completed or stale? Remove them.
|
|
36
|
+
- Is any file getting too long? Consider splitting it.
|
|
37
|
+
- Should any topic file be restructured for clarity?
|
|
38
|
+
|
|
39
|
+
Make improvements as you see fit. This is your knowledge base — keep it sharp.`;
|
|
40
|
+
|
|
41
|
+
export interface FilingDeps {
|
|
42
|
+
processMessage: (
|
|
43
|
+
conversationId: string,
|
|
44
|
+
content: string,
|
|
45
|
+
options?: { speed?: Speed },
|
|
46
|
+
) => Promise<{ messageId: string }>;
|
|
47
|
+
onConversationCreated?: (info: {
|
|
48
|
+
conversationId: string;
|
|
49
|
+
title: string;
|
|
50
|
+
}) => void;
|
|
51
|
+
getCurrentHour?: () => number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class FilingService {
|
|
55
|
+
private readonly deps: FilingDeps;
|
|
56
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
57
|
+
private activeRun: Promise<void> | null = null;
|
|
58
|
+
private _lastRunAt: number | null = null;
|
|
59
|
+
private _nextRunAt: number | null = null;
|
|
60
|
+
|
|
61
|
+
constructor(deps: FilingDeps) {
|
|
62
|
+
this.deps = deps;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get lastRunAt(): number | null {
|
|
66
|
+
return this._lastRunAt;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get nextRunAt(): number | null {
|
|
70
|
+
return this._nextRunAt;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
start(): void {
|
|
74
|
+
const config = getConfig().filing;
|
|
75
|
+
if (!config.enabled) {
|
|
76
|
+
log.info("Filing service disabled by config");
|
|
77
|
+
this._nextRunAt = null;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (this.timer) return;
|
|
81
|
+
|
|
82
|
+
log.info({ intervalMs: config.intervalMs }, "Filing service started");
|
|
83
|
+
this.scheduleNextRun(config.intervalMs);
|
|
84
|
+
this.timer = setInterval(() => {
|
|
85
|
+
this.runOnce().catch((err) => {
|
|
86
|
+
log.error({ err }, "Filing runOnce failed");
|
|
87
|
+
});
|
|
88
|
+
}, config.intervalMs);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
reconfigure(): void {
|
|
92
|
+
if (this.timer) {
|
|
93
|
+
clearInterval(this.timer);
|
|
94
|
+
this.timer = null;
|
|
95
|
+
}
|
|
96
|
+
this._nextRunAt = null;
|
|
97
|
+
this.start();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async stop(): Promise<void> {
|
|
101
|
+
if (this.timer) {
|
|
102
|
+
clearInterval(this.timer);
|
|
103
|
+
this.timer = null;
|
|
104
|
+
}
|
|
105
|
+
this._nextRunAt = null;
|
|
106
|
+
if (this.activeRun) {
|
|
107
|
+
let timerId: ReturnType<typeof setTimeout>;
|
|
108
|
+
const timeout = new Promise<void>((resolve) => {
|
|
109
|
+
timerId = setTimeout(resolve, 5_000);
|
|
110
|
+
});
|
|
111
|
+
await Promise.race([this.activeRun, timeout]);
|
|
112
|
+
clearTimeout(timerId!);
|
|
113
|
+
}
|
|
114
|
+
log.info("Filing service stopped");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async runOnce({ force = false }: { force?: boolean } = {}): Promise<boolean> {
|
|
118
|
+
const config = getConfig().filing;
|
|
119
|
+
if (!force && !config.enabled) return false;
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
!force &&
|
|
123
|
+
config.activeHoursStart != null &&
|
|
124
|
+
config.activeHoursEnd != null
|
|
125
|
+
) {
|
|
126
|
+
const hour = this.deps.getCurrentHour?.() ?? new Date().getHours();
|
|
127
|
+
if (
|
|
128
|
+
!isWithinActiveHours(
|
|
129
|
+
hour,
|
|
130
|
+
config.activeHoursStart,
|
|
131
|
+
config.activeHoursEnd,
|
|
132
|
+
)
|
|
133
|
+
) {
|
|
134
|
+
log.debug("Outside active hours, skipping filing");
|
|
135
|
+
this.scheduleNextRun(config.intervalMs);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (this.activeRun) {
|
|
141
|
+
log.debug("Previous filing run still active, skipping");
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Skip if buffer is empty — no work to do
|
|
146
|
+
if (!force && !this.hasBufferContent()) {
|
|
147
|
+
log.debug("Buffer is empty, skipping filing");
|
|
148
|
+
this.scheduleNextRun(config.intervalMs);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const run = this.executeRun();
|
|
153
|
+
this.activeRun = run;
|
|
154
|
+
try {
|
|
155
|
+
await Promise.race([
|
|
156
|
+
run,
|
|
157
|
+
new Promise<never>((_, reject) =>
|
|
158
|
+
setTimeout(
|
|
159
|
+
() => reject(new Error("Filing execution timed out")),
|
|
160
|
+
FILING_TIMEOUT_MS,
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
]);
|
|
164
|
+
} finally {
|
|
165
|
+
this.activeRun = null;
|
|
166
|
+
this._lastRunAt = Date.now();
|
|
167
|
+
this.scheduleNextRun(getConfig().filing.intervalMs);
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private scheduleNextRun(intervalMs: number): void {
|
|
173
|
+
this._nextRunAt = Date.now() + intervalMs;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private hasBufferContent(): boolean {
|
|
177
|
+
const bufferPath = join(getWorkspaceDir(), "pkb", "buffer.md");
|
|
178
|
+
if (!existsSync(bufferPath)) return false;
|
|
179
|
+
try {
|
|
180
|
+
const content = stripCommentLines(
|
|
181
|
+
readFileSync(bufferPath, "utf-8"),
|
|
182
|
+
).trim();
|
|
183
|
+
return content.length > 0;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async executeRun(): Promise<void> {
|
|
190
|
+
log.info("Running filing job");
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const config = getConfig().filing;
|
|
194
|
+
|
|
195
|
+
const conversation = bootstrapConversation({
|
|
196
|
+
conversationType: "background",
|
|
197
|
+
source: "filing",
|
|
198
|
+
groupId: "system:background",
|
|
199
|
+
origin: "filing",
|
|
200
|
+
systemHint: "Filing",
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
this.deps.onConversationCreated?.({
|
|
204
|
+
conversationId: conversation.id,
|
|
205
|
+
title: "Filing",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await this.deps.processMessage(conversation.id, FILING_PROMPT_TEMPLATE, {
|
|
209
|
+
speed: config.speed,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
log.info({ conversationId: conversation.id }, "Filing job completed");
|
|
213
|
+
} catch (err) {
|
|
214
|
+
log.error({ err }, "Filing job failed");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isWithinActiveHours(
|
|
220
|
+
hour: number,
|
|
221
|
+
start: number,
|
|
222
|
+
end: number,
|
|
223
|
+
): boolean {
|
|
224
|
+
if (start <= end) {
|
|
225
|
+
return hour >= start && hour < end;
|
|
226
|
+
}
|
|
227
|
+
return hour >= start || hour < end;
|
|
228
|
+
}
|