Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions apps/ade-cli/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,15 +761,28 @@ export async function createAdeRuntime(args: {
const ptyBackend = process.env.ADE_DISABLE_SUPERVISED_PTY_HOST === "1"
? null
: createSupervisedPtyLoader({ logger });
// The sync runtime is created after ptyService (it takes ptyService as a
// dependency), so live PTY forwarding binds late through this ref — same
// pattern as desktop main. Without this bridge, paired phones only ever
// receive terminal snapshots, never live terminal_data push.
let syncServiceForPtyEvents: ReturnType<typeof createSyncService> | null = null;
const ptyService = createPtyService({
projectRoot,
transcriptsDir: paths.transcriptsDir,
laneService,
sessionService,
processRegistry,
logger,
broadcastData: (event) => pushEvent("pty", { type: "pty_data", event }),
broadcastExit: (event) => pushEvent("pty", { type: "pty_exit", event }),
broadcastData: (event) => {
pushEvent("pty", { type: "pty_data", event });
const { projectRoot: _projectRoot, ...syncEvent } = event;
syncServiceForPtyEvents?.handlePtyData(syncEvent);
},
broadcastExit: (event) => {
pushEvent("pty", { type: "pty_exit", event });
const { projectRoot: _projectRoot, ...syncEvent } = event;
syncServiceForPtyEvents?.handlePtyExit(syncEvent);
},
onSessionEnded: (event) => {
void sessionDeltaService.computeSessionDelta(event.sessionId).catch((error) => {
logger.warn("runtime.session_delta_compute_failed", {
Expand Down Expand Up @@ -1301,6 +1314,7 @@ export async function createAdeRuntime(args: {
getModelPickerStore: () => getSharedModelPickerStore(db),
onStatusChanged: (snapshot) => pushEvent("runtime", { type: "sync-status", snapshot }),
});
syncServiceForPtyEvents = syncService;
}

if (syncService) {
Expand Down
17 changes: 13 additions & 4 deletions apps/ade-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13526,10 +13526,19 @@ async function runServe(
log: (message) => process.stderr.write(`${message}\n`),
getServiceMainPid: getRuntimeServiceMainPid,
});
})().catch((error: unknown) => {
process.stderr.write(
`ADE brain sync host startup loop failed: ${error instanceof Error ? error.message : String(error)}\n`,
);
})().catch(async (error: unknown) => {
// Cross-channel conflict (another build's live brain owns mobile sync):
// real builds never run sync-less, so fail the brain instead of coming
// up half-alive. The message carries the exact quit command.
const { SyncHostSingletonConflictError } = await import("./services/sync/syncHostSingleton");
const message = error instanceof Error ? error.message : String(error);
if (error instanceof SyncHostSingletonConflictError) {
process.stderr.write(`ADE brain refusing to run without mobile sync.\n${message}\n`);
process.exitCode = 1;
finish();
return;
}
process.stderr.write(`ADE brain sync host startup loop failed: ${message}\n`);
});
}

Expand Down
Loading
Loading