@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
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Mocks
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
mock.module("../../../util/logger.js", () => ({
|
|
10
|
+
getLogger: () =>
|
|
11
|
+
new Proxy({} as Record<string, unknown>, {
|
|
12
|
+
get: () => () => {},
|
|
13
|
+
}),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
import { getWorkspaceRoutesDir } from "../../../util/platform.js";
|
|
17
|
+
import { UserRouteDispatcher } from "../user-route-dispatcher.js";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Helpers
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
function makeRequest(
|
|
24
|
+
method: string,
|
|
25
|
+
path = "http://localhost/v1/x/test",
|
|
26
|
+
): Request {
|
|
27
|
+
return new Request(path, { method });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeHandler(relativePath: string, content: string): string {
|
|
31
|
+
const routesDir = getWorkspaceRoutesDir();
|
|
32
|
+
const fullPath = join(routesDir, relativePath);
|
|
33
|
+
const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
34
|
+
mkdirSync(dir, { recursive: true });
|
|
35
|
+
writeFileSync(fullPath, content);
|
|
36
|
+
return fullPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function readErrorBody(
|
|
40
|
+
response: Response,
|
|
41
|
+
): Promise<{ error: { code: string; message: string } }> {
|
|
42
|
+
return response.json();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Setup / teardown
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
mkdirSync(getWorkspaceRoutesDir(), { recursive: true });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
rmSync(getWorkspaceRoutesDir(), { recursive: true, force: true });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Path traversal
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
describe("path traversal", () => {
|
|
62
|
+
test("rejects paths containing '..'", async () => {
|
|
63
|
+
const dispatcher = new UserRouteDispatcher();
|
|
64
|
+
const res = await dispatcher.dispatch("../etc/passwd", makeRequest("GET"));
|
|
65
|
+
expect(res.status).toBe(400);
|
|
66
|
+
const body = await readErrorBody(res);
|
|
67
|
+
expect(body.error.code).toBe("BAD_REQUEST");
|
|
68
|
+
expect(body.error.message).toContain("Path traversal");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("rejects embedded '..' segments", async () => {
|
|
72
|
+
const dispatcher = new UserRouteDispatcher();
|
|
73
|
+
const res = await dispatcher.dispatch(
|
|
74
|
+
"foo/../../etc/passwd",
|
|
75
|
+
makeRequest("GET"),
|
|
76
|
+
);
|
|
77
|
+
expect(res.status).toBe(400);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// 404 — missing handler
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
describe("missing handler", () => {
|
|
86
|
+
test("returns 404 when no handler file exists", async () => {
|
|
87
|
+
const dispatcher = new UserRouteDispatcher();
|
|
88
|
+
const res = await dispatcher.dispatch("nonexistent", makeRequest("GET"));
|
|
89
|
+
expect(res.status).toBe(404);
|
|
90
|
+
const body = await readErrorBody(res);
|
|
91
|
+
expect(body.error.code).toBe("NOT_FOUND");
|
|
92
|
+
expect(body.error.message).toContain("/x/nonexistent");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Successful dispatch
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
describe("successful dispatch", () => {
|
|
101
|
+
test("dispatches GET to handler exporting GET function", async () => {
|
|
102
|
+
writeHandler(
|
|
103
|
+
"hello.ts",
|
|
104
|
+
`export function GET(request) {
|
|
105
|
+
return Response.json({ greeting: "hello" });
|
|
106
|
+
}`,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const dispatcher = new UserRouteDispatcher();
|
|
110
|
+
const res = await dispatcher.dispatch("hello", makeRequest("GET"));
|
|
111
|
+
expect(res.status).toBe(200);
|
|
112
|
+
const body = await res.json();
|
|
113
|
+
expect(body.greeting).toBe("hello");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("dispatches POST to handler exporting POST function", async () => {
|
|
117
|
+
writeHandler(
|
|
118
|
+
"submit.ts",
|
|
119
|
+
`export async function POST(request) {
|
|
120
|
+
return Response.json({ received: true }, { status: 201 });
|
|
121
|
+
}`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const dispatcher = new UserRouteDispatcher();
|
|
125
|
+
const res = await dispatcher.dispatch("submit", makeRequest("POST"));
|
|
126
|
+
expect(res.status).toBe(201);
|
|
127
|
+
const body = await res.json();
|
|
128
|
+
expect(body.received).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("dispatches to .js handler files", async () => {
|
|
132
|
+
writeHandler(
|
|
133
|
+
"legacy.js",
|
|
134
|
+
`export function GET(request) {
|
|
135
|
+
return Response.json({ format: "js" });
|
|
136
|
+
}`,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const dispatcher = new UserRouteDispatcher();
|
|
140
|
+
const res = await dispatcher.dispatch("legacy", makeRequest("GET"));
|
|
141
|
+
expect(res.status).toBe(200);
|
|
142
|
+
const body = await res.json();
|
|
143
|
+
expect(body.format).toBe("js");
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Index file convention
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
describe("index file convention", () => {
|
|
152
|
+
test("resolves directory to index.ts", async () => {
|
|
153
|
+
writeHandler(
|
|
154
|
+
"my-app/index.ts",
|
|
155
|
+
`export function GET(request) {
|
|
156
|
+
return Response.json({ index: true });
|
|
157
|
+
}`,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const dispatcher = new UserRouteDispatcher();
|
|
161
|
+
const res = await dispatcher.dispatch("my-app", makeRequest("GET"));
|
|
162
|
+
expect(res.status).toBe(200);
|
|
163
|
+
const body = await res.json();
|
|
164
|
+
expect(body.index).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("resolves directory to index.js when no index.ts", async () => {
|
|
168
|
+
writeHandler(
|
|
169
|
+
"fallback-app/index.js",
|
|
170
|
+
`export function GET(request) {
|
|
171
|
+
return Response.json({ index: "js" });
|
|
172
|
+
}`,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const dispatcher = new UserRouteDispatcher();
|
|
176
|
+
const res = await dispatcher.dispatch("fallback-app", makeRequest("GET"));
|
|
177
|
+
expect(res.status).toBe(200);
|
|
178
|
+
const body = await res.json();
|
|
179
|
+
expect(body.index).toBe("js");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("prefers direct file over index file", async () => {
|
|
183
|
+
writeHandler(
|
|
184
|
+
"dual.ts",
|
|
185
|
+
`export function GET(request) {
|
|
186
|
+
return Response.json({ source: "direct" });
|
|
187
|
+
}`,
|
|
188
|
+
);
|
|
189
|
+
writeHandler(
|
|
190
|
+
"dual/index.ts",
|
|
191
|
+
`export function GET(request) {
|
|
192
|
+
return Response.json({ source: "index" });
|
|
193
|
+
}`,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const dispatcher = new UserRouteDispatcher();
|
|
197
|
+
const res = await dispatcher.dispatch("dual", makeRequest("GET"));
|
|
198
|
+
expect(res.status).toBe(200);
|
|
199
|
+
const body = await res.json();
|
|
200
|
+
expect(body.source).toBe("direct");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// 405 — method not allowed
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
describe("method not allowed", () => {
|
|
209
|
+
test("returns 405 with Allow header when method not exported", async () => {
|
|
210
|
+
writeHandler(
|
|
211
|
+
"get-only.ts",
|
|
212
|
+
`export function GET(request) {
|
|
213
|
+
return Response.json({ ok: true });
|
|
214
|
+
}`,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const dispatcher = new UserRouteDispatcher();
|
|
218
|
+
const res = await dispatcher.dispatch("get-only", makeRequest("POST"));
|
|
219
|
+
expect(res.status).toBe(405);
|
|
220
|
+
expect(res.headers.get("Allow")).toBe("GET");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("lists multiple allowed methods in Allow header", async () => {
|
|
224
|
+
writeHandler(
|
|
225
|
+
"multi.ts",
|
|
226
|
+
`export function GET(request) { return new Response("ok"); }
|
|
227
|
+
export function POST(request) { return new Response("ok"); }
|
|
228
|
+
export function DELETE(request) { return new Response("ok"); }`,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const dispatcher = new UserRouteDispatcher();
|
|
232
|
+
const res = await dispatcher.dispatch("multi", makeRequest("PUT"));
|
|
233
|
+
expect(res.status).toBe(405);
|
|
234
|
+
const allow = res.headers.get("Allow");
|
|
235
|
+
expect(allow).toContain("GET");
|
|
236
|
+
expect(allow).toContain("POST");
|
|
237
|
+
expect(allow).toContain("DELETE");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Handler timeout
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
describe("handler timeout", () => {
|
|
246
|
+
test("returns 504 when handler exceeds timeout", async () => {
|
|
247
|
+
writeHandler(
|
|
248
|
+
"slow.ts",
|
|
249
|
+
`export function GET(request) {
|
|
250
|
+
return new Promise(() => {});
|
|
251
|
+
}`,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Use a very short timeout for testing
|
|
255
|
+
const dispatcher = new UserRouteDispatcher({ handlerTimeoutMs: 50 });
|
|
256
|
+
const res = await dispatcher.dispatch("slow", makeRequest("GET"));
|
|
257
|
+
expect(res.status).toBe(504);
|
|
258
|
+
const body = await readErrorBody(res);
|
|
259
|
+
expect(body.error.code).toBe("SERVICE_UNAVAILABLE");
|
|
260
|
+
expect(body.error.message).toContain("timed out");
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Handler errors
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
describe("handler errors", () => {
|
|
269
|
+
test("returns 500 when handler throws synchronously", async () => {
|
|
270
|
+
writeHandler(
|
|
271
|
+
"throws.ts",
|
|
272
|
+
`export function GET(request) {
|
|
273
|
+
throw new Error("boom");
|
|
274
|
+
}`,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const dispatcher = new UserRouteDispatcher();
|
|
278
|
+
const res = await dispatcher.dispatch("throws", makeRequest("GET"));
|
|
279
|
+
expect(res.status).toBe(500);
|
|
280
|
+
const body = await readErrorBody(res);
|
|
281
|
+
expect(body.error.code).toBe("INTERNAL_ERROR");
|
|
282
|
+
expect(body.error.message).toBe("boom");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("returns 500 when handler rejects", async () => {
|
|
286
|
+
writeHandler(
|
|
287
|
+
"rejects.ts",
|
|
288
|
+
`export async function GET(request) {
|
|
289
|
+
throw new Error("async boom");
|
|
290
|
+
}`,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const dispatcher = new UserRouteDispatcher();
|
|
294
|
+
const res = await dispatcher.dispatch("rejects", makeRequest("GET"));
|
|
295
|
+
expect(res.status).toBe(500);
|
|
296
|
+
const body = await readErrorBody(res);
|
|
297
|
+
expect(body.error.message).toBe("async boom");
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
// Mtime-based cache invalidation
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
|
|
305
|
+
describe("mtime cache", () => {
|
|
306
|
+
test("serves updated content after file modification", async () => {
|
|
307
|
+
const filePath = writeHandler(
|
|
308
|
+
"mutable.ts",
|
|
309
|
+
`export function GET(request) {
|
|
310
|
+
return Response.json({ version: 1 });
|
|
311
|
+
}`,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const dispatcher = new UserRouteDispatcher();
|
|
315
|
+
|
|
316
|
+
// First request — version 1
|
|
317
|
+
const res1 = await dispatcher.dispatch("mutable", makeRequest("GET"));
|
|
318
|
+
expect(res1.status).toBe(200);
|
|
319
|
+
const body1 = await res1.json();
|
|
320
|
+
expect(body1.version).toBe(1);
|
|
321
|
+
|
|
322
|
+
// Wait briefly to ensure mtime changes, then rewrite
|
|
323
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
324
|
+
writeFileSync(
|
|
325
|
+
filePath,
|
|
326
|
+
`export function GET(request) {
|
|
327
|
+
return Response.json({ version: 2 });
|
|
328
|
+
}`,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// Second request — should pick up version 2
|
|
332
|
+
const res2 = await dispatcher.dispatch("mutable", makeRequest("GET"));
|
|
333
|
+
expect(res2.status).toBe(200);
|
|
334
|
+
const body2 = await res2.json();
|
|
335
|
+
expect(body2.version).toBe(2);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
// Subdirectory routing
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
describe("subdirectory routing", () => {
|
|
344
|
+
test("dispatches to nested handler files", async () => {
|
|
345
|
+
writeHandler(
|
|
346
|
+
"api/v1/status.ts",
|
|
347
|
+
`export function GET(request) {
|
|
348
|
+
return Response.json({ nested: true });
|
|
349
|
+
}`,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const dispatcher = new UserRouteDispatcher();
|
|
353
|
+
const res = await dispatcher.dispatch("api/v1/status", makeRequest("GET"));
|
|
354
|
+
expect(res.status).toBe(200);
|
|
355
|
+
const body = await res.json();
|
|
356
|
+
expect(body.nested).toBe(true);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
// Description metadata
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
|
|
364
|
+
describe("description metadata", () => {
|
|
365
|
+
test("ignores non-handler exports without affecting dispatch", async () => {
|
|
366
|
+
writeHandler(
|
|
367
|
+
"with-meta.ts",
|
|
368
|
+
`export const description = "A test handler";
|
|
369
|
+
export function GET(request) {
|
|
370
|
+
return Response.json({ ok: true });
|
|
371
|
+
}`,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const dispatcher = new UserRouteDispatcher();
|
|
375
|
+
const res = await dispatcher.dispatch("with-meta", makeRequest("GET"));
|
|
376
|
+
expect(res.status).toBe(200);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
@@ -24,6 +24,7 @@ import { packageApp } from "../../bundler/app-bundler.js";
|
|
|
24
24
|
import { compileApp } from "../../bundler/app-compiler.js";
|
|
25
25
|
import { scanBundle } from "../../bundler/bundle-scanner.js";
|
|
26
26
|
import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
|
|
27
|
+
import { compareSemver } from "../../daemon/handlers/shared.js";
|
|
27
28
|
import { defaultGallery } from "../../gallery/default-gallery.js";
|
|
28
29
|
import {
|
|
29
30
|
getAppDiff,
|
|
@@ -68,17 +69,6 @@ function getSharedAppsDir(): string {
|
|
|
68
69
|
);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
/** Compare two semver strings. Returns negative if a < b, 0 if equal, positive if a > b. */
|
|
72
|
-
function compareSemver(a: string, b: string): number {
|
|
73
|
-
const pa = a.split(".").map(Number);
|
|
74
|
-
const pb = b.split(".").map(Number);
|
|
75
|
-
for (let i = 0; i < 3; i++) {
|
|
76
|
-
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
77
|
-
if (diff !== 0) return diff;
|
|
78
|
-
}
|
|
79
|
-
return 0;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
72
|
// ---------------------------------------------------------------------------
|
|
83
73
|
// Extracted business logic
|
|
84
74
|
// ---------------------------------------------------------------------------
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { applyGuardianDecision } from "../../../approvals/guardian-decision-primitive.js";
|
|
7
7
|
import type { ChannelId } from "../../../channels/types.js";
|
|
8
|
+
import { findContactChannel } from "../../../contacts/contact-store.js";
|
|
8
9
|
import {
|
|
9
10
|
getAllPendingApprovalsByGuardianChat,
|
|
10
11
|
getApprovalRequestById,
|
|
@@ -804,6 +805,25 @@ async function handleAccessRequestApproval(
|
|
|
804
805
|
return { handled: true, type: "stale_ignored" };
|
|
805
806
|
}
|
|
806
807
|
|
|
808
|
+
// Resolve display names from the contacts database for enriched payloads
|
|
809
|
+
const requesterContactResult = approval.requesterExternalUserId
|
|
810
|
+
? findContactChannel({
|
|
811
|
+
channelType: approval.channel,
|
|
812
|
+
externalUserId: approval.requesterExternalUserId,
|
|
813
|
+
})
|
|
814
|
+
: null;
|
|
815
|
+
const requesterDisplayName =
|
|
816
|
+
requesterContactResult?.contact.displayName ?? null;
|
|
817
|
+
|
|
818
|
+
const decidedByContactResult = decidedByExternalUserId
|
|
819
|
+
? findContactChannel({
|
|
820
|
+
channelType: approval.channel,
|
|
821
|
+
externalUserId: decidedByExternalUserId,
|
|
822
|
+
})
|
|
823
|
+
: null;
|
|
824
|
+
const decidedByDisplayName =
|
|
825
|
+
decidedByContactResult?.contact.displayName ?? null;
|
|
826
|
+
|
|
807
827
|
if (decisionResult.type === "denied") {
|
|
808
828
|
await notifyRequesterOfDenial({
|
|
809
829
|
replyCallbackUrl,
|
|
@@ -821,6 +841,8 @@ async function handleAccessRequestApproval(
|
|
|
821
841
|
requesterExternalUserId: approval.requesterExternalUserId,
|
|
822
842
|
requesterChatId: approval.requesterChatId,
|
|
823
843
|
decidedByExternalUserId,
|
|
844
|
+
requesterDisplayName,
|
|
845
|
+
decidedByDisplayName,
|
|
824
846
|
decision: "denied" as const,
|
|
825
847
|
};
|
|
826
848
|
|
|
@@ -952,6 +974,8 @@ async function handleAccessRequestApproval(
|
|
|
952
974
|
requesterExternalUserId: approval.requesterExternalUserId,
|
|
953
975
|
requesterChatId: approval.requesterChatId,
|
|
954
976
|
decidedByExternalUserId,
|
|
977
|
+
requesterDisplayName,
|
|
978
|
+
decidedByDisplayName,
|
|
955
979
|
decision: "approved",
|
|
956
980
|
},
|
|
957
981
|
dedupeKey: `trusted-contact:guardian-decision:${approval.id}`,
|
|
@@ -977,6 +1001,8 @@ async function handleAccessRequestApproval(
|
|
|
977
1001
|
sourceChannel: approval.channel as NotificationSourceChannel,
|
|
978
1002
|
requesterExternalUserId: approval.requesterExternalUserId,
|
|
979
1003
|
requesterChatId: approval.requesterChatId,
|
|
1004
|
+
requesterDisplayName,
|
|
1005
|
+
decidedByDisplayName,
|
|
980
1006
|
verificationSessionId: decisionResult.verificationSessionId,
|
|
981
1007
|
},
|
|
982
1008
|
dedupeKey: `trusted-contact:verification-sent:${decisionResult.verificationSessionId}`,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tar.gz archive creation utilities used by
|
|
3
|
+
* log export and profiler export routes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
/** Maximum compressed archive size (50 MB). */
|
|
9
|
+
export const MAX_ARCHIVE_BYTES = 50 * 1024 * 1024;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Attempts to create a tar.gz archive of `staging` into a Buffer.
|
|
13
|
+
* Returns the Buffer on success, or `undefined` if the archive exceeds
|
|
14
|
+
* the size limit or tar otherwise fails.
|
|
15
|
+
*/
|
|
16
|
+
export function createTarGz(
|
|
17
|
+
staging: string,
|
|
18
|
+
maxBytes: number = MAX_ARCHIVE_BYTES,
|
|
19
|
+
): ArrayBuffer | undefined {
|
|
20
|
+
const proc = spawnSync("tar", ["czf", "-", "-C", staging, "."], {
|
|
21
|
+
maxBuffer: maxBytes,
|
|
22
|
+
timeout: 30_000,
|
|
23
|
+
});
|
|
24
|
+
if (proc.status !== 0) return undefined;
|
|
25
|
+
const buf = Buffer.isBuffer(proc.stdout)
|
|
26
|
+
? proc.stdout
|
|
27
|
+
: Buffer.from(proc.stdout);
|
|
28
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
29
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
|
|
3
1
|
import { z } from "zod";
|
|
4
2
|
|
|
5
3
|
import { getCharacterComponents } from "../../avatar/character-components.js";
|
|
@@ -8,7 +6,7 @@ import {
|
|
|
8
6
|
writeTraitsAndRenderAvatar,
|
|
9
7
|
} from "../../avatar/traits-png-sync.js";
|
|
10
8
|
import { getLogger } from "../../util/logger.js";
|
|
11
|
-
import {
|
|
9
|
+
import { getAvatarImagePath } from "../../util/platform.js";
|
|
12
10
|
import { buildAssistantEvent } from "../assistant-event.js";
|
|
13
11
|
import { assistantEventHub } from "../assistant-event-hub.js";
|
|
14
12
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
@@ -18,12 +16,7 @@ import type { RouteDefinition } from "../http-router.js";
|
|
|
18
16
|
const log = getLogger("avatar-routes");
|
|
19
17
|
|
|
20
18
|
function publishAvatarUpdated(): void {
|
|
21
|
-
const avatarPath =
|
|
22
|
-
getWorkspaceDir(),
|
|
23
|
-
"data",
|
|
24
|
-
"avatar",
|
|
25
|
-
"avatar-image.png",
|
|
26
|
-
);
|
|
19
|
+
const avatarPath = getAvatarImagePath();
|
|
27
20
|
assistantEventHub
|
|
28
21
|
.publish(
|
|
29
22
|
buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, {
|
|
@@ -17,6 +17,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
19
19
|
import { getConfig } from "../../config/loader.js";
|
|
20
|
+
import { readNowScratchpad } from "../../daemon/conversation-runtime-assembly.js";
|
|
20
21
|
import { getConversationByKey } from "../../memory/conversation-key-store.js";
|
|
21
22
|
import {
|
|
22
23
|
resolveChannelPersona,
|
|
@@ -136,6 +137,18 @@ async function handleBtw(
|
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
// ----- Greeting context enrichment -----
|
|
141
|
+
// Inject NOW.md scratchpad so the model has contextual awareness (mood,
|
|
142
|
+
// current activity) and produces varied, relevant greetings instead of
|
|
143
|
+
// the same deterministic output each time.
|
|
144
|
+
let effectiveContent = trimmedContent;
|
|
145
|
+
if (conversationKey === GREETING_KEY) {
|
|
146
|
+
const now = readNowScratchpad();
|
|
147
|
+
if (now) {
|
|
148
|
+
effectiveContent = `${trimmedContent}\n\n<context>\n${now}\n</context>`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
139
152
|
// Look up an existing conversation — never create one. BTW is ephemeral
|
|
140
153
|
// (the file header promises "No messages are persisted"), so we must not
|
|
141
154
|
// call getOrCreateConversation which would insert a DB row. When no
|
|
@@ -158,7 +171,7 @@ async function handleBtw(
|
|
|
158
171
|
const userPersona = resolveGuardianPersona();
|
|
159
172
|
const channelPersona = resolveChannelPersona(undefined);
|
|
160
173
|
const result = await runBtwSidechain({
|
|
161
|
-
content:
|
|
174
|
+
content: effectiveContent,
|
|
162
175
|
conversation,
|
|
163
176
|
signal: req.signal,
|
|
164
177
|
userPersona,
|