launchMailboxWake function

Future<String?> launchMailboxWake({
  1. required ExecutionEnv env,
  2. String? wakeExecutable,
  3. required String cwd,
  4. required String sessionId,
  5. String? sessionName,
})

Spawns the detached headless wake run of an asleep mailbox in cwd (the command comes from mailboxWakeCommand). Returns an error text or null on success.

gh-1744: a mailbox cwd can name a directory that does not exist (a legacy lossy slug decode, or a workspace moved away) — spawning there dies as ProcessException: No such file or directory and the asleep mailbox can never be woken. The working directory is verified first (existing and not a plain file) and the refusal names the path, so the tool note explains the wake failure instead of surfacing a raw process exception.

What is guaranteed is backend kind semantics, not link resolution: the real IO env stats THROUGH symlinks (FileStat.stat — a link to a directory reports FileKind.directory, so symlinked workspaces wake), and MemoryExecutionEnv has no symlink model, so no current backend produces FileKind.symlink here. The guard's explicit symlink allowance is defensive, for a backend honoring pi's documented lstat contract ("symlinks are not followed automatically"): there a symlink-kind path naming an existing directory must still wake — the spawn itself follows the link (pinned by test/cli/agent_cli_mailbox_wake_test.dart). On such a backend a link to a plain file also passes this check and fails at the spawn; that hole is accepted while no backend reports the kind.

Implementation

Future<String?> launchMailboxWake({
  required ExecutionEnv env,
  String? wakeExecutable,
  required String cwd,
  required String sessionId,
  String? sessionName,
}) async {
  final info = await env.fileInfo(cwd);
  final kind = info.valueOrNull?.kind;
  if (info.isErr || (kind != FileKind.directory && kind != FileKind.symlink)) {
    return 'working directory does not exist: $cwd';
  }
  final command = mailboxWakeCommand(
    wakeExecutable: wakeExecutable,
    sessionId: sessionId,
    sessionName: sessionName,
  );
  final result = await env.exec(command, options: ShellExecOptions(cwd: cwd));
  return result.isOk
      ? null
      : 'shell exec failed: ${result.errorOrNull?.message ?? 'unknown error'}';
}