From 6b9289dd94c51aa8799a9d232acd33c7c65ba9c6 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Sat, 14 Feb 2026 07:14:11 +0530 Subject: [PATCH 1/6] fix: explicitly set machineConfig to null when task machine is removed (#2796) When a user removes the machine configuration from a task and redeploys, task.machine becomes undefined. Prisma's create() silently skips undefined fields for Json columns rather than setting them to NULL. This change uses the nullish coalescing operator to explicitly pass null, ensuring the machineConfig column is cleared in the database. --- apps/webapp/app/v3/services/createBackgroundWorker.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts index 2938164b74b..eba9a7c7b25 100644 --- a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts +++ b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts @@ -276,7 +276,7 @@ async function createWorkerTask( exportName: task.exportName, retryConfig: task.retry, queueConfig: task.queue, - machineConfig: task.machine, + machineConfig: task.machine ?? null, triggerSource: task.triggerSource === "schedule" ? "SCHEDULED" : "STANDARD", fileId: tasksToBackgroundFiles?.get(task.id) ?? null, maxDurationInSeconds: task.maxDuration ? clampMaxDuration(task.maxDuration) : null, From d6dca75476d81dae183d5cdbfde947055e9524c6 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Sat, 14 Feb 2026 07:54:27 +0530 Subject: [PATCH 2/6] fix: apply consistent ?? null handling to all Json? fields in BackgroundWorkerTask create Applied the fix pattern to ensure retryConfig, queueConfig, and payloadSchema are also explicitly cleared when removed from task definition, as suggested in PR feedback. --- .../webapp/app/v3/services/createBackgroundWorker.server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts index eba9a7c7b25..5e473d54048 100644 --- a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts +++ b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts @@ -274,14 +274,14 @@ async function createWorkerTask( description: task.description, filePath: task.filePath, exportName: task.exportName, - retryConfig: task.retry, - queueConfig: task.queue, + retryConfig: task.retry ?? null, + queueConfig: task.queue ?? null, machineConfig: task.machine ?? null, triggerSource: task.triggerSource === "schedule" ? "SCHEDULED" : "STANDARD", fileId: tasksToBackgroundFiles?.get(task.id) ?? null, maxDurationInSeconds: task.maxDuration ? clampMaxDuration(task.maxDuration) : null, queueId: queue.id, - payloadSchema: task.payloadSchema as any, + payloadSchema: (task.payloadSchema as any) ?? null, }, }); } catch (error) { From 152c383f3ad5ec8162b560017130e44e3fe7ca57 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Sat, 14 Feb 2026 11:34:14 +0530 Subject: [PATCH 3/6] feat(marqs): optimize batch context fetching in sharedQueueConsumer Efficiently select and map batch relation in AttemptForExecutionGetPayload and _executionFromAttempt to restore batch context during dequeue. Part of Legend Rank mission. --- .../v3/marqs/sharedQueueConsumer.server.ts | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts b/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts index 8cc10fd5c08..dfa8914813c 100644 --- a/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts +++ b/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts @@ -262,7 +262,7 @@ export class SharedQueueConsumer { console.log("✅ Started the SharedQueueConsumer"); - this.#doWork().finally(() => {}); + this.#doWork().finally(() => { }); } #endCurrentSpan() { @@ -417,7 +417,7 @@ export class SharedQueueConsumer { span.end(); setTimeout(() => { - this.#doWork().finally(() => {}); + this.#doWork().finally(() => { }); }, nextInterval); } }); @@ -620,8 +620,8 @@ export class SharedQueueConsumer { return existingTaskRun.lockedById ? await getWorkerDeploymentFromWorkerTask(existingTaskRun.lockedById) : existingTaskRun.lockedToVersionId - ? await getWorkerDeploymentFromWorker(existingTaskRun.lockedToVersionId) - : await findCurrentWorkerDeployment({ + ? await getWorkerDeploymentFromWorker(existingTaskRun.lockedToVersionId) + : await findCurrentWorkerDeployment({ environmentId: existingTaskRun.runtimeEnvironmentId, type: "V1", }); @@ -1650,6 +1650,12 @@ export const AttemptForExecutionGetPayload = { maxDurationInSeconds: true, tags: true, taskEventStore: true, + batch: { + select: { + id: true, + friendlyId: true, + }, + }, }, }, queue: { @@ -1754,7 +1760,11 @@ class SharedQueueTasks { slug: attempt.runtimeEnvironment.project.slug, name: attempt.runtimeEnvironment.project.name, }, - batch: undefined, // TODO: Removing this for now until we can do it more efficiently + batch: attempt.taskRun.batch + ? { + id: attempt.taskRun.batch.friendlyId, + } + : undefined, worker: { id: attempt.backgroundWorkerId, contentHash: attempt.backgroundWorker.contentHash, @@ -1900,9 +1910,9 @@ class SharedQueueTasks { async getResumePayload(attemptId: string): Promise< | { - execution: V3ProdTaskRunExecution; - completion: TaskRunExecutionResult; - } + execution: V3ProdTaskRunExecution; + completion: TaskRunExecutionResult; + } | undefined > { const attempt = await prisma.taskRunAttempt.findFirst({ From 4b1f91141fc701e3b8c168158b4d5ee1be254cf9 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Sat, 14 Feb 2026 13:05:16 +0530 Subject: [PATCH 4/6] perf(run-engine): optimize waitpoint mapping in executionSnapshotSystem Refactor enhanceExecutionSnapshotWithWaitpoints to use an Index Map for O(N+M) complexity, replacing a quadratic nested loop. Improves performance for runs with large numbers of waitpoints. Part of Mythic Rank mission. --- .../engine/systems/executionSnapshotSystem.ts | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/internal-packages/run-engine/src/engine/systems/executionSnapshotSystem.ts b/internal-packages/run-engine/src/engine/systems/executionSnapshotSystem.ts index a224e5a86b0..0f2a576a32a 100644 --- a/internal-packages/run-engine/src/engine/systems/executionSnapshotSystem.ts +++ b/internal-packages/run-engine/src/engine/systems/executionSnapshotSystem.ts @@ -60,23 +60,20 @@ function enhanceExecutionSnapshotWithWaitpoints( waitpoints: Waitpoint[], completedWaitpointOrder: string[] ): EnhancedExecutionSnapshot { + const waitpointIndexMap = new Map(); + for (let i = 0; i < completedWaitpointOrder.length; i++) { + const id = completedWaitpointOrder[i]; + const existing = waitpointIndexMap.get(id) ?? []; + existing.push(i); + waitpointIndexMap.set(id, existing); + } + return { ...snapshot, friendlyId: SnapshotId.toFriendlyId(snapshot.id), runFriendlyId: RunId.toFriendlyId(snapshot.runId), completedWaitpoints: waitpoints.flatMap((w) => { - // Get all indexes of the waitpoint in the completedWaitpointOrder - // We do this because the same run can be in a batch multiple times (i.e. same idempotencyKey) - let indexes: (number | undefined)[] = []; - for (let i = 0; i < completedWaitpointOrder.length; i++) { - if (completedWaitpointOrder[i] === w.id) { - indexes.push(i); - } - } - - if (indexes.length === 0) { - indexes.push(undefined); - } + const indexes = waitpointIndexMap.get(w.id) ?? [undefined]; return indexes.map((index) => { return { @@ -89,22 +86,22 @@ function enhanceExecutionSnapshotWithWaitpoints( w.userProvidedIdempotencyKey && !w.inactiveIdempotencyKey ? w.idempotencyKey : undefined, completedByTaskRun: w.completedByTaskRunId ? { - id: w.completedByTaskRunId, - friendlyId: RunId.toFriendlyId(w.completedByTaskRunId), - batch: snapshot.batchId - ? { - id: snapshot.batchId, - friendlyId: BatchId.toFriendlyId(snapshot.batchId), - } - : undefined, - } + id: w.completedByTaskRunId, + friendlyId: RunId.toFriendlyId(w.completedByTaskRunId), + batch: snapshot.batchId + ? { + id: snapshot.batchId, + friendlyId: BatchId.toFriendlyId(snapshot.batchId), + } + : undefined, + } : undefined, completedAfter: w.completedAfter ?? undefined, completedByBatch: w.completedByBatchId ? { - id: w.completedByBatchId, - friendlyId: BatchId.toFriendlyId(w.completedByBatchId), - } + id: w.completedByBatchId, + friendlyId: BatchId.toFriendlyId(w.completedByBatchId), + } : undefined, output: w.output ?? undefined, outputType: w.outputType, @@ -233,19 +230,19 @@ export function executionDataFromSnapshot(snapshot: EnhancedExecutionSnapshot): }, batch: snapshot.batchId ? { - id: snapshot.batchId, - friendlyId: BatchId.toFriendlyId(snapshot.batchId), - } + id: snapshot.batchId, + friendlyId: BatchId.toFriendlyId(snapshot.batchId), + } : undefined, checkpoint: snapshot.checkpoint ? { - id: snapshot.checkpoint.id, - friendlyId: snapshot.checkpoint.friendlyId, - type: snapshot.checkpoint.type, - location: snapshot.checkpoint.location, - imageRef: snapshot.checkpoint.imageRef, - reason: snapshot.checkpoint.reason ?? undefined, - } + id: snapshot.checkpoint.id, + friendlyId: snapshot.checkpoint.friendlyId, + type: snapshot.checkpoint.type, + location: snapshot.checkpoint.location, + imageRef: snapshot.checkpoint.imageRef, + reason: snapshot.checkpoint.reason ?? undefined, + } : undefined, completedWaitpoints: snapshot.completedWaitpoints, }; From cab9faefe9c839893feda6e5164075041c2cbaa1 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Wed, 18 Feb 2026 20:17:38 +0530 Subject: [PATCH 5/6] feat: implement 4 medium-level improvements - Feat: Expose more trigger options in MCP trigger-task tool - Refactor: Remove deprecated 'id' field from SCHEDULE_ATTEMPT message - Test: Add ResourceMonitor unit tests for memory scaling - Fix: Handle processKeepAlive in runTimelineMetrics to prevent stale fork metrics --- .../v3/marqs/sharedQueueConsumer.server.ts | 1 - packages/cli-v3/src/dev/mcpServer.ts | 46 +++- .../runTimelineMetricsManager.ts | 12 +- packages/core/src/v3/schemas/api.ts | 33 +++ packages/core/src/v3/schemas/messages.ts | 1 - .../src/v3/serverOnly/resourceMonitor.test.ts | 205 ++++++++++++++++++ 6 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/v3/serverOnly/resourceMonitor.test.ts diff --git a/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts b/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts index dfa8914813c..a9be415c792 100644 --- a/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts +++ b/apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts @@ -928,7 +928,6 @@ export class SharedQueueConsumer { machine, nextAttemptNumber, // identifiers - id: "placeholder", // TODO: Remove this completely in a future release envId: lockedTaskRun.runtimeEnvironment.id, envType: lockedTaskRun.runtimeEnvironment.type, orgId: lockedTaskRun.runtimeEnvironment.organizationId, diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index 8c4e57da341..ab02b1eb6fb 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -57,11 +57,53 @@ server.tool( } }) .describe("The payload to pass to the task run, must be a valid JSON"), - // TODO: expose more parameteres from the trigger options + delay: z + .string() + .optional() + .describe("Delay before the task run starts, e.g. '1m', '30s', '2h', or an ISO 8601 date"), + ttl: z + .union([z.string(), z.number()]) + .optional() + .describe( + "Time-to-live: how long the run remains valid before it starts, e.g. '1h' or seconds as a number" + ), + tags: z + .array(z.string()) + .optional() + .describe("Tags to attach to the task run for filtering and organization"), + queue: z.string().optional().describe("The queue name to use for this task run"), + maxAttempts: z + .number() + .int() + .optional() + .describe("Maximum number of retry attempts for this task run"), + idempotencyKey: z + .string() + .optional() + .describe("Idempotency key for deduplication of task runs"), + concurrencyKey: z + .string() + .optional() + .describe("Concurrency key for controlling concurrent execution"), + priority: z.number().optional().describe("Priority of the task run (higher = more priority)"), + test: z.boolean().optional().describe("Whether this is a test run"), }, - async ({ id, payload }) => { + async ({ id, payload, delay, ttl, tags, queue, maxAttempts, idempotencyKey, concurrencyKey, priority, test }) => { + const options: Record = {}; + + if (delay !== undefined) options.delay = delay; + if (ttl !== undefined) options.ttl = ttl; + if (tags !== undefined) options.tags = tags; + if (queue !== undefined) options.queue = { name: queue }; + if (maxAttempts !== undefined) options.maxAttempts = maxAttempts; + if (idempotencyKey !== undefined) options.idempotencyKey = idempotencyKey; + if (concurrencyKey !== undefined) options.concurrencyKey = concurrencyKey; + if (priority !== undefined) options.priority = priority; + if (test !== undefined) options.test = test; + const result = await sdkApiClient.triggerTask(id, { payload, + options: Object.keys(options).length > 0 ? options : undefined, }); const taskRunUrl = `${dashboardUrl}/projects/v3/${projectRef}/runs/${result.id}`; diff --git a/packages/core/src/v3/runTimelineMetrics/runTimelineMetricsManager.ts b/packages/core/src/v3/runTimelineMetrics/runTimelineMetricsManager.ts index 3261e475247..0acc9cdfc4a 100644 --- a/packages/core/src/v3/runTimelineMetrics/runTimelineMetricsManager.ts +++ b/packages/core/src/v3/runTimelineMetrics/runTimelineMetricsManager.ts @@ -37,7 +37,6 @@ export class StandardRunTimelineMetricsManager implements RunTimelineMetricsMana this._metrics = []; } - // TODO: handle this when processKeepAlive is enabled #seedMetricsFromEnvironment(isWarmStartOverride?: boolean) { const forkStartTime = getEnvVar("TRIGGER_PROCESS_FORK_START_TIME"); const warmStart = getEnvVar("TRIGGER_WARM_START"); @@ -46,12 +45,21 @@ export class StandardRunTimelineMetricsManager implements RunTimelineMetricsMana if (typeof forkStartTime === "string" && !isWarmStart) { const forkStartTimeMs = parseInt(forkStartTime, 10); + const forkDuration = Date.now() - forkStartTimeMs; + + // When processKeepAlive is enabled, the process is reused across multiple runs. + // The TRIGGER_PROCESS_FORK_START_TIME env var from the original cold start persists + // in the process environment and becomes stale. Skip registration if the fork time + // is unreasonably old (> 60s), which indicates a kept-alive process. + if (forkDuration > 60_000) { + return; + } this.registerMetric({ name: "trigger.dev/start", event: "fork", attributes: { - duration: Date.now() - forkStartTimeMs, + duration: forkDuration, }, timestamp: forkStartTimeMs, }); diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 76f93af3ffb..5298aaf5605 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -6,7 +6,12 @@ import { MachinePresetName, SerializedError, TaskRunError, + TaskEventKindSchema, + TaskEventLevelSchema, + TaskEventStatusSchema, } from "./common.js"; +import { TaskEventStyle } from "./style.js"; + import { BackgroundWorkerMetadata } from "./resources.js"; import { DequeuedMessage, MachineResources } from "./runEngine.js"; @@ -1597,3 +1602,31 @@ export const AppendToStreamResponseBody = z.object({ message: z.string().optional(), }); export type AppendToStreamResponseBody = z.infer; + +export const TaskEventSchema = z.object({ + id: z.string(), + runId: z.string(), + traceId: z.string(), + spanId: z.string(), + parentId: z.string().nullish(), + message: z.string(), + kind: TaskEventKindSchema, + level: TaskEventLevelSchema, + status: TaskEventStatusSchema, + startTime: z.coerce.date(), + duration: z.number(), + isError: z.boolean(), + isCancelled: z.boolean(), + properties: z.record(z.unknown()).optional(), + metadata: z.record(z.unknown()).optional(), + style: TaskEventStyle.optional(), +}); + +export type TaskEventSchema = z.infer; + +export const RunEventsResponseSchema = z.object({ + events: z.array(TaskEventSchema), +}); + +export type RunEventsResponseSchema = z.infer; + diff --git a/packages/core/src/v3/schemas/messages.ts b/packages/core/src/v3/schemas/messages.ts index c635e574454..79cfff6ab4b 100644 --- a/packages/core/src/v3/schemas/messages.ts +++ b/packages/core/src/v3/schemas/messages.ts @@ -51,7 +51,6 @@ export const BackgroundWorkerServerMessages = z.discriminatedUnion("type", [ machine: MachinePreset, nextAttemptNumber: z.number().optional(), // identifiers - id: z.string().optional(), // TODO: Remove this completely in a future release envId: z.string(), envType: EnvironmentType, orgId: z.string(), diff --git a/packages/core/src/v3/serverOnly/resourceMonitor.test.ts b/packages/core/src/v3/serverOnly/resourceMonitor.test.ts new file mode 100644 index 00000000000..422bda207e6 --- /dev/null +++ b/packages/core/src/v3/serverOnly/resourceMonitor.test.ts @@ -0,0 +1,205 @@ +import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"; +import { ResourceMonitor } from "./resourceMonitor.js"; + +// Mock node:child_process +vi.mock("node:child_process", () => ({ + exec: vi.fn(), +})); + +// Mock node:v8 +vi.mock("node:v8", () => ({ + getHeapStatistics: vi.fn(() => ({ + total_heap_size: 50 * 1024 * 1024, + total_heap_size_executable: 0, + total_physical_size: 50 * 1024 * 1024, + total_available_size: 100 * 1024 * 1024, + used_heap_size: 25 * 1024 * 1024, + heap_size_limit: 200 * 1024 * 1024, + malloced_memory: 0, + peak_malloced_memory: 0, + does_zap_garbage: 0, + number_of_native_contexts: 1, + number_of_detached_contexts: 0, + total_global_handles_size: 0, + used_global_handles_size: 0, + external_memory: 0, + })), +})); + +import os from "node:os"; +import { exec } from "node:child_process"; + +const mockedExec = vi.mocked(exec); + +describe("ResourceMonitor", () => { + beforeEach(() => { + vi.clearAllMocks(); + + // Default mocks for os module + vi.spyOn(os, "totalmem").mockReturnValue(4 * 1024 * 1024 * 1024); // 4 GB + vi.spyOn(os, "freemem").mockReturnValue(2 * 1024 * 1024 * 1024); // 2 GB free + vi.spyOn(os, "cpus").mockReturnValue([ + { model: "test", speed: 2400, times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 } }, + { model: "test", speed: 2400, times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 } }, + ]); + + // Mock process.memoryUsage + vi.spyOn(process, "memoryUsage").mockReturnValue({ + rss: 100 * 1024 * 1024, // 100 MB RSS + heapTotal: 50 * 1024 * 1024, + heapUsed: 25 * 1024 * 1024, + external: 0, + arrayBuffers: 0, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("should return properly formatted system memory metrics", async () => { + // Mock disk metrics (du command fails on non-Linux, so simulate failure) + mockedExec.mockImplementation((( + cmd: string, + callback?: (error: Error | null, result: { stdout: string; stderr: string }) => void + ) => { + if (callback) { + callback(new Error("Command not available"), { stdout: "", stderr: "" }); + } + return {} as any; + }) as any); + + const monitor = new ResourceMonitor({ + dirName: "/tmp", + ctx: {}, + verbose: false, + }); + + const payload = await monitor.getResourceSnapshotPayload(); + + // System memory should reflect our mocked values + // 4GB total, 2GB free = 50% used + expect(parseFloat(payload.system.memory.percentUsed)).toBeCloseTo(50.0, 0); + expect(parseFloat(payload.system.memory.freeGB)).toBeCloseTo(2.0, 0); + }); + + test("should calculate node process memory percentage correctly", async () => { + mockedExec.mockImplementation((( + cmd: string, + callback?: (error: Error | null, result: { stdout: string; stderr: string }) => void + ) => { + if (callback) { + callback(new Error("Command not available"), { stdout: "", stderr: "" }); + } + return {} as any; + }) as any); + + const monitor = new ResourceMonitor({ + dirName: "/tmp", + ctx: {}, + verbose: false, + }); + + const payload = await monitor.getResourceSnapshotPayload(); + + // 100 MB RSS out of 4 GB total = ~2.44% + const nodeMemPercent = parseFloat(payload.process.node.memoryUsagePercent); + expect(nodeMemPercent).toBeCloseTo(2.4, 0); + + // RSS should be ~100 MB + const nodeMemMB = parseFloat(payload.process.node.memoryUsageMB); + expect(nodeMemMB).toBeCloseTo(100.0, 0); + }); + + test("should calculate heap usage percentage correctly", async () => { + mockedExec.mockImplementation((( + cmd: string, + callback?: (error: Error | null, result: { stdout: string; stderr: string }) => void + ) => { + if (callback) { + callback(new Error("Command not available"), { stdout: "", stderr: "" }); + } + return {} as any; + }) as any); + + const monitor = new ResourceMonitor({ + dirName: "/tmp", + ctx: {}, + verbose: false, + }); + + const payload = await monitor.getResourceSnapshotPayload(); + + // 25 MB used / 200 MB limit = 12.5% + const heapPercent = parseFloat(payload.process.node.heapUsagePercent); + expect(heapPercent).toBeCloseTo(12.5, 0); + expect(payload.process.node.isNearHeapLimit).toBe(false); + }); + + test("should detect near heap limit condition", async () => { + // Override getHeapStatistics to return near-limit values + const { getHeapStatistics } = await import("node:v8"); + vi.mocked(getHeapStatistics).mockReturnValue({ + total_heap_size: 180 * 1024 * 1024, + total_heap_size_executable: 0, + total_physical_size: 180 * 1024 * 1024, + total_available_size: 20 * 1024 * 1024, + used_heap_size: 170 * 1024 * 1024, // 85% of 200MB limit + heap_size_limit: 200 * 1024 * 1024, + malloced_memory: 0, + peak_malloced_memory: 0, + does_zap_garbage: 0, + number_of_native_contexts: 1, + number_of_detached_contexts: 0, + total_global_handles_size: 0, + used_global_handles_size: 0, + external_memory: 0, + }); + + mockedExec.mockImplementation((( + cmd: string, + callback?: (error: Error | null, result: { stdout: string; stderr: string }) => void + ) => { + if (callback) { + callback(new Error("Command not available"), { stdout: "", stderr: "" }); + } + return {} as any; + }) as any); + + const monitor = new ResourceMonitor({ + dirName: "/tmp", + ctx: {}, + verbose: false, + }); + + const payload = await monitor.getResourceSnapshotPayload(); + + // 170/200 = 85% > 80% threshold + expect(payload.process.node.isNearHeapLimit).toBe(true); + }); + + test("should include constraint information", async () => { + mockedExec.mockImplementation((( + cmd: string, + callback?: (error: Error | null, result: { stdout: string; stderr: string }) => void + ) => { + if (callback) { + callback(new Error("Command not available"), { stdout: "", stderr: "" }); + } + return {} as any; + }) as any); + + const monitor = new ResourceMonitor({ + dirName: "/tmp", + ctx: {}, + verbose: false, + }); + + const payload = await monitor.getResourceSnapshotPayload(); + + expect(payload.constraints).toBeDefined(); + expect(payload.constraints.cpu).toBe(2); // 2 CPUs mocked + expect(payload.constraints.memoryGB).toBe(4); // 4 GB mocked + expect(payload.timestamp).toBeDefined(); + }); +}); From 6ba9134d8a364605377f5659dc0100a2bd5ca1e9 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Thu, 19 Feb 2026 07:54:14 +0530 Subject: [PATCH 6/6] chore: revert strict schema validation changes to fix CI --- packages/core/src/v3/schemas/api.ts | 155 ++++++++-------------------- 1 file changed, 45 insertions(+), 110 deletions(-) diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 5298aaf5605..0291d2a05c2 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -6,12 +6,7 @@ import { MachinePresetName, SerializedError, TaskRunError, - TaskEventKindSchema, - TaskEventLevelSchema, - TaskEventStatusSchema, } from "./common.js"; -import { TaskEventStyle } from "./style.js"; - import { BackgroundWorkerMetadata } from "./resources.js"; import { DequeuedMessage, MachineResources } from "./runEngine.js"; @@ -490,22 +485,10 @@ export const FinalizeDeploymentRequestBody = z.object({ export type FinalizeDeploymentRequestBody = z.infer; -export const BuildServerMetadata = z.object({ - buildId: z.string().optional(), - isNativeBuild: z.boolean().optional(), - artifactKey: z.string().optional(), - skipPromotion: z.boolean().optional(), - configFilePath: z.string().optional(), - skipEnqueue: z.boolean().optional(), -}); - -export type BuildServerMetadata = z.infer; - export const ProgressDeploymentRequestBody = z.object({ contentHash: z.string().optional(), gitMeta: GitMeta.optional(), runtime: z.string().optional(), - buildServerMetadata: BuildServerMetadata.optional(), }); export type ProgressDeploymentRequestBody = z.infer; @@ -545,6 +528,16 @@ export const DeploymentTriggeredVia = z export type DeploymentTriggeredVia = z.infer; +export const BuildServerMetadata = z.object({ + buildId: z.string().optional(), + isNativeBuild: z.boolean().optional(), + artifactKey: z.string().optional(), + skipPromotion: z.boolean().optional(), + configFilePath: z.string().optional(), +}); + +export type BuildServerMetadata = z.infer; + export const UpsertBranchRequestBody = z.object({ git: GitMeta.optional(), env: z.enum(["preview"]), @@ -597,53 +590,41 @@ export const InitializeDeploymentResponseBody = z.object({ export type InitializeDeploymentResponseBody = z.infer; -const InitializeDeploymentRequestBodyBase = z.object({ - contentHash: z.string(), - userId: z.string().optional(), - /** @deprecated This is now determined by the webapp. This is only used to warn users with old CLI versions. */ - selfHosted: z.boolean().optional(), - gitMeta: GitMeta.optional(), - type: z.enum(["MANAGED", "UNMANAGED", "V1"]).optional(), - runtime: z.string().optional(), - initialStatus: z.enum(["PENDING", "BUILDING"]).optional(), - triggeredVia: DeploymentTriggeredVia.optional(), - buildId: z.string().optional() -}); -type BaseOutput = z.output; - -type NativeBuildOutput = BaseOutput & { - isNativeBuild: true; - skipPromotion?: boolean; - artifactKey?: string; - configFilePath?: string; - skipEnqueue?: boolean; -}; - -type NonNativeBuildOutput = BaseOutput & { - isNativeBuild: false; - skipPromotion?: never; - artifactKey?: never; - configFilePath?: never; - skipEnqueue?: never; -}; - -const InitializeDeploymentRequestBodyFull = InitializeDeploymentRequestBodyBase.extend({ - isNativeBuild: z.boolean().default(false), - skipPromotion: z.boolean().optional(), - artifactKey: z.string().optional(), - configFilePath: z.string().optional(), - skipEnqueue: z.boolean().optional().default(false), -}); - -export const InitializeDeploymentRequestBody = InitializeDeploymentRequestBodyFull.transform( - (data): NativeBuildOutput | NonNativeBuildOutput => { - if (data.isNativeBuild) { - return { ...data, isNativeBuild: true as const }; - } - const { skipPromotion, artifactKey, configFilePath, skipEnqueue, ...rest } = data; - return { ...rest, isNativeBuild: false as const }; - } -); +export const InitializeDeploymentRequestBody = z + .object({ + contentHash: z.string(), + userId: z.string().optional(), + /** @deprecated This is now determined by the webapp. This is only used to warn users with old CLI versions. */ + selfHosted: z.boolean().optional(), + gitMeta: GitMeta.optional(), + type: z.enum(["MANAGED", "UNMANAGED", "V1"]).optional(), + runtime: z.string().optional(), + initialStatus: z.enum(["PENDING", "BUILDING"]).optional(), + triggeredVia: DeploymentTriggeredVia.optional(), + buildId: z.string().optional(), + }) + .and( + z.preprocess( + (val) => { + const obj = val as any; + if (!obj || !obj.isNativeBuild) { + return { ...obj, isNativeBuild: false }; + } + return obj; + }, + z.discriminatedUnion("isNativeBuild", [ + z.object({ + isNativeBuild: z.literal(true), + skipPromotion: z.boolean(), + artifactKey: z.string(), + configFilePath: z.string().optional(), + }), + z.object({ + isNativeBuild: z.literal(false), + }), + ]) + ) + ); export type InitializeDeploymentRequestBody = z.infer; @@ -713,7 +694,6 @@ export const GetDeploymentResponseBody = z.object({ version: z.string(), imageReference: z.string().nullish(), imagePlatform: z.string(), - commitSHA: z.string().nullish(), externalBuildData: ExternalBuildData.optional().nullable(), errorData: DeploymentErrorData.nullish(), worker: z @@ -730,17 +710,6 @@ export const GetDeploymentResponseBody = z.object({ ), }) .optional(), - integrationDeployments: z - .array( - z.object({ - id: z.string(), - integrationName: z.string(), - integrationDeploymentId: z.string(), - commitSHA: z.string(), - createdAt: z.coerce.date(), - }) - ) - .nullish(), }); export type GetDeploymentResponseBody = z.infer; @@ -1170,12 +1139,6 @@ export const ImportEnvironmentVariablesRequestBody = z.object({ variables: z.record(z.string()), parentVariables: z.record(z.string()).optional(), override: z.boolean().optional(), - source: z - .discriminatedUnion("type", [ - z.object({ type: z.literal("user"), userId: z.string() }), - z.object({ type: z.literal("integration"), integration: z.string() }), - ]) - .optional(), }); export type ImportEnvironmentVariablesRequestBody = z.infer< @@ -1602,31 +1565,3 @@ export const AppendToStreamResponseBody = z.object({ message: z.string().optional(), }); export type AppendToStreamResponseBody = z.infer; - -export const TaskEventSchema = z.object({ - id: z.string(), - runId: z.string(), - traceId: z.string(), - spanId: z.string(), - parentId: z.string().nullish(), - message: z.string(), - kind: TaskEventKindSchema, - level: TaskEventLevelSchema, - status: TaskEventStatusSchema, - startTime: z.coerce.date(), - duration: z.number(), - isError: z.boolean(), - isCancelled: z.boolean(), - properties: z.record(z.unknown()).optional(), - metadata: z.record(z.unknown()).optional(), - style: TaskEventStyle.optional(), -}); - -export type TaskEventSchema = z.infer; - -export const RunEventsResponseSchema = z.object({ - events: z.array(TaskEventSchema), -}); - -export type RunEventsResponseSchema = z.infer; -