peachsim 0.1.571 → 0.1.573
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.
- package/LICENSE +1 -1
- package/README.md +106 -106
- package/cli/app-config.ts +1 -1
- package/cli/app-fonts.ts +4 -4
- package/cli/auth.ts +11 -11
- package/cli/bin.ts +4 -4
- package/cli/bridge-flow-runner.ts +13 -13
- package/cli/browser-evals.ts +2 -2
- package/cli/cli-update.ts +6 -6
- package/cli/cloud-client.ts +16 -16
- package/cli/cloud-dispatch.ts +2 -2
- package/cli/cloud-session.ts +12 -12
- package/cli/cloud-test-tunnel.ts +4 -4
- package/cli/command-registry.ts +7 -7
- package/cli/commands/agent-wrapper.ts +7 -7
- package/cli/commands/agent.ts +16 -16
- package/cli/commands/app-fonts.ts +3 -3
- package/cli/commands/assert.ts +10 -10
- package/cli/commands/auth.ts +5 -5
- package/cli/commands/box/checkout-plane.ts +1 -1
- package/cli/commands/box/plane-share.ts +2 -2
- package/cli/commands/box/rnx-command.ts +5 -5
- package/cli/commands/box.ts +38 -38
- package/cli/commands/camera.ts +13 -13
- package/cli/commands/cleanup.ts +13 -13
- package/cli/commands/compat.ts +7 -7
- package/cli/commands/config.ts +5 -5
- package/cli/commands/control.ts +63 -63
- package/cli/commands/cpu-profile.ts +11 -11
- package/cli/commands/daemon-mac-app.ts +10 -10
- package/cli/commands/daemon.ts +29 -27
- package/cli/commands/debug.ts +35 -35
- package/cli/commands/desktop.ts +3 -3
- package/cli/commands/detox.ts +33 -33
- package/cli/commands/device.ts +9 -9
- package/cli/commands/diagnose.ts +17 -17
- package/cli/commands/do-chain.ts +4 -4
- package/cli/commands/electron.ts +9 -9
- package/cli/commands/film.ts +9 -9
- package/cli/commands/flow.ts +49 -49
- package/cli/commands/inspect/core.ts +12 -12
- package/cli/commands/inspect/count.ts +1 -1
- package/cli/commands/inspect/describe.ts +1 -1
- package/cli/commands/inspect/env.ts +2 -2
- package/cli/commands/inspect/find.ts +2 -2
- package/cli/commands/inspect/get-layout.ts +2 -2
- package/cli/commands/inspect/keyboard.ts +1 -1
- package/cli/commands/inspect/list.ts +4 -4
- package/cli/commands/inspect/memory.ts +1 -1
- package/cli/commands/inspect/resolve-target.ts +1 -1
- package/cli/commands/inspect/screens.ts +1 -1
- package/cli/commands/inspect/shared.ts +1 -1
- package/cli/commands/inspect/tree.ts +1 -1
- package/cli/commands/inspect/url.ts +1 -1
- package/cli/commands/inspect/wait-event.ts +5 -5
- package/cli/commands/inspect.ts +82 -82
- package/cli/commands/install-cli.ts +16 -16
- package/cli/commands/install-desktop.ts +26 -26
- package/cli/commands/login.ts +11 -11
- package/cli/commands/maestro-generate.ts +10 -10
- package/cli/commands/maestro.ts +77 -67
- package/cli/commands/mode.ts +4 -4
- package/cli/commands/no-bridge-hint.ts +12 -12
- package/cli/commands/perf.ts +11 -11
- package/cli/commands/permissions.ts +5 -5
- package/cli/commands/platform.ts +21 -21
- package/cli/commands/preview.ts +5 -5
- package/cli/commands/profile.ts +12 -12
- package/cli/commands/react.ts +15 -15
- package/cli/commands/record.ts +31 -31
- package/cli/commands/report-issue.ts +4 -4
- package/cli/commands/reset.ts +2 -2
- package/cli/commands/runtime.ts +23 -23
- package/cli/commands/screenshot-command.ts +2 -2
- package/cli/commands/screenshot-layers.ts +5 -5
- package/cli/commands/screenshot-mode.ts +7 -7
- package/cli/commands/screenshot.ts +24 -24
- package/cli/commands/screenshots-capture.ts +13 -13
- package/cli/commands/screenshots.ts +6 -6
- package/cli/commands/serve.ts +12 -12
- package/cli/commands/setup.ts +25 -25
- package/cli/commands/skills.ts +30 -23
- package/cli/commands/slides.ts +20 -20
- package/cli/commands/state.ts +2 -2
- package/cli/commands/storage.ts +3 -3
- package/cli/commands/telemetry.ts +2 -2
- package/cli/commands/test.ts +15 -15
- package/cli/commands/three-mode.ts +17 -17
- package/cli/commands/timeline.ts +12 -12
- package/cli/commands/upgrade.ts +11 -11
- package/cli/commands/upload.ts +26 -26
- package/cli/commands/version.ts +7 -7
- package/cli/commands/what-happened.ts +20 -20
- package/cli/current-sim.ts +3 -3
- package/cli/desktop-companion.ts +16 -16
- package/cli/drivers/electron.ts +4 -4
- package/cli/drivers/playwright-sim-host.ts +7 -7
- package/cli/drivers/playwright.ts +4 -4
- package/cli/drivers/registry.ts +1 -1
- package/cli/drivers/types.ts +5 -5
- package/cli/flow-export.ts +1 -1
- package/cli/flow-file.ts +5 -5
- package/cli/flow-session.ts +1 -1
- package/cli/help-core.ts +13 -13
- package/cli/hints.ts +14 -14
- package/cli/inspect-notice-state.ts +1 -1
- package/cli/internal-child.ts +2 -2
- package/cli/maestro-js.ts +4 -4
- package/cli/maestro-report.ts +1 -1
- package/cli/main.ts +14 -14
- package/cli/outbound-endpoints.ts +30 -30
- package/cli/parse-args.ts +7 -7
- package/cli/privacy.ts +1 -1
- package/cli/recording-access.ts +10 -10
- package/cli/resolve-assets.ts +2 -2
- package/cli/rnx-in-process.ts +5 -5
- package/cli/run-registry.ts +1 -1
- package/cli/run-rnx-core.ts +3 -3
- package/cli/run-rnx.ts +1 -1
- package/cli/runtime-notes.ts +1 -1
- package/cli/self-invocation.ts +1 -1
- package/cli/send-to-box.ts +1 -1
- package/cli/shell-init.ts +40 -40
- package/cli/ui.ts +1 -1
- package/cli/ws-bridge.ts +22 -22
- package/detox/config-loader.ts +1 -1
- package/detox/index.ts +28 -28
- package/detox/navigation.ts +2 -2
- package/dist-lib/agent-daemon-client.cjs +12 -12
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +2 -2
- package/dist-lib/agent-sessions.cjs +12 -12
- package/dist-lib/attached-projects.cjs +4 -4
- package/dist-lib/auth/shared-session.cjs +4 -4
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +4 -4
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +2 -2
- package/dist-lib/bridge-contract.mjs +2 -2
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +42 -42
- package/dist-lib/cloud.mjs +42 -42
- package/dist-lib/config.cjs +2 -2
- package/dist-lib/detox/index.cjs +29 -29
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +12 -12
- package/dist-lib/host/bridge-host.cjs +72 -71
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +6 -6
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +48 -48
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +5 -5
- package/dist-lib/menu.mjs +5 -5
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +15 -15
- package/dist-lib/profiles.cjs +4 -4
- package/dist-lib/public-brand.cjs +3 -3
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +2 -1
- package/dist-lib/sdk.cjs +9 -9
- package/dist-lib/sdk.mjs +9 -9
- package/dist-lib/skills.cjs +416 -415
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite.cjs +24 -24
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +4 -3
- package/skills/contrast/SKILL.md +75 -75
- package/skills/{rnx-debug → peach-debug}/SKILL.md +90 -90
- package/skills/{rnx-perf → peach-perf}/SKILL.md +13 -13
- package/skills/{rnx-setup → peach-setup}/SKILL.md +60 -60
- package/skills/{rnx-test → peach-test}/SKILL.md +44 -44
- package/skills/{rnx-visual → peach-visual}/SKILL.md +26 -26
- package/src/agent-daemon-client.ts +5 -5
- package/src/agent-events.ts +1 -1
- package/src/agent-identity.ts +1 -1
- package/src/agent-sessions.ts +13 -13
- package/src/attached-projects.ts +1 -1
- package/src/auth/shared-session.ts +1 -1
- package/src/bridge-constants.ts +3 -3
- package/src/bridge-contract-input.ts +1 -1
- package/src/bridge-contract.ts +3 -3
- package/src/cli-version.ts +6 -6
- package/src/cloud.ts +40 -40
- package/src/config.ts +1 -1
- package/src/connect.ts +6 -6
- package/src/dev-server-open.ts +8 -8
- package/src/disk-cleanup.ts +3 -3
- package/src/home-paths.ts +26 -26
- package/src/host/agent-host.ts +3 -3
- package/src/host/bridge-host.ts +33 -33
- package/src/host/fetch-proxy-handler.ts +1 -1
- package/src/host/plane-host.ts +2 -2
- package/src/host/replacement-module-handler.ts +2 -2
- package/src/menu.ts +2 -2
- package/src/metro-plugin.ts +7 -7
- package/src/metro-production-bundle.ts +2 -2
- package/src/native-dev-bundle-url.ts +2 -2
- package/src/playwright-host-log.ts +1 -1
- package/src/public-brand.ts +2 -2
- package/src/react-native-host-modules.ts +1 -1
- package/src/rolldown-plugin-swift.ts +3 -3
- package/src/runtime-assets.ts +11 -11
- package/src/runtime-delivery.ts +6 -6
- package/src/screenshots/orchestrate.ts +1 -1
- package/src/screenshots/org-deck.ts +1 -1
- package/src/screenshots/schema.ts +2 -2
- package/src/sim-client.ts +1 -1
- package/src/skills/builtin/compat-check.ts +2 -2
- package/src/vite-plugin-one.ts +8 -8
- package/src/vite-plugin-swift.ts +3 -3
- package/src/vite-plugin.ts +6 -19
package/src/cloud.ts
CHANGED
|
@@ -78,7 +78,7 @@ export interface ConnectRemoteSimulatorInput {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// the simulator-driving verbs. the same vocabulary the CLI drives, one for
|
|
81
|
-
// one: `
|
|
81
|
+
// one: `peach describe`, `peach do tap`, `peach wait selector`.
|
|
82
82
|
export interface RnxCommands {
|
|
83
83
|
command(
|
|
84
84
|
command: { type: string; [key: string]: unknown },
|
|
@@ -130,7 +130,7 @@ function normalizeCloudOrigin(source: string): string {
|
|
|
130
130
|
try {
|
|
131
131
|
parsed = new URL(source)
|
|
132
132
|
} catch {
|
|
133
|
-
throw new Error(`invalid
|
|
133
|
+
throw new Error(`invalid Peach Cloud origin: ${source}`)
|
|
134
134
|
}
|
|
135
135
|
if (
|
|
136
136
|
(parsed.protocol !== 'https:' && parsed.protocol !== 'http:') ||
|
|
@@ -140,16 +140,16 @@ function normalizeCloudOrigin(source: string): string {
|
|
|
140
140
|
parsed.search ||
|
|
141
141
|
parsed.hash
|
|
142
142
|
) {
|
|
143
|
-
throw new Error(`
|
|
143
|
+
throw new Error(`Peach Cloud origin must be an HTTP(S) origin: ${source}`)
|
|
144
144
|
}
|
|
145
145
|
if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname)) {
|
|
146
|
-
throw new Error(`
|
|
146
|
+
throw new Error(`Peach Cloud origin requires HTTPS outside loopback: ${source}`)
|
|
147
147
|
}
|
|
148
148
|
return parsed.origin
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
function resolveOrigin(value?: string): string {
|
|
152
|
-
const configured = value?.trim() || process.env.
|
|
152
|
+
const configured = value?.trim() || process.env.PEACH_CLOUD_ORIGIN?.trim()
|
|
153
153
|
return normalizeCloudOrigin(configured || rnxPublicBrand.origin)
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -159,14 +159,14 @@ function responseError(status: number, text: string): Error {
|
|
|
159
159
|
const parsed: unknown = JSON.parse(text)
|
|
160
160
|
const error = isRecord(parsed) ? parsed.error : null
|
|
161
161
|
if (isRecord(error) && typeof error.message === 'string') {
|
|
162
|
-
return new Error(`
|
|
162
|
+
return new Error(`Peach Cloud request failed (${status}): ${error.message}`)
|
|
163
163
|
}
|
|
164
164
|
} catch {
|
|
165
165
|
// body was not json; fall through to the raw text
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
return new Error(
|
|
169
|
-
`
|
|
169
|
+
`Peach Cloud request failed (${status})${text.trim() ? `: ${text.trim()}` : ''}`,
|
|
170
170
|
)
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -269,10 +269,10 @@ function readJsArtifact(bytes: Uint8Array): string {
|
|
|
269
269
|
try {
|
|
270
270
|
source = new TextDecoder('utf-8', { fatal: true }).decode(bytes)
|
|
271
271
|
} catch {
|
|
272
|
-
throw new Error('
|
|
272
|
+
throw new Error('peach Cloud artifact must be valid UTF-8 JavaScript, not bytecode')
|
|
273
273
|
}
|
|
274
274
|
if (source.includes('\0') || !source.includes('__d(') || !source.includes('__r(')) {
|
|
275
|
-
throw new Error('
|
|
275
|
+
throw new Error('peach Cloud artifact must be a Metro JavaScript bundle')
|
|
276
276
|
}
|
|
277
277
|
return source
|
|
278
278
|
}
|
|
@@ -284,7 +284,7 @@ function readClaim(value: unknown): RnxCloudClaim {
|
|
|
284
284
|
!value.id ||
|
|
285
285
|
typeof value.expiresAt !== 'number'
|
|
286
286
|
) {
|
|
287
|
-
throw new Error('
|
|
287
|
+
throw new Error('Peach Cloud returned an invalid claim')
|
|
288
288
|
}
|
|
289
289
|
return { id: value.id, expiresAt: value.expiresAt }
|
|
290
290
|
}
|
|
@@ -305,7 +305,7 @@ function readArtifact(
|
|
|
305
305
|
!Number.isInteger(value.bytes) ||
|
|
306
306
|
value.bytes !== expectedBytes
|
|
307
307
|
) {
|
|
308
|
-
throw new Error('
|
|
308
|
+
throw new Error('Peach Cloud returned an invalid artifact receipt')
|
|
309
309
|
}
|
|
310
310
|
return { id: expectedId, bytes: expectedBytes }
|
|
311
311
|
}
|
|
@@ -320,14 +320,14 @@ function readSimulatorCreate(
|
|
|
320
320
|
claim: RnxCloudClaim
|
|
321
321
|
artifact: RnxCloudBoxCreateReceipt['artifact']
|
|
322
322
|
} {
|
|
323
|
-
if (!isRecord(value)) throw new Error('
|
|
323
|
+
if (!isRecord(value)) throw new Error('Peach Cloud create returned an invalid simulator')
|
|
324
324
|
const claim = readClaim(value.claim)
|
|
325
325
|
if (
|
|
326
326
|
typeof value.simId !== 'string' ||
|
|
327
327
|
!value.simId ||
|
|
328
328
|
typeof value.token !== 'string'
|
|
329
329
|
) {
|
|
330
|
-
throw new Error('
|
|
330
|
+
throw new Error('Peach Cloud create returned an invalid simulator')
|
|
331
331
|
}
|
|
332
332
|
return {
|
|
333
333
|
simId: value.simId,
|
|
@@ -347,7 +347,7 @@ function readBoxCreate(
|
|
|
347
347
|
simulatorToken: string
|
|
348
348
|
} {
|
|
349
349
|
const artifactId = artifactDigestId(expectedSha256)
|
|
350
|
-
if (!isRecord(value)) throw new Error('
|
|
350
|
+
if (!isRecord(value)) throw new Error('Peach Cloud create returned invalid JSON')
|
|
351
351
|
const simulator = readSimulatorCreate(value.simulator, artifactId, expectedBytes)
|
|
352
352
|
if (
|
|
353
353
|
typeof value.boxId !== 'string' ||
|
|
@@ -356,7 +356,7 @@ function readBoxCreate(
|
|
|
356
356
|
typeof value.token !== 'string' ||
|
|
357
357
|
!value.token
|
|
358
358
|
) {
|
|
359
|
-
throw new Error('
|
|
359
|
+
throw new Error('Peach Cloud create returned an invalid box receipt')
|
|
360
360
|
}
|
|
361
361
|
return {
|
|
362
362
|
receipt: {
|
|
@@ -383,14 +383,14 @@ function cloudCommand(
|
|
|
383
383
|
if (key !== 'id' && key !== 'simId') clean[key] = value
|
|
384
384
|
}
|
|
385
385
|
if (!isRnxCloudCommandType(command.type)) {
|
|
386
|
-
throw new Error(`
|
|
386
|
+
throw new Error(`Peach Cloud does not support bridge command type ${command.type}`)
|
|
387
387
|
}
|
|
388
388
|
// a verb outside this token's scopes never leaves the sdk: the refusal
|
|
389
389
|
// names the scope before a request. the full token carries every scope
|
|
390
390
|
// except debug, so evaluate/call/close stop here for it.
|
|
391
391
|
if (!rnxCloudScopesAllowCommand(scopes, command.type)) {
|
|
392
392
|
throw new Error(
|
|
393
|
-
`
|
|
393
|
+
`Peach Cloud command ${command.type} requires the ${rnxCloudCommandScope(command.type)} scope`,
|
|
394
394
|
)
|
|
395
395
|
}
|
|
396
396
|
return { ...clean, type: command.type }
|
|
@@ -427,14 +427,14 @@ export class RemoteSimulator {
|
|
|
427
427
|
layers: options?.layers ?? 'tenant',
|
|
428
428
|
})
|
|
429
429
|
if (typeof result !== 'string' || !result.startsWith('data:image/')) {
|
|
430
|
-
throw new Error('
|
|
430
|
+
throw new Error('Peach Cloud screenshot did not return an image data url')
|
|
431
431
|
}
|
|
432
432
|
return result
|
|
433
433
|
},
|
|
434
434
|
capture: async () => {
|
|
435
435
|
const result = await this.sendCommand({ type: 'capture' })
|
|
436
436
|
if (!isRnxScreenCapture(result)) {
|
|
437
|
-
throw new Error('
|
|
437
|
+
throw new Error('Peach Cloud capture returned an invalid screen capture')
|
|
438
438
|
}
|
|
439
439
|
return result
|
|
440
440
|
},
|
|
@@ -444,13 +444,13 @@ export class RemoteSimulator {
|
|
|
444
444
|
options.strategy !== 'data' &&
|
|
445
445
|
options.strategy !== 'full'
|
|
446
446
|
) {
|
|
447
|
-
throw new Error('
|
|
447
|
+
throw new Error('Peach Cloud reset strategy must be data or full')
|
|
448
448
|
}
|
|
449
449
|
if (
|
|
450
450
|
options?.from !== undefined &&
|
|
451
451
|
(typeof options.from !== 'string' || options.from.length === 0)
|
|
452
452
|
) {
|
|
453
|
-
throw new Error('
|
|
453
|
+
throw new Error('Peach Cloud reset from must name a simulator')
|
|
454
454
|
}
|
|
455
455
|
return this.sendCommand(
|
|
456
456
|
options === undefined
|
|
@@ -525,7 +525,7 @@ export class RemoteSimulator {
|
|
|
525
525
|
)
|
|
526
526
|
} catch (error) {
|
|
527
527
|
throw new Error(
|
|
528
|
-
`could not reach
|
|
528
|
+
`could not reach Peach Cloud at ${this.session.origin}: ${
|
|
529
529
|
error instanceof Error ? error.message : String(error)
|
|
530
530
|
}`,
|
|
531
531
|
)
|
|
@@ -536,14 +536,14 @@ export class RemoteSimulator {
|
|
|
536
536
|
try {
|
|
537
537
|
parsed = JSON.parse(text)
|
|
538
538
|
} catch {
|
|
539
|
-
throw new Error('
|
|
539
|
+
throw new Error('Peach Cloud command returned unreadable JSON')
|
|
540
540
|
}
|
|
541
541
|
if (!isRecord(parsed) || parsed.id !== body.id) {
|
|
542
|
-
throw new Error('
|
|
542
|
+
throw new Error('Peach Cloud command returned an invalid command receipt')
|
|
543
543
|
}
|
|
544
544
|
if (typeof parsed.error === 'string' && parsed.error) throw new Error(parsed.error)
|
|
545
545
|
if (!Object.hasOwn(parsed, 'result')) {
|
|
546
|
-
throw new Error('
|
|
546
|
+
throw new Error('Peach Cloud command receipt has no result')
|
|
547
547
|
}
|
|
548
548
|
return parsed.result
|
|
549
549
|
}
|
|
@@ -570,11 +570,11 @@ export class RemoteSimulator {
|
|
|
570
570
|
try {
|
|
571
571
|
parsed = JSON.parse(text)
|
|
572
572
|
} catch {
|
|
573
|
-
throw new Error('
|
|
573
|
+
throw new Error('Peach Cloud claim returned unreadable JSON')
|
|
574
574
|
}
|
|
575
575
|
const claim = readClaim(isRecord(parsed) ? parsed.claim : null)
|
|
576
576
|
if (claim.id !== claimId) {
|
|
577
|
-
throw new Error('
|
|
577
|
+
throw new Error('Peach Cloud confirmation replaced the initial claim')
|
|
578
578
|
}
|
|
579
579
|
this.session = { ...this.session, claim }
|
|
580
580
|
return claim
|
|
@@ -611,17 +611,17 @@ export async function createRemoteSimulator(
|
|
|
611
611
|
): Promise<RemoteSimulator> {
|
|
612
612
|
if (input.artifact.byteLength > RNX_CLOUD_MAX_ARTIFACT_BYTES) {
|
|
613
613
|
throw new Error(
|
|
614
|
-
`
|
|
614
|
+
`peach Cloud artifact is ${input.artifact.byteLength} bytes and exceeds the ${RNX_CLOUD_MAX_ARTIFACT_BYTES}-byte limit`,
|
|
615
615
|
)
|
|
616
616
|
}
|
|
617
617
|
if (input.artifact.byteLength <= 0) {
|
|
618
|
-
throw new Error('
|
|
618
|
+
throw new Error('peach Cloud artifact is empty')
|
|
619
619
|
}
|
|
620
620
|
readJsArtifact(input.artifact)
|
|
621
621
|
const origin = resolveOrigin(input.origin)
|
|
622
622
|
const sha256 = await sha256Hex(input.artifact)
|
|
623
623
|
if (input.sha256 && input.sha256 !== sha256) {
|
|
624
|
-
throw new Error('
|
|
624
|
+
throw new Error('peach Cloud artifact sha256 does not match the provided digest')
|
|
625
625
|
}
|
|
626
626
|
const device = input.device?.trim() || DEFAULT_DEVICE
|
|
627
627
|
let response: Response
|
|
@@ -640,7 +640,7 @@ export async function createRemoteSimulator(
|
|
|
640
640
|
})
|
|
641
641
|
} catch (error) {
|
|
642
642
|
throw new Error(
|
|
643
|
-
`could not reach
|
|
643
|
+
`could not reach Peach Cloud at ${origin}: ${
|
|
644
644
|
error instanceof Error ? error.message : String(error)
|
|
645
645
|
}`,
|
|
646
646
|
)
|
|
@@ -651,7 +651,7 @@ export async function createRemoteSimulator(
|
|
|
651
651
|
try {
|
|
652
652
|
parsed = JSON.parse(responseText)
|
|
653
653
|
} catch {
|
|
654
|
-
throw new Error('
|
|
654
|
+
throw new Error('Peach Cloud create returned unreadable JSON')
|
|
655
655
|
}
|
|
656
656
|
const created = readBoxCreate(parsed, input.artifact.byteLength, sha256)
|
|
657
657
|
const simulator = new RemoteSimulator({
|
|
@@ -677,7 +677,7 @@ function readSimArtifact(value: unknown): RnxCloudCreateReceipt['artifact'] {
|
|
|
677
677
|
!Number.isInteger(value.bytes) ||
|
|
678
678
|
value.bytes <= 0
|
|
679
679
|
) {
|
|
680
|
-
throw new Error('
|
|
680
|
+
throw new Error('Peach Cloud returned an invalid simulator artifact')
|
|
681
681
|
}
|
|
682
682
|
return { id: `sha256:${value.sha256}`, bytes: value.bytes }
|
|
683
683
|
}
|
|
@@ -707,7 +707,7 @@ function readConnectScopes(
|
|
|
707
707
|
) ||
|
|
708
708
|
typeof value.expiresAt !== 'number'
|
|
709
709
|
) {
|
|
710
|
-
throw new Error('
|
|
710
|
+
throw new Error('Peach Cloud returned an invalid scoped token')
|
|
711
711
|
}
|
|
712
712
|
return Object.freeze([...value.scopes])
|
|
713
713
|
}
|
|
@@ -718,8 +718,8 @@ function readConnectScopes(
|
|
|
718
718
|
export async function connect(
|
|
719
719
|
input: ConnectRemoteSimulatorInput,
|
|
720
720
|
): Promise<RemoteSimulator> {
|
|
721
|
-
if (!input.id) throw new Error('
|
|
722
|
-
if (!input.token) throw new Error('
|
|
721
|
+
if (!input.id) throw new Error('Peach Cloud connect needs a simulator id')
|
|
722
|
+
if (!input.token) throw new Error('Peach Cloud connect needs a token')
|
|
723
723
|
const origin = resolveOrigin(input.origin)
|
|
724
724
|
let response: Response
|
|
725
725
|
try {
|
|
@@ -729,7 +729,7 @@ export async function connect(
|
|
|
729
729
|
)
|
|
730
730
|
} catch (error) {
|
|
731
731
|
throw new Error(
|
|
732
|
-
`could not reach
|
|
732
|
+
`could not reach Peach Cloud at ${origin}: ${
|
|
733
733
|
error instanceof Error ? error.message : String(error)
|
|
734
734
|
}`,
|
|
735
735
|
)
|
|
@@ -740,14 +740,14 @@ export async function connect(
|
|
|
740
740
|
try {
|
|
741
741
|
parsed = JSON.parse(text)
|
|
742
742
|
} catch {
|
|
743
|
-
throw new Error('
|
|
743
|
+
throw new Error('Peach Cloud connect returned unreadable JSON')
|
|
744
744
|
}
|
|
745
745
|
if (!isRecord(parsed) || !isRecord(parsed.sim)) {
|
|
746
|
-
throw new Error('
|
|
746
|
+
throw new Error('Peach Cloud connect returned an invalid simulator')
|
|
747
747
|
}
|
|
748
748
|
const sim = parsed.sim
|
|
749
749
|
if (typeof sim.simId !== 'string' || !sim.simId) {
|
|
750
|
-
throw new Error('
|
|
750
|
+
throw new Error('Peach Cloud connect returned an invalid simulator')
|
|
751
751
|
}
|
|
752
752
|
return new RemoteSimulator({
|
|
753
753
|
origin,
|
package/src/config.ts
CHANGED
|
@@ -114,7 +114,7 @@ export function readRNXConfigFromSearchParams<
|
|
|
114
114
|
return hasRNXConfig(parsed) ? parsed : undefined
|
|
115
115
|
} catch (error) {
|
|
116
116
|
const message = error instanceof Error ? error.message : String(error)
|
|
117
|
-
console.warn(`[
|
|
117
|
+
console.warn(`[peach] invalid ${RNX_CONFIG_QUERY_PARAM}: ${message}`)
|
|
118
118
|
return undefined
|
|
119
119
|
}
|
|
120
120
|
}
|
package/src/connect.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { SimClient, SimConnectError } from './sim-client'
|
|
|
15
15
|
export interface ConnectOptions {
|
|
16
16
|
/** ws bridge port. default: the resolved bridge world (lockfiles). */
|
|
17
17
|
port?: number
|
|
18
|
-
/** sim id to attach to (as shown by `
|
|
18
|
+
/** sim id to attach to (as shown by `peach list`). default: the primary sim. */
|
|
19
19
|
sim?: string
|
|
20
20
|
/** per-command bridge timeout in ms. default 15000. */
|
|
21
21
|
timeoutMs?: number
|
|
@@ -34,13 +34,13 @@ function pickSim(
|
|
|
34
34
|
`no sim connected with id ${wanted} on bridge :${port}` +
|
|
35
35
|
(open.length
|
|
36
36
|
? ` — connected: ${open.map((sim) => sim.id).join(', ')}`
|
|
37
|
-
: ' — no sims connected; open one with `
|
|
37
|
+
: ' — no sims connected; open one with `peach open <port>`'),
|
|
38
38
|
)
|
|
39
39
|
}
|
|
40
40
|
return match
|
|
41
41
|
}
|
|
42
|
-
// same session affinity as the CLI: a sim pinned by `
|
|
43
|
-
// `
|
|
42
|
+
// same session affinity as the CLI: a sim pinned by `peach open` /
|
|
43
|
+
// `peach use` in this terminal wins over the primary election, so a script
|
|
44
44
|
// run next to CLI commands drives the sim the terminal is working with. a
|
|
45
45
|
// stale pin (sim gone) falls through to the primary.
|
|
46
46
|
const pinned = readCurrentSimId()
|
|
@@ -51,7 +51,7 @@ function pickSim(
|
|
|
51
51
|
const primary = open.find((sim) => sim.isPrimary) ?? open[0]
|
|
52
52
|
if (!primary) {
|
|
53
53
|
throw new SimConnectError(
|
|
54
|
-
`no sims connected on bridge :${port} — open one with \`
|
|
54
|
+
`no sims connected on bridge :${port} — open one with \`peach open <port>\``,
|
|
55
55
|
)
|
|
56
56
|
}
|
|
57
57
|
return primary
|
|
@@ -85,7 +85,7 @@ export async function connect(options: ConnectOptions = {}): Promise<SimClient>
|
|
|
85
85
|
discovery.close()
|
|
86
86
|
if (error instanceof SimConnectError) throw error
|
|
87
87
|
const message = error instanceof Error ? error.message : String(error)
|
|
88
|
-
throw new SimConnectError(`${message} — is the
|
|
88
|
+
throw new SimConnectError(`${message} — is the peach daemon or dev shell running?`)
|
|
89
89
|
}
|
|
90
90
|
discovery.close()
|
|
91
91
|
|
package/src/dev-server-open.ts
CHANGED
|
@@ -16,7 +16,7 @@ export function resolveRnxDesktopAppName(
|
|
|
16
16
|
if (!open) return null
|
|
17
17
|
if (typeof open === 'object') {
|
|
18
18
|
const appName = open.appName.trim()
|
|
19
|
-
if (!appName) throw new Error('
|
|
19
|
+
if (!appName) throw new Error('peach: open.appName must not be empty')
|
|
20
20
|
return appName
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -27,7 +27,7 @@ export function resolveRnxDesktopAppName(
|
|
|
27
27
|
? Reflect.get(packageJson, 'name')
|
|
28
28
|
: undefined
|
|
29
29
|
if (typeof packageName !== 'string' || !packageName.trim()) {
|
|
30
|
-
throw new Error(`
|
|
30
|
+
throw new Error(`peach: ${packageJsonPath} must declare a package name`)
|
|
31
31
|
}
|
|
32
32
|
return packageName.trim()
|
|
33
33
|
}
|
|
@@ -40,7 +40,7 @@ export function tagRnxBundleForPlugin(
|
|
|
40
40
|
appName?: string | null,
|
|
41
41
|
): string {
|
|
42
42
|
const absolute = /^[a-z][a-z\d+.-]*:\/\//i.test(bundleUrl)
|
|
43
|
-
const url = new URL(bundleUrl, absolute ? undefined : 'http://
|
|
43
|
+
const url = new URL(bundleUrl, absolute ? undefined : 'http://peach.local')
|
|
44
44
|
if (appName) {
|
|
45
45
|
url.searchParams.set('rnxAppName', appName)
|
|
46
46
|
url.searchParams.set(
|
|
@@ -73,23 +73,23 @@ export function observeRnxDesktopLaunch(child: ChildProcess, appName: string): v
|
|
|
73
73
|
})
|
|
74
74
|
child.once('error', (error) => {
|
|
75
75
|
spawnFailed = true
|
|
76
|
-
console.error(`[
|
|
76
|
+
console.error(`[peach] could not open ${appName}: ${error.message}`)
|
|
77
77
|
})
|
|
78
78
|
child.once('close', (code, signal) => {
|
|
79
79
|
if (spawnFailed) return
|
|
80
80
|
if (code === 0) {
|
|
81
|
-
console.log(`[
|
|
81
|
+
console.log(`[peach] opened ${appName} in the desktop simulator`)
|
|
82
82
|
return
|
|
83
83
|
}
|
|
84
84
|
const detail = stderr.trim() || (signal ? `signal ${signal}` : `exit ${code}`)
|
|
85
|
-
console.error(`[
|
|
85
|
+
console.error(`[peach] could not open ${appName}: ${detail}`)
|
|
86
86
|
})
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export function launchRnxDesktop(bundleUrl: string, appName: string): void {
|
|
90
90
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
91
91
|
const binPath = path.join(packageRoot, 'dist-cli', 'bin.js')
|
|
92
|
-
console.log(`[
|
|
92
|
+
console.log(`[peach] opening ${appName} in the desktop simulator`)
|
|
93
93
|
const child = spawn(
|
|
94
94
|
process.execPath,
|
|
95
95
|
[binPath, ...buildRnxDesktopOpenArgs(bundleUrl)],
|
|
@@ -141,7 +141,7 @@ export function openRnxDesktopWhenMetroIsReady({
|
|
|
141
141
|
if (finished) return
|
|
142
142
|
if (Date.now() >= deadline) {
|
|
143
143
|
console.error(
|
|
144
|
-
`[
|
|
144
|
+
`[peach] could not open ${appName}: Metro did not become ready on port ${port}`,
|
|
145
145
|
)
|
|
146
146
|
finish(false)
|
|
147
147
|
return
|
package/src/disk-cleanup.ts
CHANGED
|
@@ -11,7 +11,7 @@ const AUTOMATIC_CLEANUP_LOCK_STALE_MS = 6 * 60 * 60 * 1000
|
|
|
11
11
|
// a deferral is recorded so the retry does not happen on literally every
|
|
12
12
|
// command. cleanup defers whenever anything is in use, and on a machine that
|
|
13
13
|
// always has a sim or daemon running that is the permanent state: the complete
|
|
14
|
-
// marker is never written, so every
|
|
14
|
+
// marker is never written, so every peach invocation spawned a detached worker
|
|
15
15
|
// that rescanned the home directory, reclaimed nothing, and printed the same
|
|
16
16
|
// "N is in use and will be retried on a later run" line into the caller's
|
|
17
17
|
// stderr. measured on this machine: an identical 662.2 MiB across every
|
|
@@ -209,7 +209,7 @@ function collectRecordingEntries(
|
|
|
209
209
|
})
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
// videos staged by `
|
|
212
|
+
// videos staged by `peach camera play`. a fixture is only a copy of a file
|
|
213
213
|
// the user still has, so it is cheap to reclaim, but the automatic sweep must
|
|
214
214
|
// not pull one out from under a sim that is playing it right now. aggressive
|
|
215
215
|
// only, matching recordings.
|
|
@@ -336,7 +336,7 @@ export function planSootsimCleanup(
|
|
|
336
336
|
function assertInsideHome(home: string, target: string): void {
|
|
337
337
|
const relative = path.relative(home, target)
|
|
338
338
|
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
339
|
-
throw new Error(`refusing cleanup target outside the
|
|
339
|
+
throw new Error(`refusing cleanup target outside the peach data home: ${target}`)
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
342
|
|
package/src/home-paths.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// canonical filesystem layout under ~/.
|
|
1
|
+
// canonical filesystem layout under ~/.peach/. shared by the CLI and the
|
|
2
2
|
// electron main process so every surface agrees where runtimes, the daemon
|
|
3
3
|
// lockfile, and caches live.
|
|
4
4
|
//
|
|
5
|
-
// ~/.
|
|
5
|
+
// ~/.peach/
|
|
6
6
|
// ├── runtimes/
|
|
7
|
-
// │ ├── <version>/ unpacked dist/ of
|
|
7
|
+
// │ ├── <version>/ unpacked dist/ of peach-engine
|
|
8
8
|
// │ └── active text file with the active version string (win-safe
|
|
9
9
|
// │ alternative to a symlink)
|
|
10
10
|
// ├── electron/
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// ├── profiles/
|
|
13
13
|
// │ └── profiles.json storage profile metadata
|
|
14
14
|
// ├── cache/
|
|
15
|
-
// │ └──
|
|
15
|
+
// │ └── peach-runtime-<version>.tar.gz
|
|
16
16
|
// ├── daemon.json lockfile: pid, ports, active runtime, heartbeat
|
|
17
17
|
// ├── dev-bridges/ live Vite dev bridge records, keyed by bound port
|
|
18
18
|
// ├── runtime-upgraded.json one-shot notice written by the daemon's
|
|
@@ -29,7 +29,7 @@ import fs from 'node:fs'
|
|
|
29
29
|
import { homedir } from 'node:os'
|
|
30
30
|
import path from 'node:path'
|
|
31
31
|
|
|
32
|
-
export const
|
|
32
|
+
export const PEACH_HOME_ENV = 'PEACH_HOME'
|
|
33
33
|
export const ACTIVE_RUNTIME_FILE = 'active'
|
|
34
34
|
export const DAEMON_LOCKFILE = 'daemon.json'
|
|
35
35
|
export const DEV_BRIDGES_DIR = 'dev-bridges'
|
|
@@ -38,12 +38,12 @@ export const CLI_UPDATE_FILE = 'cli-update.json'
|
|
|
38
38
|
export const DAEMON_HEARTBEAT_STALE_MS = 30_000
|
|
39
39
|
|
|
40
40
|
export function rnxHomeDir(): string {
|
|
41
|
-
const override = process.env[
|
|
41
|
+
const override = process.env[PEACH_HOME_ENV]
|
|
42
42
|
if (override && override.length > 0) return path.resolve(override)
|
|
43
|
-
return path.join(homedir(), '.
|
|
43
|
+
return path.join(homedir(), '.peach')
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// detect when
|
|
46
|
+
// detect when peach is running from a source checkout (the Contrast monorepo)
|
|
47
47
|
// rather than a published npm install. used to skip auto-install of the
|
|
48
48
|
// persistent launchd / systemd agent: dev shells shouldn't register an agent
|
|
49
49
|
// whose Program path points at workspace artifacts that change between
|
|
@@ -57,10 +57,10 @@ export function rnxHomeDir(): string {
|
|
|
57
57
|
// install path from this repo
|
|
58
58
|
//
|
|
59
59
|
// signal: realpath of process.argv[1] lands inside a `packages/peachsim/`
|
|
60
|
-
// directory. workspace bin symlinks (`node_modules/.bin/
|
|
60
|
+
// directory. workspace bin symlinks (`node_modules/.bin/peachsim` →
|
|
61
61
|
// `packages/peachsim/dist-cli/bin.js`) and bun-direct invocations
|
|
62
62
|
// (`bun packages/peachsim/cli/bin.ts ...`) both match; published installs
|
|
63
|
-
// resolve under `node_modules/
|
|
63
|
+
// resolve under `node_modules/peachsim/` instead.
|
|
64
64
|
export function isSootsimDevCheckout(): boolean {
|
|
65
65
|
if (process.env.SOOTSIM_FORCE_DAEMON_INSTALL === '1') return false
|
|
66
66
|
const env = process.env.SOOTSIM_DEV
|
|
@@ -70,7 +70,7 @@ export function isSootsimDevCheckout(): boolean {
|
|
|
70
70
|
if (!argv1) return false
|
|
71
71
|
try {
|
|
72
72
|
const real = fs.realpathSync(argv1)
|
|
73
|
-
return real.includes(`${path.sep}packages${path.sep}
|
|
73
|
+
return real.includes(`${path.sep}packages${path.sep}peachsim${path.sep}`)
|
|
74
74
|
} catch {
|
|
75
75
|
return false
|
|
76
76
|
}
|
|
@@ -95,8 +95,8 @@ export function isDevWorkstation(): boolean {
|
|
|
95
95
|
// the persistent launchd / systemd daemon must never auto-install or run on a
|
|
96
96
|
// dev checkout or a dev workstation. either makes the background agent point at
|
|
97
97
|
// moving workspace artifacts / dev env and serve stale assets that shadow the
|
|
98
|
-
// live dev stack. setup and the launchd-spawned `
|
|
99
|
-
// this; a foreground `
|
|
98
|
+
// live dev stack. setup and the launchd-spawned `peach serve` boot consult
|
|
99
|
+
// this; a foreground `peach serve` the dev starts
|
|
100
100
|
// by hand is intentional and stays allowed.
|
|
101
101
|
export function shouldSkipPersistentDaemon(): boolean {
|
|
102
102
|
return isSootsimDevCheckout() || isDevWorkstation()
|
|
@@ -130,7 +130,7 @@ export function profilesDir(): string {
|
|
|
130
130
|
return path.join(rnxHomeDir(), 'profiles')
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
// video fixtures staged for `
|
|
133
|
+
// video fixtures staged for `peach camera play`. the CLI copies the user's
|
|
134
134
|
// file in here and the daemon serves it by basename from this one directory,
|
|
135
135
|
// so the route can never be talked into reading anywhere else on disk.
|
|
136
136
|
export function cameraFixturesDir(): string {
|
|
@@ -148,11 +148,11 @@ export function daemonAppDir(): string {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
export function daemonAppBundlePath(): string {
|
|
151
|
-
return path.join(daemonAppDir(), '
|
|
151
|
+
return path.join(daemonAppDir(), 'Peach Daemon.app')
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
export function daemonAppLauncherPath(): string {
|
|
155
|
-
return path.join(daemonAppBundlePath(), 'Contents', 'MacOS', '
|
|
155
|
+
return path.join(daemonAppBundlePath(), 'Contents', 'MacOS', 'peach-daemon')
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
export function cacheDir(): string {
|
|
@@ -182,9 +182,9 @@ export function cliUpdateFilePath(): string {
|
|
|
182
182
|
return path.join(rnxHomeDir(), CLI_UPDATE_FILE)
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
// --- shared
|
|
185
|
+
// --- shared peach config ---------------------------------------------------
|
|
186
186
|
//
|
|
187
|
-
// ~/.
|
|
187
|
+
// ~/.peach/config.json is the single user-level config file every peach
|
|
188
188
|
// surface (cli, electron renderer, dev browser via electron ipc) agrees on.
|
|
189
189
|
//
|
|
190
190
|
// privacy preferences live in the nested settings object so the cli, desktop
|
|
@@ -293,8 +293,8 @@ export function readOrCreateAnonymousId(): string {
|
|
|
293
293
|
|
|
294
294
|
// --- default box ----------------------------------------------------------
|
|
295
295
|
//
|
|
296
|
-
// the box every box verb acts on when the command line names none. `
|
|
297
|
-
// use` writes it and `
|
|
296
|
+
// the box every box verb acts on when the command line names none. `peach box
|
|
297
|
+
// use` writes it and `peach box stop` clears it when it stops that box.
|
|
298
298
|
//
|
|
299
299
|
// the id and the display name, and nothing else. a Box is reached with a
|
|
300
300
|
// bearer token the account service hands out per attach, so writing one here
|
|
@@ -327,7 +327,7 @@ export function writeDefaultBox(box: DefaultBox | null): void {
|
|
|
327
327
|
// the only place its "updated to vX" line lands is the daemon's own stderr,
|
|
328
328
|
// which nobody reads. it also drops a one-shot notice file here; the next
|
|
329
329
|
// interactive CLI invocation prints it once and deletes it, so users learn
|
|
330
|
-
// their engine moved without having to run `
|
|
330
|
+
// their engine moved without having to run `peach version`.
|
|
331
331
|
|
|
332
332
|
export interface RuntimeUpgradeNotice {
|
|
333
333
|
from: string | null
|
|
@@ -421,7 +421,7 @@ export interface DaemonLockfile {
|
|
|
421
421
|
/** http runtime server port (where electron loads the renderer from). */
|
|
422
422
|
runtimePort: number
|
|
423
423
|
/** active runtime version at boot, or null if the daemon booted with no
|
|
424
|
-
* runtime installed. updated live when the user runs `
|
|
424
|
+
* runtime installed. updated live when the user runs `peach runtime use`. */
|
|
425
425
|
activeRuntime: string | null
|
|
426
426
|
/** absolute path to the active runtime's dist directory, or null. */
|
|
427
427
|
activeRuntimeDir: string | null
|
|
@@ -470,7 +470,7 @@ interface DaemonClaim {
|
|
|
470
470
|
export function readDaemonLockfile(): DaemonLockfile | null {
|
|
471
471
|
try {
|
|
472
472
|
// cap the read so a junk/large file on the lockfile path can't OOM
|
|
473
|
-
// the CLI when someone has been messing with ~/.
|
|
473
|
+
// the CLI when someone has been messing with ~/.peach/.
|
|
474
474
|
const fd = fs.openSync(daemonLockfilePath(), 'r')
|
|
475
475
|
try {
|
|
476
476
|
const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES)
|
|
@@ -618,17 +618,17 @@ export function writeDaemonLockfile(data: DaemonLockfile): void {
|
|
|
618
618
|
const existing = readDaemonLockfile()
|
|
619
619
|
if (!existing) {
|
|
620
620
|
if (createDaemonLockfileExclusive(data)) return
|
|
621
|
-
throw new Error('another
|
|
621
|
+
throw new Error('another peach daemon claimed the lockfile before this write')
|
|
622
622
|
}
|
|
623
623
|
if (!sameDaemonOwner(existing, data)) {
|
|
624
|
-
throw new Error('refusing to overwrite another
|
|
624
|
+
throw new Error('refusing to overwrite another peach daemon lockfile')
|
|
625
625
|
}
|
|
626
626
|
const tmp = `${lockfilePath}.${data.pid}.${randomUUID()}.tmp`
|
|
627
627
|
fs.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
|
|
628
628
|
try {
|
|
629
629
|
const current = readDaemonLockfile()
|
|
630
630
|
if (!current || !sameDaemonOwner(current, data)) {
|
|
631
|
-
throw new Error('
|
|
631
|
+
throw new Error('peach daemon lock ownership changed during heartbeat')
|
|
632
632
|
}
|
|
633
633
|
fs.renameSync(tmp, lockfilePath)
|
|
634
634
|
} finally {
|
package/src/host/agent-host.ts
CHANGED
|
@@ -67,7 +67,7 @@ interface PendingPromptEcho {
|
|
|
67
67
|
export interface AgentHostOptions {
|
|
68
68
|
/** callback so hosts with a Contrast-env context (electron, dev middleware)
|
|
69
69
|
* can inject their own exclude list. defaults to the env-derived list
|
|
70
|
-
* the rest of the repo uses, so `
|
|
70
|
+
* the rest of the repo uses, so `peach serve` standalone does the
|
|
71
71
|
* right thing. */
|
|
72
72
|
getExcludePorts?: () => number[]
|
|
73
73
|
/** returns the active CLI lease for a sim. used only for prompt routing to
|
|
@@ -84,7 +84,7 @@ export interface AgentCliLeaseSnapshot {
|
|
|
84
84
|
/** default excludes keep the dev-server scanner from hitting Contrast's own
|
|
85
85
|
* web / zero / r2 ports. mirrored from
|
|
86
86
|
* sootsim-engine/src/dev-scan-excludes.ts — duplicated rather than
|
|
87
|
-
* imported because
|
|
87
|
+
* imported because peach is a dependency of sootsim-engine, not the
|
|
88
88
|
* other way around. */
|
|
89
89
|
function defaultExcludePorts(): number[] {
|
|
90
90
|
return [
|
|
@@ -102,7 +102,7 @@ export class AgentHost {
|
|
|
102
102
|
// every session.
|
|
103
103
|
private sessionsBySocket = new Map<WebSocket, Set<string>>()
|
|
104
104
|
// every connected socket (regardless of role) — gets session-status
|
|
105
|
-
// pushes so a CLI `
|
|
105
|
+
// pushes so a CLI `peach agent sessions --watch` can react to state
|
|
106
106
|
// changes driven by another client.
|
|
107
107
|
private allSockets = new Set<WebSocket>()
|
|
108
108
|
// agent wrappers also emit prompt-received from the FIFO reader, but the
|