peachsim 0.1.637 → 0.1.638

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 (79) hide show
  1. package/README.md +8 -5
  2. package/cli/bridge-flow-runner.ts +442 -14
  3. package/cli/commands/inspect/core.ts +93 -24
  4. package/cli/commands/maestro.ts +2 -2
  5. package/cli/commands/serve.ts +13 -1
  6. package/cli/drivers/playwright-provisioning.ts +8 -16
  7. package/cli/drivers/playwright-sim-host.ts +3 -0
  8. package/cli/drivers/playwright.ts +1 -0
  9. package/cli/internal-child.ts +51 -8
  10. package/dist-lib/agent-daemon-client.cjs +1 -1
  11. package/dist-lib/agent-events.cjs +1 -1
  12. package/dist-lib/agent-identity.cjs +1 -1
  13. package/dist-lib/agent-sessions.cjs +1 -1
  14. package/dist-lib/attached-projects.cjs +1 -1
  15. package/dist-lib/auth/shared-session.cjs +1 -1
  16. package/dist-lib/backend-origin.cjs +1 -1
  17. package/dist-lib/beta.cjs +1 -1
  18. package/dist-lib/beta.mjs +1 -1
  19. package/dist-lib/bridge-constants.cjs +1 -1
  20. package/dist-lib/bridge-contract-input.cjs +3 -2
  21. package/dist-lib/bridge-contract-input.mjs +3 -2
  22. package/dist-lib/bridge-contract.cjs +1 -1
  23. package/dist-lib/bridge-contract.mjs +1 -1
  24. package/dist-lib/capture-contract.cjs +1 -1
  25. package/dist-lib/capture-contract.mjs +1 -1
  26. package/dist-lib/cli-constants.cjs +1 -1
  27. package/dist-lib/cloud-contract.cjs +1 -1
  28. package/dist-lib/cloud-contract.mjs +1 -1
  29. package/dist-lib/cloud-test-args.cjs +1 -1
  30. package/dist-lib/cloud-test-args.mjs +1 -1
  31. package/dist-lib/cloud.cjs +1 -1
  32. package/dist-lib/cloud.mjs +1 -1
  33. package/dist-lib/config.cjs +1 -1
  34. package/dist-lib/detox/index.cjs +1 -1
  35. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  36. package/dist-lib/home-paths.cjs +1 -1
  37. package/dist-lib/host/bridge-host.cjs +4 -2
  38. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  39. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  40. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  41. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  42. package/dist-lib/host/websocket-proxy.cjs +1 -1
  43. package/dist-lib/index.cjs +76 -20
  44. package/dist-lib/jump-to-source-babel.cjs +1 -1
  45. package/dist-lib/jump-to-source-native.cjs +1 -1
  46. package/dist-lib/kotlin-host.cjs +1 -1
  47. package/dist-lib/menu.cjs +1 -1
  48. package/dist-lib/menu.mjs +1 -1
  49. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  51. package/dist-lib/metro-production-bundle.cjs +1 -1
  52. package/dist-lib/metro-production-bundle.mjs +1 -1
  53. package/dist-lib/metro.cjs +1 -1
  54. package/dist-lib/native-app-identity.cjs +1 -1
  55. package/dist-lib/native-app-identity.mjs +1 -1
  56. package/dist-lib/profiles.cjs +1 -1
  57. package/dist-lib/public-brand.cjs +1 -1
  58. package/dist-lib/react-native-host-modules.cjs +1 -1
  59. package/dist-lib/react-native-host-modules.mjs +1 -1
  60. package/dist-lib/render-mode.cjs +1 -1
  61. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  62. package/dist-lib/sdk.cjs +78 -20
  63. package/dist-lib/sdk.mjs +77 -20
  64. package/dist-lib/skills.cjs +394 -62
  65. package/dist-lib/swift-compile.cjs +89 -7
  66. package/dist-lib/swift-compile.mjs +89 -7
  67. package/dist-lib/swift-package.cjs +1 -1
  68. package/dist-lib/swift-package.mjs +1 -1
  69. package/dist-lib/swift-submit.cjs +1 -1
  70. package/dist-lib/swift-submit.mjs +1 -1
  71. package/dist-lib/swift.cjs +122 -34
  72. package/dist-lib/vite-swift.cjs +89 -7
  73. package/dist-lib/vite.cjs +1 -1
  74. package/package.json +1 -1
  75. package/src/bridge-contract-input.ts +3 -2
  76. package/src/bridge-contract.ts +8 -0
  77. package/src/host/bridge-host.ts +5 -5
  78. package/src/native-seam-manifest.ts +4 -6
  79. package/src/swift-compile.ts +105 -9
@@ -229,6 +229,8 @@ interface BridgeForwardedCommand {
229
229
  export interface BridgeHostOptions {
230
230
  /** use 0 for an OS-assigned port; startAsync returns the bound address. */
231
231
  port?: number
232
+ /** listener address; defaults to 127.0.0.1. */
233
+ host?: string
232
234
  openUrl?: (url: string, options?: OpenUrlOptions) => Promise<void> | void
233
235
  /** Contrast-specific excludes for the agent host's dev-server scan. passed
234
236
  * through to AgentHost; see packages/peach-engine/src/dev-scan-excludes.ts
@@ -404,6 +406,7 @@ export class SootSimBridgeHost {
404
406
  // of reconnecting the zombie straight back in.
405
407
  private static SIM_IDLE_REAP_TTL_MS = 30 * 60_000
406
408
 
409
+ private bindHost: string
407
410
  private preferredPort: number
408
411
  private portFallbackCount: number
409
412
  private shouldWriteLockfile: boolean
@@ -438,6 +441,7 @@ export class SootSimBridgeHost {
438
441
  private static SCAN_FRESH_MS = 2000
439
442
 
440
443
  constructor(opts: BridgeHostOptions = {}) {
444
+ this.bindHost = opts.host ?? '127.0.0.1'
441
445
  this.preferredPort = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT
442
446
  this.port = this.preferredPort
443
447
  // default off — callers that want the lockfile (peach serve) opt in.
@@ -533,11 +537,7 @@ export class SootSimBridgeHost {
533
537
  }
534
538
  server.once('error', onError)
535
539
 
536
- // loopback-only bind. we listen on 127.0.0.1 explicitly because the
537
- // daemon serves the active runtime's dist as plain static files —
538
- // never expose that to LAN peers. tests + the electron renderer use
539
- // 127.0.0.1 explicitly to avoid the localhost-vs-::1 DNS coinflip.
540
- server.listen(port, '127.0.0.1', () => {
540
+ server.listen(port, this.bindHost, () => {
541
541
  if (settled) return
542
542
  const address = server.address()
543
543
  if (!address || typeof address === 'string') {
@@ -153,15 +153,13 @@ export const NATIVE_SEAM_MANIFEST: NativeSeamEntry[] = [
153
153
  },
154
154
  {
155
155
  pkg: '@bam.tech/react-native-image-resizer',
156
- // upstream is not shipped to most guest bundles via node_modules — it
157
- // arrives through the bundle-loader stub-registry which routes the
158
- // package entry to `stubs/bam-react-native-image-resizer.ts`. listed here
159
- // for documentation parity; vite-path redirect would key on
160
- // `NativeImageResizer`.
156
+ // a Metro bundle reaches the same seam through the ImageResizer
157
+ // TurboModule that `stubs/bam-react-native-image-resizer-register.ts`
158
+ // registers.
161
159
  seamBasenames: ['NativeImageResizer'],
162
160
  target: seamPath('bam-react-native-image-resizer'),
163
161
  notes:
164
- 'ImageResizer TurboModule — browser-side canvas resize implementing the iOS contain/cover/stretch + onlyScaleDown semantics from ios/ImageResizer.mm',
162
+ 'ImageResizer TurboModule following ios/ImageResizer.mm: loader decode, rotation, scaling, and the file written to the app container',
165
163
  },
166
164
  {
167
165
  pkg: 'react-native-adjust',
@@ -224,8 +224,8 @@ export function swiftArtifactHash(
224
224
  moduleRoot: string,
225
225
  ): string {
226
226
  const hash = createHash('sha256')
227
- // v2: sources compile with os(iOS) true (swiftSourceAsIOS)
228
- hash.update(`swift-compile/v2\n${release}\n`)
227
+ // v3: sources compile with os(iOS) true on code lines (swiftSourceAsIOS)
228
+ hash.update(`swift-compile/v3\n${release}\n`)
229
229
  if (input.oneModule) hash.update('one-module\n')
230
230
  hash.update(linkedModuleDigest(moduleRoot))
231
231
  for (const file of Object.keys(input.files).sort()) {
@@ -347,15 +347,111 @@ function syncSwiftTree(base: string, files: Map<string, string>): void {
347
347
  // every iOS branch of the app would compile out. a condition line names iOS
348
348
  // as `true` padded to the same length, so diagnostics keep their columns;
349
349
  // os(WASI) still holds for code written for wasm. only `#if` and `#elseif`
350
- // lines change, and the artifact hash stays over the original text.
351
- const CONDITION_LINE = /^([ \t]*#(?:if|elseif)\b)(.*)$/gm
350
+ // lines that begin in code change: a line inside a string literal or a block
351
+ // comment is content. the artifact hash stays over the original text.
352
+ const DIRECTIVE = /^[ \t]*#(?:if|elseif)\b/
352
353
  const OS_IOS = /\bos\(\s*iOS\s*\)/g
353
354
  export function swiftSourceAsIOS(source: string): string {
354
- return source.replace(
355
- CONDITION_LINE,
356
- (_, directive: string, condition: string) =>
357
- directive + condition.replace(OS_IOS, (match) => 'true'.padEnd(match.length)),
358
- )
355
+ const code = swiftCodeLineStarts(source)
356
+ let offset = 0
357
+ return source
358
+ .split('\n')
359
+ .map((line) => {
360
+ const start = offset
361
+ offset += line.length + 1
362
+ if (!code.has(start) || !DIRECTIVE.test(line)) return line
363
+ return line.replace(OS_IOS, (match) => 'true'.padEnd(match.length))
364
+ })
365
+ .join('\n')
366
+ }
367
+
368
+ // the offsets of the lines that begin in code, from a scan of Swift's lexical
369
+ // contexts: nested block comments, line comments, string literals (single and
370
+ // multiline, raw with any number of #, interpolations holding code and
371
+ // strings of their own) and #/ /# regex literals. a bare /regex/ literal
372
+ // reads as code.
373
+ type Lexical =
374
+ | { kind: 'code'; parens: number }
375
+ | { kind: 'block'; depth: number }
376
+ | { kind: 'string'; hashes: number; multiline: boolean }
377
+ | { kind: 'regex'; hashes: number }
378
+ function swiftCodeLineStarts(source: string): Set<number> {
379
+ const starts = new Set<number>()
380
+ const stack: Lexical[] = [{ kind: 'code', parens: 0 }]
381
+ const hashesAt = (at: number) => {
382
+ let end = at
383
+ while (source[end] === '#') end++
384
+ return end - at
385
+ }
386
+ const closes = (at: number, hashes: number) => source.startsWith('#'.repeat(hashes), at)
387
+ let i = 0
388
+ while (i < source.length) {
389
+ const top = stack[stack.length - 1]
390
+ if ((i === 0 || source[i - 1] === '\n') && top.kind === 'code' && stack.length === 1)
391
+ starts.add(i)
392
+ const char = source[i]
393
+ if (top.kind === 'block') {
394
+ if (source.startsWith('/*', i)) {
395
+ top.depth++
396
+ i += 2
397
+ } else if (source.startsWith('*/', i)) {
398
+ if (--top.depth === 0) stack.pop()
399
+ i += 2
400
+ } else i++
401
+ continue
402
+ }
403
+ if (top.kind === 'string') {
404
+ if (char === '\\' && closes(i + 1, top.hashes)) {
405
+ const after = i + 1 + top.hashes
406
+ if (source[after] === '(') {
407
+ stack.push({ kind: 'code', parens: 0 })
408
+ i = after + 1
409
+ } else i = after + 1
410
+ continue
411
+ }
412
+ const quote = top.multiline ? '"""' : '"'
413
+ if (source.startsWith(quote, i) && closes(i + quote.length, top.hashes)) {
414
+ stack.pop()
415
+ i += quote.length + top.hashes
416
+ } else i++
417
+ continue
418
+ }
419
+ if (top.kind === 'regex') {
420
+ if (char === '/' && closes(i + 1, top.hashes)) {
421
+ stack.pop()
422
+ i += 1 + top.hashes
423
+ } else i++
424
+ continue
425
+ }
426
+ if (source.startsWith('//', i)) {
427
+ const end = source.indexOf('\n', i)
428
+ i = end === -1 ? source.length : end
429
+ } else if (source.startsWith('/*', i)) {
430
+ stack.push({ kind: 'block', depth: 1 })
431
+ i += 2
432
+ } else if (char === '"' || char === '#') {
433
+ const hashes = hashesAt(i)
434
+ const at = i + hashes
435
+ if (source.startsWith('"""', at)) {
436
+ stack.push({ kind: 'string', hashes, multiline: true })
437
+ i = at + 3
438
+ } else if (source[at] === '"') {
439
+ stack.push({ kind: 'string', hashes, multiline: false })
440
+ i = at + 1
441
+ } else if (hashes > 0 && source[at] === '/') {
442
+ stack.push({ kind: 'regex', hashes })
443
+ i = at + 1
444
+ } else i = Math.max(at, i + 1)
445
+ } else if (char === '(') {
446
+ top.parens++
447
+ i++
448
+ } else if (char === ')') {
449
+ if (top.parens === 0 && stack.length > 1) stack.pop()
450
+ else top.parens--
451
+ i++
452
+ } else i++
453
+ }
454
+ return starts
359
455
  }
360
456
 
361
457
  const compiledSource = (file: string, contents: string) =>