diff --git a/package.json b/package.json index 0d765662d1e..7555a607efc 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "deploy:boxel-host": "pnpm run build-common-deps && cd packages/host && BASE_REALM_HOSTING_DISABLED=true NODE_OPTIONS='--max_old_space_size=8192' pnpm exec ember deploy", "deploy:boxel-host:preview-staging": "pnpm run build-common-deps && cd packages/host && BASE_REALM_HOSTING_DISABLED=true NODE_OPTIONS='--max_old_space_size=8192' pnpm exec ember deploy s3-preview-staging --verbose", "deploy:boxel-host:preview-production": "pnpm run build-common-deps && cd packages/host && BASE_REALM_HOSTING_DISABLED=true NODE_OPTIONS='--max_old_space_size=8192' pnpm exec ember deploy s3-preview-production --verbose", - "deploy:boxel-ui":"pnpm run build-common-deps && cd packages/boxel-ui/test-app && pnpm exec ember deploy", + "deploy:boxel-ui": "pnpm run build-common-deps && cd packages/boxel-ui/test-app && pnpm exec ember deploy", "deploy:boxel-ui:preview-staging": "pnpm run build-common-deps && cd packages/boxel-ui/test-app && pnpm exec ember deploy s3-preview-staging --verbose", "lint": "pnpm run --filter './packages/**' --if-present -r lint", "lint:fix": "pnpm run --filter './packages/**' --if-present -r lint:fix", @@ -28,7 +28,8 @@ "@glimmer/tracking>@glimmer/validator": "0.84.3", "@glimmer/component": "^2.0.0", "jsesc": "^3.0.0", - "ember-modifier": "^4.1.0" + "ember-modifier": "^4.1.0", + "tracked-built-ins": "^4.1.2" }, "peerDependencyRules": { "allowedVersions": { diff --git a/packages/base/components/cards-grid-layout.gts b/packages/base/components/cards-grid-layout.gts index 4472df63225..67981c216e6 100644 --- a/packages/base/components/cards-grid-layout.gts +++ b/packages/base/components/cards-grid-layout.gts @@ -20,7 +20,7 @@ import { type Sort, } from '@cardstack/runtime-common'; -import type { CardContext, BoxComponent } from '../card-api'; +import type { BoxComponent, CardContext } from '../card-api'; import CardList from './card-list'; import { htmlSafe } from '@ember/template'; @@ -150,7 +150,7 @@ export default class CardsGridLayout extends Component { {{#if (eq @activeFilter.displayName 'Highlights')}}
- {{#if this.getAiAppGeneratorCard}} + {{#if this.aiAppGeneratorCard}}
{ class='highlights-card-container' data-test-highlights-card-container='ai-app-generator' > - +
{{/if}} - {{#if this.getWelcomeToBoxelCard}} + {{#if this.welcomeToBoxelCard}}
{ data-test-highlights-card-container='welcome-to-boxel' >
- +
{{/if}} - {{#if this.getCommunityCards}} + {{#if this.communityCards}}
{ class='section-header' data-test-section-header='join-the-community' >JOIN THE COMMUNITY - +
{{/if}} @@ -347,15 +347,15 @@ export default class CardsGridLayout extends Component { return this.args.activeFilter.displayName !== 'Highlights'; } - private get getWelcomeToBoxelCard() { + private get welcomeToBoxelCard() { return this.args.activeFilter.cards?.[0]; } - private get getAiAppGeneratorCard() { + private get aiAppGeneratorCard() { return this.args.activeFilter.cards?.[1]; } - private get getCommunityCards() { + private get communityCards() { return this.args.activeFilter.cards?.[2]; } diff --git a/packages/base/field-component.gts b/packages/base/field-component.gts index 650971ee6ab..019d6457b9c 100644 --- a/packages/base/field-component.gts +++ b/packages/base/field-component.gts @@ -86,18 +86,14 @@ export class CardContextConsumer extends Component }; } - + } export class CardCrudFunctionsConsumer extends Component { @consume(CardCrudFunctionsContextName) declare cardCrudFunctions: CardCrudFunctions; - + } interface DefaultFormatConsumerSignature { @@ -113,9 +109,7 @@ export class DefaultFormatsConsumer extends Component - {{yield this.effectiveDefaultFormats}} - + } interface DefaultFormatsProviderSignature { @@ -137,9 +131,7 @@ interface PermissionsConsumerSignature { export class PermissionsConsumer extends Component { @consume(PermissionsContextName) declare permissions: Permissions | undefined; - + } const componentCache = initSharedState( @@ -527,7 +519,11 @@ function getFields(card: typeof CardDef): { } return [[maybeFieldName, maybeField]]; }); - fields = { ...fields, ...Object.fromEntries(currentFields) }; + fields = Object.assign( + Object.create(null), + fields, + Object.fromEntries(currentFields), + ); obj = Reflect.getPrototypeOf(obj); } return fields; diff --git a/packages/base/field-support.ts b/packages/base/field-support.ts index 6ff64d4db8d..66ee5c93c35 100644 --- a/packages/base/field-support.ts +++ b/packages/base/field-support.ts @@ -280,7 +280,11 @@ export function getFields( } return [[maybeFieldName, maybeField]]; }); - fields = { ...fields, ...Object.fromEntries(currentFields) }; + fields = Object.assign( + Object.create(null), + fields, + Object.fromEntries(currentFields), + ); obj = Reflect.getPrototypeOf(obj); } return fields; diff --git a/packages/boxel-icons/unpublished-development-types/index.d.ts b/packages/boxel-icons/unpublished-development-types/index.d.ts index 4f7deab2532..1afd42fe9be 100644 --- a/packages/boxel-icons/unpublished-development-types/index.d.ts +++ b/packages/boxel-icons/unpublished-development-types/index.d.ts @@ -2,7 +2,7 @@ // These will *not* be published as part of your addon, so be careful that your published code does not rely on them! import '@glint/environment-ember-loose'; -import 'ember-source/types/stable'; +import 'ember-source/types'; declare module '@glint/environment-ember-loose/registry' { export default interface Registry { diff --git a/packages/boxel-motion/addon/src/types/@cardstack/boxel-motion/index.d.ts b/packages/boxel-motion/addon/src/types/@cardstack/boxel-motion/index.d.ts new file mode 100644 index 00000000000..a2e59775409 --- /dev/null +++ b/packages/boxel-motion/addon/src/types/@cardstack/boxel-motion/index.d.ts @@ -0,0 +1,3 @@ +import 'ember-source/types'; + +export {}; diff --git a/packages/boxel-motion/test-app/tests/test-helper.js b/packages/boxel-motion/test-app/tests/test-helper.js new file mode 100644 index 00000000000..0892b1fb6e8 --- /dev/null +++ b/packages/boxel-motion/test-app/tests/test-helper.js @@ -0,0 +1,14 @@ +import { setApplication } from '@ember/test-helpers'; +import Application from 'boxel-motion-test-app/app'; +import config from 'boxel-motion-test-app/config/environment'; +import { start, setupEmberOnerrorValidation } from 'ember-qunit'; +import { loadTests } from 'ember-qunit/test-loader'; +import * as QUnit from 'qunit'; +import { setup } from 'qunit-dom'; + +setApplication(Application.create(config.APP)); + +setup(QUnit.assert); +setupEmberOnerrorValidation(); +loadTests(); +start(); diff --git a/packages/boxel-motion/test-app/types/boxel-motion-demo-app/index.d.ts b/packages/boxel-motion/test-app/types/boxel-motion-demo-app/index.d.ts new file mode 100644 index 00000000000..a2e59775409 --- /dev/null +++ b/packages/boxel-motion/test-app/types/boxel-motion-demo-app/index.d.ts @@ -0,0 +1,3 @@ +import 'ember-source/types'; + +export {}; diff --git a/packages/boxel-ui/addon/src/types/@cardstack/boxel-ui/index.d.ts b/packages/boxel-ui/addon/src/types/@cardstack/boxel-ui/index.d.ts index 42b849012e4..a2e59775409 100644 --- a/packages/boxel-ui/addon/src/types/@cardstack/boxel-ui/index.d.ts +++ b/packages/boxel-ui/addon/src/types/@cardstack/boxel-ui/index.d.ts @@ -1,4 +1,3 @@ import 'ember-source/types'; -import 'ember-source/types/preview'; export {}; diff --git a/packages/boxel-ui/test-app/tests/test-helper.js b/packages/boxel-ui/test-app/tests/test-helper.js index 8b4e5bf003e..fcad112a38a 100644 --- a/packages/boxel-ui/test-app/tests/test-helper.js +++ b/packages/boxel-ui/test-app/tests/test-helper.js @@ -3,7 +3,8 @@ import config from 'test-app/config/environment'; import * as QUnit from 'qunit'; import { setApplication } from '@ember/test-helpers'; import { setup } from 'qunit-dom'; -import { start } from 'ember-qunit'; +import { loadTests } from 'ember-qunit/test-loader'; +import { start, setupEmberOnerrorValidation } from 'ember-qunit'; import { setRunOptions, setupConsoleLogger, @@ -23,5 +24,6 @@ setRunOptions({ }, }); setupConsoleLogger(); - +setupEmberOnerrorValidation(); +loadTests(); start(); diff --git a/packages/boxel-ui/test-app/types/boxel-ui-test-app/index.d.ts b/packages/boxel-ui/test-app/types/boxel-ui-test-app/index.d.ts index 42b849012e4..a2e59775409 100644 --- a/packages/boxel-ui/test-app/types/boxel-ui-test-app/index.d.ts +++ b/packages/boxel-ui/test-app/types/boxel-ui-test-app/index.d.ts @@ -1,4 +1,3 @@ import 'ember-source/types'; -import 'ember-source/types/preview'; export {}; diff --git a/packages/host/app/deprecation-workflow.js b/packages/host/app/deprecation-workflow.js index 7e793b0fd30..ed6e5da65e9 100644 --- a/packages/host/app/deprecation-workflow.js +++ b/packages/host/app/deprecation-workflow.js @@ -1,3 +1,30 @@ import setupDeprecationWorkflow from 'ember-cli-deprecation-workflow'; -setupDeprecationWorkflow(); +setupDeprecationWorkflow({ + workflow: [ + { + handler: 'silence', + matchId: 'importing-inject-from-ember-service', + }, + { + handler: 'silence', + matchId: 'deprecate-import--set-classic-decorator-from-ember', + }, + { + handler: 'silence', + matchId: 'deprecate-import-view-utils-from-ember', + }, + { + handler: 'silence', + matchId: 'deprecate-import-env-from-ember', + }, + { + handler: 'silence', + matchId: 'deprecate-import-onerror-from-ember', + }, + { + handler: 'silence', + matchId: 'deprecate-import-libraries-from-ember', + }, + ], +}); diff --git a/packages/host/app/index.html b/packages/host/app/index.html index 7bebf9bb510..345c7e7d75d 100644 --- a/packages/host/app/index.html +++ b/packages/host/app/index.html @@ -42,13 +42,23 @@
{{content-for "body"}} - - - - - {{content-for "body-footer"}} - + + + + + + {{content-for "body-footer"}} + + diff --git a/packages/host/app/lib/file-def-manager.ts b/packages/host/app/lib/file-def-manager.ts index c26b3264dc2..586e3141f2b 100644 --- a/packages/host/app/lib/file-def-manager.ts +++ b/packages/host/app/lib/file-def-manager.ts @@ -14,6 +14,7 @@ import { inferContentType, SupportedMimeType, relativeTo, + unresolveCardReference, type LooseSingleCardDocument, type ResolvedCodeRef, } from '@cardstack/runtime-common'; @@ -337,7 +338,7 @@ export default class FileDefManagerImpl const content = JSON.stringify(entry.serialization); const contentHash = await this.getContentHash(content); let fileDef = this.fileAPI.createFileDef({ - sourceUrl: entry.card.id, + sourceUrl: entry.card.id ? unresolveCardReference(entry.card.id) : '', name: entry.card.cardTitle, contentType: SupportedMimeType.CardJson, contentHash, diff --git a/packages/host/app/lib/html-component.ts b/packages/host/app/lib/html-component.ts index 9dce0f50d31..b5bf0eb3fb3 100644 --- a/packages/host/app/lib/html-component.ts +++ b/packages/host/app/lib/html-component.ts @@ -2,13 +2,12 @@ import { setComponentManager } from '@ember/component'; import { capabilities } from '@ember/component'; import { setComponentTemplate } from '@ember/component'; -import templateOnly from '@ember/component/template-only'; import { htmlSafe, type SafeString } from '@ember/template'; import { precompileTemplate } from '@ember/template-compilation'; -import { modifier } from 'ember-modifier'; +import { template } from '@ember/template-compiler/runtime'; -import { compiler } from '@cardstack/runtime-common/etc'; +import { modifier } from 'ember-modifier'; import type { ComponentLike } from '@glint/template'; @@ -72,10 +71,7 @@ export function htmlComponent( if (cache.has(source)) { component = cache.get(source)!; } else { - component = setComponentTemplate( - compiler.compile(source, { strictMode: true }), - templateOnly(), - ) as TopElement; + component = template(source) as TopElement; cache.set(source, component); } diff --git a/packages/host/app/lib/isolated-render.gts b/packages/host/app/lib/isolated-render.gts index 79986cf0005..407b488fa62 100644 --- a/packages/host/app/lib/isolated-render.gts +++ b/packages/host/app/lib/isolated-render.gts @@ -1,11 +1,11 @@ -import { getComponentTemplate } from '@ember/component'; import { destroy } from '@ember/destroyable'; import type Owner from '@ember/owner'; -// @ts-expect-error -import { createConstRef } from '@glimmer/reference'; -// @ts-expect-error -import { renderMain, inTransaction } from '@glimmer/runtime'; +// prettier-ignore +// @ts-ignore - no types for @glimmer/runtime +import { renderComponent as glimmerRenderComponent, inTransaction } from '@glimmer/runtime'; +// @ts-ignore - no types for @glimmer/validator +import { resetTracking } from '@glimmer/validator'; import { CardError } from '@cardstack/runtime-common/error'; @@ -33,50 +33,30 @@ export function render( owner: Owner, format?: Format, ): void { - // this needs to be a template-only component because the way we're invoking it - // just grabs the template and would drop any associated class. - const root = ; - + // `renderComponent()` creates a live Glimmer tree. Dropping the DOM nodes + // without destroying the previous render leaks that tree across rerenders. teardown(element); removeChildren(element); - let { _runtime, _context, _owner, _builder } = owner.lookup( - 'renderer:-dom', - ) as any; - let self = createConstRef({}, 'this'); - let layout = (getComponentTemplate as any)(root)(_owner).asLayout(); - let iterator = renderMain( - _runtime, - _context, - _owner, - self, - _builder(_runtime.env, { element }), - layout, - ); - let vm = (iterator as any).vm; + let { + state: { owner: _owner, builder: _builder, context: _context }, + } = owner.lookup('renderer:-dom') as any; + let result: ActiveRender | undefined; try { - inTransaction(_runtime.env, () => { - result = vm._execute(); + inTransaction(_context.env, () => { + let iterator = glimmerRenderComponent( + _context, + _builder(_context.env, { element }), + _owner, + C, + { format }, + ); + result = iterator.sync(); }); } catch (err: any) { - // This is to compensate for the commitCacheGroup op code that is not called because - // of the error being thrown here. we do this so we can keep the TRANSACTION_STACK - // balanced (which would otherwise cause consumed tags to leak into subsequent frames). - // I'm not adding this to a "finally" because when there is no error, the VM will - // process an op code that will do this organically. It's only when there is an error - // that we need to step in and do this by hand. Within the vm[STACKS] is a the stack - // for the cache group. We need to call a commit for each item in this stack. - let vmSymbols = Object.fromEntries( - Object.getOwnPropertySymbols(vm).map((s) => [s.toString(), s]), - ); - let stacks = vm[vmSymbols['Symbol(STACKS)']]; - let stackSize = stacks.cache.stack.length; - for (let i = 0; i < stackSize; i++) { - vm.commitCacheGroup(); - } - + resetTracking(); let error = new CardError( `Encountered error rendering HTML for card: ${err.message}`, ); diff --git a/packages/host/app/lib/matrix-classes/message-builder.ts b/packages/host/app/lib/matrix-classes/message-builder.ts index b8cecb116d6..1be9eaeee62 100644 --- a/packages/host/app/lib/matrix-classes/message-builder.ts +++ b/packages/host/app/lib/matrix-classes/message-builder.ts @@ -224,6 +224,7 @@ export default class MessageBuilder { ? (this.event.content[APP_BOXEL_CONTINUATION_OF_CONTENT_KEY] ?? null) : null; message.setUpdated(new Date()); + message.status = this.event.status; message.errorMessage = this.errorMessage; message.reloadBillingData = shouldReloadBillingData(this.event.content); diff --git a/packages/host/app/lib/matrix-classes/message.ts b/packages/host/app/lib/matrix-classes/message.ts index 20d87815da6..a48cf56f9ca 100644 --- a/packages/host/app/lib/matrix-classes/message.ts +++ b/packages/host/app/lib/matrix-classes/message.ts @@ -63,7 +63,7 @@ export class Message implements RoomMessageInterface { @tracked _isCanceled?: boolean; @tracked hasContinuation?: boolean; @tracked continuedInMessage?: Message | null; - continuationOf?: string | null; + @tracked continuationOf?: string | null; attachedCardIds?: string[] | null; attachedFiles?: FileDef[]; @@ -71,15 +71,15 @@ export class Message implements RoomMessageInterface { attachedSkillCardIds?: string[] | null; index?: number; transactionId?: string | null; - errorMessage?: string; + @tracked errorMessage?: string; clientGeneratedId?: string; isDebugMessage?: boolean; reloadBillingData?: boolean; isCodePatchCorrectness?: boolean; author: RoomMember; - status: EventStatus | null; - _updated: Date; + @tracked status: EventStatus | null; + @tracked _updated: Date; eventId: string; roomId: string; agentId?: string; diff --git a/packages/host/app/resources/room.ts b/packages/host/app/resources/room.ts index a011096bc82..6508b96f6b9 100644 --- a/packages/host/app/resources/room.ts +++ b/packages/host/app/resources/room.ts @@ -637,7 +637,15 @@ export class RoomResource extends Resource { let effectiveEventId = this.getEffectiveEventId(event); event = this.getAggregatedReplacement(event); - let message = this._messageCache.get(effectiveEventId); + let clientGeneratedId = + 'clientGeneratedId' in event.content + ? event.content.clientGeneratedId + : undefined; + let message = + this._messageCache.get(effectiveEventId) ?? + (clientGeneratedId + ? this._messageCache.get(clientGeneratedId) + : undefined); if (!message?.isStreamingOfEventFinished) { let author = this.upsertRoomMember({ roomId, diff --git a/packages/host/app/services/command-service.ts b/packages/host/app/services/command-service.ts index 828069da51c..bc0ee776cc4 100644 --- a/packages/host/app/services/command-service.ts +++ b/packages/host/app/services/command-service.ts @@ -3,6 +3,7 @@ import type Owner from '@ember/owner'; import { debounce, schedule } from '@ember/runloop'; import Service, { service } from '@ember/service'; +import { buildWaiter } from '@ember/test-waiters'; import { isTesting } from '@embroider/macros'; import Ajv from 'ajv'; @@ -54,6 +55,10 @@ type GenericCommand = Command< typeof CardDef | undefined >; +const commandProcessingWaiter = buildWaiter( + 'command-service:command-processing', +); + export default class CommandService extends Service { @service declare private loaderService: LoaderService; @service declare private matrixService: MatrixService; @@ -292,198 +297,214 @@ export default class CommandService extends Service { } private async drainCommandProcessingQueue() { - await this.flushCommandProcessingQueue; + let waiterToken = commandProcessingWaiter.beginAsync(); + try { + await this.flushCommandProcessingQueue; - let finishedProcessingCommands: () => void; - this.flushCommandProcessingQueue = new Promise( - (res) => (finishedProcessingCommands = res), - ); + let finishedProcessingCommands: () => void; + this.flushCommandProcessingQueue = new Promise( + (res) => (finishedProcessingCommands = res), + ); - let commandSpecs = [...this.commandProcessingEventQueue]; - this.commandProcessingEventQueue = []; + let commandSpecs = [...this.commandProcessingEventQueue]; + this.commandProcessingEventQueue = []; - while (commandSpecs.length > 0) { - let [roomId, eventId] = commandSpecs.shift()!.split('|'); + while (commandSpecs.length > 0) { + let [roomId, eventId] = commandSpecs.shift()!.split('|'); - let roomResource = this.matrixService.roomResources.get(roomId!); - if (!roomResource) { - throw new Error( - `Room resource not found for room id ${roomId}, this should not happen`, - ); - } - let timeout = Date.now() + 60_000; // reset the timer to avoid a long wait if the room resource is processing - let currentRoomProcessingTimestamp = roomResource.processingLastStartedAt; - while ( - roomResource.isProcessing && - currentRoomProcessingTimestamp === - roomResource.processingLastStartedAt && - Date.now() < timeout - ) { - // wait for the room resource to finish processing - await delay(100); - } - if ( - roomResource.isProcessing && - currentRoomProcessingTimestamp === roomResource.processingLastStartedAt - ) { - // room seems to be stuck processing, so we will log and skip this event - console.error( - `Room resource for room ${roomId} seems to be stuck processing, skipping event ${eventId}`, - ); - continue; - } - - let message = roomResource.messages.find((m) => m.eventId === eventId); - if (!message) { - continue; - } - if (message.agentId !== this.matrixService.agentId) { - // This command was sent by another agent, so we will not auto-execute it - continue; - } - - // Collect all ready commands for this message - let readyCommands: any[] = []; - for (let messageCommand of message.commands) { - if (this.currentlyExecutingCommandRequestIds.has(messageCommand.id!)) { - continue; + let roomResource = this.matrixService.roomResources.get(roomId!); + if (!roomResource) { + throw new Error( + `Room resource not found for room id ${roomId}, this should not happen`, + ); } - if (this.executedCommandRequestIds.has(messageCommand.id!)) { - continue; + let timeout = Date.now() + 60_000; // reset the timer to avoid a long wait if the room resource is processing + let currentRoomProcessingTimestamp = + roomResource.processingLastStartedAt; + while ( + roomResource.isProcessing && + currentRoomProcessingTimestamp === + roomResource.processingLastStartedAt && + Date.now() < timeout + ) { + // wait for the room resource to finish processing + await delay(100); } if ( - messageCommand.status === 'applied' || - messageCommand.status === 'invalid' + roomResource.isProcessing && + currentRoomProcessingTimestamp === + roomResource.processingLastStartedAt ) { + // room seems to be stuck processing, so we will log and skip this event + console.error( + `Room resource for room ${roomId} seems to be stuck processing, skipping event ${eventId}`, + ); continue; } - if (!messageCommand.name) { + + let message = roomResource.messages.find((m) => m.eventId === eventId); + if (!message) { continue; } - - let isValid = await this.validate(messageCommand); - if (!isValid) { + if (message.agentId !== this.matrixService.agentId) { + // This command was sent by another agent, so we will not auto-execute it continue; } - // Get the LLM mode that was active when this message was created - let messageTimestamp = message.created.getTime(); - let activeModeAtMessageTime = - roomResource.getActiveLLMModeAtTimestamp(messageTimestamp); + // Collect all ready commands for this message + let readyCommands: any[] = []; + for (let messageCommand of message.commands) { + if ( + this.currentlyExecutingCommandRequestIds.has(messageCommand.id!) + ) { + continue; + } + if (this.executedCommandRequestIds.has(messageCommand.id!)) { + continue; + } + if ( + messageCommand.status === 'applied' || + messageCommand.status === 'invalid' + ) { + continue; + } + if (!messageCommand.name) { + continue; + } - // Auto-execute if LLM mode is 'act' AND the command came after the LLM mode was set to 'act', - // or if requiresApproval is false - let shouldAutoExecute = false; - let isCheckCorrectnessCommand = - messageCommand.name === CHECK_CORRECTNESS_COMMAND_NAME; + let isValid = await this.validate(messageCommand); + if (!isValid) { + continue; + } - if ( - isCheckCorrectnessCommand || - messageCommand.requiresApproval === false || - activeModeAtMessageTime === 'act' - ) { - shouldAutoExecute = true; - } + // Get the LLM mode that was active when this message was created + let messageTimestamp = message.created.getTime(); + let activeModeAtMessageTime = + roomResource.getActiveLLMModeAtTimestamp(messageTimestamp); + + // Auto-execute if LLM mode is 'act' AND the command came after the LLM mode was set to 'act', + // or if requiresApproval is false + let shouldAutoExecute = false; + let isCheckCorrectnessCommand = + messageCommand.name === CHECK_CORRECTNESS_COMMAND_NAME; + + if ( + isCheckCorrectnessCommand || + messageCommand.requiresApproval === false || + activeModeAtMessageTime === 'act' + ) { + shouldAutoExecute = true; + } - if (shouldAutoExecute) { - readyCommands.push(messageCommand); + if (shouldAutoExecute) { + readyCommands.push(messageCommand); + } } - } - // Execute ready commands, tracking accept-all state if multiple commands - if (readyCommands.length > 0) { - // This is an "accept all" operation - multiple commands ready for execution - this.acceptingAllRoomIds.add(roomId!); - try { - for (let command of readyCommands) { - this.run.perform(command); + // Execute ready commands, tracking accept-all state if multiple commands + if (readyCommands.length > 0) { + // This is an "accept all" operation - multiple commands ready for execution + this.acceptingAllRoomIds.add(roomId!); + try { + for (let command of readyCommands) { + this.run.perform(command); + } + } finally { + this.acceptingAllRoomIds.delete(roomId!); } - } finally { - this.acceptingAllRoomIds.delete(roomId!); } } + finishedProcessingCommands!(); + } finally { + commandProcessingWaiter.endAsync(waiterToken); } - finishedProcessingCommands!(); } private async drainCodePatchProcessingQueue() { - await this.flushCodePatchProcessingQueue; + let waiterToken = commandProcessingWaiter.beginAsync(); + try { + await this.flushCodePatchProcessingQueue; - let finishedProcessingCodePatches: () => void; - this.flushCodePatchProcessingQueue = new Promise( - (res) => (finishedProcessingCodePatches = res), - ); + let finishedProcessingCodePatches: () => void; + this.flushCodePatchProcessingQueue = new Promise( + (res) => (finishedProcessingCodePatches = res), + ); - let codePatchSpecs = [...this.codePatchProcessingEventQueue]; - this.codePatchProcessingEventQueue = []; + let codePatchSpecs = [...this.codePatchProcessingEventQueue]; + this.codePatchProcessingEventQueue = []; - while (codePatchSpecs.length > 0) { - let [roomId, eventId] = codePatchSpecs.shift()!.split('|'); + while (codePatchSpecs.length > 0) { + let [roomId, eventId] = codePatchSpecs.shift()!.split('|'); - let roomResource = this.matrixService.roomResources.get(roomId!); - if (!roomResource) { - throw new Error( - `Room resource not found for room id ${roomId}, this should not happen`, - ); - } - let timeout = Date.now() + 60_000; // reset the timer to avoid a long wait if the room resource is processing - let currentRoomProcessingTimestamp = roomResource.processingLastStartedAt; - while ( - roomResource.isProcessing && - currentRoomProcessingTimestamp === - roomResource.processingLastStartedAt && - Date.now() < timeout - ) { - // wait for the room resource to finish processing - await delay(100); - } - if ( - roomResource.isProcessing && - currentRoomProcessingTimestamp === roomResource.processingLastStartedAt - ) { - // room seems to be stuck processing, so we will log and skip this event - console.error( - `Room resource for room ${roomId} seems to be stuck processing, skipping code patch event ${eventId}`, - ); - continue; - } - let message = roomResource.messages.find((m) => m.eventId === eventId); - if (!message) { - continue; - } - if (message.agentId !== this.matrixService.agentId) { - // This code patch was sent by another agent, so we will not auto-execute it - continue; - } + let roomResource = this.matrixService.roomResources.get(roomId!); + if (!roomResource) { + throw new Error( + `Room resource not found for room id ${roomId}, this should not happen`, + ); + } + let timeout = Date.now() + 60_000; // reset the timer to avoid a long wait if the room resource is processing + let currentRoomProcessingTimestamp = + roomResource.processingLastStartedAt; + while ( + roomResource.isProcessing && + currentRoomProcessingTimestamp === + roomResource.processingLastStartedAt && + Date.now() < timeout + ) { + // wait for the room resource to finish processing + await delay(100); + } + if ( + roomResource.isProcessing && + currentRoomProcessingTimestamp === + roomResource.processingLastStartedAt + ) { + // room seems to be stuck processing, so we will log and skip this event + console.error( + `Room resource for room ${roomId} seems to be stuck processing, skipping code patch event ${eventId}`, + ); + continue; + } + let message = roomResource.messages.find((m) => m.eventId === eventId); + if (!message) { + continue; + } + if (message.agentId !== this.matrixService.agentId) { + // This code patch was sent by another agent, so we will not auto-execute it + continue; + } - // Get the LLM mode that was active when this message was created - let messageTimestamp = message.created.getTime(); - let activeModeAtMessageTime = - roomResource.getActiveLLMModeAtTimestamp(messageTimestamp); - // Only auto-apply if in 'act' mode - if (activeModeAtMessageTime !== 'act') { - continue; - } + // Get the LLM mode that was active when this message was created + let messageTimestamp = message.created.getTime(); + let activeModeAtMessageTime = + roomResource.getActiveLLMModeAtTimestamp(messageTimestamp); + // Only auto-apply if in 'act' mode + if (activeModeAtMessageTime !== 'act') { + continue; + } - // Auto-apply all ready code patches from this message - if (message.htmlParts) { - let readyCodePatches = this.getReadyCodePatches(message.htmlParts); - let uniqueFiles = new Set( - readyCodePatches.map((patch) => patch.fileUrl), - ); + // Auto-apply all ready code patches from this message + if (message.htmlParts) { + let readyCodePatches = this.getReadyCodePatches(message.htmlParts); + let uniqueFiles = new Set( + readyCodePatches.map((patch) => patch.fileUrl), + ); - if (readyCodePatches.length > 0 || uniqueFiles.size > 0) { - // This is an "accept all" operation - multiple patches OR patches across multiple files - this.acceptingAllRoomIds.add(roomId!); - try { - await this.executeReadyCodePatches(roomId!, message.htmlParts); - } finally { - this.acceptingAllRoomIds.delete(roomId!); + if (readyCodePatches.length > 0 || uniqueFiles.size > 0) { + // This is an "accept all" operation - multiple patches OR patches across multiple files + this.acceptingAllRoomIds.add(roomId!); + try { + await this.executeReadyCodePatches(roomId!, message.htmlParts); + } finally { + this.acceptingAllRoomIds.delete(roomId!); + } } } } + finishedProcessingCodePatches!(); + } finally { + commandProcessingWaiter.endAsync(waiterToken); } - finishedProcessingCodePatches!(); } get commandContext(): CommandContext { diff --git a/packages/host/ember-cli-build.js b/packages/host/ember-cli-build.js index 38f2d403dff..0e599e62264 100644 --- a/packages/host/ember-cli-build.js +++ b/packages/host/ember-cli-build.js @@ -76,9 +76,6 @@ module.exports = function (defaults) { plugins: [ new GlimmerScopedCSSWebpackPlugin(), new MonacoWebpackPlugin(), - new webpack.ProvidePlugin({ - process: 'process', - }), new webpack.IgnorePlugin({ resourceRegExp: /^https:\/\/cardstack\.com\/base/, }), diff --git a/packages/host/package.json b/packages/host/package.json index a23a935dadf..83f0acfe903 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -48,8 +48,8 @@ "@cardstack/runtime-common": "workspace:*", "@cardstack/view-transitions": "catalog:", "@ember/optional-features": "^2.0.0", - "@ember/string": "^3.1.1", - "@ember/test-helpers": "catalog:", + "@ember/string": "^4.0.0", + "@ember/test-helpers": "^5.4.1", "@ember/test-waiters": "catalog:", "@embroider/compat": "^3.5.5", "@embroider/core": "^3.4.15", @@ -103,7 +103,7 @@ "ember-async-data": "^1.0.3", "ember-auto-import": "^2.7.2", "ember-basic-dropdown": "8.0.4", - "ember-cli": "~5.4.1", + "ember-cli": "~6.10.0", "ember-cli-app-version": "^6.0.1", "ember-cli-babel": "^8.2.0", "ember-cli-clean-css": "^3.0.0", @@ -129,12 +129,12 @@ "ember-freestyle": "^0.20.0", "ember-keyboard": "^8.2.1", "ember-lifeline": "^7.0.0", - "ember-load-initializers": "^2.1.2", + "ember-load-initializers": "^3.0.0", "ember-modifier": "^4.1.0", - "ember-page-title": "^8.2.3", - "ember-provide-consume-context": "^0.7.0", + "ember-page-title": "^9.0.3", + "ember-provide-consume-context": "^0.8.0", "ember-qunit": "catalog:", - "ember-resolver": "^11.0.1", + "ember-resolver": "^13.0.0", "ember-resources": "catalog:", "ember-route-template": "^1.0.3", "ember-source": "catalog:", diff --git a/packages/host/tests/index.html b/packages/host/tests/index.html index b42a2122447..b5698418a7f 100644 --- a/packages/host/tests/index.html +++ b/packages/host/tests/index.html @@ -25,6 +25,15 @@
+