handleLine method

Future<void> handleLine(
  1. String line, {
  2. required FutureOr<void> emit(
    1. String reply
    ),
})

Processes one inbound NDJSON line; every reply is emitted through emit as a complete NDJSON line. Never throws for message-level problems — they become error envelopes.

Implementation

Future<void> handleLine(
  String line, {
  required FutureOr<void> Function(String reply) emit,
}) async {
  final trimmed = line.trim();
  if (trimmed.isEmpty) return;

  Map<String, Object?>? decoded;
  try {
    decoded = ZapProtocol.decodeLine(trimmed);
  } on FormatException catch (e) {
    _emitError(
      emit,
      code: 'schema',
      message:
          'line is not a valid ZAP '
          'NDJSON message: ${e.message}',
      details: [e.toString()],
    );
    return;
  }

  // Version gate before anything else is interpreted.
  if (decoded['zap'] != zapProtocolVersion) {
    _emitError(
      emit,
      code: 'version',
      message:
          'unsupported ZAP protocol version '
          '"${decoded['zap']}" (this host speaks $zapProtocolVersion)',
      inReplyTo: decoded['id'] is String ? decoded['id'] as String : null,
    );
    return;
  }

  // Structural validation (schema).
  final validation = ZapValidator.validate(decoded);
  if (!validation.ok) {
    _emitError(
      emit,
      code: 'schema',
      message:
          'message rejected: ${validation.issues.length} schema '
          'violation(s)',
      inReplyTo: decoded['id'] is String ? decoded['id'] as String : null,
      details: [for (final i in validation.issues) i.toString()],
    );
    return;
  }

  final type = decoded['type'] as String;

  // Direction gate: only missions and checkpoint save/restore come in.
  if (type != 'mission') {
    final kind = decoded['kind'];
    final inboundKind = kind == 'save' || kind == 'restore';
    if (type != 'checkpoint' || !inboundKind) {
      _emitError(
        emit,
        code: 'direction',
        message:
            '"$type${type == 'checkpoint' ? ' ($kind)' : ''}" is a '
            'host-to-agent message; only mission and checkpoint '
            'save/restore may be sent inbound',
        inReplyTo: decoded['id'] as String?,
      );
      return;
    }
    await _handleCheckpoint(decoded, emit);
    return;
  }

  await _handleMission(decoded, emit);
}