stageflow 0.30.0 → 0.31.0

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.
@@ -1,7 +1,15 @@
1
1
  import { type StageProviderSupport } from "./providerSupport.js";
2
2
  export declare function cursorExtensionEntryInPackage(packageRoot: string): string | undefined;
3
+ export declare function resolveBundledCursorExtensionPath(): string | undefined;
4
+ /** `$STAGEFLOW_HOME/agent/npm` install (Pi-style); after bundled dependency. */
5
+ export declare function resolveAgentHomeCursorExtensionPath(): string | undefined;
3
6
  export declare function resolveCursorExtensionPath(): string | undefined;
4
7
  export declare function isCursorModelRef(modelRef: string): boolean;
5
8
  export declare function workshopCursorBridgeHint(toolNames: readonly string[]): string;
6
9
  export declare function cursorBridgePrompt(message: string, modelId: string | undefined, toolNames: readonly string[]): string;
7
10
  export declare const cursorProviderSupport: StageProviderSupport;
11
+ export declare function readCursorApiKey(env?: NodeJS.ProcessEnv): string | undefined;
12
+ export declare function getCursorHostStatus(env?: NodeJS.ProcessEnv): {
13
+ cursorSdkReady: boolean;
14
+ cursorApiKeyConfigured: boolean;
15
+ };
@@ -5,10 +5,13 @@
5
5
  * module's prepare()/env sealing path.
6
6
  */
7
7
  import { existsSync, readFileSync } from "node:fs";
8
- import os from "node:os";
8
+ import { createRequire } from "node:module";
9
9
  import path from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
+ import { readSecretFromEnvOrFile } from "../config/secretFromEnvOrFile.js";
12
+ import { globalStageflowHome } from "../project/globalHome.js";
11
13
  import { registerProviderSupport, } from "./providerSupport.js";
14
+ import { stageflowAgentAuthPath } from "../runtime/credentialBinding.js";
12
15
  const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES";
13
16
  /**
14
17
  * Locate the pi-cursor-sdk extension entry without opening full global package
@@ -17,21 +20,18 @@ const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES";
17
20
  * Resolution order:
18
21
  * 1. STAGEFLOW_CURSOR_EXTENSION (absolute path to the extension .ts/.js)
19
22
  * 2. Path package from the Pi agent settings.json (same source interactive pi uses)
20
- * 3. npm install under <pi-agent>/npm/node_modules/pi-cursor-sdk
23
+ * 3. npm dependency `pi-cursor-sdk` bundled with the Stageflow install (repo or global)
24
+ * 4. npm install under <stageflow-agent>/npm/node_modules/pi-cursor-sdk
21
25
  * (`dist/index.js` for 0.3+, `src/index.ts` for older publishes)
22
- * 4. Sibling checkout at ../pi-cursor-sdk relative to this repo
26
+ * 5. Sibling checkout at ../pi-cursor-sdk relative to this repo
23
27
  *
24
- * A stage worker's HOME is an empty attempt directory, so the Pi agent dir is
25
- * the directory of STAGEFLOW_PI_HOME_AUTH_PATH when the Host set it, not
26
- * os.homedir().
28
+ * A stage worker's HOME is an empty attempt directory; the agent dir comes from
29
+ * STAGEFLOW_AGENT_AUTH_PATH when the Host set it.
27
30
  */
28
31
  const CURSOR_PACKAGE_ENTRIES = ["dist/index.js", "src/index.ts"];
29
- /** Pi agent dir. Stage workers must not use os.homedir(): HOME is the attempt dir. */
32
+ /** Stageflow agent dir ($STAGEFLOW_HOME/agent). Stage workers must not use os.homedir(): HOME is the attempt dir. */
30
33
  function piAgentDir() {
31
- const auth = process.env.STAGEFLOW_PI_HOME_AUTH_PATH?.trim();
32
- if (auth)
33
- return path.dirname(path.resolve(auth));
34
- return path.join(os.homedir(), ".pi", "agent");
34
+ return path.dirname(stageflowAgentAuthPath());
35
35
  }
36
36
  export function cursorExtensionEntryInPackage(packageRoot) {
37
37
  for (const rel of CURSOR_PACKAGE_ENTRIES) {
@@ -42,6 +42,21 @@ export function cursorExtensionEntryInPackage(packageRoot) {
42
42
  }
43
43
  return undefined;
44
44
  }
45
+ export function resolveBundledCursorExtensionPath() {
46
+ const require = createRequire(import.meta.url);
47
+ let packageRoot;
48
+ try {
49
+ packageRoot = path.dirname(require.resolve("pi-cursor-sdk/package.json"));
50
+ }
51
+ catch {
52
+ return undefined;
53
+ }
54
+ return cursorExtensionEntryInPackage(packageRoot);
55
+ }
56
+ /** `$STAGEFLOW_HOME/agent/npm` install (Pi-style); after bundled dependency. */
57
+ export function resolveAgentHomeCursorExtensionPath() {
58
+ return cursorExtensionEntryInPackage(path.join(piAgentDir(), "npm", "node_modules", "pi-cursor-sdk"));
59
+ }
45
60
  export function resolveCursorExtensionPath() {
46
61
  const fromEnv = process.env.STAGEFLOW_CURSOR_EXTENSION?.trim();
47
62
  if (fromEnv && existsSync(fromEnv)) {
@@ -51,7 +66,11 @@ export function resolveCursorExtensionPath() {
51
66
  if (fromSettings) {
52
67
  return fromSettings;
53
68
  }
54
- const npmEntry = cursorExtensionEntryInPackage(path.join(piAgentDir(), "npm", "node_modules", "pi-cursor-sdk"));
69
+ const bundled = resolveBundledCursorExtensionPath();
70
+ if (bundled) {
71
+ return bundled;
72
+ }
73
+ const npmEntry = resolveAgentHomeCursorExtensionPath();
55
74
  if (npmEntry) {
56
75
  return npmEntry;
57
76
  }
@@ -78,7 +97,7 @@ function sealCursorSettingSources() {
78
97
  function missingExtensionReason(modelRef) {
79
98
  return [
80
99
  `Model "${modelRef}" requires pi-cursor-sdk, but no extension entry was found.`,
81
- "Install with `pi install npm:pi-cursor-sdk`, or set STAGEFLOW_CURSOR_EXTENSION",
100
+ `Install Stageflow with npm (includes pi-cursor-sdk), or set STAGEFLOW_CURSOR_EXTENSION`,
82
101
  "to the absolute path of pi-cursor-sdk/dist/index.js (or src/index.ts).",
83
102
  "Also ensure a Cursor SDK API key is available via Pi /login or CURSOR_API_KEY.",
84
103
  ].join(" ");
@@ -122,6 +141,37 @@ export const cursorProviderSupport = {
122
141
  ].join(" ");
123
142
  },
124
143
  };
144
+ const CURSOR_API_KEY_FILE_NAME = "cursor-api-key";
145
+ export function readCursorApiKey(env = process.env) {
146
+ let fromEnv;
147
+ try {
148
+ fromEnv = readSecretFromEnvOrFile(env, "CURSOR_API_KEY");
149
+ }
150
+ catch {
151
+ return undefined;
152
+ }
153
+ if (fromEnv !== undefined && fromEnv.trim() !== "") {
154
+ return fromEnv.trim();
155
+ }
156
+ const filePath = path.join(globalStageflowHome(), "agent", CURSOR_API_KEY_FILE_NAME);
157
+ if (!existsSync(filePath)) {
158
+ return undefined;
159
+ }
160
+ try {
161
+ const raw = readFileSync(filePath, "utf8").replace(/\r?\n$/, "").trim();
162
+ return raw.length > 0 ? raw : undefined;
163
+ }
164
+ catch {
165
+ return undefined;
166
+ }
167
+ }
168
+ export function getCursorHostStatus(env = process.env) {
169
+ const key = readCursorApiKey(env);
170
+ return {
171
+ cursorSdkReady: resolveCursorExtensionPath() !== undefined,
172
+ cursorApiKeyConfigured: key !== undefined,
173
+ };
174
+ }
125
175
  registerProviderSupport(cursorProviderSupport);
126
176
  function resolveFromPiSettings() {
127
177
  const agentDir = piAgentDir();
@@ -25,12 +25,16 @@ export type ProvidersListResult = {
25
25
  via: "pi";
26
26
  providers: ProviderSummary[];
27
27
  };
28
- export type PiHomeDetectResult = {
29
- piHomeUsable: boolean;
28
+ export type ProvidersDetectResult = {
30
29
  credentialSource?: CredentialSource;
31
30
  provisional: boolean;
32
31
  source: CredentialSource;
32
+ authConfigured: boolean;
33
+ cursorSdkReady: boolean;
34
+ cursorApiKeyConfigured: boolean;
33
35
  };
36
+ /** @deprecated Use ProvidersDetectResult */
37
+ export type PiHomeDetectResult = ProvidersDetectResult;
34
38
  export type ProviderAuthRuntime = {
35
39
  getProviders(): readonly Provider[];
36
40
  getProvider(providerId: string): Provider | undefined;
@@ -84,7 +88,7 @@ export type OauthLoginResult = {
84
88
  };
85
89
  export declare function loginWithOauth(cwd: string, providerId: string, interaction: AuthInteraction, ctx?: ProviderAuthContext): Promise<OauthLoginResult>;
86
90
  export declare function logoutProvider(cwd: string, providerId: string, ctx?: ProviderAuthContext): Promise<ProviderAuthStatus>;
87
- export declare function detectPiHome(cwd: string): PiHomeDetectResult;
91
+ export declare function detectPiHome(cwd: string): ProvidersDetectResult;
88
92
  export declare function getCredentialSourceSettings(cwd: string): {
89
93
  credentialSource?: CredentialSource;
90
94
  binding: {
@@ -1,5 +1,6 @@
1
1
  import { CredentialSynchronizationError, ModelRuntime, } from "@earendil-works/pi-coding-agent";
2
- import { isUsableAuthFile, parseCredentialSource, piHomeAuthPath, resolveCredentialBinding, writeCredentialSourceToContext, } from "../runtime/credentialBinding.js";
2
+ import { parseCredentialSource, resolveCredentialBinding, writeCredentialSourceToContext, isUsableAuthFile, } from "../runtime/credentialBinding.js";
3
+ import { getCursorHostStatus } from "./cursorProvider.js";
3
4
  import { readPersistedCredentialSourceFromContext, } from "../runtime/settingsFile.js";
4
5
  import { resolveProjectContext } from "../project/resolveProjectContext.js";
5
6
  export function makeMutationLock() {
@@ -251,10 +252,11 @@ export function detectPiHome(cwd) {
251
252
  const binding = resolveCredentialBinding(projectCtx);
252
253
  const persisted = readPersistedCredentialSourceFromContext(projectCtx);
253
254
  return {
254
- piHomeUsable: isUsableAuthFile(piHomeAuthPath()),
255
255
  ...(persisted !== undefined ? { credentialSource: persisted } : {}),
256
256
  provisional: binding.provisional,
257
257
  source: binding.source,
258
+ authConfigured: isUsableAuthFile(binding.authPath),
259
+ ...getCursorHostStatus(),
258
260
  };
259
261
  }
260
262
  export function getCredentialSourceSettings(cwd) {
@@ -272,7 +274,7 @@ export function getCredentialSourceSettings(cwd) {
272
274
  export function setCredentialSource(cwd, credentialSource) {
273
275
  const parsed = parseCredentialSource(credentialSource);
274
276
  if (parsed === undefined) {
275
- throw new ProviderAuthError('credentialSource must be "pi_home" or "sf_owned"', 400);
277
+ throw new ProviderAuthError('credentialSource must be "sf_owned"', 400);
276
278
  }
277
279
  const projectCtx = resolveProjectContext(cwd);
278
280
  writeCredentialSourceToContext(projectCtx, parsed);
@@ -215,6 +215,13 @@ async function freeDiskCheck(home, env) {
215
215
  const size = await readFilesystemSize(home);
216
216
  const floor = resolveMinFreeDiskFloor(env.STAGEFLOW_MIN_FREE_DISK_BYTES, size.totalBytes);
217
217
  const freeGiB = (size.freeBytes / (1024 * 1024 * 1024)).toFixed(2);
218
+ if (floor === 0) {
219
+ return {
220
+ id: "free_disk",
221
+ status: "pass",
222
+ message: `${home} has ${freeGiB} GiB free (free-disk admission disabled)`,
223
+ };
224
+ }
218
225
  if (size.freeBytes < floor) {
219
226
  return {
220
227
  id: "free_disk",
@@ -1,6 +1,6 @@
1
1
  import type { AuthInteraction } from "@earendil-works/pi-ai";
2
2
  import { type ProviderAuthContext } from "../agent/providerAuth.js";
3
- export declare const PROVIDERS_USAGE = "Usage:\n sf providers list\n sf providers status [--provider <id>]\n sf providers detect\n sf providers source [get | set <pi_home|sf_owned>]\n sf providers login <providerId> [--type api_key|oauth] [--api-key-env <VAR>]\n sf providers logout <providerId>";
3
+ export declare const PROVIDERS_USAGE = "Usage:\n sf providers list\n sf providers status [--provider <id>]\n sf providers detect\n sf providers source [get | set sf_owned]\n sf providers login <providerId> [--type api_key|oauth] [--api-key-env <VAR>]\n sf providers logout <providerId>";
4
4
  export type ProvidersCommandIo = {
5
5
  log: (line: string) => void;
6
6
  error: (line: string) => void;
@@ -6,7 +6,7 @@ export const PROVIDERS_USAGE = `Usage:
6
6
  sf providers list
7
7
  sf providers status [--provider <id>]
8
8
  sf providers detect
9
- sf providers source [get | set <pi_home|sf_owned>]
9
+ sf providers source [get | set sf_owned]
10
10
  sf providers login <providerId> [--type api_key|oauth] [--api-key-env <VAR>]
11
11
  sf providers logout <providerId>`;
12
12
  const defaultIo = {
@@ -167,7 +167,7 @@ export async function runProvidersCommand(args, cwd, io = {}, ctx = defaultConte
167
167
  ? `credentialSource=${detected.credentialSource}`
168
168
  : "credentialSource=(unset)";
169
169
  out.log([
170
- `piHomeUsable=${detected.piHomeUsable}`,
170
+ `authConfigured=${detected.authConfigured}`,
171
171
  sourceLine,
172
172
  `provisional=${detected.provisional}`,
173
173
  `bindingSource=${detected.source}`,
@@ -188,8 +188,8 @@ export async function runProvidersCommand(args, cwd, io = {}, ctx = defaultConte
188
188
  }
189
189
  if (action === "set") {
190
190
  const value = parsed.positionals[1];
191
- if (value !== "pi_home" && value !== "sf_owned") {
192
- out.error('source set requires pi_home or sf_owned');
191
+ if (value !== "sf_owned") {
192
+ out.error('source set requires sf_owned');
193
193
  out.error(PROVIDERS_USAGE);
194
194
  return 1;
195
195
  }
package/dist/cli.js CHANGED
@@ -69,7 +69,7 @@ const USAGE = `Usage:
69
69
  sf providers list
70
70
  sf providers status [--provider <id>]
71
71
  sf providers detect
72
- sf providers source [get | set <pi_home|sf_owned>]
72
+ sf providers source [get | set sf_owned]
73
73
  sf providers login <providerId> [--type api_key|oauth] [--api-key-env <VAR>]
74
74
  sf providers logout <providerId>
75
75
  sf skills list
@@ -1,3 +1,3 @@
1
1
  export declare const PACKAGE_NAME = "stageflow";
2
- export declare const PACKAGE_VERSION = "0.30.0";
2
+ export declare const PACKAGE_VERSION = "0.31.0";
3
3
  export declare const BUILD_SHA: string;
@@ -1,3 +1,3 @@
1
1
  export const PACKAGE_NAME = "stageflow";
2
- export const PACKAGE_VERSION = "0.30.0";
2
+ export const PACKAGE_VERSION = "0.31.0";
3
3
  export const BUILD_SHA = process.env.STAGEFLOW_BUILD_SHA ?? "unknown";
@@ -32,7 +32,7 @@ export declare function durableRootDiskBreakdown(durableRoot: string, options?:
32
32
  export declare function parseDiskBytesThreshold(raw: string | undefined, filesystemTotalBytes: number): number | undefined;
33
33
  /**
34
34
  * Resolve `STAGEFLOW_MIN_FREE_DISK_BYTES`: bytes or `%` of filesystem.
35
- * Unset / empty / invalid → `max(2 GiB, 10% of filesystemTotalBytes)`.
35
+ * Unset / empty / invalid → `0` (admission check disabled). Set explicitly to enable.
36
36
  */
37
37
  export declare function resolveMinFreeDiskFloor(raw: string | undefined, filesystemTotalBytes: number): number;
38
38
  /**
@@ -162,20 +162,15 @@ export function parseDiskBytesThreshold(raw, filesystemTotalBytes) {
162
162
  return undefined;
163
163
  return n;
164
164
  }
165
- const DEFAULT_MIN_FREE_DISK_BYTES = 2 * 1024 * 1024 * 1024;
166
165
  /**
167
166
  * Resolve `STAGEFLOW_MIN_FREE_DISK_BYTES`: bytes or `%` of filesystem.
168
- * Unset / empty / invalid → `max(2 GiB, 10% of filesystemTotalBytes)`.
167
+ * Unset / empty / invalid → `0` (admission check disabled). Set explicitly to enable.
169
168
  */
170
169
  export function resolveMinFreeDiskFloor(raw, filesystemTotalBytes) {
171
170
  const parsed = parseDiskBytesThreshold(raw, filesystemTotalBytes);
172
171
  if (parsed !== undefined)
173
172
  return parsed;
174
- const total = Number.isFinite(filesystemTotalBytes) && filesystemTotalBytes > 0
175
- ? filesystemTotalBytes
176
- : 0;
177
- const tenPercent = Math.floor(total * 0.1);
178
- return Math.max(DEFAULT_MIN_FREE_DISK_BYTES, tenPercent);
173
+ return 0;
179
174
  }
180
175
  /**
181
176
  * One named warning when free space is below `STAGEFLOW_DISK_WARN_BYTES`. Never throws.
@@ -7,14 +7,17 @@ export type CredentialBinding = {
7
7
  provisional: boolean;
8
8
  };
9
9
  export type ResolveCredentialBindingOptions = {
10
- piHomeAuthPath?: string;
10
+ agentAuthPath?: string;
11
11
  };
12
12
  export declare function sfOwnedAgentDir(): string;
13
13
  export declare function sfOwnedAuthPath(): string;
14
- export declare const PI_HOME_AUTH_PATH_ENV = "STAGEFLOW_PI_HOME_AUTH_PATH";
15
- /** A stage worker's HOME is an empty attempt dir, so the Host passes its own Pi auth path through the environment. */
14
+ export declare const STAGEFLOW_AGENT_AUTH_PATH_ENV = "STAGEFLOW_AGENT_AUTH_PATH";
15
+ /** @deprecated Use STAGEFLOW_AGENT_AUTH_PATH_ENV. */
16
+ export declare const PI_HOME_AUTH_PATH_ENV = "STAGEFLOW_AGENT_AUTH_PATH";
17
+ export declare function stageflowAgentAuthPath(override?: string): string;
18
+ /** @deprecated Use stageflowAgentAuthPath */
16
19
  export declare function piHomeAuthPath(override?: string): string;
17
20
  export declare function isUsableAuthFile(authPath: string): boolean;
18
21
  export declare function ensureSfOwnedAuthStore(): string;
19
- export declare function resolveCredentialBinding(ctx: ProjectContext | string, options?: ResolveCredentialBindingOptions): CredentialBinding;
22
+ export declare function resolveCredentialBinding(ctx: ProjectContext | string, _options?: ResolveCredentialBindingOptions): CredentialBinding;
20
23
  export { readCredentialSourceFromFile, writeCredentialSourceToFile, } from "./settingsFile.js";
@@ -1,5 +1,4 @@
1
1
  import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
  import { ensureGlobalHome } from "../project/globalHome.js";
5
4
  import { resolveProjectContext, } from "../project/resolveProjectContext.js";
@@ -11,12 +10,23 @@ export function sfOwnedAgentDir() {
11
10
  export function sfOwnedAuthPath() {
12
11
  return path.join(sfOwnedAgentDir(), "auth.json");
13
12
  }
14
- export const PI_HOME_AUTH_PATH_ENV = "STAGEFLOW_PI_HOME_AUTH_PATH";
15
- /** A stage worker's HOME is an empty attempt dir, so the Host passes its own Pi auth path through the environment. */
13
+ export const STAGEFLOW_AGENT_AUTH_PATH_ENV = "STAGEFLOW_AGENT_AUTH_PATH";
14
+ /** @deprecated Use STAGEFLOW_AGENT_AUTH_PATH_ENV. */
15
+ export const PI_HOME_AUTH_PATH_ENV = STAGEFLOW_AGENT_AUTH_PATH_ENV;
16
+ export function stageflowAgentAuthPath(override) {
17
+ if (override !== undefined) {
18
+ return path.resolve(override);
19
+ }
20
+ const fromEnv = process.env[STAGEFLOW_AGENT_AUTH_PATH_ENV]?.trim() ||
21
+ process.env.STAGEFLOW_PI_HOME_AUTH_PATH?.trim();
22
+ if (fromEnv) {
23
+ return path.resolve(fromEnv);
24
+ }
25
+ return sfOwnedAuthPath();
26
+ }
27
+ /** @deprecated Use stageflowAgentAuthPath */
16
28
  export function piHomeAuthPath(override) {
17
- return (override ??
18
- (process.env[PI_HOME_AUTH_PATH_ENV] || undefined) ??
19
- path.join(os.homedir(), ".pi", "agent", "auth.json"));
29
+ return stageflowAgentAuthPath(override);
20
30
  }
21
31
  export function isUsableAuthFile(authPath) {
22
32
  if (!existsSync(authPath))
@@ -43,35 +53,14 @@ export function ensureSfOwnedAuthStore() {
43
53
  }
44
54
  return authPath;
45
55
  }
46
- export function resolveCredentialBinding(ctx, options = {}) {
56
+ export function resolveCredentialBinding(ctx, _options = {}) {
47
57
  const projectCtx = typeof ctx === "string" ? resolveProjectContext(ctx) : ctx;
48
- const piHome = piHomeAuthPath(options.piHomeAuthPath);
49
58
  const persisted = readCredentialSourceFromContext(projectCtx);
50
- if (persisted !== undefined) {
51
- if (persisted === "sf_owned") {
52
- return {
53
- source: "sf_owned",
54
- authPath: ensureSfOwnedAuthStore(),
55
- provisional: false,
56
- };
57
- }
58
- return {
59
- source: "pi_home",
60
- authPath: piHome,
61
- provisional: false,
62
- };
63
- }
64
- if (isUsableAuthFile(piHome)) {
65
- return {
66
- source: "pi_home",
67
- authPath: piHome,
68
- provisional: true,
69
- };
70
- }
59
+ const authPath = ensureSfOwnedAuthStore();
71
60
  return {
72
61
  source: "sf_owned",
73
- authPath: ensureSfOwnedAuthStore(),
74
- provisional: true,
62
+ authPath,
63
+ provisional: persisted === undefined,
75
64
  };
76
65
  }
77
66
  export { readCredentialSourceFromFile, writeCredentialSourceToFile, } from "./settingsFile.js";
@@ -971,6 +971,7 @@ export async function runPipelineDag(options) {
971
971
  attempt,
972
972
  env: stageBinding.env,
973
973
  bindingKind: stageBinding.kind,
974
+ model: stage.model,
974
975
  grants,
975
976
  attemptHome,
976
977
  ...(browserEnv !== undefined ? { browserEnv } : {}),
@@ -1979,6 +1979,7 @@ export class RunManager {
1979
1979
  sessionFilePath,
1980
1980
  env: stageBinding.env,
1981
1981
  bindingKind: stageBinding.kind,
1982
+ ...(resumedStage !== undefined ? { model: resumedStage.model } : {}),
1982
1983
  ...(this.options.operatorCatalog !== undefined
1983
1984
  ? { operatorCatalog: this.options.operatorCatalog }
1984
1985
  : {}),
@@ -2506,17 +2507,26 @@ export class RunManager {
2506
2507
  };
2507
2508
  }
2508
2509
  async checkDiskFloorAdmission() {
2510
+ const raw = process.env.STAGEFLOW_MIN_FREE_DISK_BYTES;
2511
+ if (raw === undefined || raw.trim() === "") {
2512
+ return undefined;
2513
+ }
2509
2514
  try {
2510
2515
  const readFree = this.options.freeSpaceReader ?? readFilesystemSize;
2511
2516
  const size = await readFree(globalStageflowHome());
2512
- const minFreeBytes = resolveMinFreeDiskFloor(process.env.STAGEFLOW_MIN_FREE_DISK_BYTES, size.totalBytes);
2517
+ const minFreeBytes = resolveMinFreeDiskFloor(raw, size.totalBytes);
2518
+ if (minFreeBytes === 0) {
2519
+ return undefined;
2520
+ }
2513
2521
  if (size.freeBytes < minFreeBytes) {
2514
2522
  return this.insufficientDiskFailure(size.freeBytes, minFreeBytes);
2515
2523
  }
2516
2524
  return undefined;
2517
2525
  }
2518
2526
  catch (err) {
2519
- // Default floor is always active via resolveMinFreeDiskFloor — fail closed.
2527
+ if (resolveMinFreeDiskFloor(raw, 0) === 0 && !raw.trim().endsWith("%")) {
2528
+ return undefined;
2529
+ }
2520
2530
  return {
2521
2531
  ok: false,
2522
2532
  reason: `Disk free-space check failed: ${err instanceof Error ? err.message : String(err)}`,
@@ -2,9 +2,9 @@ import type { ProjectContext } from "../project/resolveProjectContext.js";
2
2
  import type { StageflowContext } from "../project/resolveStageflowContext.js";
3
3
  export declare const SETTINGS_FILE_NAME = "settings.json";
4
4
  export declare const INVALID_SLOT_COUNT_MESSAGE = "maxConcurrent must be an integer >= 1";
5
- export declare const INVALID_CREDENTIAL_SOURCE_MESSAGE = "credentialSource must be \"pi_home\" or \"sf_owned\"";
5
+ export declare const INVALID_CREDENTIAL_SOURCE_MESSAGE = "credentialSource must be \"sf_owned\"";
6
6
  export declare const INVALID_WORKSHOP_MODEL_MESSAGE = "workshopModel must be a non-empty string";
7
- export type CredentialSource = "pi_home" | "sf_owned";
7
+ export type CredentialSource = "sf_owned";
8
8
  export type FactorySettings = {
9
9
  maxConcurrent?: number;
10
10
  credentialSource?: CredentialSource;
@@ -5,7 +5,7 @@ import { globalStageflowHome } from "../project/globalHome.js";
5
5
  import { storeRootFor } from "../runstore/paths.js";
6
6
  export const SETTINGS_FILE_NAME = "settings.json";
7
7
  export const INVALID_SLOT_COUNT_MESSAGE = "maxConcurrent must be an integer >= 1";
8
- export const INVALID_CREDENTIAL_SOURCE_MESSAGE = 'credentialSource must be "pi_home" or "sf_owned"';
8
+ export const INVALID_CREDENTIAL_SOURCE_MESSAGE = 'credentialSource must be "sf_owned"';
9
9
  export const INVALID_WORKSHOP_MODEL_MESSAGE = "workshopModel must be a non-empty string";
10
10
  export function globalSettingsFilePath() {
11
11
  return path.join(globalStageflowHome(), SETTINGS_FILE_NAME);
@@ -29,9 +29,12 @@ export function parseSlotCount(value) {
29
29
  return value;
30
30
  }
31
31
  export function parseCredentialSource(value) {
32
- if (value === "pi_home" || value === "sf_owned") {
32
+ if (value === "sf_owned") {
33
33
  return value;
34
34
  }
35
+ if (value === "pi_home") {
36
+ return "sf_owned";
37
+ }
35
38
  return undefined;
36
39
  }
37
40
  export function parseWorkshopModelSetting(value) {
@@ -18,6 +18,7 @@ export type StageLaunchInput = {
18
18
  grants?: ResolvedStageGrants;
19
19
  attemptHome?: string;
20
20
  browserEnv?: Record<string, string>;
21
+ model?: string;
21
22
  };
22
23
  export type StageLaunchResult = {
23
24
  type: "succeeded";
@@ -1,9 +1,10 @@
1
1
  import * as childProcess from "node:child_process";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { logger as rootLogger, resolveLogMaxLineBytes, } from "../logging/logger.js";
4
+ import { isCursorModelRef, readCursorApiKey } from "../agent/cursorProvider.js";
4
5
  import { BROWSER_ENV_PREFIX } from "../browser/browserHost.js";
5
6
  import { PACKAGE_VERSION } from "../package-meta.js";
6
- import { PI_HOME_AUTH_PATH_ENV, piHomeAuthPath } from "./credentialBinding.js";
7
+ import { PI_HOME_AUTH_PATH_ENV, stageflowAgentAuthPath } from "./credentialBinding.js";
7
8
  import { redactString } from "../logging/redact.js";
8
9
  import { getNamedSecrets } from "../logging/namedSecrets.js";
9
10
  import { readMaxActiveStageProcesses, } from "./stageConcurrency.js";
@@ -338,8 +339,15 @@ export class StageProcessLauncher {
338
339
  : overlaid;
339
340
  const childEnv = {
340
341
  ...withBrowser,
341
- [PI_HOME_AUTH_PATH_ENV]: piHomeAuthPath(),
342
+ [PI_HOME_AUTH_PATH_ENV]: stageflowAgentAuthPath(),
342
343
  };
344
+ const cursorApiKey = readCursorApiKey(hostEnv);
345
+ if (isCursorModelRef(input.model ?? "") &&
346
+ cursorApiKey !== undefined &&
347
+ cursorApiKey !== "" &&
348
+ childEnv.CURSOR_API_KEY === undefined) {
349
+ childEnv.CURSOR_API_KEY = cursorApiKey;
350
+ }
343
351
  const child = this.forkFn(this.cliEntry, args, {
344
352
  cwd: input.rootDir,
345
353
  env: { ...childEnv, [SF_STAGE_WORKER]: "1" },
@@ -20,6 +20,7 @@ import { createRunChangeBus, getRunChangeBusFromWrappedStore, isRunStoreWrapped,
20
20
  import { createMcpHttpHandler, resolveMcpStateless, } from "../mcp/server.js";
21
21
  import { loadHostConfig, } from "../config/hostConfig.js";
22
22
  import { bootProviderConfig } from "../agent/bootProviderConfig.js";
23
+ import { readCursorApiKey } from "../agent/cursorProvider.js";
23
24
  import { applyPendingRestoreAtBoot, } from "../runstore/restore.js";
24
25
  import { assertTmpdirUsable } from "../runstore/assertTmpdir.js";
25
26
  import { logger as rootLogger } from "../logging/logger.js";
@@ -80,6 +81,11 @@ export async function bootstrapStageflowHost(options) {
80
81
  const ctx = await resolveStageflowContext(invocationCwd);
81
82
  const cwd = ctx.invocationCwd;
82
83
  ensureGlobalHome();
84
+ const cursorKey = readCursorApiKey(env);
85
+ if (cursorKey !== undefined &&
86
+ (env.CURSOR_API_KEY === undefined || env.CURSOR_API_KEY.trim() === "")) {
87
+ process.env.CURSOR_API_KEY = cursorKey;
88
+ }
83
89
  assertTmpdirUsable(env);
84
90
  const hostConfig = options.hostConfig ??
85
91
  (options.skipHostConfig