@hraness/slopcamera 3.2.6 → 3.2.8

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.
Files changed (78) hide show
  1. package/NOTICE.md +1 -7
  2. package/PRIVACY.md +16 -7
  3. package/README.md +41 -13
  4. package/apps/desktop/README.md +14 -130
  5. package/apps/desktop/application/context.ts +0 -9
  6. package/apps/desktop/application/default-registry.ts +0 -10
  7. package/apps/desktop/application/operation.ts +1 -5
  8. package/apps/desktop/application/operations/index.ts +0 -1
  9. package/apps/desktop/cli/args.ts +22 -86
  10. package/apps/desktop/cli/command-host-resources.ts +5 -1
  11. package/apps/desktop/cli/commands.ts +37 -67
  12. package/apps/desktop/cli/help.ts +24 -20
  13. package/apps/desktop/cli/html-scene.ts +479 -0
  14. package/apps/desktop/cli/io.ts +2 -1
  15. package/apps/desktop/cli/main.ts +28 -47
  16. package/apps/desktop/cli/media-ingest-model.ts +2 -0
  17. package/apps/desktop/cli/media-ingest-program.ts +9 -4
  18. package/apps/desktop/cli/menubar.ts +244 -0
  19. package/apps/desktop/cli/music-analysis-service.ts +61 -0
  20. package/apps/desktop/cli/portable-surface.ts +13 -4
  21. package/apps/desktop/cli/root-help-intro.ts +14 -0
  22. package/apps/desktop/code/application-node-planner.ts +0 -47
  23. package/apps/desktop/code/host-source-layout.ts +46 -0
  24. package/apps/desktop/code/public.ts +0 -1
  25. package/apps/desktop/code/runtime-identity.ts +50 -8
  26. package/apps/desktop/code/semantic-builder.ts +0 -43
  27. package/apps/desktop/code/source-bundle.ts +23 -6
  28. package/apps/desktop/code/source-typecheck.ts +131 -43
  29. package/apps/desktop/code/worker-client.ts +14 -2
  30. package/apps/desktop/core/music-analysis.ts +124 -1
  31. package/apps/desktop/dist/cli/main.js +261 -248
  32. package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
  33. package/apps/desktop/html-overlay/contracts.ts +3 -0
  34. package/apps/desktop/html-overlay/index.ts +28 -0
  35. package/apps/desktop/html-overlay/music-clock.ts +126 -0
  36. package/apps/desktop/html-overlay/rigged-glb.ts +429 -0
  37. package/apps/desktop/html-overlay/runtime.ts +4 -0
  38. package/apps/desktop/html-overlay/scaffolds.ts +2 -1
  39. package/apps/desktop/html-overlay/scene.ts +76 -0
  40. package/dist/cli.js +83 -81
  41. package/dist/{index-jchqst8w.js → index-7131pg1c.js} +43 -236
  42. package/dist/{index-mcy8z0br.js → index-fava6pge.js} +1 -1
  43. package/dist/{index-z7239b4h.js → index-h1k0fnjq.js} +1 -1
  44. package/dist/{index-p63wavx0.js → index-zfnddgay.js} +1 -1
  45. package/dist/index.js +4 -18
  46. package/dist/operations.js +2 -2
  47. package/dist/vectorize/worker.js +1 -1
  48. package/dist/workflow.js +3 -3
  49. package/docs/README.md +2 -1
  50. package/docs/studio.md +1 -1
  51. package/examples/html/music-video.html +514 -0
  52. package/examples/html/music-video.json +16 -0
  53. package/package.json +8 -21
  54. package/skills/slopcamera/SKILL.md +10 -1
  55. package/skills/slopcamera/references/diagrams.md +5 -14
  56. package/skills/slopcamera/references/install.md +2 -2
  57. package/skills/slopcamera/references/music-video.md +169 -0
  58. package/skills/slopcamera/references/reference-led-3d.md +1 -1
  59. package/skills/slopcamera/references/support.md +25 -0
  60. package/skills/slopcamera/references/video-projects.md +6 -2
  61. package/skills/slopcamera/references/web-media-excerpts.md +120 -0
  62. package/skills/slopcamera/references/workflows-sdk.md +1 -1
  63. package/src/cli.ts +28 -98
  64. package/src/index.ts +0 -23
  65. package/src/support-completion.ts +16 -0
  66. package/src/support.ts +42 -0
  67. package/src/version.ts +1 -1
  68. package/apps/desktop/application/operations/recording/index.ts +0 -5
  69. package/apps/desktop/application/operations/recording/pause.ts +0 -34
  70. package/apps/desktop/application/operations/recording/resume.ts +0 -34
  71. package/apps/desktop/application/operations/recording/shared.ts +0 -199
  72. package/apps/desktop/application/operations/recording/start.ts +0 -169
  73. package/apps/desktop/application/operations/recording/stop.ts +0 -34
  74. package/apps/desktop/capture/protocol.ts +0 -689
  75. package/apps/desktop/cli/capture-bundle.ts +0 -1117
  76. package/apps/desktop/cli/recording-controller.ts +0 -1567
  77. package/apps/desktop/cli/recording-daemon.ts +0 -931
  78. package/src/desktop.ts +0 -253
@@ -9,7 +9,6 @@ import {
9
9
  extname,
10
10
  join,
11
11
  relative,
12
- resolve,
13
12
  sep,
14
13
  } from "node:path";
15
14
 
@@ -31,6 +30,7 @@ import {
31
30
  WorkflowRuntimeIdentitySchema,
32
31
  type WorkflowRuntimeIdentity,
33
32
  } from "./contracts";
33
+ import { hostSourceRoots } from "./host-source-layout";
34
34
  import type { WorkflowSourceBundle } from "./source-bundle";
35
35
 
36
36
  export interface CreateWorkflowRuntimeIdentityOptions {
@@ -38,7 +38,7 @@ export interface CreateWorkflowRuntimeIdentityOptions {
38
38
  readonly bundle: WorkflowSourceBundle;
39
39
  }
40
40
 
41
- const APPLICATION_BUILD_DOMAIN = "studio.application-build/v1";
41
+ const APPLICATION_BUILD_DOMAIN = "studio.application-build/v2";
42
42
  const HOST_APPLICATION_BUILD_DOMAIN = "studio.host-application-build/v1";
43
43
  const APPLICATION_SOURCE_EXTENSIONS = new Set([
44
44
  ".json",
@@ -52,17 +52,23 @@ const APPLICATION_SOURCE_EXTENSIONS = new Set([
52
52
  const DESKTOP_SOURCE_DIRECTORIES = [
53
53
  "analysis",
54
54
  "application",
55
- "capture",
56
55
  "cli",
57
56
  "code",
58
57
  "contracts",
59
58
  "core",
60
- "runtime",
61
59
  "studio",
62
60
  "workflows",
63
61
  ] as const;
64
62
  const MAXIMUM_APPLICATION_SOURCE_FILES = 4_096;
65
63
  const MAXIMUM_APPLICATION_SOURCE_FILE_BYTES = 64 * 1024 * 1024;
64
+ // The reviewed packed boundary never ships the Zig runtime build tree or the
65
+ // root lockfile, while the committed CLI bundle ships beside the checked
66
+ // sources in both layouts. Each of these inputs stays hash-bound wherever it
67
+ // is present and is skipped only when it is absent.
68
+ const OPTIONAL_APPLICATION_SOURCE_FILES = new Set<string>([
69
+ "apps/desktop/dist/cli/main.js",
70
+ "bun.lock",
71
+ ]);
66
72
  let cachedApplicationBuild: Promise<string> | undefined;
67
73
 
68
74
  function isWithin(root: string, candidate: string): boolean {
@@ -137,6 +143,22 @@ async function collectSourceFiles(
137
143
  }
138
144
  }
139
145
 
146
+ async function presentDesktopDirectory(
147
+ desktopRoot: string,
148
+ directory: string,
149
+ ): Promise<string | undefined> {
150
+ const path = join(desktopRoot, directory);
151
+ try {
152
+ if ((await lstat(path)).isDirectory()) return path;
153
+ return undefined;
154
+ } catch (error) {
155
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
156
+ return undefined;
157
+ }
158
+ throw error;
159
+ }
160
+ }
161
+
140
162
  async function computeApplicationBuildIdentity(options: {
141
163
  readonly desktopRoot: string;
142
164
  readonly repositoryRoot: string;
@@ -151,9 +173,16 @@ async function computeApplicationBuildIdentity(options: {
151
173
  }
152
174
  const sources: Array<{ readonly logicalPath: string; readonly path: string }> = [];
153
175
  for (const directory of DESKTOP_SOURCE_DIRECTORIES) {
176
+ const path = await presentDesktopDirectory(desktopRoot, directory);
177
+ if (path === undefined) {
178
+ throw new ApplicationError(
179
+ "not-found",
180
+ `Application build source directory is missing: apps/desktop/${directory}`,
181
+ );
182
+ }
154
183
  await collectSourceFiles(
155
184
  desktopRoot,
156
- join(desktopRoot, directory),
185
+ path,
157
186
  `apps/desktop/${directory}`,
158
187
  sources,
159
188
  );
@@ -176,10 +205,20 @@ async function computeApplicationBuildIdentity(options: {
176
205
  );
177
206
  }
178
207
  for (const [logicalPath, path] of [
208
+ ["apps/desktop/dist/cli/main.js", join(desktopRoot, "dist", "cli", "main.js")],
179
209
  ["bun.lock", join(repositoryRoot, "bun.lock")],
180
210
  ["package.json", join(repositoryRoot, "package.json")],
181
211
  ] as const) {
182
- const physical = await realpath(path);
212
+ let physical: string;
213
+ try {
214
+ physical = await realpath(path);
215
+ } catch (error) {
216
+ if (
217
+ OPTIONAL_APPLICATION_SOURCE_FILES.has(logicalPath)
218
+ && error instanceof Error && "code" in error && error.code === "ENOENT"
219
+ ) continue;
220
+ throw error;
221
+ }
183
222
  if (!isWithin(repositoryRoot, physical)) {
184
223
  throw new ApplicationError("unsafe-path", `Application build source escapes: ${logicalPath}`);
185
224
  }
@@ -227,8 +266,11 @@ export async function createApplicationBuildIdentity(options: {
227
266
  readonly desktopRoot?: string;
228
267
  readonly repositoryRoot?: string;
229
268
  } = {}): Promise<string> {
230
- const defaultDesktopRoot = resolve(import.meta.dir, "..");
231
- const defaultRepositoryRoot = resolve(defaultDesktopRoot, "../..");
269
+ // Inside the committed bundle, import.meta.dir is apps/desktop/dist/cli;
270
+ // the checked host sources stay at their installed paths below apps/desktop.
271
+ const defaults = hostSourceRoots(import.meta.dir);
272
+ const defaultDesktopRoot = defaults.desktopRoot;
273
+ const defaultRepositoryRoot = defaults.repositoryRoot;
232
274
  if (options.desktopRoot !== undefined || options.repositoryRoot !== undefined) {
233
275
  return await computeApplicationBuildIdentity({
234
276
  desktopRoot: options.desktopRoot ?? defaultDesktopRoot,
@@ -50,11 +50,6 @@ import type {
50
50
  ProjectRenderPlanOutput,
51
51
  ProjectSnapshotOutput,
52
52
  PromoteVariantSelectionOutput,
53
- RecordingPauseOutput,
54
- RecordingResumeOutput,
55
- RecordingStartInput,
56
- RecordingStartOutput,
57
- RecordingStopOutput,
58
53
  SceneAnalysisOperationInput,
59
54
  SceneAnalysisOperationOutput,
60
55
  SelectVariantInput,
@@ -301,8 +296,6 @@ export type MediaAudioEffectsOptions =
301
296
  OperationInputValue<MediaAudioEffectsInput>;
302
297
  export type MediaColorGradeOptions =
303
298
  OperationInputValue<MediaColorGradeInput>;
304
- export type RecordingStartOptions =
305
- OperationInputValue<RecordingStartInput>;
306
299
 
307
300
  export type FaceFollowOptions = {
308
301
  readonly [Key in keyof Omit<FollowFacesInput, "project">]:
@@ -1344,42 +1337,6 @@ export class WorkflowBuilder {
1344
1337
  }, options),
1345
1338
  });
1346
1339
 
1347
- readonly recording = Object.freeze({
1348
- pause: (
1349
- key: string,
1350
- options?: OperationNodeOptions,
1351
- ): Ref<RecordingPauseOutput> => this.#graph.operationByKind(key, {
1352
- input: {},
1353
- kind: "recording.pause",
1354
- version: 1,
1355
- }, options),
1356
- resume: (
1357
- key: string,
1358
- options?: OperationNodeOptions,
1359
- ): Ref<RecordingResumeOutput> => this.#graph.operationByKind(key, {
1360
- input: {},
1361
- kind: "recording.resume",
1362
- version: 1,
1363
- }, options),
1364
- start: (
1365
- key: string,
1366
- input: RecordingStartOptions,
1367
- options?: OperationNodeOptions,
1368
- ): Ref<RecordingStartOutput> => this.#graph.operationByKind(key, {
1369
- input,
1370
- kind: "recording.start",
1371
- version: 2,
1372
- }, options),
1373
- stop: (
1374
- key: string,
1375
- options?: OperationNodeOptions,
1376
- ): Ref<RecordingStopOutput> => this.#graph.operationByKind(key, {
1377
- input: {},
1378
- kind: "recording.stop",
1379
- version: 1,
1380
- }, options),
1381
- });
1382
-
1383
1340
  private constructor(graph: WorkflowGraphBuilder) {
1384
1341
  this.#graph = graph;
1385
1342
  }
@@ -28,6 +28,7 @@ import {
28
28
  canonicalJson,
29
29
  sha256Hex,
30
30
  } from "../core/canonical-json";
31
+ import { hostSourceRoots } from "./host-source-layout";
31
32
  import type { WorkflowBundleIdentity as GraphWorkflowBundleIdentity } from "./contracts";
32
33
  import { typecheckWorkflowSnapshot } from "./source-typecheck";
33
34
  import { captureWorkerProcessStartIdentity } from "./worker-process-identity";
@@ -52,6 +53,10 @@ const MAX_TIMER_DURATION_MS = 2_147_483_647;
52
53
  const BUNDLER_GUARDIAN_HANDSHAKE_MS = 2_000;
53
54
  const BUNDLER_GUARDIAN_DIAGNOSTIC_BYTES = 2_000;
54
55
 
56
+ // Spawned helpers always run the checked TypeScript sources, never a path
57
+ // derived from this module's bundled location below apps/desktop/dist/cli.
58
+ const HOST_ROOTS = hostSourceRoots(import.meta.dir);
59
+
55
60
  const BUNDLE_SUBPROCESS_SOURCE = String.raw`
56
61
  import { fstatSync } from "node:fs";
57
62
  const processIdentity = await import(process.argv[5]);
@@ -756,11 +761,16 @@ async function startBundlerRetirementGuardian(options: {
756
761
  "--has-lease",
757
762
  options.inheritedHostResourceFileDescriptor === 0 ? "false" : "true",
758
763
  "--identity-module",
759
- pathToFileURL(join(import.meta.dir, "worker-process-identity.ts")).href,
764
+ pathToFileURL(join(
765
+ HOST_ROOTS.desktopRoot,
766
+ "code",
767
+ "worker-process-identity.ts",
768
+ )).href,
760
769
  "--host-resource-lock-module",
761
770
  pathToFileURL(join(
762
- import.meta.dir,
763
- "../../../src/host-resource-posix.ts",
771
+ HOST_ROOTS.repositoryRoot,
772
+ "src",
773
+ "host-resource-posix.ts",
764
774
  )).href,
765
775
  ],
766
776
  {
@@ -941,6 +951,7 @@ async function buildWorkflowBundle(
941
951
  modules: readonly CapturedSourceModule[],
942
952
  aliases: Readonly<Record<string, string>>,
943
953
  configSearchPath: string,
954
+ hostBoundary: string,
944
955
  includeRuntimeTypes: boolean,
945
956
  semanticCheck: boolean,
946
957
  inheritedHostResourceFileDescriptor: number,
@@ -979,6 +990,7 @@ async function buildWorkflowBundle(
979
990
  aliases,
980
991
  configSearchPath,
981
992
  entryPath,
993
+ hostBoundary,
982
994
  includeRuntimeTypes,
983
995
  sourceRoot,
984
996
  });
@@ -997,7 +1009,11 @@ async function buildWorkflowBundle(
997
1009
  outputPath,
998
1010
  inheritedHostResourceFileDescriptor === 0 ? "0" : "3",
999
1011
  String(maximumBundleBytes),
1000
- pathToFileURL(join(import.meta.dir, "worker-process-identity.ts")).href,
1012
+ pathToFileURL(join(
1013
+ HOST_ROOTS.desktopRoot,
1014
+ "code",
1015
+ "worker-process-identity.ts",
1016
+ )).href,
1001
1017
  ],
1002
1018
  {
1003
1019
  cwd: sourceRoot,
@@ -1571,14 +1587,15 @@ async function bundleWorkflowSourceInternal(
1571
1587
  const aliasSpecifiers = new Set(sourceGraph.bareImports);
1572
1588
  const aliases = await bareImportAliases(
1573
1589
  [...aliasSpecifiers].sort(),
1574
- resolve(options.bareImportResolutionRoot ?? import.meta.dir),
1590
+ resolve(options.bareImportResolutionRoot ?? HOST_ROOTS.desktopRoot),
1575
1591
  );
1576
1592
  const entryRelativePath = relative(allowedRoot, entryPath);
1577
1593
  const bytes = await buildWorkflowBundle(
1578
1594
  entryRelativePath,
1579
1595
  sourceGraph.modules,
1580
1596
  aliases,
1581
- resolve(options.bareImportResolutionRoot ?? import.meta.dir),
1597
+ resolve(options.bareImportResolutionRoot ?? HOST_ROOTS.desktopRoot),
1598
+ HOST_ROOTS.repositoryRoot,
1582
1599
  (
1583
1600
  sourceGraph.bareImports.some(specifier => (
1584
1601
  specifier === "bun" || specifier.startsWith("node:")
@@ -1,4 +1,4 @@
1
- import { dirname, isAbsolute, relative, resolve } from "node:path";
1
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
2
2
 
3
3
  import ts from "typescript";
4
4
 
@@ -12,11 +12,38 @@ const WORKFLOW_GLOBALS_SOURCE = [
12
12
  "declare const process: any;",
13
13
  "",
14
14
  ].join("\n");
15
+ // Packed distributions ship no host tsconfig; this closed fallback preserves
16
+ // the host compiler contract instead of adopting a foreign caller tsconfig.
17
+ const PACKED_WORKFLOW_CONFIG = {
18
+ compilerOptions: {
19
+ allowImportingTsExtensions: true,
20
+ exactOptionalPropertyTypes: true,
21
+ isolatedModules: true,
22
+ jsx: "react-jsx",
23
+ lib: ["ES2023", "DOM", "DOM.Iterable"],
24
+ module: "Preserve",
25
+ moduleDetection: "force",
26
+ moduleResolution: "Bundler",
27
+ noEmit: true,
28
+ noFallthroughCasesInSwitch: true,
29
+ noImplicitOverride: true,
30
+ noImplicitReturns: true,
31
+ noUncheckedIndexedAccess: true,
32
+ noUnusedLocals: true,
33
+ noUnusedParameters: true,
34
+ skipLibCheck: true,
35
+ strict: true,
36
+ target: "ES2023",
37
+ useUnknownInCatchVariables: true,
38
+ verbatimModuleSyntax: true,
39
+ },
40
+ };
15
41
 
16
42
  export interface TypecheckWorkflowSnapshotOptions {
17
43
  readonly aliases: Readonly<Record<string, string>>;
18
44
  readonly configSearchPath: string;
19
45
  readonly entryPath: string;
46
+ readonly hostBoundary: string;
20
47
  readonly includeRuntimeTypes: boolean;
21
48
  readonly sourceRoot: string;
22
49
  }
@@ -26,6 +53,37 @@ function isWithin(root: string, candidate: string): boolean {
26
53
  return fromRoot === "" || (!fromRoot.startsWith("..") && !isAbsolute(fromRoot));
27
54
  }
28
55
 
56
+ /** Finds the host tsconfig without crossing into a foreign caller tree. */
57
+ function boundedHostConfigPath(
58
+ searchPath: string,
59
+ boundary: string,
60
+ ): string | undefined {
61
+ const limit = resolve(boundary);
62
+ let candidate = resolve(searchPath);
63
+ while (isWithin(limit, candidate)) {
64
+ const path = join(candidate, "tsconfig.json");
65
+ if (ts.sys.fileExists(path)) return path;
66
+ const parent = dirname(candidate);
67
+ if (parent === candidate) return undefined;
68
+ candidate = parent;
69
+ }
70
+ return undefined;
71
+ }
72
+
73
+ /**
74
+ * Resolves the installed @types/bun package from the host tree so runtime
75
+ * types never depend on the caller's working directory or dependencies.
76
+ */
77
+ function bunTypeRoots(hostBoundary: string): string | undefined {
78
+ try {
79
+ return dirname(dirname(
80
+ Bun.resolveSync("@types/bun/package.json", resolve(hostBoundary)),
81
+ ));
82
+ } catch {
83
+ return undefined;
84
+ }
85
+ }
86
+
29
87
  function diagnosticText(
30
88
  diagnostic: ts.Diagnostic,
31
89
  sourceRoot: string,
@@ -77,49 +135,72 @@ function boundedDiagnostics(
77
135
  export function typecheckWorkflowSnapshot(
78
136
  options: TypecheckWorkflowSnapshotOptions,
79
137
  ): void {
80
- const configPath = ts.findConfigFile(
81
- resolve(options.configSearchPath),
82
- path => ts.sys.fileExists(path),
83
- "tsconfig.json",
138
+ const configPath = boundedHostConfigPath(
139
+ options.configSearchPath,
140
+ options.hostBoundary,
84
141
  );
142
+ const hostDirectory = configPath === undefined
143
+ ? resolve(options.hostBoundary)
144
+ : dirname(configPath);
145
+ let parsedOptions: ts.CompilerOptions;
85
146
  if (configPath === undefined) {
86
- throw new ApplicationError(
87
- "internal",
88
- "Workflow semantic checking could not locate the host TypeScript configuration.",
147
+ const parsed = ts.parseJsonConfigFileContent(
148
+ PACKED_WORKFLOW_CONFIG,
149
+ ts.sys,
150
+ hostDirectory,
151
+ {
152
+ composite: false,
153
+ declaration: false,
154
+ emitDeclarationOnly: false,
155
+ incremental: false,
156
+ noEmit: true,
157
+ },
158
+ "slopcamera-packed-workflow.tsconfig.json",
89
159
  );
90
- }
91
- const config = ts.readConfigFile(configPath, path => ts.sys.readFile(path));
92
- if (config.error !== undefined) {
93
- throw new ApplicationError(
94
- "internal",
95
- `Workflow semantic checking could not read the host TypeScript configuration: ${
96
- diagnosticText(config.error, options.sourceRoot)
97
- }`,
98
- );
99
- }
100
- const parsed = ts.parseJsonConfigFileContent(
101
- config.config,
102
- ts.sys,
103
- dirname(configPath),
104
- {
105
- composite: false,
106
- declaration: false,
107
- emitDeclarationOnly: false,
108
- incremental: false,
109
- noEmit: true,
110
- },
111
- configPath,
112
- );
113
- if (parsed.errors.length > 0) {
114
- throw new ApplicationError(
115
- "internal",
116
- `Workflow semantic checking could not parse the host TypeScript configuration:\n${
117
- boundedDiagnostics(parsed.errors, options.sourceRoot)
118
- }`,
160
+ if (parsed.errors.length > 0) {
161
+ throw new ApplicationError(
162
+ "internal",
163
+ `Workflow semantic checking could not apply its packed TypeScript configuration:\n${
164
+ boundedDiagnostics(parsed.errors, options.sourceRoot)
165
+ }`,
166
+ );
167
+ }
168
+ parsedOptions = parsed.options;
169
+ } else {
170
+ const config = ts.readConfigFile(configPath, path => ts.sys.readFile(path));
171
+ if (config.error !== undefined) {
172
+ throw new ApplicationError(
173
+ "internal",
174
+ `Workflow semantic checking could not read the host TypeScript configuration: ${
175
+ diagnosticText(config.error, options.sourceRoot)
176
+ }`,
177
+ );
178
+ }
179
+ const parsed = ts.parseJsonConfigFileContent(
180
+ config.config,
181
+ ts.sys,
182
+ hostDirectory,
183
+ {
184
+ composite: false,
185
+ declaration: false,
186
+ emitDeclarationOnly: false,
187
+ incremental: false,
188
+ noEmit: true,
189
+ },
190
+ configPath,
119
191
  );
192
+ if (parsed.errors.length > 0) {
193
+ throw new ApplicationError(
194
+ "internal",
195
+ `Workflow semantic checking could not parse the host TypeScript configuration:\n${
196
+ boundedDiagnostics(parsed.errors, options.sourceRoot)
197
+ }`,
198
+ );
199
+ }
200
+ parsedOptions = parsed.options;
120
201
  }
121
202
  const typecheckImporterPath = resolve(
122
- dirname(configPath),
203
+ hostDirectory,
123
204
  "slopcamera-workflow-typecheck.ts",
124
205
  );
125
206
  const aliasPaths = Object.fromEntries(
@@ -132,7 +213,7 @@ export function typecheckWorkflowSnapshot(
132
213
  const declaration = ts.resolveModuleName(
133
214
  specifier,
134
215
  typecheckImporterPath,
135
- parsed.options,
216
+ parsedOptions,
136
217
  ts.sys,
137
218
  ).resolvedModule?.resolvedFileName;
138
219
  return [
@@ -143,8 +224,14 @@ export function typecheckWorkflowSnapshot(
143
224
  ];
144
225
  }),
145
226
  );
227
+ // Runtime-typed workflows use the host's installed @types/bun when it is
228
+ // present and the explicit any-globals shim when it is not.
229
+ const runtimeTypeRoots = options.includeRuntimeTypes
230
+ ? bunTypeRoots(options.hostBoundary)
231
+ : undefined;
232
+ const realRuntimeTypes = runtimeTypeRoots !== undefined;
146
233
  const compilerOptions: ts.CompilerOptions = {
147
- ...parsed.options,
234
+ ...parsedOptions,
148
235
  baseUrl: options.sourceRoot,
149
236
  composite: false,
150
237
  declaration: false,
@@ -152,10 +239,11 @@ export function typecheckWorkflowSnapshot(
152
239
  incremental: false,
153
240
  noEmit: true,
154
241
  paths: {
155
- ...parsed.options.paths,
242
+ ...parsedOptions.paths,
156
243
  ...aliasPaths,
157
244
  },
158
- types: options.includeRuntimeTypes ? ["bun"] : [],
245
+ ...(realRuntimeTypes ? { typeRoots: [runtimeTypeRoots] } : {}),
246
+ types: realRuntimeTypes ? ["bun"] : [],
159
247
  };
160
248
  delete compilerOptions.tsBuildInfoFile;
161
249
  const host = ts.createCompilerHost(compilerOptions, true);
@@ -189,7 +277,7 @@ export function typecheckWorkflowSnapshot(
189
277
  options: compilerOptions,
190
278
  rootNames: [
191
279
  options.entryPath,
192
- ...(options.includeRuntimeTypes ? [] : [WORKFLOW_GLOBALS_PATH]),
280
+ ...(realRuntimeTypes ? [] : [WORKFLOW_GLOBALS_PATH]),
193
281
  ],
194
282
  });
195
283
  const diagnostics = program.getSourceFiles()
@@ -22,6 +22,7 @@ import { z } from "zod";
22
22
 
23
23
  import { ApplicationError } from "../application/errors";
24
24
  import type { OperationDiscovery } from "../application/registry";
25
+ import { hostSourceRoots } from "./host-source-layout";
25
26
  import { canonicalJson } from "../core/canonical-json";
26
27
  import {
27
28
  AuthoredWorkflowGraphV1Schema,
@@ -46,6 +47,9 @@ const DEFAULT_TIMEOUT_MS = 30_000;
46
47
  const DEFAULT_MAXIMUM_DIAGNOSTIC_BYTES = 64 * 1024;
47
48
  const CODE_WORKER_CANCEL_GRACE_MS = 250;
48
49
  export const MAX_CODE_WORKER_POOL_SIZE = 4;
50
+ // Spawned helpers always run the checked TypeScript sources, never a path
51
+ // derived from this module's bundled location below apps/desktop/dist/cli.
52
+ const HOST_ROOTS = hostSourceRoots(import.meta.dir);
49
53
 
50
54
  const WorkerBuiltWorkflowSchema = z.strictObject({
51
55
  graph: AuthoredWorkflowGraphV1Schema,
@@ -856,7 +860,11 @@ async function startCodeWorkerLeaseGuardian(options: {
856
860
  "Code-worker host-resource lease descriptor is unsafe to inherit.",
857
861
  );
858
862
  }
859
- const guardianEntryPath = join(import.meta.dir, "worker-lease-guardian.ts");
863
+ const guardianEntryPath = join(
864
+ HOST_ROOTS.desktopRoot,
865
+ "code",
866
+ "worker-lease-guardian.ts",
867
+ );
860
868
  const guardian = spawn(
861
869
  options.bunExecutable,
862
870
  [
@@ -1435,7 +1443,11 @@ export async function startCodeWorker(
1435
1443
  );
1436
1444
  }
1437
1445
  const workerEntryPath = resolve(
1438
- options.workerEntryPath ?? join(import.meta.dir, "worker-entry.ts"),
1446
+ options.workerEntryPath ?? join(
1447
+ HOST_ROOTS.desktopRoot,
1448
+ "code",
1449
+ "worker-entry.ts",
1450
+ ),
1439
1451
  );
1440
1452
  const workerEntrySha256 = createHash("sha256")
1441
1453
  .update(await readFile(workerEntryPath))