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
2 changes: 1 addition & 1 deletion experiments/shell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ R1 的 [#12–#17 限定合同](../../docs/milestones/R1-local-text.md)和 [#18

## 选区丢弃诊断

几何/DPI/缩放变化后若选区被 `TextSelection::reconcile` 丢弃,可用环境变量 `TERMFORGE_SELECTION_DIAG=<jsonl 路径>` 打开一次性诊断:每次丢弃写入一行 `{"schema":"termforge-selection-discard-v1","reason":"...","row":N}`(`row` 是发生不一致的选区行序号,`0` 表示与具体行无关的分支),`reason` 取 `epoch-mismatch`、`anchor-missing-or-shrunk`、`row-identity-replaced`、`row-text-changed`、`endpoint-unavailable` 之一。未设置该变量时不写任何文件、行为不变;另有 `TERMFORGE_CAPTURE_OUTPUT=<路径>` 会把该窗口消费的**全部输出字节**追加写入文件(同样 opt-in、默认不写),供失败时逐字节回放;`experiments/shell/run.py` 的 `execute()` 会自动把该变量指向本次场景产物目录下的 `selection-discard.jsonl`,因此失败时该文件随 `build/test-artifacts/` 一起保留。`selection_core` 里新增的分组只验证观察器能收到分支名。
几何/DPI/缩放变化后若选区被 `TextSelection::reconcile` 丢弃,可用环境变量 `TERMFORGE_SELECTION_DIAG=<jsonl 路径>` 打开一次性诊断:每次丢弃写入一行 `{"schema":"termforge-selection-discard-v1","reason":"...","row":N}`(`row` 是发生不一致的选区行序号,`0` 表示与具体行无关的分支),`reason` 取 `epoch-mismatch`、`anchor-missing-or-shrunk`、`row-identity-replaced`、`row-text-changed`、`endpoint-unavailable` 之一。未设置该变量时不写任何文件、行为不变;另有 `TERMFORGE_CAPTURE_OUTPUT=<路径>` 会把该窗口消费的**全部输出字节**追加写入文件(同样 opt-in、默认不写),供失败时逐字节回放;`TERMFORGE_OUTPUT_TRACE=<路径>` 则按块记录 `terminal/t_ms/offset/bytes/columns/rows`,用于判断"某批重绘字节被消费时网格已经是新尺寸还是旧尺寸"。`experiments/shell/run.py` 的 `execute()` 会自动把该变量指向本次场景产物目录下的 `selection-discard.jsonl`,因此失败时该文件随 `build/test-artifacts/` 一起保留。`selection_core` 里新增的分组只验证观察器能收到分支名。

## 所有权与调度

Expand Down
1 change: 1 addition & 0 deletions experiments/shell/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ def execute(exe, mode, out, timeout=40, settings_source=None, *, resources=False
environment = dict(os.environ)
environment.setdefault("TERMFORGE_SELECTION_DIAG", str((out / "selection-discard.jsonl").resolve()))
environment.setdefault("TERMFORGE_CAPTURE_OUTPUT", str((out / "output.bin").resolve()))
environment.setdefault("TERMFORGE_OUTPUT_TRACE", str((out / "output-trace.jsonl").resolve()))
process = subprocess.Popen([str(exe.with_name("shell_test_launcher.exe").resolve()), desktop.full_name,
mode, str((out / "frame.bmp").resolve())],
cwd=out, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Expand Down
17 changes: 17 additions & 0 deletions experiments/shell/shell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ class ShellWindow {
inline static uint64_t next_identity_ = 0;
const uint64_t identity_ = ++next_identity_;
std::ofstream capture_output_;
std::ofstream trace_output_;
uint64_t trace_offset_ = 0;
std::unique_ptr<tf_shell::ClipboardReadJob> clipboard_read_;
std::optional<tf_shell::PreparedPaste> pending_paste_;
std::unique_ptr<tf_shell::PastePrompt> paste_prompt_;
Expand Down Expand Up @@ -818,6 +820,19 @@ class ShellWindow {
capture_output_.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
capture_output_.flush();
}
// Opt-in trace of what the grid looked like when each chunk was
// consumed: TERMFORGE_OUTPUT_TRACE=<path>. This is what tells a
// resize-triggered repaint apart from ordinary output in a failure
// artifact; nothing is written unless the variable is set.
if (trace_output_) {
trace_output_ << "{\"schema\":\"termforge-output-trace-v1\",\"terminal\":" << identity_
<< ",\"t_ms\":" << GetTickCount64()
<< ",\"offset\":" << trace_offset_ << ",\"bytes\":" << bytes.size()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record offsets into the shared capture file

In multi-session scenarios, every ShellWindow appends to the same TERMFORGE_CAPTURE_OUTPUT file configured by execute(), but trace_offset_ starts at zero independently for each window. Consequently, the second terminal's first trace entry reports offset 0 even though its bytes follow another terminal's output, and subsequent offsets remain incorrect whenever sessions interleave. This makes the new offset/geometry evidence point at unrelated bytes in output.bin, undermining resize diagnosis. Record the actual shared capture position for each chunk, or give each terminal a separate capture file whose offsets match this counter.

Useful? React with 👍 / 👎.

<< ",\"columns\":" << parser_.screen.width() << ",\"rows\":" << parser_.screen.height()
<< "}\n";
trace_output_.flush();
trace_offset_ += bytes.size();
}
parser_.feed(bytes); batch += bytes.size();
#ifdef TERMFORGE_CANDIDATE_CORE
route_terminal_events();
Expand Down Expand Up @@ -1004,6 +1019,8 @@ class ShellWindow {
// Opt-in full output capture for replayable failure evidence.
const std::string capture = env_path("TERMFORGE_CAPTURE_OUTPUT");
if (!capture.empty()) capture_output_.open(capture, std::ios::binary | std::ios::app);
const std::string trace = env_path("TERMFORGE_OUTPUT_TRACE");
if (!trace.empty()) trace_output_.open(trace, std::ios::app);
snapshot_ = std::make_shared<const Snapshot>(viewport_.snapshot(parser_.screen), 0);
}
~ShellWindow() {
Expand Down
Loading