diff --git a/experiments/shell/local_shell_diagnostics.hpp b/experiments/shell/local_shell_diagnostics.hpp index b88fdb22..f6d07f9b 100644 --- a/experiments/shell/local_shell_diagnostics.hpp +++ b/experiments/shell/local_shell_diagnostics.hpp @@ -45,4 +45,72 @@ inline void local_shell_size_readiness(const std::filesystem::path& directory, s <<",\"observed_output_tail_bytes\":"<" || text == "TF> "; }; + const auto end_line = [&] { + if (literal && line == marker) output_seen = true; + else if (literal && output_seen && prompt(line)) prompt_seen = true; + line.clear(); + literal = true; + }; + for (std::size_t i = 0; i < raw.size();) { + const auto byte = static_cast(raw[i++]); + if (byte == 0x1b) { + if (i == raw.size()) { malformed = true; break; } + const char kind = raw[i++]; + if (kind == ']') { + bool terminated = false; + while (i < raw.size()) { + const char next = raw[i++]; + if (next == '\a') { terminated = true; break; } + if (next == '\x1b') { + if (i == raw.size() || raw[i++] != '\\') { malformed = true; break; } + terminated = true; + break; + } + } + if (!terminated) { malformed = true; break; } + } else if (kind == '[') { + while (i < raw.size() && ((raw[i] >= '0' && raw[i] <= '9') || raw[i] == ';' || raw[i] == '?')) ++i; + if (i == raw.size()) { malformed = true; break; } + ++i; + } else { malformed = true; break; } + } else if (byte == '\r') { + } else if (byte == '\n') { + end_line(); + } else if (byte == '\b') { + if (output_seen) { malformed = true; break; } + literal = false; + } else { + if (byte < 0x20 || byte == 0x7f) { malformed = true; break; } + line.push_back(static_cast(byte)); + } + } + std::string escaped; + for (const char ch : line) { + if (escaped.size() > 160) { escaped += "..."; break; } + const auto byte = static_cast(ch); + if (byte >= 0x20 && byte < 0x7f && ch != '"' && ch != '\\') escaped.push_back(ch); + else { + char buffer[8]{}; + std::snprintf(buffer, sizeof(buffer), "\\x%02x", byte); + escaped += buffer; + } + } + std::ofstream progress(directory/"shells-transport-probe.jsonl", std::ios::binary | std::ios::app); + progress << "{\"schema\":\"termforge-local-shell-transport-probe-v1\",\"role\":\"" << role + << "\",\"marker\":\"" << marker + << "\",\"output_seen\":" << (output_seen ? "true" : "false") + << ",\"prompt_seen\":" << (prompt_seen ? "true" : "false") + << ",\"malformed\":" << (malformed ? "true" : "false") + << ",\"last_line\":\"" << escaped << "\"}\n"; + progress.close(); +} + } // namespace tf_shell_test diff --git a/experiments/shell/workspace_shells_scenario.hpp b/experiments/shell/workspace_shells_scenario.hpp index 7fd41847..91a03892 100644 --- a/experiments/shell/workspace_shells_scenario.hpp +++ b/experiments/shell/workspace_shells_scenario.hpp @@ -19,10 +19,12 @@ inline void Workspace::scenario_shells() { const std::string role(tab.launch->kind.begin(), tab.launch->kind.end()); // WSL also has to show a fresh prompt after its marker; record that // condition explicitly instead of leaving it to inference. + const std::string size_marker = "TF_SIZE_" + std::to_string(columns) + "x" + std::to_string(rows); const bool transport_completed = tab.launch->kind != L"wsl" || - tf_shell_test::transport_output_completed( - tab.shell->observed_output(), - "TF_SIZE_" + std::to_string(columns) + "x" + std::to_string(rows)); + tf_shell_test::transport_output_completed(tab.shell->observed_output(), size_marker); + if (tab.launch->kind == L"wsl") + tf_shell_test::local_shell_transport_probe(diagnostic_directory, role, + tab.shell->observed_output(), size_marker); tf_shell_test::local_shell_size_readiness( diagnostic_directory, role, static_cast(columns), static_cast(rows), tab.shell->settled(),