@vellumai/assistant 0.5.1 → 0.5.3
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/ARCHITECTURE.md +163 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/docs/skills.md +100 -0
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
- package/src/__tests__/conversation-agent-loop.test.ts +297 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-wipe.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
- package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +32 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/inline-command-runner.test.ts +311 -0
- package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
- package/src/__tests__/list-messages-attachments.test.ts +96 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
- package/src/__tests__/memory-brief-time.test.ts +285 -0
- package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
- package/src/__tests__/memory-chunk-archive.test.ts +400 -0
- package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
- package/src/__tests__/memory-episode-archive.test.ts +370 -0
- package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-observation-archive.test.ts +375 -0
- package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
- package/src/__tests__/memory-recall-quality.test.ts +7 -7
- package/src/__tests__/memory-reducer-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +699 -0
- package/src/__tests__/memory-reducer.test.ts +698 -0
- package/src/__tests__/memory-regressions.test.ts +6 -4
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/parse-identity-fields.test.ts +129 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-load-inline-command.test.ts +598 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
- package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
- package/src/__tests__/skills-transitive-hash.test.ts +333 -0
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +24 -0
- package/src/config/loader.ts +65 -0
- package/src/config/raw-config-utils.ts +58 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +20 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-simplified.ts +101 -0
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +8 -6
- package/src/config/skills.ts +50 -4
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
- package/src/daemon/conversation-agent-loop.ts +127 -20
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +50 -16
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +22 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +170 -24
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +69 -30
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +156 -0
- package/src/daemon/handlers/config-model.ts +62 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/identity.ts +12 -1
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +115 -65
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +18 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/followups/followup-store.ts +47 -1
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/archive-store.ts +400 -0
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/brief-formatting.ts +33 -0
- package/src/memory/brief-open-loops.ts +266 -0
- package/src/memory/brief-time.ts +161 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +591 -8
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +40 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +114 -21
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +2 -4
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +6 -0
- package/src/memory/jobs-worker.ts +12 -0
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/185-memory-brief-state.ts +52 -0
- package/src/memory/migrations/186-memory-archive.ts +109 -0
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +99 -0
- package/src/memory/reducer.ts +453 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/memory-archive.ts +121 -0
- package/src/memory/schema/memory-brief.ts +55 -0
- package/src/memory/schema/oauth.ts +6 -0
- package/src/memory/search/semantic.ts +17 -4
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +99 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +160 -14
- package/src/permissions/defaults.ts +14 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +74 -0
- package/src/runtime/routes/conversation-query-routes.ts +187 -10
- package/src/runtime/routes/conversation-routes.ts +269 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +2 -35
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1212 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +94 -5
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +30 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +30 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/skills/inline-command-expansions.ts +204 -0
- package/src/skills/inline-command-render.ts +127 -0
- package/src/skills/inline-command-runner.ts +242 -0
- package/src/skills/transitive-version-hash.ts +88 -0
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/tasks/task-store.ts +43 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +140 -6
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +24 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +11 -1
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { v4 as uuid } from "uuid";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getApp,
|
|
5
|
+
getAppPreview,
|
|
6
|
+
resolveAppDir,
|
|
7
|
+
updateApp,
|
|
8
|
+
} from "../memory/app-store.js";
|
|
4
9
|
import type { ToolExecutionResult } from "../tools/types.js";
|
|
5
10
|
import { getLogger } from "../util/logger.js";
|
|
6
11
|
import { isPlainObject } from "../util/object.js";
|
|
@@ -167,6 +172,7 @@ export interface SurfaceConversationContext {
|
|
|
167
172
|
emit(type: string, message: string, meta?: Record<string, unknown>): void;
|
|
168
173
|
};
|
|
169
174
|
sendToClient(msg: ServerMessage): void;
|
|
175
|
+
broadcastToAllClients?(msg: ServerMessage): void;
|
|
170
176
|
pendingSurfaceActions: Map<string, { surfaceType: SurfaceType }>;
|
|
171
177
|
lastSurfaceAction: Map<
|
|
172
178
|
string,
|
|
@@ -187,6 +193,7 @@ export interface SurfaceConversationContext {
|
|
|
187
193
|
}
|
|
188
194
|
>;
|
|
189
195
|
surfaceUndoStacks: Map<string, string[]>;
|
|
196
|
+
accumulatedSurfaceState: Map<string, Record<string, unknown>>;
|
|
190
197
|
/** Request IDs that originated from surface action button clicks (not regular user messages). */
|
|
191
198
|
surfaceActionRequestIds: Set<string>;
|
|
192
199
|
currentTurnSurfaces: Array<{
|
|
@@ -346,6 +353,39 @@ function handleDocumentContentChanged(
|
|
|
346
353
|
}
|
|
347
354
|
}
|
|
348
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Handle state_update action from a dynamic page.
|
|
358
|
+
* Accumulates state via shallow merge without triggering an LLM turn.
|
|
359
|
+
*/
|
|
360
|
+
function handleStateUpdate(
|
|
361
|
+
ctx: SurfaceConversationContext,
|
|
362
|
+
surfaceId: string,
|
|
363
|
+
data?: Record<string, unknown>,
|
|
364
|
+
): void {
|
|
365
|
+
if (!data) {
|
|
366
|
+
log.debug({ surfaceId }, "state_update action called with no data");
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const surfaceState = ctx.surfaceState.get(surfaceId);
|
|
371
|
+
if (!surfaceState || surfaceState.surfaceType !== "dynamic_page") {
|
|
372
|
+
log.warn(
|
|
373
|
+
{ surfaceId, surfaceType: surfaceState?.surfaceType },
|
|
374
|
+
"state_update action received for non-dynamic_page surface",
|
|
375
|
+
);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const existing = ctx.accumulatedSurfaceState.get(surfaceId) ?? {};
|
|
380
|
+
const merged = { ...existing, ...data };
|
|
381
|
+
ctx.accumulatedSurfaceState.set(surfaceId, merged);
|
|
382
|
+
|
|
383
|
+
log.debug(
|
|
384
|
+
{ surfaceId, accumulatedState: merged },
|
|
385
|
+
"Accumulated surface state updated",
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
349
389
|
export function pushUndoState(
|
|
350
390
|
surfaceUndoStacks: Map<string, string[]>,
|
|
351
391
|
surfaceId: string,
|
|
@@ -543,22 +583,76 @@ export function handleSurfaceAction(
|
|
|
543
583
|
const pending = ctx.pendingSurfaceActions.get(surfaceId);
|
|
544
584
|
|
|
545
585
|
// When surfaces are restored from history (e.g. onboarding cards), there is
|
|
546
|
-
// no in-memory pendingSurfaceActions entry.
|
|
547
|
-
//
|
|
548
|
-
// so we can handle them without stored state.
|
|
586
|
+
// no in-memory pendingSurfaceActions entry. Handle non-terminal actions
|
|
587
|
+
// directly, and forward custom/relay actions to the LLM.
|
|
549
588
|
if (!pending) {
|
|
589
|
+
// Non-terminal actions don't need stored state — handle directly.
|
|
590
|
+
if (actionId === "selection_changed") {
|
|
591
|
+
log.debug(
|
|
592
|
+
{ surfaceId, data },
|
|
593
|
+
"Selection changed (history-restored, not forwarding)",
|
|
594
|
+
);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (actionId === "content_changed") {
|
|
598
|
+
log.debug(
|
|
599
|
+
{ surfaceId },
|
|
600
|
+
"Content changed (history-restored, no surface state — skipping)",
|
|
601
|
+
);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (actionId === "state_update") {
|
|
605
|
+
if (data) {
|
|
606
|
+
const existing = ctx.accumulatedSurfaceState.get(surfaceId) ?? {};
|
|
607
|
+
ctx.accumulatedSurfaceState.set(surfaceId, { ...existing, ...data });
|
|
608
|
+
}
|
|
609
|
+
log.debug(
|
|
610
|
+
{ surfaceId, data },
|
|
611
|
+
"Silent state accumulated (history-restored)",
|
|
612
|
+
);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Determine message content from the action.
|
|
550
617
|
const isRelay = actionId === "relay_prompt" || actionId === "agent_prompt";
|
|
551
618
|
const prompt =
|
|
552
619
|
isRelay && typeof data?.prompt === "string" ? data.prompt.trim() : "";
|
|
553
620
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
621
|
+
// Read accumulated state once — used by both relay and custom action paths.
|
|
622
|
+
const accState = ctx.accumulatedSurfaceState.get(surfaceId);
|
|
623
|
+
const hasAccState = accState && Object.keys(accState).length > 0;
|
|
624
|
+
|
|
625
|
+
let content: string;
|
|
626
|
+
let displayContent: string | undefined;
|
|
627
|
+
if (prompt) {
|
|
628
|
+
content = prompt;
|
|
629
|
+
// Re-append accumulated state so the LLM sees it, matching the pending path.
|
|
630
|
+
if (hasAccState) {
|
|
631
|
+
content += `\n\nAccumulated surface state: ${JSON.stringify(accState)}`;
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
// Custom action from an app (e.g. sendAction('answer_selected', {...}))
|
|
635
|
+
const summary = actionId
|
|
636
|
+
.replace(/_/g, " ")
|
|
637
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
638
|
+
content = `[User action on app: ${summary}]`;
|
|
639
|
+
if (data && Object.keys(data).length > 0) {
|
|
640
|
+
content += `\n\nAction data: ${JSON.stringify(data)}`;
|
|
641
|
+
}
|
|
642
|
+
if (hasAccState) {
|
|
643
|
+
content += `\n\nAccumulated surface state: ${JSON.stringify(accState)}`;
|
|
644
|
+
}
|
|
645
|
+
displayContent = summary;
|
|
557
646
|
}
|
|
558
647
|
|
|
559
648
|
const requestId = uuid();
|
|
560
649
|
ctx.surfaceActionRequestIds.add(requestId);
|
|
561
|
-
|
|
650
|
+
// Use broadcastToAllClients (publishes to the SSE event hub) instead of
|
|
651
|
+
// sendToClient, which is reset to a no-op between HTTP requests. Without
|
|
652
|
+
// this, surface action responses are persisted to DB but never reach the
|
|
653
|
+
// client's SSE stream.
|
|
654
|
+
const emit = ctx.broadcastToAllClients ?? ctx.sendToClient.bind(ctx);
|
|
655
|
+
const onEvent = (msg: ServerMessage) => emit(msg);
|
|
562
656
|
|
|
563
657
|
ctx.traceEmitter.emit("request_received", "Surface action received", {
|
|
564
658
|
requestId,
|
|
@@ -567,11 +661,15 @@ export function handleSurfaceAction(
|
|
|
567
661
|
});
|
|
568
662
|
|
|
569
663
|
const result = ctx.enqueueMessage(
|
|
570
|
-
|
|
664
|
+
content,
|
|
571
665
|
[],
|
|
572
666
|
onEvent,
|
|
573
667
|
requestId,
|
|
574
668
|
surfaceId,
|
|
669
|
+
undefined,
|
|
670
|
+
undefined,
|
|
671
|
+
undefined,
|
|
672
|
+
displayContent,
|
|
575
673
|
);
|
|
576
674
|
|
|
577
675
|
if (result.rejected) {
|
|
@@ -579,18 +677,26 @@ export function handleSurfaceAction(
|
|
|
579
677
|
return;
|
|
580
678
|
}
|
|
581
679
|
|
|
680
|
+
// One-shot: clear accumulated state now that the message has been accepted.
|
|
681
|
+
// Deferred until after rejection check so state is preserved for retry on rejection.
|
|
682
|
+
if (hasAccState) {
|
|
683
|
+
ctx.accumulatedSurfaceState.delete(surfaceId);
|
|
684
|
+
}
|
|
685
|
+
|
|
582
686
|
// Echo the prompt to the client so it appears in the chat UI.
|
|
583
687
|
// Deferred until after rejection check to avoid ghost messages.
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
688
|
+
if (prompt) {
|
|
689
|
+
emit({
|
|
690
|
+
type: "user_message_echo",
|
|
691
|
+
text: prompt,
|
|
692
|
+
conversationId: ctx.conversationId,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
589
695
|
|
|
590
696
|
if (result.queued) {
|
|
591
697
|
log.info(
|
|
592
698
|
{ surfaceId, actionId, requestId },
|
|
593
|
-
"
|
|
699
|
+
"Surface action queued (conversation busy, history-restored)",
|
|
594
700
|
);
|
|
595
701
|
return;
|
|
596
702
|
}
|
|
@@ -598,22 +704,31 @@ export function handleSurfaceAction(
|
|
|
598
704
|
// Conversation is idle — process the message immediately.
|
|
599
705
|
log.info(
|
|
600
706
|
{ surfaceId, actionId, requestId },
|
|
601
|
-
"Processing
|
|
707
|
+
"Processing surface action immediately (history-restored)",
|
|
602
708
|
);
|
|
603
709
|
ctx
|
|
604
|
-
.processMessage(
|
|
710
|
+
.processMessage(
|
|
711
|
+
content,
|
|
712
|
+
[],
|
|
713
|
+
onEvent,
|
|
714
|
+
requestId,
|
|
715
|
+
surfaceId,
|
|
716
|
+
undefined,
|
|
717
|
+
undefined,
|
|
718
|
+
displayContent,
|
|
719
|
+
)
|
|
605
720
|
.catch((err) => {
|
|
606
721
|
const message = err instanceof Error ? err.message : String(err);
|
|
607
722
|
log.error(
|
|
608
723
|
{ err, surfaceId, actionId },
|
|
609
|
-
"Failed to process history-restored
|
|
724
|
+
"Failed to process history-restored surface action",
|
|
610
725
|
);
|
|
611
726
|
onEvent(
|
|
612
727
|
buildConversationErrorMessage(ctx.conversationId, {
|
|
613
728
|
code: "CONVERSATION_PROCESSING_FAILED",
|
|
614
729
|
userMessage: `Something went wrong: ${message}`,
|
|
615
730
|
retryable: false,
|
|
616
|
-
debugDetails: `History-restored
|
|
731
|
+
debugDetails: `History-restored surface action processing failed: ${message}`,
|
|
617
732
|
errorCategory: "processing_failed",
|
|
618
733
|
}),
|
|
619
734
|
);
|
|
@@ -637,6 +752,14 @@ export function handleSurfaceAction(
|
|
|
637
752
|
handleDocumentContentChanged(ctx, surfaceId, data);
|
|
638
753
|
return;
|
|
639
754
|
}
|
|
755
|
+
|
|
756
|
+
// state_update is a silent accumulation action — merge data into accumulated
|
|
757
|
+
// state without triggering an LLM turn.
|
|
758
|
+
if (actionId === "state_update") {
|
|
759
|
+
handleStateUpdate(ctx, surfaceId, data);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
|
|
640
763
|
// Merge stored action-level data (from ui_show definition) with client-sent
|
|
641
764
|
// data. This is critical for relay_prompt buttons: the client only sends the
|
|
642
765
|
// actionId, but the prompt payload lives in the action definition's data.
|
|
@@ -663,11 +786,16 @@ export function handleSurfaceAction(
|
|
|
663
786
|
surfaceData,
|
|
664
787
|
);
|
|
665
788
|
|
|
789
|
+
// Use broadcastToAllClients so events reach the SSE hub — sendToClient is
|
|
790
|
+
// reset to a no-op between HTTP requests (see history-restored path for
|
|
791
|
+
// full rationale).
|
|
792
|
+
const emit = ctx.broadcastToAllClients ?? ctx.sendToClient.bind(ctx);
|
|
793
|
+
|
|
666
794
|
// Forms are one-shot surfaces — auto-complete immediately so the client
|
|
667
795
|
// transitions from the "Submitting…" spinner to a completion chip without
|
|
668
796
|
// requiring the LLM to call ui_dismiss.
|
|
669
797
|
if (pending.surfaceType === "form") {
|
|
670
|
-
|
|
798
|
+
emit({
|
|
671
799
|
type: "ui_surface_complete",
|
|
672
800
|
conversationId: ctx.conversationId,
|
|
673
801
|
surfaceId,
|
|
@@ -694,6 +822,10 @@ export function handleSurfaceAction(
|
|
|
694
822
|
selectedIds,
|
|
695
823
|
);
|
|
696
824
|
}
|
|
825
|
+
const accumulatedState = ctx.accumulatedSurfaceState.get(surfaceId);
|
|
826
|
+
if (accumulatedState && Object.keys(accumulatedState).length > 0) {
|
|
827
|
+
fallbackContent += `\n\nAccumulated surface state: ${JSON.stringify(accumulatedState)}`;
|
|
828
|
+
}
|
|
697
829
|
// When a relay_prompt button also carries selection data (e.g. list/table
|
|
698
830
|
// surface with a canned prompt + user-selected rows), append the selection
|
|
699
831
|
// context so the LLM sees both the prompt and the user's selections.
|
|
@@ -707,6 +839,11 @@ export function handleSurfaceAction(
|
|
|
707
839
|
);
|
|
708
840
|
}
|
|
709
841
|
}
|
|
842
|
+
// When prompt is truthy, fallbackContent (which includes accumulated state)
|
|
843
|
+
// is discarded. Re-append accumulated state so the LLM sees it.
|
|
844
|
+
if (prompt && accumulatedState && Object.keys(accumulatedState).length > 0) {
|
|
845
|
+
content += `\n\nAccumulated surface state: ${JSON.stringify(accumulatedState)}`;
|
|
846
|
+
}
|
|
710
847
|
// Show the user plain-text instead of raw JSON action data.
|
|
711
848
|
const displayContent = prompt
|
|
712
849
|
? undefined
|
|
@@ -719,7 +856,7 @@ export function handleSurfaceAction(
|
|
|
719
856
|
|
|
720
857
|
const requestId = uuid();
|
|
721
858
|
ctx.surfaceActionRequestIds.add(requestId);
|
|
722
|
-
const onEvent = (msg: ServerMessage) =>
|
|
859
|
+
const onEvent = (msg: ServerMessage) => emit(msg);
|
|
723
860
|
|
|
724
861
|
ctx.traceEmitter.emit("request_received", "Surface action received", {
|
|
725
862
|
requestId,
|
|
@@ -743,10 +880,16 @@ export function handleSurfaceAction(
|
|
|
743
880
|
return;
|
|
744
881
|
}
|
|
745
882
|
|
|
883
|
+
// One-shot: clear accumulated state now that the message has been accepted.
|
|
884
|
+
// Deferred until after rejection check so state is preserved for retry on rejection.
|
|
885
|
+
if (accumulatedState && Object.keys(accumulatedState).length > 0) {
|
|
886
|
+
ctx.accumulatedSurfaceState.delete(surfaceId);
|
|
887
|
+
}
|
|
888
|
+
|
|
746
889
|
// Echo the user's prompt to the client so it appears in the chat UI.
|
|
747
890
|
// Deferred until after rejection check to avoid ghost messages.
|
|
748
891
|
if (shouldRelayPrompt && prompt) {
|
|
749
|
-
|
|
892
|
+
emit({
|
|
750
893
|
type: "user_message_echo",
|
|
751
894
|
text: prompt,
|
|
752
895
|
conversationId: ctx.conversationId,
|
|
@@ -811,7 +954,7 @@ export function handleSurfaceAction(
|
|
|
811
954
|
}
|
|
812
955
|
|
|
813
956
|
/**
|
|
814
|
-
* After an
|
|
957
|
+
* After an app_refresh, refresh any active surface that displays the updated app.
|
|
815
958
|
*/
|
|
816
959
|
export function refreshSurfacesForApp(
|
|
817
960
|
ctx: SurfaceConversationContext,
|
|
@@ -860,7 +1003,7 @@ export function refreshSurfacesForApp(
|
|
|
860
1003
|
refreshed = true;
|
|
861
1004
|
log.info(
|
|
862
1005
|
{ conversationId: ctx.conversationId, surfaceId, appId },
|
|
863
|
-
"Auto-refreshed surface after
|
|
1006
|
+
"Auto-refreshed surface after app_refresh",
|
|
864
1007
|
);
|
|
865
1008
|
}
|
|
866
1009
|
return refreshed;
|
|
@@ -1201,6 +1344,7 @@ export async function surfaceProxyResolver(
|
|
|
1201
1344
|
ctx.surfaceState.delete(surfaceId);
|
|
1202
1345
|
ctx.surfaceUndoStacks.delete(surfaceId);
|
|
1203
1346
|
ctx.lastSurfaceAction.delete(surfaceId);
|
|
1347
|
+
ctx.accumulatedSurfaceState.delete(surfaceId);
|
|
1204
1348
|
return {
|
|
1205
1349
|
content: lastAction ? "Surface completed" : "Surface dismissed",
|
|
1206
1350
|
isError: false,
|
|
@@ -1219,9 +1363,11 @@ export async function surfaceProxyResolver(
|
|
|
1219
1363
|
const defaultPreview = { title: app.name, subtitle: app.description };
|
|
1220
1364
|
|
|
1221
1365
|
const storedPreview = getAppPreview(app.id);
|
|
1366
|
+
const { dirName } = resolveAppDir(app.id);
|
|
1222
1367
|
const surfaceData: DynamicPageSurfaceData = {
|
|
1223
1368
|
html: app.htmlDefinition,
|
|
1224
1369
|
appId: app.id,
|
|
1370
|
+
dirName,
|
|
1225
1371
|
preview: {
|
|
1226
1372
|
...defaultPreview,
|
|
1227
1373
|
...preview,
|
|
@@ -560,8 +560,6 @@ export interface SkillProjectionContext {
|
|
|
560
560
|
};
|
|
561
561
|
/** True when no client is connected (HTTP-only). */
|
|
562
562
|
readonly hasNoClient?: boolean;
|
|
563
|
-
/** True when the conversation has user-uploaded attachments. */
|
|
564
|
-
hasAttachments?: boolean;
|
|
565
563
|
}
|
|
566
564
|
|
|
567
565
|
// ── Conditional tool sets ────────────────────────────────────────────
|
|
@@ -573,7 +571,6 @@ const HOST_TOOL_NAMES = new Set([
|
|
|
573
571
|
"host_file_edit",
|
|
574
572
|
"host_bash",
|
|
575
573
|
]);
|
|
576
|
-
const ASSET_TOOL_NAMES = new Set(["asset_search", "asset_materialize"]);
|
|
577
574
|
const CLIENT_CAPABILITY_TOOL_NAMES = new Set(["app_open"]);
|
|
578
575
|
const PLATFORM_TOOL_NAMES = new Set(["request_system_permission"]);
|
|
579
576
|
|
|
@@ -597,9 +594,6 @@ export function isToolActiveForContext(
|
|
|
597
594
|
// unchecked host command execution on the daemon host.
|
|
598
595
|
return !ctx.hasNoClient;
|
|
599
596
|
}
|
|
600
|
-
if (ASSET_TOOL_NAMES.has(name)) {
|
|
601
|
-
return ctx.hasAttachments ?? false;
|
|
602
|
-
}
|
|
603
597
|
if (CLIENT_CAPABILITY_TOOL_NAMES.has(name)) {
|
|
604
598
|
return !ctx.hasNoClient;
|
|
605
599
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { getConversation } from "../memory/conversation-crud.js";
|
|
4
|
+
import { resolveConversationDirectoryPaths } from "../memory/conversation-directories.js";
|
|
1
5
|
import { renderWorkspaceTopLevelContext } from "../workspace/top-level-renderer.js";
|
|
2
6
|
import { scanTopLevelDirectories } from "../workspace/top-level-scanner.js";
|
|
3
7
|
|
|
@@ -5,6 +9,7 @@ import { scanTopLevelDirectories } from "../workspace/top-level-scanner.js";
|
|
|
5
9
|
* Subset of Conversation state that workspace context helpers need.
|
|
6
10
|
*/
|
|
7
11
|
export interface WorkspaceConversationContext {
|
|
12
|
+
conversationId: string;
|
|
8
13
|
workingDir: string;
|
|
9
14
|
workspaceTopLevelContext: string | null;
|
|
10
15
|
workspaceTopLevelDirty: boolean;
|
|
@@ -17,6 +22,21 @@ export function refreshWorkspaceTopLevelContextIfNeeded(
|
|
|
17
22
|
if (!ctx.workspaceTopLevelDirty && ctx.workspaceTopLevelContext != null)
|
|
18
23
|
return;
|
|
19
24
|
const snapshot = scanTopLevelDirectories(ctx.workingDir);
|
|
20
|
-
|
|
25
|
+
const conversation = getConversation(ctx.conversationId);
|
|
26
|
+
let currentConversationPath: string | null = null;
|
|
27
|
+
if (conversation && typeof conversation.createdAt === "number") {
|
|
28
|
+
const { resolvedDirName } = resolveConversationDirectoryPaths(
|
|
29
|
+
conversation.id,
|
|
30
|
+
conversation.createdAt,
|
|
31
|
+
join(ctx.workingDir, "conversations"),
|
|
32
|
+
);
|
|
33
|
+
currentConversationPath = `conversations/${resolvedDirName}/`;
|
|
34
|
+
}
|
|
35
|
+
ctx.workspaceTopLevelContext = renderWorkspaceTopLevelContext(snapshot, {
|
|
36
|
+
currentConversationPath,
|
|
37
|
+
currentConversationAttachmentsPath: currentConversationPath
|
|
38
|
+
? `${currentConversationPath}attachments/`
|
|
39
|
+
: null,
|
|
40
|
+
});
|
|
21
41
|
ctx.workspaceTopLevelDirty = false;
|
|
22
42
|
}
|
|
@@ -81,7 +81,6 @@ import type {
|
|
|
81
81
|
ChannelCapabilities,
|
|
82
82
|
TrustContext,
|
|
83
83
|
} from "./conversation-runtime-assembly.js";
|
|
84
|
-
import { messagesContainAttachments } from "./conversation-runtime-assembly.js";
|
|
85
84
|
import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
86
85
|
import {
|
|
87
86
|
createSurfaceMutex,
|
|
@@ -164,7 +163,6 @@ export class Conversation {
|
|
|
164
163
|
/** @internal */ contextCompactedAt: number | null = null;
|
|
165
164
|
/** @internal */ currentRequestId?: string;
|
|
166
165
|
/** @internal */ hasNoClient = false;
|
|
167
|
-
/** @internal */ hasAttachments = false;
|
|
168
166
|
/** @internal */ headlessLock = false;
|
|
169
167
|
/** @internal */ taskRunId?: string;
|
|
170
168
|
/** @internal */ callSessionId?: string;
|
|
@@ -197,9 +195,24 @@ export class Conversation {
|
|
|
197
195
|
>();
|
|
198
196
|
/** @internal */ surfaceState = new Map<
|
|
199
197
|
string,
|
|
200
|
-
{
|
|
198
|
+
{
|
|
199
|
+
surfaceType: SurfaceType;
|
|
200
|
+
data: SurfaceData;
|
|
201
|
+
title?: string;
|
|
202
|
+
actions?: Array<{
|
|
203
|
+
id: string;
|
|
204
|
+
label: string;
|
|
205
|
+
style?: string;
|
|
206
|
+
data?: Record<string, unknown>;
|
|
207
|
+
}>;
|
|
208
|
+
}
|
|
201
209
|
>();
|
|
202
210
|
/** @internal */ surfaceUndoStacks = new Map<string, string[]>();
|
|
211
|
+
/** @internal */ accumulatedSurfaceState = new Map<
|
|
212
|
+
string,
|
|
213
|
+
Record<string, unknown>
|
|
214
|
+
>();
|
|
215
|
+
/** @internal */ broadcastToAllClients?: (msg: ServerMessage) => void;
|
|
203
216
|
/** @internal */ withSurface = createSurfaceMutex();
|
|
204
217
|
/** @internal */ currentTurnSurfaces: Array<{
|
|
205
218
|
surfaceId: string;
|
|
@@ -246,6 +259,7 @@ export class Conversation {
|
|
|
246
259
|
this.provider = provider;
|
|
247
260
|
this.workingDir = workingDir;
|
|
248
261
|
this.sendToClient = sendToClient;
|
|
262
|
+
this.broadcastToAllClients = broadcastToAllClients;
|
|
249
263
|
this.memoryPolicy = memoryPolicy
|
|
250
264
|
? { ...memoryPolicy }
|
|
251
265
|
: { ...DEFAULT_MEMORY_POLICY };
|
|
@@ -379,13 +393,41 @@ export class Conversation {
|
|
|
379
393
|
|
|
380
394
|
async loadFromDb(): Promise<void> {
|
|
381
395
|
await loadFromDbImpl(this);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
396
|
+
this.restoreSurfaceStateFromHistory();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Scan loaded conversation history for ui_surface content blocks and
|
|
401
|
+
* populate surfaceState so that findConversationBySurfaceId works for
|
|
402
|
+
* surfaces restored from history (e.g. after daemon restart).
|
|
403
|
+
*
|
|
404
|
+
* Only scans live (non-compacted) messages in this.messages — not all DB
|
|
405
|
+
* rows — because surface IDs are not globally unique and restoring stale
|
|
406
|
+
* compacted surfaces would let findConversationBySurfaceId route actions
|
|
407
|
+
* to the wrong conversation.
|
|
408
|
+
*/
|
|
409
|
+
private restoreSurfaceStateFromHistory(): void {
|
|
410
|
+
this.surfaceState.clear();
|
|
411
|
+
for (const msg of this.messages) {
|
|
412
|
+
if (!Array.isArray(msg.content)) continue;
|
|
413
|
+
for (const block of msg.content) {
|
|
414
|
+
const b = block as unknown as Record<string, unknown>;
|
|
415
|
+
if (b.type === "ui_surface" && typeof b.surfaceId === "string") {
|
|
416
|
+
this.surfaceState.set(b.surfaceId, {
|
|
417
|
+
surfaceType: (b.surfaceType ?? "dynamic_page") as SurfaceType,
|
|
418
|
+
data: (b.data ?? {}) as SurfaceData,
|
|
419
|
+
title: b.title as string | undefined,
|
|
420
|
+
actions: Array.isArray(b.actions)
|
|
421
|
+
? (b.actions as Array<{
|
|
422
|
+
id: string;
|
|
423
|
+
label: string;
|
|
424
|
+
style?: string;
|
|
425
|
+
data?: Record<string, unknown>;
|
|
426
|
+
}>)
|
|
427
|
+
: undefined,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
389
431
|
}
|
|
390
432
|
}
|
|
391
433
|
|
|
@@ -606,6 +648,18 @@ export class Conversation {
|
|
|
606
648
|
"Resuming after approval",
|
|
607
649
|
);
|
|
608
650
|
|
|
651
|
+
// Sync the canonical guardian request status so stale "pending" DB
|
|
652
|
+
// records don't get matched by later guardian reply routing. Best-effort:
|
|
653
|
+
// CAS may harmlessly fail if the canonical decision primitive already
|
|
654
|
+
// resolved the request (e.g. channel approval path).
|
|
655
|
+
try {
|
|
656
|
+
resolveCanonicalGuardianRequest(requestId, "pending", {
|
|
657
|
+
status: resolvedState,
|
|
658
|
+
});
|
|
659
|
+
} catch {
|
|
660
|
+
// Canonical request tracking should not break the primary approval flow.
|
|
661
|
+
}
|
|
662
|
+
|
|
609
663
|
// Cascade to other pending confirmations that match this decision
|
|
610
664
|
this.cascadePendingApprovals(requestId, decision, selectedPattern);
|
|
611
665
|
}
|
|
@@ -648,10 +702,11 @@ export class Conversation {
|
|
|
648
702
|
// Consume from pending-interactions tracker
|
|
649
703
|
pendingInteractions.resolve(candidateId);
|
|
650
704
|
|
|
651
|
-
// Resolve via handleConfirmationResponse which emits events
|
|
652
|
-
// Use simple "allow"/"deny" so the
|
|
653
|
-
// duplicate rules or re-activate
|
|
654
|
-
// terminates because allow/deny exit
|
|
705
|
+
// Resolve via handleConfirmationResponse which emits events and
|
|
706
|
+
// syncs canonical status. Use simple "allow"/"deny" so the
|
|
707
|
+
// permission-checker won't save duplicate rules or re-activate
|
|
708
|
+
// temporary modes. Recursion terminates because allow/deny exit
|
|
709
|
+
// cascadePendingApprovals early.
|
|
655
710
|
this.handleConfirmationResponse(
|
|
656
711
|
candidateId,
|
|
657
712
|
cascadeResult.allow ? "allow" : "deny",
|
|
@@ -663,17 +718,6 @@ export class Conversation {
|
|
|
663
718
|
causedByRequestId: primaryRequestId,
|
|
664
719
|
},
|
|
665
720
|
);
|
|
666
|
-
|
|
667
|
-
// Sync the canonical guardian request status for the cascaded request.
|
|
668
|
-
// Best-effort: canonical request tracking should not break the cascade flow.
|
|
669
|
-
try {
|
|
670
|
-
const targetStatus = cascadeResult.allow ? "approved" : "denied";
|
|
671
|
-
resolveCanonicalGuardianRequest(candidateId, "pending", {
|
|
672
|
-
status: targetStatus,
|
|
673
|
-
});
|
|
674
|
-
} catch {
|
|
675
|
-
// Ignore — canonical request tracking is best-effort
|
|
676
|
-
}
|
|
677
721
|
}
|
|
678
722
|
}
|
|
679
723
|
|
|
@@ -880,11 +924,6 @@ export class Conversation {
|
|
|
880
924
|
if (!this.processing) {
|
|
881
925
|
await this.ensureActorScopedHistory();
|
|
882
926
|
}
|
|
883
|
-
// One-way flag: once an attachment arrives, asset tools stay available
|
|
884
|
-
// for the remainder of the conversation.
|
|
885
|
-
if (!this.hasAttachments && attachments.length > 0) {
|
|
886
|
-
this.hasAttachments = true;
|
|
887
|
-
}
|
|
888
927
|
return persistUserMessageImpl(
|
|
889
928
|
this,
|
|
890
929
|
content,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { getWorkspacePromptPath } from "../util/platform.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The canned assistant response for the wake-up greeting on a fresh workspace.
|
|
7
|
+
* Warm, non-presumptuous greeting that communicates "I'm new," "I improve over
|
|
8
|
+
* time," "I'm ready to be useful," and "you're in control."
|
|
9
|
+
*/
|
|
10
|
+
export const CANNED_FIRST_GREETING =
|
|
11
|
+
"Hey. I'm brand new, no name, no memories, nothing yet. The more we work together, the more context and memory I build, and the better I get. But let's not wait around. Throw a question at me, give me a task, or ask what I can do.";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns `true` when all of the following are true:
|
|
15
|
+
* - `conversationMessageCount === 0` (no prior messages in this conversation)
|
|
16
|
+
* - BOOTSTRAP.md exists at the workspace prompt path
|
|
17
|
+
* - The trimmed content matches the macOS wake-up greeting (case-insensitive)
|
|
18
|
+
*/
|
|
19
|
+
export function isWakeUpGreeting(
|
|
20
|
+
content: string,
|
|
21
|
+
conversationMessageCount: number,
|
|
22
|
+
): boolean {
|
|
23
|
+
if (conversationMessageCount !== 0) return false;
|
|
24
|
+
if (!existsSync(getWorkspacePromptPath("BOOTSTRAP.md"))) return false;
|
|
25
|
+
return content.trim().toLowerCase() === "wake up, my friend.";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns the canned first-greeting string. Simple getter that exists to keep
|
|
30
|
+
* the call site consistent and allow future flexibility (e.g., locale-aware
|
|
31
|
+
* greetings) without changing the API.
|
|
32
|
+
*/
|
|
33
|
+
export function getCannedFirstGreeting(): string {
|
|
34
|
+
return CANNED_FIRST_GREETING;
|
|
35
|
+
}
|