@vellumai/assistant 0.5.0 → 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__/assistant-feature-flags-integration.test.ts +7 -9
- 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-execution-feature-gates.test.ts +3 -3
- 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__/filesystem-tools.test.ts +4 -2
- 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 +103 -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__/skill-feature-flags-integration.test.ts +18 -17
- package/src/__tests__/skill-feature-flags.test.ts +13 -13
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
- 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__/system-prompt.test.ts +8 -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 +16 -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 +46 -42
- 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/history-repair.ts +28 -8
- 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 +62 -19
- package/src/prompts/system-prompt.ts +2 -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 +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 +4 -21
- 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,7 +2,6 @@ import { describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import type { AppDefinition } from "../memory/app-store.js";
|
|
4
4
|
import type { AppStore } from "../tools/apps/executors.js";
|
|
5
|
-
import type { EditEngineResult } from "../tools/shared/filesystem/edit-engine.js";
|
|
6
5
|
import type { ToolContext } from "../tools/types.js";
|
|
7
6
|
|
|
8
7
|
// ---------------------------------------------------------------------------
|
|
@@ -26,24 +25,11 @@ function makeMockStore(overrides: Partial<AppStore> = {}): AppStore {
|
|
|
26
25
|
return {
|
|
27
26
|
getApp: () => makeApp(),
|
|
28
27
|
listApps: () => [makeApp()],
|
|
29
|
-
queryAppRecords: () => [],
|
|
30
|
-
listAppFiles: () => ["index.html"],
|
|
31
|
-
readAppFile: () => "<h1>Hi</h1>",
|
|
32
28
|
createApp: (params) =>
|
|
33
29
|
makeApp({ name: params.name, description: params.description }),
|
|
34
30
|
updateApp: (id, updates) => makeApp({ id, ...updates }),
|
|
35
31
|
deleteApp: () => {},
|
|
36
32
|
writeAppFile: () => {},
|
|
37
|
-
editAppFile: () =>
|
|
38
|
-
({
|
|
39
|
-
ok: true,
|
|
40
|
-
updatedContent: "new",
|
|
41
|
-
matchCount: 1,
|
|
42
|
-
matchMethod: "exact",
|
|
43
|
-
similarity: 1,
|
|
44
|
-
actualOld: "old",
|
|
45
|
-
actualNew: "new",
|
|
46
|
-
}) as EditEngineResult,
|
|
47
33
|
...overrides,
|
|
48
34
|
};
|
|
49
35
|
}
|
|
@@ -66,6 +52,11 @@ const mockStore = makeMockStore();
|
|
|
66
52
|
mock.module("../memory/app-store.js", () => ({
|
|
67
53
|
...mockStore,
|
|
68
54
|
getAppsDir: () => "/tmp/test-apps",
|
|
55
|
+
getAppDirPath: (appId: string) => `/tmp/test-apps/${appId}`,
|
|
56
|
+
resolveAppDir: (id: string) => ({
|
|
57
|
+
dirName: id,
|
|
58
|
+
appDir: `/tmp/test-apps/${id}`,
|
|
59
|
+
}),
|
|
69
60
|
isMultifileApp: (app: AppDefinition) => app.formatVersion === 2,
|
|
70
61
|
}));
|
|
71
62
|
|
|
@@ -85,13 +76,7 @@ mock.module("../bundler/app-compiler.js", () => ({
|
|
|
85
76
|
|
|
86
77
|
import * as appCreateScript from "../config/bundled-skills/app-builder/tools/app-create.js";
|
|
87
78
|
import * as appDeleteScript from "../config/bundled-skills/app-builder/tools/app-delete.js";
|
|
88
|
-
import * as
|
|
89
|
-
import * as appFileListScript from "../config/bundled-skills/app-builder/tools/app-file-list.js";
|
|
90
|
-
import * as appFileReadScript from "../config/bundled-skills/app-builder/tools/app-file-read.js";
|
|
91
|
-
import * as appFileWriteScript from "../config/bundled-skills/app-builder/tools/app-file-write.js";
|
|
92
|
-
import * as appListScript from "../config/bundled-skills/app-builder/tools/app-list.js";
|
|
93
|
-
import * as appQueryScript from "../config/bundled-skills/app-builder/tools/app-query.js";
|
|
94
|
-
import * as appUpdateScript from "../config/bundled-skills/app-builder/tools/app-update.js";
|
|
79
|
+
import * as appRefreshScript from "../config/bundled-skills/app-builder/tools/app-refresh.js";
|
|
95
80
|
|
|
96
81
|
// ---------------------------------------------------------------------------
|
|
97
82
|
// Tests
|
|
@@ -142,58 +127,6 @@ describe("app-builder skill tool scripts", () => {
|
|
|
142
127
|
});
|
|
143
128
|
});
|
|
144
129
|
|
|
145
|
-
// ---- app-list ----------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
describe("app-list", () => {
|
|
148
|
-
test("exports a run function", () => {
|
|
149
|
-
expect(typeof appListScript.run).toBe("function");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("delegates to executeAppList and returns result", async () => {
|
|
153
|
-
const result = await appListScript.run({}, makeContext());
|
|
154
|
-
expect(result.isError).toBe(false);
|
|
155
|
-
const parsed = JSON.parse(result.content);
|
|
156
|
-
expect(Array.isArray(parsed)).toBe(true);
|
|
157
|
-
expect(parsed.length).toBeGreaterThan(0);
|
|
158
|
-
expect(parsed[0].id).toBe("app-1");
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// ---- app-query ---------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
describe("app-query", () => {
|
|
165
|
-
test("exports a run function", () => {
|
|
166
|
-
expect(typeof appQueryScript.run).toBe("function");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("delegates to executeAppQuery and returns result", async () => {
|
|
170
|
-
const result = await appQueryScript.run(
|
|
171
|
-
{ app_id: "app-1" },
|
|
172
|
-
makeContext(),
|
|
173
|
-
);
|
|
174
|
-
expect(result.isError).toBe(false);
|
|
175
|
-
expect(JSON.parse(result.content)).toEqual([]);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// ---- app-update --------------------------------------------------------
|
|
180
|
-
|
|
181
|
-
describe("app-update", () => {
|
|
182
|
-
test("exports a run function", () => {
|
|
183
|
-
expect(typeof appUpdateScript.run).toBe("function");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("delegates to executeAppUpdate and returns result", async () => {
|
|
187
|
-
const result = await appUpdateScript.run(
|
|
188
|
-
{ app_id: "app-1", name: "Updated" },
|
|
189
|
-
makeContext(),
|
|
190
|
-
);
|
|
191
|
-
expect(result.isError).toBe(false);
|
|
192
|
-
const parsed = JSON.parse(result.content);
|
|
193
|
-
expect(parsed.name).toBe("Updated");
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
130
|
// ---- app-delete --------------------------------------------------------
|
|
198
131
|
|
|
199
132
|
describe("app-delete", () => {
|
|
@@ -213,93 +146,22 @@ describe("app-builder skill tool scripts", () => {
|
|
|
213
146
|
});
|
|
214
147
|
});
|
|
215
148
|
|
|
216
|
-
// ---- app-
|
|
149
|
+
// ---- app-refresh -------------------------------------------------------
|
|
217
150
|
|
|
218
|
-
describe("app-
|
|
151
|
+
describe("app-refresh", () => {
|
|
219
152
|
test("exports a run function", () => {
|
|
220
|
-
expect(typeof
|
|
153
|
+
expect(typeof appRefreshScript.run).toBe("function");
|
|
221
154
|
});
|
|
222
155
|
|
|
223
|
-
test("delegates to
|
|
224
|
-
const result = await
|
|
156
|
+
test("delegates to executeAppRefresh and returns result", async () => {
|
|
157
|
+
const result = await appRefreshScript.run(
|
|
225
158
|
{ app_id: "app-1" },
|
|
226
159
|
makeContext(),
|
|
227
160
|
);
|
|
228
161
|
expect(result.isError).toBe(false);
|
|
229
|
-
expect(JSON.parse(result.content)).toEqual(["index.html"]);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// ---- app-file-read -----------------------------------------------------
|
|
234
|
-
|
|
235
|
-
describe("app-file-read", () => {
|
|
236
|
-
test("exports a run function", () => {
|
|
237
|
-
expect(typeof appFileReadScript.run).toBe("function");
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("delegates to executeAppFileRead and returns formatted content", async () => {
|
|
241
|
-
const result = await appFileReadScript.run(
|
|
242
|
-
{ app_id: "app-1", path: "index.html" },
|
|
243
|
-
makeContext(),
|
|
244
|
-
);
|
|
245
|
-
expect(result.isError).toBe(false);
|
|
246
|
-
// Content should include line numbers
|
|
247
|
-
expect(result.content).toContain("1\t");
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// ---- app-file-edit -----------------------------------------------------
|
|
252
|
-
|
|
253
|
-
describe("app-file-edit", () => {
|
|
254
|
-
test("exports a run function", () => {
|
|
255
|
-
expect(typeof appFileEditScript.run).toBe("function");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("delegates to executeAppFileEdit and returns result", async () => {
|
|
259
|
-
const result = await appFileEditScript.run(
|
|
260
|
-
{
|
|
261
|
-
app_id: "app-1",
|
|
262
|
-
path: "index.html",
|
|
263
|
-
old_string: "old",
|
|
264
|
-
new_string: "new",
|
|
265
|
-
},
|
|
266
|
-
makeContext(),
|
|
267
|
-
);
|
|
268
|
-
expect(result.isError).toBe(false);
|
|
269
162
|
const parsed = JSON.parse(result.content);
|
|
270
|
-
expect(parsed.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
test("returns error when old_string is empty", async () => {
|
|
274
|
-
const result = await appFileEditScript.run(
|
|
275
|
-
{
|
|
276
|
-
app_id: "app-1",
|
|
277
|
-
path: "index.html",
|
|
278
|
-
old_string: "",
|
|
279
|
-
new_string: "new",
|
|
280
|
-
},
|
|
281
|
-
makeContext(),
|
|
282
|
-
);
|
|
283
|
-
expect(result.isError).toBe(true);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// ---- app-file-write ----------------------------------------------------
|
|
288
|
-
|
|
289
|
-
describe("app-file-write", () => {
|
|
290
|
-
test("exports a run function", () => {
|
|
291
|
-
expect(typeof appFileWriteScript.run).toBe("function");
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test("delegates to executeAppFileWrite and returns result", async () => {
|
|
295
|
-
const result = await appFileWriteScript.run(
|
|
296
|
-
{ app_id: "app-1", path: "new.html", content: "<div/>" },
|
|
297
|
-
makeContext(),
|
|
298
|
-
);
|
|
299
|
-
expect(result.isError).toBe(false);
|
|
300
|
-
const parsed = JSON.parse(result.content);
|
|
301
|
-
expect(parsed.written).toBe(true);
|
|
302
|
-
expect(parsed.path).toBe("new.html");
|
|
163
|
+
expect(parsed.refreshed).toBe(true);
|
|
164
|
+
expect(parsed.appId).toBe("app-1");
|
|
303
165
|
});
|
|
304
166
|
});
|
|
305
167
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Guard test: files outside app-store.ts must not import getAppsDir and use
|
|
6
|
+
* it to construct paths with an app ID. All app path construction must go
|
|
7
|
+
* through getAppDirPath() or resolveAppDir() from app-store.ts.
|
|
8
|
+
*
|
|
9
|
+
* This prevents regressions where new code bypasses the dirName-based path
|
|
10
|
+
* resolution and constructs UUID-based paths directly.
|
|
11
|
+
*
|
|
12
|
+
* Allowlist: only app-store.ts itself, app-git-service.ts (uses getAppsDir
|
|
13
|
+
* for the git repo root, not for per-app paths), and workspace migrations
|
|
14
|
+
* (self-contained, don't import from app-store).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Files that are permitted to import getAppsDir. */
|
|
18
|
+
const ALLOWLIST = new Set([
|
|
19
|
+
"assistant/src/memory/app-store.ts", // defines getAppsDir
|
|
20
|
+
"assistant/src/memory/app-git-service.ts", // uses getAppsDir for git repo root, not per-app paths
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function isTestFile(filePath: string): boolean {
|
|
24
|
+
return (
|
|
25
|
+
filePath.includes("/__tests__/") ||
|
|
26
|
+
filePath.endsWith(".test.ts") ||
|
|
27
|
+
filePath.endsWith(".test.js") ||
|
|
28
|
+
filePath.endsWith(".spec.ts")
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isMigrationFile(filePath: string): boolean {
|
|
33
|
+
return filePath.includes("/workspace/migrations/");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("app directory path construction guard", () => {
|
|
37
|
+
test("no non-allowlisted production files import getAppsDir", () => {
|
|
38
|
+
// Search for files that import getAppsDir (not just mention it in comments)
|
|
39
|
+
const pattern = "import.*getAppsDir.*from|getAppsDir\\(\\)";
|
|
40
|
+
|
|
41
|
+
let grepOutput = "";
|
|
42
|
+
try {
|
|
43
|
+
grepOutput = execSync(`git grep -lE '${pattern}' -- '*.ts'`, {
|
|
44
|
+
encoding: "utf-8",
|
|
45
|
+
cwd: process.cwd() + "/..",
|
|
46
|
+
}).trim();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
// Exit code 1 means no matches
|
|
49
|
+
if ((err as { status?: number }).status === 1) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const files = grepOutput.split("\n").filter((f) => f.length > 0);
|
|
56
|
+
const violations = files.filter((f) => {
|
|
57
|
+
if (isTestFile(f)) return false;
|
|
58
|
+
if (isMigrationFile(f)) return false;
|
|
59
|
+
if (ALLOWLIST.has(f)) return false;
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (violations.length > 0) {
|
|
64
|
+
const message = [
|
|
65
|
+
"Found non-allowlisted production files importing or using getAppsDir().",
|
|
66
|
+
"Use getAppDirPath(appId) or resolveAppDir(appId) from app-store.ts instead.",
|
|
67
|
+
"",
|
|
68
|
+
"Violations:",
|
|
69
|
+
...violations.map((f) => ` - ${f}`),
|
|
70
|
+
"",
|
|
71
|
+
"To fix: replace getAppsDir() + appId path construction with getAppDirPath(appId).",
|
|
72
|
+
"If this is an intentional exception, add it to the ALLOWLIST in app-dir-path-guard.test.ts.",
|
|
73
|
+
].join("\n");
|
|
74
|
+
|
|
75
|
+
expect(violations, message).toEqual([]);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -11,14 +11,7 @@ mock.module("../bundler/app-compiler.js", () => ({
|
|
|
11
11
|
|
|
12
12
|
import type { AppDefinition } from "../memory/app-store.js";
|
|
13
13
|
import type { AppStore } from "../tools/apps/executors.js";
|
|
14
|
-
import {
|
|
15
|
-
executeAppCreate,
|
|
16
|
-
executeAppFileEdit,
|
|
17
|
-
executeAppFileList,
|
|
18
|
-
executeAppFileRead,
|
|
19
|
-
executeAppFileWrite,
|
|
20
|
-
resolveAppFilePath,
|
|
21
|
-
} from "../tools/apps/executors.js";
|
|
14
|
+
import { executeAppCreate } from "../tools/apps/executors.js";
|
|
22
15
|
|
|
23
16
|
// ---------------------------------------------------------------------------
|
|
24
17
|
// Helpers
|
|
@@ -60,298 +53,15 @@ function mockStore(
|
|
|
60
53
|
return {
|
|
61
54
|
getApp: (id: string) => (id === app.id ? app : null),
|
|
62
55
|
listApps: () => [app],
|
|
63
|
-
queryAppRecords: () => [],
|
|
64
|
-
listAppFiles: () => Object.keys(files).sort(),
|
|
65
|
-
readAppFile: (_appId: string, path: string) => {
|
|
66
|
-
if (!(path in files)) throw new Error(`File not found: ${path}`);
|
|
67
|
-
return files[path];
|
|
68
|
-
},
|
|
69
56
|
createApp: () => app,
|
|
70
57
|
updateApp: () => app,
|
|
71
58
|
deleteApp: () => {},
|
|
72
59
|
writeAppFile: (_appId: string, path: string, content: string) => {
|
|
73
60
|
files[path] = content;
|
|
74
61
|
},
|
|
75
|
-
editAppFile: (
|
|
76
|
-
_appId: string,
|
|
77
|
-
path: string,
|
|
78
|
-
oldStr: string,
|
|
79
|
-
newStr: string,
|
|
80
|
-
_replaceAll?: boolean,
|
|
81
|
-
) => {
|
|
82
|
-
if (!(path in files)) throw new Error(`File not found: ${path}`);
|
|
83
|
-
const content = files[path];
|
|
84
|
-
if (!content.includes(oldStr)) {
|
|
85
|
-
return { ok: false as const, reason: "not_found" as const };
|
|
86
|
-
}
|
|
87
|
-
const updated = content.replace(oldStr, newStr);
|
|
88
|
-
files[path] = updated;
|
|
89
|
-
return {
|
|
90
|
-
ok: true as const,
|
|
91
|
-
updatedContent: updated,
|
|
92
|
-
matchCount: 1,
|
|
93
|
-
matchMethod: "exact" as const,
|
|
94
|
-
similarity: 1,
|
|
95
|
-
actualOld: oldStr,
|
|
96
|
-
actualNew: newStr,
|
|
97
|
-
};
|
|
98
|
-
},
|
|
99
62
|
};
|
|
100
63
|
}
|
|
101
64
|
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
// resolveAppFilePath
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
|
|
106
|
-
describe("resolveAppFilePath", () => {
|
|
107
|
-
test("prepends src/ for multifile app with plain path", () => {
|
|
108
|
-
const app = makeMultifileApp();
|
|
109
|
-
expect(resolveAppFilePath(app, "main.tsx")).toBe("src/main.tsx");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("prepends src/ for nested path in multifile app", () => {
|
|
113
|
-
const app = makeMultifileApp();
|
|
114
|
-
expect(resolveAppFilePath(app, "components/Header.tsx")).toBe(
|
|
115
|
-
"src/components/Header.tsx",
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("passes through src/ prefix unchanged for multifile app", () => {
|
|
120
|
-
const app = makeMultifileApp();
|
|
121
|
-
expect(resolveAppFilePath(app, "src/main.tsx")).toBe("src/main.tsx");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("passes through dist/ prefix unchanged for multifile app", () => {
|
|
125
|
-
const app = makeMultifileApp();
|
|
126
|
-
expect(resolveAppFilePath(app, "dist/bundle.js")).toBe("dist/bundle.js");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("passes through records/ prefix unchanged for multifile app", () => {
|
|
130
|
-
const app = makeMultifileApp();
|
|
131
|
-
expect(resolveAppFilePath(app, "records/data.json")).toBe(
|
|
132
|
-
"records/data.json",
|
|
133
|
-
);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test("does not modify path for legacy app", () => {
|
|
137
|
-
const app = makeLegacyApp();
|
|
138
|
-
expect(resolveAppFilePath(app, "main.tsx")).toBe("main.tsx");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("does not modify path for legacy app (formatVersion undefined)", () => {
|
|
142
|
-
const app = makeLegacyApp({ formatVersion: undefined });
|
|
143
|
-
expect(resolveAppFilePath(app, "styles.css")).toBe("styles.css");
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("does not modify path for legacy app (formatVersion 1)", () => {
|
|
147
|
-
const app = makeLegacyApp({ formatVersion: 1 });
|
|
148
|
-
expect(resolveAppFilePath(app, "index.html")).toBe("index.html");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("strips ./ prefix and prepends src/ for multifile app", () => {
|
|
152
|
-
const app = makeMultifileApp();
|
|
153
|
-
expect(resolveAppFilePath(app, "./main.tsx")).toBe("src/main.tsx");
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("strips ./ prefix for known top-level dir in multifile app", () => {
|
|
157
|
-
const app = makeMultifileApp();
|
|
158
|
-
expect(resolveAppFilePath(app, "./src/main.tsx")).toBe("src/main.tsx");
|
|
159
|
-
expect(resolveAppFilePath(app, "./dist/bundle.js")).toBe("dist/bundle.js");
|
|
160
|
-
expect(resolveAppFilePath(app, "./records/data.json")).toBe(
|
|
161
|
-
"records/data.json",
|
|
162
|
-
);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
// executeAppFileWrite
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
|
-
|
|
170
|
-
describe("executeAppFileWrite", () => {
|
|
171
|
-
test("resolves plain path to src/ for multifile app", () => {
|
|
172
|
-
const files: Record<string, string> = {};
|
|
173
|
-
const app = makeMultifileApp();
|
|
174
|
-
const store = mockStore(app, files);
|
|
175
|
-
|
|
176
|
-
const result = executeAppFileWrite(
|
|
177
|
-
{ app_id: app.id, path: "main.tsx", content: "export default 1;" },
|
|
178
|
-
store,
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
expect(result.isError).toBe(false);
|
|
182
|
-
expect(JSON.parse(result.content).path).toBe("src/main.tsx");
|
|
183
|
-
expect(files["src/main.tsx"]).toBe("export default 1;");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("passes through src/ path unchanged for multifile app", () => {
|
|
187
|
-
const files: Record<string, string> = {};
|
|
188
|
-
const app = makeMultifileApp();
|
|
189
|
-
const store = mockStore(app, files);
|
|
190
|
-
|
|
191
|
-
const result = executeAppFileWrite(
|
|
192
|
-
{ app_id: app.id, path: "src/main.tsx", content: "export default 2;" },
|
|
193
|
-
store,
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
expect(result.isError).toBe(false);
|
|
197
|
-
expect(JSON.parse(result.content).path).toBe("src/main.tsx");
|
|
198
|
-
expect(files["src/main.tsx"]).toBe("export default 2;");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("does not modify path for legacy app", () => {
|
|
202
|
-
const files: Record<string, string> = {};
|
|
203
|
-
const app = makeLegacyApp();
|
|
204
|
-
const store = mockStore(app, files);
|
|
205
|
-
|
|
206
|
-
const result = executeAppFileWrite(
|
|
207
|
-
{ app_id: app.id, path: "index.html", content: "<html></html>" },
|
|
208
|
-
store,
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
expect(result.isError).toBe(false);
|
|
212
|
-
expect(JSON.parse(result.content).path).toBe("index.html");
|
|
213
|
-
expect(files["index.html"]).toBe("<html></html>");
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// ---------------------------------------------------------------------------
|
|
218
|
-
// executeAppFileRead
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
|
|
221
|
-
describe("executeAppFileRead", () => {
|
|
222
|
-
test("resolves plain path to src/ for multifile app", () => {
|
|
223
|
-
const app = makeMultifileApp();
|
|
224
|
-
const store = mockStore(app, { "src/main.tsx": "line1\nline2" });
|
|
225
|
-
|
|
226
|
-
const result = executeAppFileRead(
|
|
227
|
-
{ app_id: app.id, path: "main.tsx" },
|
|
228
|
-
store,
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
expect(result.isError).toBe(false);
|
|
232
|
-
expect(result.content).toContain("line1");
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test("can read dist/ files explicitly for multifile app", () => {
|
|
236
|
-
const app = makeMultifileApp();
|
|
237
|
-
const store = mockStore(app, { "dist/bundle.js": "bundled code" });
|
|
238
|
-
|
|
239
|
-
const result = executeAppFileRead(
|
|
240
|
-
{ app_id: app.id, path: "dist/bundle.js" },
|
|
241
|
-
store,
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
expect(result.isError).toBe(false);
|
|
245
|
-
expect(result.content).toContain("bundled code");
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test("does not modify path for legacy app", () => {
|
|
249
|
-
const app = makeLegacyApp();
|
|
250
|
-
const store = mockStore(app, { "index.html": "<html>hello</html>" });
|
|
251
|
-
|
|
252
|
-
const result = executeAppFileRead(
|
|
253
|
-
{ app_id: app.id, path: "index.html" },
|
|
254
|
-
store,
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
expect(result.isError).toBe(false);
|
|
258
|
-
expect(result.content).toContain("<html>hello</html>");
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// ---------------------------------------------------------------------------
|
|
263
|
-
// executeAppFileEdit
|
|
264
|
-
// ---------------------------------------------------------------------------
|
|
265
|
-
|
|
266
|
-
describe("executeAppFileEdit", () => {
|
|
267
|
-
test("resolves plain path to src/ for multifile app", () => {
|
|
268
|
-
const files = { "src/main.tsx": "const x = 1;" };
|
|
269
|
-
const app = makeMultifileApp();
|
|
270
|
-
const store = mockStore(app, files);
|
|
271
|
-
|
|
272
|
-
const result = executeAppFileEdit(
|
|
273
|
-
{
|
|
274
|
-
app_id: app.id,
|
|
275
|
-
path: "main.tsx",
|
|
276
|
-
old_string: "const x = 1;",
|
|
277
|
-
new_string: "const x = 2;",
|
|
278
|
-
},
|
|
279
|
-
store,
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
expect(result.isError).toBe(false);
|
|
283
|
-
expect(files["src/main.tsx"]).toBe("const x = 2;");
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("does not modify path for legacy app", () => {
|
|
287
|
-
const files = { "index.html": "<p>old</p>" };
|
|
288
|
-
const app = makeLegacyApp();
|
|
289
|
-
const store = mockStore(app, files);
|
|
290
|
-
|
|
291
|
-
const result = executeAppFileEdit(
|
|
292
|
-
{
|
|
293
|
-
app_id: app.id,
|
|
294
|
-
path: "index.html",
|
|
295
|
-
old_string: "<p>old</p>",
|
|
296
|
-
new_string: "<p>new</p>",
|
|
297
|
-
},
|
|
298
|
-
store,
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
expect(result.isError).toBe(false);
|
|
302
|
-
expect(files["index.html"]).toBe("<p>new</p>");
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// ---------------------------------------------------------------------------
|
|
307
|
-
// executeAppFileList
|
|
308
|
-
// ---------------------------------------------------------------------------
|
|
309
|
-
|
|
310
|
-
describe("executeAppFileList", () => {
|
|
311
|
-
test("returns clean file paths and separate buildOutput for multifile app", () => {
|
|
312
|
-
const app = makeMultifileApp();
|
|
313
|
-
const store = mockStore(app, {
|
|
314
|
-
"src/main.tsx": "",
|
|
315
|
-
"src/components/Header.tsx": "",
|
|
316
|
-
"dist/index.html": "",
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const result = executeAppFileList({ app_id: app.id }, store);
|
|
320
|
-
const parsed = JSON.parse(result.content) as {
|
|
321
|
-
files: string[];
|
|
322
|
-
buildOutput: string[];
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
// File paths must be clean — no annotations appended
|
|
326
|
-
expect(parsed.files).toContain("src/main.tsx");
|
|
327
|
-
expect(parsed.files).toContain("src/components/Header.tsx");
|
|
328
|
-
expect(parsed.files).toContain("dist/index.html");
|
|
329
|
-
expect(
|
|
330
|
-
parsed.files.every((f: string) => !f.includes("[build output]")),
|
|
331
|
-
).toBe(true);
|
|
332
|
-
|
|
333
|
-
// Build output files listed separately
|
|
334
|
-
expect(parsed.buildOutput).toEqual(["dist/index.html"]);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
test("does not annotate files for legacy app", () => {
|
|
338
|
-
const app = makeLegacyApp();
|
|
339
|
-
const store = mockStore(app, {
|
|
340
|
-
"index.html": "",
|
|
341
|
-
"styles.css": "",
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
const result = executeAppFileList({ app_id: app.id }, store);
|
|
345
|
-
const parsed = JSON.parse(result.content) as string[];
|
|
346
|
-
|
|
347
|
-
expect(parsed).toContain("index.html");
|
|
348
|
-
expect(parsed).toContain("styles.css");
|
|
349
|
-
expect(parsed.every((f: string) => !f.includes("[build output]"))).toBe(
|
|
350
|
-
true,
|
|
351
|
-
);
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
|
|
355
65
|
// ---------------------------------------------------------------------------
|
|
356
66
|
// executeAppCreate
|
|
357
67
|
// ---------------------------------------------------------------------------
|
|
@@ -14,7 +14,7 @@ mock.module("../util/platform.js", () => ({
|
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
// Re-import after mocking so modules use our temp dir
|
|
17
|
-
const { createApp, updateApp,
|
|
17
|
+
const { createApp, updateApp, getAppDirPath } =
|
|
18
18
|
await import("../memory/app-store.js");
|
|
19
19
|
const {
|
|
20
20
|
getAppHistory,
|
|
@@ -147,7 +147,7 @@ describe("App Git History", () => {
|
|
|
147
147
|
|
|
148
148
|
// Current file should show new content
|
|
149
149
|
const currentContent = readFileSync(
|
|
150
|
-
join(
|
|
150
|
+
join(getAppDirPath(app.id), "index.html"),
|
|
151
151
|
"utf-8",
|
|
152
152
|
);
|
|
153
153
|
expect(currentContent).toContain("version two");
|
|
@@ -169,7 +169,7 @@ describe("App Git History", () => {
|
|
|
169
169
|
|
|
170
170
|
// Verify current content is "new content"
|
|
171
171
|
let current = readFileSync(
|
|
172
|
-
join(
|
|
172
|
+
join(getAppDirPath(app.id), "index.html"),
|
|
173
173
|
"utf-8",
|
|
174
174
|
);
|
|
175
175
|
expect(current).toContain("new content");
|
|
@@ -178,7 +178,7 @@ describe("App Git History", () => {
|
|
|
178
178
|
await restoreAppVersion(app.id, originalHash);
|
|
179
179
|
|
|
180
180
|
// Verify content is restored
|
|
181
|
-
current = readFileSync(join(
|
|
181
|
+
current = readFileSync(join(getAppDirPath(app.id), "index.html"), "utf-8");
|
|
182
182
|
expect(current).toContain("original content");
|
|
183
183
|
|
|
184
184
|
// Verify a restore commit was created
|
|
@@ -37,6 +37,7 @@ const apps = new Map<string, typeof legacyApp | typeof multifileApp>([
|
|
|
37
37
|
mock.module("../memory/app-store.js", () => ({
|
|
38
38
|
getApp: (id: string) => apps.get(id) ?? null,
|
|
39
39
|
getAppsDir: () => "/fake/apps",
|
|
40
|
+
getAppDirPath: (appId: string) => `/fake/apps/${appId}`,
|
|
40
41
|
isMultifileApp: (app: Record<string, unknown>) => app.formatVersion === 2,
|
|
41
42
|
}));
|
|
42
43
|
|