From 3ce624a5b146990308e3c8abf27c3566745746ca Mon Sep 17 00:00:00 2001 From: finalerock44 Date: Mon, 26 Oct 2026 12:11:19 +0000 Subject: [PATCH 1/4] feat(cloud): say which flows use extended runtime, and that they cost 2x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Any org can now opt a flow into the 20-minute extended runtime with DEVICECLOUD_OVERRIDE_EXTENDED_RUNTIME: true, and each such flow is billed at 2x. `dcd cloud` already listed the override under "Overrides"; it now adds one line after them, e.g. "2 flows with extended runtime: up to 20 min each, billed at 2×", so the price is clear before the run. It counts the same values the API treats as opted in (true and 'true'). The MCP run_cloud_test tool's waitTimeoutSeconds description now says that such a flow can run for up to 20 minutes, past the 600 s default. --- src/commands/cloud.ts | 13 ++++++++++- src/mcp/tools/run-cloud-test.ts | 4 +++- src/services/execution-plan.service.ts | 13 +++++++++++ test/unit/extended-runtime.test.ts | 32 ++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/unit/extended-runtime.test.ts diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index fc3f04f..204665d 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -12,7 +12,7 @@ import { writeJSONFile, } from '../methods.js'; import { DeviceValidationService } from '../services/device-validation.service.js'; -import { plan } from '../services/execution-plan.service.js'; +import { countExtendedRuntimeFlows, plan } from '../services/execution-plan.service.js'; import { isSubmitEndpointMissing, submitWithRetry, @@ -774,6 +774,17 @@ export const cloudCommand = defineCommand({ ), ); } + + // The 2x is charged per flow, so say it before the run, not on the bill. + const extendedFlows = countExtendedRuntimeFlows(flowOverrides); + if (extendedFlows > 0) { + submitRows.push( + '', + ui.note( + `${extendedFlows} flow${extendedFlows === 1 ? '' : 's'} with extended runtime: up to 20 min each, billed at 2×`, + ), + ); + } } out(ui.section('Submitting new job')); diff --git a/src/mcp/tools/run-cloud-test.ts b/src/mcp/tools/run-cloud-test.ts index 99d1437..d099fe3 100644 --- a/src/mcp/tools/run-cloud-test.ts +++ b/src/mcp/tools/run-cloud-test.ts @@ -115,7 +115,9 @@ export function registerRunCloudTest(server: McpServer): void { .min(30) .max(3600) .optional() - .describe('Max seconds to wait when wait is true (default 600)'), + .describe( + 'Max seconds to wait when wait is true (default 600). A flow with DEVICECLOUD_OVERRIDE_EXTENDED_RUNTIME: true in its env can run for up to 20 minutes, so raise this when the run includes one.', + ), }, annotations: { title: 'Run a cloud test', diff --git a/src/services/execution-plan.service.ts b/src/services/execution-plan.service.ts index 994c378..04fad05 100644 --- a/src/services/execution-plan.service.ts +++ b/src/services/execution-plan.service.ts @@ -173,6 +173,19 @@ function extractDeviceCloudOverrides( return overrides; } +/** + * How many flows opt into the extended test runtime (up to 20 minutes, billed + * at 2x) with `DEVICECLOUD_OVERRIDE_EXTENDED_RUNTIME`. Matches what the API + * accepts: YAML `true` or the string `'true'` (dcd#1624). + */ +export function countExtendedRuntimeFlows( + flowOverrides: Record>, +): number { + return Object.values(flowOverrides).filter( + ({ EXTENDED_RUNTIME }) => EXTENDED_RUNTIME === true || EXTENDED_RUNTIME === 'true', + ).length; +} + /** * Generate execution plan for a single flow file * @param normalizedInput - Normalized path to the flow file diff --git a/test/unit/extended-runtime.test.ts b/test/unit/extended-runtime.test.ts new file mode 100644 index 0000000..a390e93 --- /dev/null +++ b/test/unit/extended-runtime.test.ts @@ -0,0 +1,32 @@ +import { expect } from 'chai'; + +import { countExtendedRuntimeFlows } from '../../src/services/execution-plan.service.js'; + +/** + * `dcd cloud` notes how many flows opted into the extended runtime, because + * the API bills each of them at 2x (dcd#1624). It must count exactly the + * values the API treats as opted in: YAML `true` and the string 'true'. + */ +describe('countExtendedRuntimeFlows', () => { + it('counts flows whose override is true or "true"', () => { + expect( + countExtendedRuntimeFlows({ + 'a.yaml': { EXTENDED_RUNTIME: true }, + 'b.yaml': { EXTENDED_RUNTIME: 'true' }, + 'c.yaml': { DEVICE_LOCALE: 'de_DE' }, + 'd.yaml': {}, + }), + ).to.equal(2); + }); + + it('ignores values the API would not treat as opted in', () => { + expect( + countExtendedRuntimeFlows({ + 'a.yaml': { EXTENDED_RUNTIME: false }, + 'b.yaml': { EXTENDED_RUNTIME: 'false' }, + 'c.yaml': { EXTENDED_RUNTIME: 'TRUE' }, + 'd.yaml': { EXTENDED_RUNTIME: 1 }, + }), + ).to.equal(0); + }); +}); From ea193693690712de892f9f5eb262bc9382376008 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:24:43 +0000 Subject: [PATCH 2/4] Clarify billing details in extended runtime test Updated comment to clarify billing for extended runtime flows. --- test/unit/extended-runtime.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/extended-runtime.test.ts b/test/unit/extended-runtime.test.ts index a390e93..56c6ac1 100644 --- a/test/unit/extended-runtime.test.ts +++ b/test/unit/extended-runtime.test.ts @@ -4,7 +4,7 @@ import { countExtendedRuntimeFlows } from '../../src/services/execution-plan.ser /** * `dcd cloud` notes how many flows opted into the extended runtime, because - * the API bills each of them at 2x (dcd#1624). It must count exactly the + * the API bills each of them at 2x. It must count exactly the * values the API treats as opted in: YAML `true` and the string 'true'. */ describe('countExtendedRuntimeFlows', () => { From 8b4ec468bad8af110ce689b2c82acf5323d9c45c Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:25:29 +0000 Subject: [PATCH 3/4] Fix comment formatting in countExtendedRuntimeFlows --- src/services/execution-plan.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/execution-plan.service.ts b/src/services/execution-plan.service.ts index 04fad05..eeb266a 100644 --- a/src/services/execution-plan.service.ts +++ b/src/services/execution-plan.service.ts @@ -176,7 +176,7 @@ function extractDeviceCloudOverrides( /** * How many flows opt into the extended test runtime (up to 20 minutes, billed * at 2x) with `DEVICECLOUD_OVERRIDE_EXTENDED_RUNTIME`. Matches what the API - * accepts: YAML `true` or the string `'true'` (dcd#1624). + * accepts: YAML `true` or the string `'true'`. */ export function countExtendedRuntimeFlows( flowOverrides: Record>, From c3b7fd2b390656cff7380f5c4a13f896a1a708d5 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:26:32 +0000 Subject: [PATCH 4/4] Update src/services/execution-plan.service.ts Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- src/services/execution-plan.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/execution-plan.service.ts b/src/services/execution-plan.service.ts index eeb266a..41ff84d 100644 --- a/src/services/execution-plan.service.ts +++ b/src/services/execution-plan.service.ts @@ -175,7 +175,7 @@ function extractDeviceCloudOverrides( /** * How many flows opt into the extended test runtime (up to 20 minutes, billed - * at 2x) with `DEVICECLOUD_OVERRIDE_EXTENDED_RUNTIME`. Matches what the API + * accepts: YAML `true` or the string `'true'`. * accepts: YAML `true` or the string `'true'`. */ export function countExtendedRuntimeFlows(