@vellumai/assistant 0.5.1 → 0.5.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/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- 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 +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -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-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-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__/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__/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-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- 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__/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__/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__/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/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 +8 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- 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 +48 -7
- package/src/daemon/conversation-agent-loop.ts +56 -19
- 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 +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -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/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/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- 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 +28 -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 +9 -7
- 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 +1 -4
- 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/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- 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 +97 -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 +71 -8
- 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 +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -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 +4 -0
- 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 +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- 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/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/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 +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- 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/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 +10 -0
- 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
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* Regression tests for app surface refresh and eventing side effects in
|
|
3
3
|
* createToolExecutor (conversation-tool-setup.ts).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* skill-origin tools. These tests verify that contract.
|
|
5
|
+
* The app_refresh hook is the sole hook for app change refresh. These tests
|
|
6
|
+
* verify that app_refresh, app_create, and app_delete hooks fire correctly,
|
|
7
|
+
* and that removed hooks (app_update, app_file_edit, app_file_write) no
|
|
8
|
+
* longer trigger side effects.
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -69,6 +68,7 @@ function makeCtx(overrides: Partial<ToolSetupContext> = {}): ToolSetupContext {
|
|
|
69
68
|
{ surfaceType: SurfaceType; data: SurfaceData; title?: string }
|
|
70
69
|
>(),
|
|
71
70
|
surfaceUndoStacks: new Map(),
|
|
71
|
+
accumulatedSurfaceState: new Map(),
|
|
72
72
|
surfaceActionRequestIds: new Set<string>(),
|
|
73
73
|
currentTurnSurfaces: [],
|
|
74
74
|
isProcessing: () => false,
|
|
@@ -109,9 +109,9 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
109
109
|
updatePublishedSpy.mockClear();
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
// ──
|
|
112
|
+
// ── app_refresh ─────────────────────────────────────────────────────
|
|
113
113
|
|
|
114
|
-
describe("
|
|
114
|
+
describe("app_refresh", () => {
|
|
115
115
|
test("triggers refreshSurfacesForApp when result is not an error", async () => {
|
|
116
116
|
const ctx = makeCtx();
|
|
117
117
|
const executor = makeFakeExecutor({
|
|
@@ -129,7 +129,7 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
129
129
|
broadcastSpy,
|
|
130
130
|
);
|
|
131
131
|
|
|
132
|
-
await toolFn("
|
|
132
|
+
await toolFn("app_refresh", { app_id: "app-1" });
|
|
133
133
|
|
|
134
134
|
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
135
135
|
expect((refreshSpy.mock.calls as unknown[][])[0][0]).toBe(ctx);
|
|
@@ -150,7 +150,7 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
150
150
|
broadcastSpy,
|
|
151
151
|
);
|
|
152
152
|
|
|
153
|
-
await toolFn("
|
|
153
|
+
await toolFn("app_refresh", { app_id: "app-42" });
|
|
154
154
|
|
|
155
155
|
expect(broadcastSpy).toHaveBeenCalledTimes(1);
|
|
156
156
|
expect((broadcastSpy.mock.calls as unknown[][])[0][0]).toEqual({
|
|
@@ -172,7 +172,7 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
172
172
|
mock(() => {}),
|
|
173
173
|
);
|
|
174
174
|
|
|
175
|
-
await toolFn("
|
|
175
|
+
await toolFn("app_refresh", { app_id: "app-publish" });
|
|
176
176
|
|
|
177
177
|
// updatePublishedAppDeployment is called with void (fire-and-forget),
|
|
178
178
|
// so just verify it was invoked.
|
|
@@ -199,7 +199,7 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
199
199
|
broadcastSpy,
|
|
200
200
|
);
|
|
201
201
|
|
|
202
|
-
await toolFn("
|
|
202
|
+
await toolFn("app_refresh", { app_id: "app-err" });
|
|
203
203
|
|
|
204
204
|
expect(refreshSpy).not.toHaveBeenCalled();
|
|
205
205
|
expect(broadcastSpy).not.toHaveBeenCalled();
|
|
@@ -220,303 +220,13 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
220
220
|
broadcastSpy,
|
|
221
221
|
);
|
|
222
222
|
|
|
223
|
-
await toolFn("
|
|
223
|
+
await toolFn("app_refresh", {});
|
|
224
224
|
|
|
225
225
|
expect(refreshSpy).not.toHaveBeenCalled();
|
|
226
226
|
expect(broadcastSpy).not.toHaveBeenCalled();
|
|
227
227
|
});
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
// ── app_file_edit ───────────────────────────────────────────────────
|
|
231
|
-
|
|
232
|
-
describe("app_file_edit", () => {
|
|
233
|
-
test("triggers refreshSurfacesForApp with fileChange flag", async () => {
|
|
234
|
-
const ctx = makeCtx();
|
|
235
|
-
const executor = makeFakeExecutor({
|
|
236
|
-
content: '{"ok":true}',
|
|
237
|
-
isError: false,
|
|
238
|
-
});
|
|
239
|
-
const broadcastSpy = mock(() => {});
|
|
240
|
-
|
|
241
|
-
const toolFn = createToolExecutor(
|
|
242
|
-
executor as unknown as ToolExecutor,
|
|
243
|
-
noopPrompter,
|
|
244
|
-
noopSecretPrompter,
|
|
245
|
-
ctx,
|
|
246
|
-
noopLifecycleHandler,
|
|
247
|
-
broadcastSpy,
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
await toolFn("app_file_edit", {
|
|
251
|
-
app_id: "app-edit",
|
|
252
|
-
path: "index.html",
|
|
253
|
-
old_string: "old",
|
|
254
|
-
new_string: "new",
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
258
|
-
expect((refreshSpy.mock.calls as unknown[][])[0][1]).toBe("app-edit");
|
|
259
|
-
// Verify opts include fileChange: true
|
|
260
|
-
expect((refreshSpy.mock.calls as unknown[][])[0][2]).toEqual({
|
|
261
|
-
fileChange: true,
|
|
262
|
-
status: undefined,
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test("propagates status field through refresh opts", async () => {
|
|
267
|
-
const ctx = makeCtx();
|
|
268
|
-
const executor = makeFakeExecutor({
|
|
269
|
-
content: '{"ok":true}',
|
|
270
|
-
isError: false,
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
const toolFn = createToolExecutor(
|
|
274
|
-
executor as unknown as ToolExecutor,
|
|
275
|
-
noopPrompter,
|
|
276
|
-
noopSecretPrompter,
|
|
277
|
-
ctx,
|
|
278
|
-
noopLifecycleHandler,
|
|
279
|
-
mock(() => {}),
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
await toolFn("app_file_edit", {
|
|
283
|
-
app_id: "app-status",
|
|
284
|
-
path: "styles.css",
|
|
285
|
-
old_string: "x",
|
|
286
|
-
new_string: "y",
|
|
287
|
-
status: "updating styles",
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
291
|
-
expect((refreshSpy.mock.calls as unknown[][])[0][2]).toEqual({
|
|
292
|
-
fileChange: true,
|
|
293
|
-
status: "updating styles",
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test("broadcasts app_files_changed for file edit", async () => {
|
|
298
|
-
const ctx = makeCtx();
|
|
299
|
-
const executor = makeFakeExecutor({ content: "{}", isError: false });
|
|
300
|
-
const broadcastSpy = mock(() => {});
|
|
301
|
-
|
|
302
|
-
const toolFn = createToolExecutor(
|
|
303
|
-
executor as unknown as ToolExecutor,
|
|
304
|
-
noopPrompter,
|
|
305
|
-
noopSecretPrompter,
|
|
306
|
-
ctx,
|
|
307
|
-
noopLifecycleHandler,
|
|
308
|
-
broadcastSpy,
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
await toolFn("app_file_edit", {
|
|
312
|
-
app_id: "app-edit-bc",
|
|
313
|
-
path: "f",
|
|
314
|
-
old_string: "a",
|
|
315
|
-
new_string: "b",
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
expect(broadcastSpy).toHaveBeenCalledTimes(1);
|
|
319
|
-
expect((broadcastSpy.mock.calls as unknown[][])[0][0]).toEqual({
|
|
320
|
-
type: "app_files_changed",
|
|
321
|
-
appId: "app-edit-bc",
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
test("calls updatePublishedAppDeployment for file edit", async () => {
|
|
326
|
-
const ctx = makeCtx();
|
|
327
|
-
const executor = makeFakeExecutor({ content: "{}", isError: false });
|
|
328
|
-
|
|
329
|
-
const toolFn = createToolExecutor(
|
|
330
|
-
executor as unknown as ToolExecutor,
|
|
331
|
-
noopPrompter,
|
|
332
|
-
noopSecretPrompter,
|
|
333
|
-
ctx,
|
|
334
|
-
noopLifecycleHandler,
|
|
335
|
-
mock(() => {}),
|
|
336
|
-
);
|
|
337
|
-
|
|
338
|
-
await toolFn("app_file_edit", {
|
|
339
|
-
app_id: "app-pub-edit",
|
|
340
|
-
path: "f",
|
|
341
|
-
old_string: "a",
|
|
342
|
-
new_string: "b",
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
expect(updatePublishedSpy).toHaveBeenCalledTimes(1);
|
|
346
|
-
expect((updatePublishedSpy.mock.calls as unknown[][])[0][0]).toBe(
|
|
347
|
-
"app-pub-edit",
|
|
348
|
-
);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
test("skips side effects when result is an error", async () => {
|
|
352
|
-
const ctx = makeCtx();
|
|
353
|
-
const executor = makeFakeExecutor({ content: "Error", isError: true });
|
|
354
|
-
const broadcastSpy = mock(() => {});
|
|
355
|
-
|
|
356
|
-
const toolFn = createToolExecutor(
|
|
357
|
-
executor as unknown as ToolExecutor,
|
|
358
|
-
noopPrompter,
|
|
359
|
-
noopSecretPrompter,
|
|
360
|
-
ctx,
|
|
361
|
-
noopLifecycleHandler,
|
|
362
|
-
broadcastSpy,
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
await toolFn("app_file_edit", {
|
|
366
|
-
app_id: "app-err",
|
|
367
|
-
path: "f",
|
|
368
|
-
old_string: "a",
|
|
369
|
-
new_string: "b",
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
expect(refreshSpy).not.toHaveBeenCalled();
|
|
373
|
-
expect(broadcastSpy).not.toHaveBeenCalled();
|
|
374
|
-
expect(updatePublishedSpy).not.toHaveBeenCalled();
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
// ── app_file_write ──────────────────────────────────────────────────
|
|
379
|
-
|
|
380
|
-
describe("app_file_write", () => {
|
|
381
|
-
test("triggers refreshSurfacesForApp with fileChange flag", async () => {
|
|
382
|
-
const ctx = makeCtx();
|
|
383
|
-
const executor = makeFakeExecutor({
|
|
384
|
-
content: '{"written":true}',
|
|
385
|
-
isError: false,
|
|
386
|
-
});
|
|
387
|
-
const broadcastSpy = mock(() => {});
|
|
388
|
-
|
|
389
|
-
const toolFn = createToolExecutor(
|
|
390
|
-
executor as unknown as ToolExecutor,
|
|
391
|
-
noopPrompter,
|
|
392
|
-
noopSecretPrompter,
|
|
393
|
-
ctx,
|
|
394
|
-
noopLifecycleHandler,
|
|
395
|
-
broadcastSpy,
|
|
396
|
-
);
|
|
397
|
-
|
|
398
|
-
await toolFn("app_file_write", {
|
|
399
|
-
app_id: "app-write",
|
|
400
|
-
path: "new.html",
|
|
401
|
-
content: "<div/>",
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
405
|
-
expect((refreshSpy.mock.calls as unknown[][])[0][1]).toBe("app-write");
|
|
406
|
-
expect((refreshSpy.mock.calls as unknown[][])[0][2]).toEqual({
|
|
407
|
-
fileChange: true,
|
|
408
|
-
status: undefined,
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
test("propagates status field through refresh opts", async () => {
|
|
413
|
-
const ctx = makeCtx();
|
|
414
|
-
const executor = makeFakeExecutor({
|
|
415
|
-
content: '{"written":true}',
|
|
416
|
-
isError: false,
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
const toolFn = createToolExecutor(
|
|
420
|
-
executor as unknown as ToolExecutor,
|
|
421
|
-
noopPrompter,
|
|
422
|
-
noopSecretPrompter,
|
|
423
|
-
ctx,
|
|
424
|
-
noopLifecycleHandler,
|
|
425
|
-
mock(() => {}),
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
await toolFn("app_file_write", {
|
|
429
|
-
app_id: "app-ws",
|
|
430
|
-
path: "f.txt",
|
|
431
|
-
content: "hi",
|
|
432
|
-
status: "adding dark mode",
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
436
|
-
expect((refreshSpy.mock.calls as unknown[][])[0][2]).toEqual({
|
|
437
|
-
fileChange: true,
|
|
438
|
-
status: "adding dark mode",
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
test("broadcasts app_files_changed for file write", async () => {
|
|
443
|
-
const ctx = makeCtx();
|
|
444
|
-
const executor = makeFakeExecutor({ content: "{}", isError: false });
|
|
445
|
-
const broadcastSpy = mock(() => {});
|
|
446
|
-
|
|
447
|
-
const toolFn = createToolExecutor(
|
|
448
|
-
executor as unknown as ToolExecutor,
|
|
449
|
-
noopPrompter,
|
|
450
|
-
noopSecretPrompter,
|
|
451
|
-
ctx,
|
|
452
|
-
noopLifecycleHandler,
|
|
453
|
-
broadcastSpy,
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
await toolFn("app_file_write", {
|
|
457
|
-
app_id: "app-write-bc",
|
|
458
|
-
path: "f",
|
|
459
|
-
content: "x",
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
expect(broadcastSpy).toHaveBeenCalledTimes(1);
|
|
463
|
-
expect((broadcastSpy.mock.calls as unknown[][])[0][0]).toEqual({
|
|
464
|
-
type: "app_files_changed",
|
|
465
|
-
appId: "app-write-bc",
|
|
466
|
-
});
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test("calls updatePublishedAppDeployment for file write", async () => {
|
|
470
|
-
const ctx = makeCtx();
|
|
471
|
-
const executor = makeFakeExecutor({ content: "{}", isError: false });
|
|
472
|
-
|
|
473
|
-
const toolFn = createToolExecutor(
|
|
474
|
-
executor as unknown as ToolExecutor,
|
|
475
|
-
noopPrompter,
|
|
476
|
-
noopSecretPrompter,
|
|
477
|
-
ctx,
|
|
478
|
-
noopLifecycleHandler,
|
|
479
|
-
mock(() => {}),
|
|
480
|
-
);
|
|
481
|
-
|
|
482
|
-
await toolFn("app_file_write", {
|
|
483
|
-
app_id: "app-pub-write",
|
|
484
|
-
path: "f",
|
|
485
|
-
content: "x",
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
expect(updatePublishedSpy).toHaveBeenCalledTimes(1);
|
|
489
|
-
expect((updatePublishedSpy.mock.calls as unknown[][])[0][0]).toBe(
|
|
490
|
-
"app-pub-write",
|
|
491
|
-
);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
test("skips side effects when result is an error", async () => {
|
|
495
|
-
const ctx = makeCtx();
|
|
496
|
-
const executor = makeFakeExecutor({ content: "Error", isError: true });
|
|
497
|
-
const broadcastSpy = mock(() => {});
|
|
498
|
-
|
|
499
|
-
const toolFn = createToolExecutor(
|
|
500
|
-
executor as unknown as ToolExecutor,
|
|
501
|
-
noopPrompter,
|
|
502
|
-
noopSecretPrompter,
|
|
503
|
-
ctx,
|
|
504
|
-
noopLifecycleHandler,
|
|
505
|
-
broadcastSpy,
|
|
506
|
-
);
|
|
507
|
-
|
|
508
|
-
await toolFn("app_file_write", {
|
|
509
|
-
app_id: "app-err",
|
|
510
|
-
path: "f",
|
|
511
|
-
content: "x",
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
expect(refreshSpy).not.toHaveBeenCalled();
|
|
515
|
-
expect(broadcastSpy).not.toHaveBeenCalled();
|
|
516
|
-
expect(updatePublishedSpy).not.toHaveBeenCalled();
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
|
|
520
230
|
// ── app_create side effects ─────────────────────────────────────────
|
|
521
231
|
|
|
522
232
|
describe("app_create side effects", () => {
|
|
@@ -616,7 +326,7 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
616
326
|
|
|
617
327
|
describe("name-based hooks fire for skill-origin tools", () => {
|
|
618
328
|
test("hooks fire purely on tool name, regardless of tool origin", async () => {
|
|
619
|
-
// The key invariant: createToolExecutor uses `name === '
|
|
329
|
+
// The key invariant: createToolExecutor uses `name === 'app_refresh'`
|
|
620
330
|
// string comparison, not tool metadata or origin. This means skill-
|
|
621
331
|
// projected tools with the same name trigger the same afterExecute
|
|
622
332
|
// hooks as core tools.
|
|
@@ -633,22 +343,14 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
633
343
|
broadcastSpy,
|
|
634
344
|
);
|
|
635
345
|
|
|
636
|
-
// Simulate calling
|
|
637
|
-
for (const toolName of [
|
|
638
|
-
"app_update",
|
|
639
|
-
"app_file_edit",
|
|
640
|
-
"app_file_write",
|
|
641
|
-
]) {
|
|
346
|
+
// Simulate calling app_refresh by name (as the agent loop does)
|
|
347
|
+
for (const toolName of ["app_refresh"]) {
|
|
642
348
|
refreshSpy.mockClear();
|
|
643
349
|
broadcastSpy.mockClear();
|
|
644
350
|
updatePublishedSpy.mockClear();
|
|
645
351
|
|
|
646
352
|
await toolFn(toolName, {
|
|
647
353
|
app_id: "skill-app",
|
|
648
|
-
path: "f",
|
|
649
|
-
old_string: "a",
|
|
650
|
-
new_string: "b",
|
|
651
|
-
content: "x",
|
|
652
354
|
});
|
|
653
355
|
|
|
654
356
|
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
@@ -675,7 +377,15 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
675
377
|
broadcastSpy,
|
|
676
378
|
);
|
|
677
379
|
|
|
678
|
-
for (const toolName of [
|
|
380
|
+
for (const toolName of [
|
|
381
|
+
"read_file",
|
|
382
|
+
"write_file",
|
|
383
|
+
"shell",
|
|
384
|
+
"app_list",
|
|
385
|
+
"app_update",
|
|
386
|
+
"app_file_edit",
|
|
387
|
+
"app_file_write",
|
|
388
|
+
]) {
|
|
679
389
|
refreshSpy.mockClear();
|
|
680
390
|
broadcastSpy.mockClear();
|
|
681
391
|
updatePublishedSpy.mockClear();
|
|
@@ -706,7 +416,7 @@ describe("session-tool-setup app refresh side effects", () => {
|
|
|
706
416
|
);
|
|
707
417
|
|
|
708
418
|
// Should not throw even though broadcastToAllClients is undefined
|
|
709
|
-
const result = await toolFn("
|
|
419
|
+
const result = await toolFn("app_refresh", { app_id: "app-no-bc" });
|
|
710
420
|
|
|
711
421
|
expect(result.isError).toBe(false);
|
|
712
422
|
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
@@ -56,6 +56,7 @@ function makeCtx(overrides: Partial<ToolSetupContext> = {}): ToolSetupContext {
|
|
|
56
56
|
{ surfaceType: SurfaceType; data: SurfaceData; title?: string }
|
|
57
57
|
>(),
|
|
58
58
|
surfaceUndoStacks: new Map(),
|
|
59
|
+
accumulatedSurfaceState: new Map(),
|
|
59
60
|
surfaceActionRequestIds: new Set<string>(),
|
|
60
61
|
currentTurnSurfaces: [],
|
|
61
62
|
isProcessing: () => false,
|
|
@@ -56,6 +56,7 @@ function makeCtx(overrides: Partial<ToolSetupContext> = {}): ToolSetupContext {
|
|
|
56
56
|
{ surfaceType: SurfaceType; data: SurfaceData; title?: string }
|
|
57
57
|
>(),
|
|
58
58
|
surfaceUndoStacks: new Map(),
|
|
59
|
+
accumulatedSurfaceState: new Map(),
|
|
59
60
|
surfaceActionRequestIds: new Set<string>(),
|
|
60
61
|
currentTurnSurfaces: [],
|
|
61
62
|
isProcessing: () => false,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
5
|
|
|
3
6
|
import type { AgentEvent } from "../agent/loop.js";
|
|
7
|
+
import { getConversationDirName } from "../memory/conversation-disk-view.js";
|
|
4
8
|
import type { Message, ProviderResponse } from "../providers/types.js";
|
|
5
9
|
|
|
6
10
|
// ---------------------------------------------------------------------------
|
|
@@ -79,6 +83,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
79
83
|
getMessages: () => [],
|
|
80
84
|
getConversation: () => ({
|
|
81
85
|
id: "conv-1",
|
|
86
|
+
createdAt: Date.parse("2026-03-19T12:00:00.000Z"),
|
|
82
87
|
contextSummary: null,
|
|
83
88
|
contextCompactedMessageCount: 0,
|
|
84
89
|
contextCompactedAt: null,
|
|
@@ -175,7 +180,7 @@ import { Conversation } from "../daemon/conversation.js";
|
|
|
175
180
|
// Helpers
|
|
176
181
|
// ---------------------------------------------------------------------------
|
|
177
182
|
|
|
178
|
-
function makeConversation(): Conversation {
|
|
183
|
+
function makeConversation(workingDir = "/tmp"): Conversation {
|
|
179
184
|
const provider = {
|
|
180
185
|
name: "mock",
|
|
181
186
|
async sendMessage(): Promise<ProviderResponse> {
|
|
@@ -193,10 +198,17 @@ function makeConversation(): Conversation {
|
|
|
193
198
|
"system prompt",
|
|
194
199
|
4096,
|
|
195
200
|
() => {},
|
|
196
|
-
|
|
201
|
+
workingDir,
|
|
197
202
|
);
|
|
198
203
|
}
|
|
199
204
|
|
|
205
|
+
const conversationDirName = getConversationDirName(
|
|
206
|
+
"conv-1",
|
|
207
|
+
Date.parse("2026-03-19T12:00:00.000Z"),
|
|
208
|
+
);
|
|
209
|
+
const conversationPath = `conversations/${conversationDirName}/`;
|
|
210
|
+
const conversationAttachmentsPath = `${conversationPath}attachments/`;
|
|
211
|
+
|
|
200
212
|
// ---------------------------------------------------------------------------
|
|
201
213
|
// Tests
|
|
202
214
|
// ---------------------------------------------------------------------------
|
|
@@ -224,6 +236,12 @@ describe("Conversation workspace cache state", () => {
|
|
|
224
236
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
225
237
|
"</workspace_top_level>",
|
|
226
238
|
);
|
|
239
|
+
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
240
|
+
`Current conversation folder: ${conversationPath}`,
|
|
241
|
+
);
|
|
242
|
+
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
243
|
+
`Attachment files: ${conversationAttachmentsPath}`,
|
|
244
|
+
);
|
|
227
245
|
});
|
|
228
246
|
|
|
229
247
|
test("refreshWorkspaceTopLevelContextIfNeeded no-ops when not dirty and cache exists", () => {
|
|
@@ -257,4 +275,28 @@ describe("Conversation workspace cache state", () => {
|
|
|
257
275
|
);
|
|
258
276
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
259
277
|
});
|
|
278
|
+
|
|
279
|
+
test("workspace hints follow the resolved legacy directory when canonical is absent", () => {
|
|
280
|
+
const workspaceRoot = mkdtempSync(
|
|
281
|
+
join(tmpdir(), "conversation-workspace-cache-state-"),
|
|
282
|
+
);
|
|
283
|
+
const legacyDirName = `conv-1_2026-03-19T12-00-00.000Z`;
|
|
284
|
+
mkdirSync(join(workspaceRoot, "conversations", legacyDirName), {
|
|
285
|
+
recursive: true,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const tempConversation = makeConversation(workspaceRoot);
|
|
290
|
+
tempConversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
291
|
+
|
|
292
|
+
expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
|
|
293
|
+
`Current conversation folder: conversations/${legacyDirName}/`,
|
|
294
|
+
);
|
|
295
|
+
expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
|
|
296
|
+
`Attachment files: conversations/${legacyDirName}/attachments/`,
|
|
297
|
+
);
|
|
298
|
+
} finally {
|
|
299
|
+
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
260
302
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import type { AgentEvent } from "../agent/loop.js";
|
|
4
|
+
import { getConversationDirName } from "../memory/conversation-disk-view.js";
|
|
4
5
|
import type { Message, ProviderResponse } from "../providers/types.js";
|
|
5
6
|
|
|
6
7
|
// ---------------------------------------------------------------------------
|
|
@@ -115,6 +116,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
115
116
|
getMessages: () => [],
|
|
116
117
|
getConversation: () => ({
|
|
117
118
|
id: "conv-1",
|
|
119
|
+
createdAt: Date.parse("2026-03-19T12:00:00.000Z"),
|
|
118
120
|
contextSummary: null,
|
|
119
121
|
contextCompactedMessageCount: 0,
|
|
120
122
|
contextCompactedAt: null,
|
|
@@ -277,6 +279,13 @@ function messageText(message: Message): string {
|
|
|
277
279
|
.join("\n");
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
const conversationDirName = getConversationDirName(
|
|
283
|
+
"conv-1",
|
|
284
|
+
Date.parse("2026-03-19T12:00:00.000Z"),
|
|
285
|
+
);
|
|
286
|
+
const conversationPath = `conversations/${conversationDirName}/`;
|
|
287
|
+
const conversationAttachmentsPath = `${conversationPath}attachments/`;
|
|
288
|
+
|
|
280
289
|
// ---------------------------------------------------------------------------
|
|
281
290
|
// Tests
|
|
282
291
|
// ---------------------------------------------------------------------------
|
|
@@ -312,6 +321,8 @@ describe("Conversation workspace injection", () => {
|
|
|
312
321
|
const runtimeUser = runCalls[0][runCalls[0].length - 1];
|
|
313
322
|
const text = messageText(runtimeUser);
|
|
314
323
|
expect(text).toContain("Root: /tmp");
|
|
324
|
+
expect(text).toContain(`Current conversation folder: ${conversationPath}`);
|
|
325
|
+
expect(text).toContain(`Attachment files: ${conversationAttachmentsPath}`);
|
|
315
326
|
});
|
|
316
327
|
|
|
317
328
|
test("workspace context is prepended before user text", async () => {
|
|
@@ -251,6 +251,9 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
251
251
|
"tools/claude-code/claude-code.ts", // Claude Code tool API key lookup
|
|
252
252
|
"workspace/migrations/006-services-config.ts", // services config migration reads provider API keys
|
|
253
253
|
"cli/commands/avatar.ts", // CLI avatar generation API key lookup
|
|
254
|
+
"config/bundled-skills/slack/tools/shared.ts", // Slack skill bot token lookup
|
|
255
|
+
"daemon/conversation-process.ts", // masked provider key display
|
|
256
|
+
"daemon/handlers/config-model.ts", // masked provider key display
|
|
254
257
|
]);
|
|
255
258
|
|
|
256
259
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -102,13 +102,10 @@ mock.module("../daemon/handlers/config-slack-channel.js", () => ({
|
|
|
102
102
|
slackChannelConfigCalls.push({ botToken, appToken });
|
|
103
103
|
|
|
104
104
|
const { credentialKey } = await import("../security/credential-key.js");
|
|
105
|
-
const {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const { upsertCredentialMetadata } = await import(
|
|
110
|
-
"../tools/credentials/metadata-store.js"
|
|
111
|
-
);
|
|
105
|
+
const { getSecureKeyAsync, setSecureKeyAsync } =
|
|
106
|
+
await import("../security/secure-keys.js");
|
|
107
|
+
const { upsertCredentialMetadata } =
|
|
108
|
+
await import("../tools/credentials/metadata-store.js");
|
|
112
109
|
|
|
113
110
|
const hasExistingBotToken = !!(await getSecureKeyAsync(
|
|
114
111
|
credentialKey("slack_channel", "bot_token"),
|
|
@@ -646,9 +643,7 @@ describe("credential_store tool — prompt action", () => {
|
|
|
646
643
|
);
|
|
647
644
|
expect(appResult.isError).toBe(false);
|
|
648
645
|
expect(manualConnectionStore["slack_channel"]).toBeUndefined();
|
|
649
|
-
expect(slackChannelConfigCalls).toEqual([
|
|
650
|
-
{ appToken: "xapp-test-token" },
|
|
651
|
-
]);
|
|
646
|
+
expect(slackChannelConfigCalls).toEqual([{ appToken: "xapp-test-token" }]);
|
|
652
647
|
expect(appResult.content).toContain("connection incomplete");
|
|
653
648
|
|
|
654
649
|
const botResult = await credentialStoreTool.execute(
|