Phiên bản: 0.20.0 · Cập nhật: 2026-10-14
Ba tầng, một quy tắc: mỗi tính năng ra kèm một test ở tầng thấp nhất có thể thấy nó. Bản thân các tệp test là bản kiểm kê đầy đủ; trang này nói mỗi tầng dùng để làm gì, chạy ra sao, và — ở mục cuối trang — tính năng nào được test ở đâu, để người sửa một tính năng biết test nào phải đổi theo. Số lượng test không giữ ở đây — chạy lệnh.
| Tầng | Chạy bằng | Thấy được gì |
|---|---|---|
pytest (tests/) |
uv run pytest -q |
vòng lặp, tool, store, provider, profile, scheduler, kênh, HTTP API và các stream SSE của nó — mọi thứ server làm, với model thay bằng MY_AGENT_ROUTES=fake:echo (hoặc fake:slow: cùng model giả, nhưng chờ giữa hai mảnh để nhìn được stream tới dần) |
vitest (web/) |
cd web && npm test |
parser, reducer, API client, từng component, và cả App chạy trên một fake server trong bộ nhớ. Worker tắt web storage riêng của Node, nên máy Node 26 và CI Node 24 cùng dùng storage của jsdom, và mỗi test bắt đầu với storage rỗng (test-environment.test.ts) |
Playwright (web/e2e/) |
cd web && npm run e2e |
trình duyệt thật trên Vite dev server thật, /api do một mock trong test trả lời; dùng cho các luồng chỉ hỏng trong trình duyệt (SSE reconnect, layout ở bề rộng điện thoại, drawer điện thoại là modal, bàn phím). Proxy /api của dev server khi chạy e2e trỏ vào một cổng không ai nghe (API_ORIGIN), nên spec nào quên mock sẽ hỏng chứ không gọi vào đội đang chạy trên máy (dev-proxy.spec.ts) |
Trình trợ giúp desktop là một chương trình Swift riêng, ngoài ba tầng này: bộ XCTest của nó chạy
bằng scripts/desktop-helper-check.sh trên macOS; xem Desktop của agent.
Ưu tiên tầng thấp nhất: một quy tắc của vòng lặp thuộc về pytest, một reducer thuộc về vitest, và
Playwright chỉ cho những gì chỉ DOM mới cho thấy. Hành vi vắt qua nhiều tầng (một duyệt
tạm dừng vòng lặp và thanh hiện ra; nâng trần chi phí gỡ over_budget ở server và mở lại
ô soạn) có một test ở mỗi bên ranh giới. Fake server của vitest và mock
của Playwright tự tính
lại những trường server suy ra (như over_budget từ trần), để test không tin vào một con số
cũ. Route của Playwright trả cả thân một lần, nên một stream còn đang chạy khi bấm Dừng được
dựng ngay trong trang bằng addInitScript (test Dừng trong chat-smoke.spec.ts), và luồng
hoạt động cũng vậy khi một số đếm phải đổi sau khi trang đã mở (manage-row-phone.spec.ts, spec
này còn chờ font tải xong rồi mới mở mục, vì font đến muộn làm mọi pill rộng ra); còn run do
kênh khác chạy và nút Thử lại của luồng trực tiếp có test App riêng trong
app-thread-refresh.test.tsx; những lần tải lại chạy đua với lượt của chính tab này (một tin
gửi khi lần tải còn trên đường; một run của kênh khác đã chạy từ trước khi gửi, bắt đầu sau run
của tab, hay được một quyết định ở nơi khác tiếp tục trong lúc quyết định ở đây nhận 409; hai
lượt liền nhau của tab, và run mà quyết định ở đây tiếp tục, vẫn là của tab nên không tải thêm
lần nào; một lần tải, một lần tải hỏng (500 hay mất mạng) hay một 409 của cuộc trò chuyện vừa
rời trả về khi cuộc khác đã mở; lời báo “đã được xử lý” còn nguyên qua lần tải do run được tiếp
tục hay luồng kết nối lại, và chỉ mất khi lần tải mang về một yêu cầu mới hay khi mở cuộc khác)
nằm ở
app-thread-refresh-races.test.tsx, nơi mỗi fetch lấy
câu trả lời lúc gửi nhưng chỉ trao ra khi test mở “cửa” của nó; các lần đọc danh sách run
chồng lên nhau (câu trả lời đọc trước khi run kết thúc lại về sau cùng) được giữ cửa theo cùng
cách trong hooks/use-activity.test.ts. Việc dò bản mới so entry có hash của trang với entry mà /
đang phục vụ, nên nó im trên Vite dev server; version-refresh.spec.ts và test App của nó
gắn một entry giả vào trang để trang trông như bản build. Bố cục những gì chat nói về trạng
thái của nó ở bề rộng điện thoại (dòng trạng thái giữ một chiều cao khi nút Thử lại hiện ra)
nằm ở chat-state-phone.spec.ts; luồng trực tiếp được một route giữ lại để bắt được cả lúc
đang kết nối lẫn lúc đã mất.
Fake server của vitest giữ yêu cầu duyệt đang mở như server thật: một lượt dừng ở
approval_required để lại yêu cầu trên cuộc trò chuyện, và quyết định cho một yêu cầu đã đóng
nhận 409. Nhờ vậy việc duyệt ngay trong Quản lý › Duyệt được test ở vitest cả khi tab khác hay
lượt quét hết hạn giành trước (báo “đã được xử lý”, không báo bận), cả khi đồng hồ đếm về 0
(nút khoá, danh sách tải lại, rồi yêu cầu được đọc lại cho tới khi lượt quét — có thể trễ vài
phút sau hạn — khép nó), còn Playwright chỉ giữ phần chỉ trình duyệt thấy: bấm Cho phép
trên bề rộng 390px mà trang không cuộn ngang, và trên màn điện thoại thấp 390×664 mở đầy đủ một
tệp 120 dòng mà các nút quyết định vẫn trên màn hình, trang không cuộn. Lịch sử duyệt bên dưới
được đọc lại mỗi khi một yêu cầu có thể vừa khép: run của nó chạy tiếp, run kết thúc (kể cả khi
chạy tiếp và kết thúc đến trong cùng một lần cập nhật), run của nó chạy tiếp đúng lúc run của một
cuộc khác bắt đầu chờ (trong một snapshot khi luồng kết nối lại hay hai run đến cùng lúc: số run
chờ và số run xong đều giữ nguyên, chỉ run nào đang chờ là đổi), một run trang chưa từng thấy chờ
đến nơi đã xong, hay hàng vừa quyết định thấy run dừng chờ yêu cầu kế tiếp; mỗi đường có một ca
trong app-attention.test.tsx.
Run dừng chờ duyệt sống qua lần khởi động lại: tests/test_activity_restart.py dựng một
activity hub thứ hai trên cùng store như một tiến trình mới sẽ dựng, rồi kiểm tra quyết định
đến sau đó chạy tiếp chính run đã dừng (kể cả sau hai lần khởi động lại), còn run chờ duyệt
không còn gì để chờ (yêu cầu đã được quyết khi không tiến trình nào giữ run, run không thuộc
cuộc nào, một lần dừng cũ hơn của cùng cuộc, hay yêu cầu đang chờ thuộc cuộc khác) bị đóng là
interrupted; một step chạy tiếp sau khi máy khởi động lại không bao giờ báo thời lượng âm. Xoá
một cuộc chỉ đóng run chờ duyệt của chính cuộc đó, không đụng run đang chờ ở cuộc khác hay lượt
còn đang chạy. Xoá cuộc đang chờ duyệt cũng đánh thức những ai đang chờ nó:
tests/test_memory_conversation_title.py kiểm tra không trả tiền cho model đặt tên một cuộc đã
bị xoá, hay đã được người dùng tự đặt tên, trong lúc chờ; tests/test_tools_delegate.py kiểm
tra agent cha đọc được lời báo cuộc con đã bị xoá thay vì một KeyError trần.
Vài test bảo vệ repo chứ không phải một tính năng:
scripts/ lẫn của
web (.ts, .tsx, .css dưới web/src, không tính tệp test và web/src/test/), để module đọc
gọn trong một màn hình; thư mục nào được kiểm cũng phải còn tệp nguồn, và web phải còn tệp nguồn
của từng loại, để dời thư mục đi không làm test đạt trên rỗng;my_agent_crew/server/static có mặt và được phục vụ ở /, với 404 của /api/*
vẫn là JSON; mọi icon và manifest mà trang và manifest trỏ tới đều có trong bundle và được
phục vụ đúng content-type;Content-Security-Policy: frame-ancestors 'self', thành một header riêng đứng
sau policy mà route tự đặt (raw, tệp của agent và trang chạy của canvas giữ nguyên policy sandbox
của chúng); luồng SSE vẫn tới client lúc còn mở (tests/test_security_headers.py,
tests/test_artifact_render.py);RuntimeError (đặt trước route bắt mọi địa chỉ để phục vụ trang) trả 500 “Internal Server
Error” vẫn mang đúng một header Content-Security-Policy: frame-ancestors 'self'. Header được
bọc quanh cả chồng middleware bằng SecuredApp chứ không thêm bằng add_middleware, vì
Starlette đặt bộ xử lý lỗi máy chủ ở ngoài mọi middleware đã thêm, nên trang 500 đi ra không có
header (tests/test_security_headers.py);Sec-Fetch-Site là
cross-site, same-site hay một giá trị lạ thì 403 CROSS_SITE_REQUEST ở mọi method và cả khi
đường dẫn viết bằng %61 hay %2F; same-origin, none và không có header (curl, eval,
Telegram) thì qua; trang, asset và đường dẫn chỉ bắt đầu giống /api không bị chặn, vì link từ
nơi khác vào app phải mở được; lý do này không bị ghi log như một tên host cần cho phép, còn tên
host lạ vẫn nhận lời khuyên MY_AGENT_ALLOWED_HOSTS dù request cũng là cross-site; ngoại lệ duy
nhất là đúng địa chỉ /api/artifacts/{id}/render, vì trang chạy của canvas có origin ẩn danh nên
tải lại nó là cross-site: /render/, /render kèm xuống dòng (%0A), /render/x,
a%2Fb/render, raw, versions, danh sách và canvas không đi theo ngoại lệ ấy, Origin hay host
lạ vẫn bị chặn, và địa chỉ đó chỉ nhận GET nên cho qua không đổi được gì
(tests/test_local_guard.py);pypdf và
pypdfium2 chưa được nạp — chúng chỉ nạp khi có PDF cần đọc;/api/stats không tính lại giữa hai lần ghi và mang sẵn cache
theo agent (trang Chi phí không tải lại 500 run kèm các bước của chúng chỉ để cộng hai con
số), tài sản có hash được nén gzip và cache vĩnh viễn còn luồng SSE không bị nén;web/e2e/legibility-smoke.spec.ts đo trong trình duyệt thật, trên style đã
tính, độ tương phản WCAG (≥4.5:1) của chữ trên nền tô và nền trũng ở cả sáng lẫn tối, kể cả
khi hover nút chính và nút “Xem đầy đủ” cỡ nhỏ trên nền vàng của thanh duyệt; yêu cầu đặt
trong một dòng của thẻ “Cần bạn” mang màu chữ thường chứ không màu vàng của thẻ; vòng focus
của checkbox, công tắc và ô tìm kiếm; bố cục của notice ở chat và ở màn quản lý; cỡ chữ ô soạn trí nhớ ≥16px để iPhone không phóng to trang; và khối tham số
đầy đủ của một tool call xuống dòng trong bề rộng 390px thay vì cuộn ngang. jsdom không
tính cascade, nên các thứ này chỉ đo được ở đây;git diff --exit-code, nên thay đổi web không bao giờ
được commit mà thiếu bundle của nó../scripts/gates.sh chạy mọi cổng CI theo thứ tự và dừng ở cổng đỏ đầu tiên; xem
code-standards.md. Danh sách cổng
là .github/workflows/ci.yml, trừ job desktop-helper: job ấy cần macOS và bộ công cụ Swift nên
gates.sh không chạy nó; ai sửa desktop-helper/ chạy scripts/desktop-helper-check.sh trên máy
Mac trước khi đẩy.
Dev server của Playwright nghe ở cổng 4173. Khi hai checkout cùng chạy e2e,
E2E_PORT=<cổng> npm run e2e dời nó sang cổng khác: trên cùng một cổng, lần chạy sau dùng lại
server của lần trước và lặng lẽ test mã nguồn của checkout kia. Vitest (qua TZ) và Playwright
(qua timezoneId) đều chạy ở múi giờ Asia/Ho_Chi_Minh, nên ranh giới ngày (“Hôm nay”,
“Hôm qua”, ghi chú hôm nay) rơi cùng một chỗ trên máy và trên CI, vốn chạy UTC; test về ngày cứ
viết theo giờ Việt Nam, không tự đặt múi giờ.
scripts/llm_bench.py đo các model ứng viên ngay trên vòng lặp agent, tách khỏi crew đang
chạy: mỗi model một home tạm dưới --out, một server riêng trên --port (mặc định 8797,
không bao giờ là cổng live), không có token Telegram, không có tuyến live. Từ môi trường của
người chạy, server chỉ nhận những biến một chương trình cần để chạy (PATH, HOME, LANG và
các LC_*, TERM, TMPDIR, USER, LOGNAME, SHELL, TZ) cùng OPENROUTER_API_KEY; hook
và lệnh server chạy cũng chỉ thấy chừng ấy. Hai bộ việc, chọn bằng --tasks short,
--tasks multi hoặc liệt kê id (mặc định chạy cả hai):
scripts/llm_bench_tasks.py, bộ ngắn: trả lời suông, ghi rồi đọc tệp, lệnh shell, giao
việc cho agent helper, tóm tắt tài liệu.scripts/llm_bench_tasks_multi.py, bộ chuỗi: pipeline (tính, ghi, đọc lại), revise
(đọc, tạo bản sửa, grep kiểm tra, trả lời từ nguồn), shell_chain (ba lệnh shell nối
nhau), delegate_write (helper làm nhiều bước rồi master đọc lại), delegate_twice (hai
lần giao việc nối tiếp), delegate_fanout (giao cho helper và auditor cùng lúc).Mỗi việc được chấm đúng/sai (việc giao việc còn phải để lại đủ số run con) và đo thời gian
tường, số lần gọi model, thời gian tới token đầu (first_token_ms của bước model), phần
prompt được cache và chi phí, đọc từ /api/activity/runs. Kết quả ghi ra results.json và
results.md sau mỗi model:
uv run python scripts/llm_bench.py --models deepseek/deepseek-v4-flash,qwen/qwen3.7-flash --out /tmp/llm-bench
uv run python scripts/llm_bench.py --models deepseek/deepseek-v4-flash --tasks multi --out /tmp/llm-bench
uv run python scripts/llm_bench.py --models deepseek/deepseek-v4-flash@deepinfra --out /tmp/llm-bench
Dạng model@provider ghim một provider OpenRouter (openrouter_providers, không dự phòng) trong
home tạm, để so cùng một model qua các bên khác nhau.
Một lượt hỏi người dùng (ask_user) tính là hỏng — bench không có ai trả lời; yêu cầu duyệt
tool thì bench tự duyệt và tính vào thời gian lượt. Cổng bận thì script từ chối chạy thay vì
giết chủ cổng.
Bench ở trên so các model với nhau; kiểm thử hành vi hỏi câu khác: agent của mình — đúng
persona, đúng bộ tool, đúng model mà crew thật đang dùng — có làm điều nó phải làm và tránh điều
nó không được làm không. Đó là chỗ duy nhất bắt được kiểu hỏng “sửa persona xong, agent quên một
luật cũ” hay “thêm tool xong, agent gọi nó bừa”; model giả không thấy được những thứ đó.
scripts/run_evals.py chơi các case viết tay trên một bản sao của home và chấm cái agent
cố làm: gọi tool nào, xin duyệt việc gì, nói gì. Nó không chấm một lệnh in ra gì, vì lệnh cần
duyệt bị từ chối theo mặc định.
Chạy tay, không nằm trong CI: tốn tiền thật, và một model không trả lời hai lần giống nhau. Chạy trước mỗi lần phát hành và sau mỗi lần đổi persona, prompt hay bộ tool của một agent:
export OPENROUTER_API_KEY=... # bí mật duy nhất server eval được nhận
uv run python scripts/run_evals.py --runs 3 --max-usd 0.5
uv run python scripts/run_evals.py --only <id>,<id> --runs 1
uv run python scripts/run_evals.py --dry-run
<home>/evals/*.yaml, cạnh các agent nó thử (--cases chỉ một tệp hay
thư mục khác, --home chọn home khác); scripts/eval_example_cases.yaml là mẫu có chú thích.
Một case có id, agent, messages (mỗi tin một lượt), approvals (deny mặc định, hoặc
approve), answers (trả lời lần lượt cho câu hỏi của agent) và expect: calls_tool,
not_calls_tool, asks_approval (mỗi mục là {name, args_regex?, agent?, turn?}; regex tìm
trong tham số viết thành JSON, lượt đếm từ 1 theo các tin case đã gửi, nên ghi chú server tự
viết vào vai người, như của loop guard, không mở lượt mới), max_calls, reply_contains,
reply_not_contains (không phân biệt hoa thường và dấu), delegates_to ({agent, outcome?};
outcome là một trong năm giá trị của dòng outcome= trong kết quả giao việc) và max_cost_usd.
Khoá lạ, regex hỏng hay id trùng bị từ chối trước khi tốn đồng nào. Việc một agent con làm
tính vào lượt của cha đã giao nó. Cuộc trò chuyện không giữ đủ các tin đã gửi thì lần chơi
hỏng, thay vì đếm lệch lượt.messages: đó là việc người làm trên panel giữa hai
lượt, đi qua đúng các route REST mà web dùng, với tư cách người. create_canvas: {title,
content, kind?} tạo canvas trong cuộc trò chuyện và mở nó (kind là markdown, mặc định, hay
code). edit_canvas: {old, new} thay chỗ duy nhất có old trong canvas đang mở rồi lưu trên
đúng bản vừa đọc, như editor. select_canvas: <đoạn> chọn chỗ duy nhất có đoạn ấy, đếm dòng
như panel. Chưa mở canvas nào thì bước làm trên canvas mới đổi gần nhất của cuộc trò chuyện. Từ
lúc có canvas mở, mọi tin gửi đi mang canvas đó như tin từ web; vùng chọn chỉ đi cùng tin ngay
sau, nên một bước chọn phải có tin theo sau. Một tin có : phải để trong ngoặc kép, không thì
YAML đọc nó thành một bước. Bước không làm được (cuộc trò chuyện chưa có canvas, old hay đoạn
chọn không có đúng một lần, server từ chối lần lưu hay trả lời ở dạng panel không đọc được)
làm lần chơi hỏng và không gửi tin sau; mất server thì dừng cả cuộc eval như một lượt.
edit_canvas có new trùng old bị từ chối, vì lần sửa ấy không đổi gì.eval_note.py). Agent chỉ biết một bước canvas qua ghi
chú canvas server lưu cùng tin kế của người. Nên tin chứa mỗi chữ case gửi phải có ghi chú
nêu mọi canvas người đã tạo hay lưu kể từ tin trước, ở đúng bản người để lại, theo một trong
các dạng ghi chú báo thay đổi (canvas chưa đọc, đoạn diff, đã lên bản, sửa nhiều chỗ), và
trích đoạn tin ấy mang theo đúng như ghi chú trích; dòng nêu canvas đang mở thì không tính.
Thiếu thì lần chơi hỏng, vì kỳ vọng của case sẽ chấm một agent chưa hề nghe về bước ấy. Case
lưu canvas về đúng nội dung agent đã nghe trước tin kế, hay đổi nhiều canvas tới mức ghi chú
phải dồn vào dòng đếm cuối, cũng hỏng ở đây: ghi chú không nêu riêng canvas ấy.canvas_count (số canvas), canvas_contains và canvas_not_contains (có trong một canvas nào
đó, hay không còn trong canvas nào; không phân biệt hoa thường và dấu) và canvas_not_in_chat:
true. Kỳ vọng cuối hỏng khi các tin agent viết trong cuộc trò chuyện gốc, ở mọi lượt, lặp lại
từ một nửa số cụm ba chữ liền nhau của một canvas (eval_paste.py). Canvas và từng tin được
đọc như một dải chữ, bỏ dấu đầu dòng, số thứ tự, chữ đậm, hoa thường và dấu, nên dán nguyên
văn, thành bảng, đổi dấu phân cách hay dồn về một dòng đều bị bắt. Không tính tiêu đề và đề
mục canvas mở đầu bằng, vì đó là cách chat gọi tên canvas; mọi cụm lặp lại nằm gọn trong một
dòng là trích dẫn (dòng vừa sửa chẳng hạn), không phải dán; không cụm nào nối hai tin. Dán mà
bỏ nhãn của mọi dòng ngắn (thứ trong tuần của một kế hoạch mỗi ngày một phòng) thì còn quá ít
cụm để bắt. Canvas là chỗ làm việc, chat là chỗ nói về nó.--runs lần (mặc định 3) và đạt khi ít nhất hai phần ba số lần đạt. Số tiền
là mức sổ cái của server tăng lên kể từ lúc bắt đầu, lời gọi bên cạnh lượt cũng tính; xoá một
cuộc trò chuyện thì sổ cái mất các lượt của nó, nên runner cộng giá các lượt ấy trước khi xoá.
Trước mỗi lần chơi mà đã hết --max-usd (mặc định 0.5) thì dừng. Có lời gọi provider không
báo giá thì tổng chỉ là cận dưới và báo cáo nói vậy.eval_reset.py). Các lần chơi dùng
chung một server, và không có bước này thì lần sau chấm cả những gì lần trước để lại: cuộc
trò chuyện mới mở đầu bằng tóm tắt của cuộc trước trong cùng kênh, conversation_search và
artifact_list thấy chat và canvas cũ, memory_save để lại ghi chú. Nên trước mỗi lần,
runner xoá mọi canvas rồi mọi cuộc trò chuyện, kiểm server không còn cái nào, rồi đặt lại các
tệp bộ nhớ (những gì crew biết về người, ghi chú và wiki của từng agent cùng mục lục) như lúc
eval bắt đầu, không bao giờ ngoài thư mục chạy và không theo symlink. Không đặt lại được thì
lần chơi không chạy và eval dừng như mất server. Bước này không đặt lại những gì một tool cần
duyệt đổi trong case approve, tệp agent con trả về workspace, lịch agent tạo hay đề xuất bộ
nhớ chờ người: case cần những thứ đó sạch thì đừng gây ra chúng.<out>/results/ (--out, mặc định $TMPDIR/my-agent-crew-evals, không
được nằm trong repo): results.md nêu kỳ vọng nào hỏng ở lần chơi nào, kèm lời đáp rút gọn;
results.json; server.log. Ghi sau mỗi lần chơi, nên cuộc chạy bị cắt vẫn để lại số của
những lần đã xong. transcripts/ giữ mỗi lần chơi một tệp JSON (<thứ tự case>-<id>-<lần>.json):
cuộc trò chuyện gốc, các cuộc con, các canvas và các lần xin duyệt, để xem lại agent đã nói và
làm gì sau khi bản sao đã xoá. Những tệp ấy có thể chứa mọi thứ các agent biết (bộ nhớ,
persona, dữ liệu workspace), nên tự xoá tay khi xong, như với --keep-home.--turn-timeout (300 giây, tính cả khi luồng SSE vẫn gửi keep-alive) hay một
lỗi HTTP dừng cả cuộc eval: nó không còn biết server có sống không. Ctrl-C và SIGTERM cũng
dừng server và xoá bản sao. Đừng đặt --turn-timeout lớn hơn thời gian delegate chờ một
agent con (hạn duyệt cộng 300 giây, mặc định 900): khi đó lượt cha có thể xong trong lúc con
còn chạy, và bước canvas hay tin kế tiếp đua với con. Case có giao việc hay lượt dài thì chạy
với --turn-timeout 900.--dry-run đổi model bằng fake:echo trên một home tổng hợp và bộ case mẫu, chỉ để kiểm
dây nối; chỉ một lần chơi không kết thúc được (lỗi, hết giờ, mất server) mới tính là hỏng,
vì model giả không thể thoả các kỳ vọng.Không có gì chạy trên crew thật. Home và các workspace mà agent dùng được chép vào <out>/run
(chỉ chủ sở hữu đọc được); một server khởi động trên bản sao ở --port (mặc định 8798, không
bao giờ là cổng live) với --no-schedule — không scheduler, không Telegram hay kênh nào — và
HOME trỏ vào bản sao. Cả hai biến mất khi xong; --keep-home giữ bản sao lại, và vì nó chứa
dữ liệu của các agent, tự xoá tay. Bản sao:
env, agent.sqlite3*, backups, logs, channels, run-server.zsh, evals,
spill, tệp offset, mọi .env*, thư mục git, .venv, node_modules, __pycache__, kit nằm
ngoài các thư mục crew đọc, symlink (không chép cũng không theo) và những gì giữ một phiên
đăng nhập: cookie, token, khoá riêng, hồ sơ trình duyệt. Báo cáo đếm những gì bị bỏ;schedules, telegram, memory_consolidate và shell_allow_patterns khỏi manifest
của từng agent (shell_allow_patterns cũng bị bỏ khỏi config.yaml), và viết lại mọi đường
dẫn cho nằm trong thư mục chạy. Đường dẫn ra ngoài home và các workspace bị từ chối trước khi
chép gì; một workspace là hay chứa thư mục home của bạn cũng vậy;env của home không bao giờ được đọc hay chép,
và từ môi trường của người chạy server chỉ nhận đúng danh sách biến của server bench. Token
bot, khoá của dịch vụ khác, socket của ssh-agent hay thiết lập MY_AGENT_* dành cho server
live, kể cả MY_AGENT_SHELL_ALLOW_PATTERNS, đều ở ngoài, nên hook và lệnh của bản sao cũng
không thấy.Mọi cuộc trò chuyện của eval không tự động (autonomous tắt) và bản sao không còn danh sách
cho phép lệnh shell, nên mọi shell_run đều hỏi trước. Danh sách cho phép mà còn sót thì một
lệnh khớp nó chạy không ai hỏi, kể cả lệnh nêu đường dẫn live, nên build_home từ chối một bản
sao còn danh sách ấy. Runner từ chối hoặc duyệt theo approvals của case; kể cả khi duyệt, một
lệnh nêu đường dẫn của home hay workspace live vẫn bị từ chối, vì lệnh ấy có thể đọc hay ghi cây
live. Câu hỏi của agent lấy câu trả lời kế tiếp trong answers; hết câu trả lời thì lần chơi
hỏng. Agent con xin duyệt trong cuộc trò chuyện của nó, luồng của cha không mang những yêu cầu
ấy, nên runner hỏi các cuộc con mỗi nửa giây và trả lời theo cùng chính sách; không có nó, lượt
của cha chờ mãi.
Rủi ro còn lại, đã thu hẹp chứ chưa xoá: persona của vài agent gõ lệnh kèm đường dẫn tuyệt
đối vào cây live. Mọi lệnh shell đều hỏi và runner không duyệt lệnh nêu đường dẫn ấy, nên một
lệnh như vậy không chạy; nhưng runner chỉ nhận ra đường dẫn viết nguyên văn, nên một lệnh dựng
đường dẫn bằng cách khác vẫn có thể lọt nếu case duyệt. Vì thế mọi case mặc định deny và chấm
cái agent thử làm; approve chỉ dành cho việc không đụng đường dẫn live. Các lớp chặn còn
lại: HOME của server trỏ vào bản sao, môi trường của server, và của mọi hook hay lệnh nó
chạy, chỉ có một danh sách nhỏ biến, bản sao không có .venv và không có symlink. Một chuỗi trong manifest hay config.yaml vẫn nêu
đường dẫn live được báo trong cảnh báo của lần chạy chứ không bị viết lại.
Model giả (fake:echo) cấp cho mỗi lệnh gọi một mã riêng, như provider thật vẫn làm: agent con
được tìm lại theo mã lệnh gọi của cha, nên hai cuộc trò chuyện giao việc ở cùng một chỗ mà cùng
mã thì cuộc sau nhận nhầm agent con của cuộc trước.
Mã nằm trong scripts/, tách theo việc để mỗi tệp ≤ 200 dòng như mã của gói (cùng một test
kiểm cả hai): run_evals.py (điểm vào), eval_cli.py (tuỳ chọn, chọn case, từ chối sớm),
eval_play.py (chơi một lần, chạy hết các case, bản ghi), eval_reset.py (đưa server và bộ
nhớ về như lúc đầu trước mỗi lần chơi), eval_home.py + eval_copy.py + eval_layout.py (bản sao), eval_cases.py +
eval_expect.py + eval_check.py (đọc và chấm case), eval_shape.py (dạng giá trị chung của
case, kỳ vọng và bước), eval_canvas.py (bước canvas), eval_note.py (bước canvas có tới
agent không), eval_paste.py (luật chép canvas vào chat), eval_observe.py (biến một cuộc trò chuyện thành thứ chấm được), eval_client.py (duyệt, câu
hỏi, sổ cái, canvas), eval_report.py (báo cáo). Server và client HTTP chia với bench: llm_bench_server.py, llm_bench_client.py.
MY_AGENT_ROUTES=fake:echo trên một MY_AGENT_HOME tạm, rồi qua UI hoặc curl:
chat → /tool workspace_list {"path":"."} → /tool workspace_write {...} → duyệt → tệp tồn tại
trong MY_AGENT_HOME/workspace. Với profile agent có lịch: POST /api/jobs/<agent>/<schedule>/run
phải tạo ra một run trên /api/activity/runs và một thẻ trong rail. Với ít nhất một agent
khác đã cài: POST /api/inbound {"text": "Nhờ kongming …"} (với fake:echo, viết thẳng
/tool delegate {"agent": "kongming", "task": "…"} vì provider giả không tự chọn tool) phải trả
lời bằng chính lời của agent con (lượt chỉ có một lần giao việc thì không kể lại) và để lại một
run con có source là delegate:<conversation id> trên /api/activity/runs. Đây là bước kiểm tra cần lặp lại trước khi gắn tag phát hành. Duyệt tool
qua API: id nằm trong sự kiện SSE approval_required của luồng tin nhắn, vì GET /api/approvals
chỉ liệt kê các yêu cầu đã được quyết.
CLAUDE.md đòi trang này nối mỗi tính năng với test giữ nó. Tệp vitest tính từ web/src/, spec
Playwright từ web/e2e/; test Python ghi theo node id của pytest, chạy thẳng được bằng
uv run pytest <node id>. Chữ trong ngoặc kép đứng sau một tệp là tiêu đề describe, it hay
test trong tệp đó, chép nguyên văn để grep tìm thấy. Bản đồ bắt đầu từ các tính năng ra sau
bản 0.8.0; phần có từ trước được kể theo tầng ở trên và vào đây khi được sửa tới. Thêm hay đổi
tên một test thì sửa dòng của nó trong cùng commit.
components/cap-editor.test.tsx (“raises the cap by a step above the current one”,
“takes a typed 0 as no cap at all”, “refuses a negative cap without asking the server”);
app-budget-notices.test.tsx (“the budget notices”,
“the keyboard after a raise from a budget notice”); app.test.tsx
(“raises a spent cap from the over-budget notice and lets the composer write again”,
“offers the cap on a budget halt and says when the server refused it”);
components/activity-chrome.test.tsx
“says a spent budget locks the thread over an earlier note, until a turn runs”chat-smoke.spec.ts
“a spent cap is raised from the budget pill’s card and the composer writes again”;
chat-state-phone.spec.ts “a spent budget’s notice keeps its raise on one line”state/conversation-money.test.ts (số đã tiêu là số khung chat vừa nghe khi dòng của
cuộc trò chuyện chưa được đọc lại, là số của dòng khi dòng được đọc sau đó, và là số của dòng
khi khung chat đang hiện không phải của cuộc trò chuyện này; chạm trần theo đúng luật của server
trên một con số ấy: trần lớn hơn không mà số đã tiêu đã tới; với dòng có trong danh sách thì
theo lời server, vốn được đọc lại khi một run xong; với cuộc trò chuyện ngoài danh sách thì tự
tính, vì không gì đọc lại dòng của nó; không có trần thì không bao giờ là quá trần; giữ lời
server cho cuộc trò chuyện ngoài danh sách được tải lên khi đã quá trần, và buông khi một trần
đã nâng được tải về); app-unlisted-spend.test.tsx (một con số ở cả hai nơi cho cuộc trò chuyện
ngoài danh sách khi lượt của nó xong, và cho cuộc trò chuyện trong danh sách trước khi dòng của
nó được đọc lại; lượt kết thúc bằng lỗi, bằng một câu hỏi người dùng, hay do người dùng dừng thì
số được đọc lại cho cả hai nơi; trần đã hết ở cuộc trò chuyện ngoài danh sách khoá ô soạn và mời
một lần nâng khi lượt dừng, nâng thì ô soạn mở lại);
hooks/use-external-run-refresh-own-run.test.ts (run do chính tab này đọc, ở cuộc trò chuyện
ngoài danh sách: kết thúc error hay awaiting_approval, vốn không nói gì về tiền, thì nợ một
lần tải; kết thúc done hay halted thì không, vì sự kiện cuối của lượt đã nói số tiền; run
còn chạy thì không; lần tải ấy chỉ một lần dù run đã xong được nhìn lại bao nhiêu lần; ở cuộc
trò chuyện trong danh sách thì không kết thúc nào nợ lần tải nào);
hooks/use-external-run-refresh.test.ts (các ca có từ trước nay nói rõ cuộc trò chuyện có trong
danh sách)lib/error-text.test.ts; hooks/use-thread.test.ts nhóm “a request of the thread that
fails” (“tells a conversation that cannot be loaded in our words”, “tells a decision that cannot
be sent in our words too”: mất kết nối ở hai chỗ ấy hiện câu của ta, không phải câu tiếng Anh
của trình duyệt); components/cap-editor.test.tsx
(“says in words why the server refused a cap, not in its validation dump”,
“keeps a refusal the server wrote as a sentence”,
“says the conversation is gone when the server no longer has it”,
“says the server could not be reached rather than the browser’s own words”)tests/test_server_api.py::test_raising_a_spent_cap_lifts_the_budget_block_and_a_negative_cap_is_refusedcomponents/attention-center.test.tsx
(“decides a waiting tool call where it is listed and lets the row go once the turn ends”,
“answers a waiting question with one of its choices and lets the row go”,
“counts down to the deadline, then disables the buttons, says it expired and reloads”,
“does not read the list again while an expired request cannot be read”,
“reads a request the sweep is late for less often as it waits, and never gives up”,
“says which ask pattern stopped a command”,
“keeps the %s of two requests of one second where it was listed while its turn resumes”, với
first và second — hàng vừa duyệt giữ đúng chỗ trong lúc lượt chạy tiếp, không tụt xuống dưới
một yêu cầu khác bắt đầu cùng giây, nên nút của yêu cầu kia không trượt vào dưới con trỏ);
components/expiry-countdown.test.tsx;
components/attention-seen.test.tsx
(“hides a failure once it is marked as read and keeps the others”,
“points to the requests waiting in another section instead of saying nothing waits”);
lib/seen-runs.test.ts; app-attention.test.tsx
“counts a waiting request in the title, lands on it and settles it in place”,
“lists a request settled elsewhere %s”,
“lists a request settled elsewhere as another run starts waiting, %s”,
“lists a request settled elsewhere whose run waits again on its next tool, %s” (lịch sử duyệt
đọc lại cả khi yêu cầu được quyết ở chỗ khác và chính run đó dừng lại ở tool kế tiếp — cùng
id, cùng trạng thái, chỉ số bước tăng; khoá là lib/run-progress.ts waitingKey, test ở
lib/run-progress.test.ts “waitingKey”);
components/conversation-activity.test.tsx
“asks for the approvals again when a decided turn waits on its next tool” (lịch sử duyệt
trong dải hoạt động của cuộc trò chuyện cũng vậy);
hooks/use-attention-badge.test.tsx
“prefixes the tab title with the requests waiting, not the failures”;
screens/manage-screen.test.tsx
“counts waiting requests on Duyệt and failures on Hoạt động, each named”;
app-activity.test.tsx
“counts a run waiting for approval as waiting, not as running, on the chat and the crew”;
components/components.test.tsx “ApprovalHistory” (trang lớn dần mỗi lần bấm Xem thêm)activity-smoke.spec.ts
“a waiting request is approved from Quản lý without opening the chat”;
legibility-smoke.spec.ts
“a request in an attention row reads in body text, not the card’s amber”tests/test_server_api.py::test_a_request_the_ask_list_stopped_still_says_why_when_read_backcomponents/agent-editor/schedules-section.test.tsx
(“saves a new row as exactly one timing and one action, with no kind”,
“holds the save and names the box while a timing cannot be read”,
“holds the save and names the box while two rows are given the same id”);
components/agent-editor/channel-section.test.tsx; hooks/agent-draft-checks.test.ts;
hooks/schedule-ids.test.ts; hooks/use-agent-draft.test.ts
(“never sends the derived kind, even on a row that still carries it”,
“reads the list again when the save’s answer lacks declared”,
“sends an emptied consolidation cron as null, which takes the key out of the file”). Fake server
của vitest và mock của Playwright cùng kiểm lịch theo luật của server, lấy từ
test/schedule-contract.tsschedule-editor-smoke.spec.ts; jobs-list-smoke.spec.ts (Sửa lịch mở trình sửa ở
đúng dòng của job ấy, còn job gom ghi nhớ, vốn không có dòng, thì ở tiêu đề phần lịch; chỗ dừng
nằm ngay dưới thanh của trình sửa chứ không thấp hơn, ở 390px và 1280px; thanh dính sát mép
trên); manage-smoke.spec.ts
“a schedule is added from the jobs list through the agent’s editor”tests/test_api_agents_edit.py::test_a_schedule_carrying_its_derived_kind_is_refused,
tests/test_api_agents_edit.py::test_a_timing_the_clock_cannot_read_is_refused_before_it_is_written,
tests/test_api_agents_edit.py::test_a_blank_row_that_lands_on_a_kept_rows_id_is_refused,
tests/test_api_agents_edit.py::test_rows_an_edit_leaves_alone_do_not_hold_the_save,
tests/test_api_agents_edit.py::test_a_schedule_cannot_take_the_consolidation_jobs_id,
tests/test_api_agents_edit.py::test_a_saved_schedule_comes_back_in_the_shape_it_can_be_resent_in,
tests/test_api_agents_edit.py::test_clearing_memory_consolidation_removes_the_keytests/test_profile_schedule_ids.py (bảy kiểu trùng, mỗi kiểu ở master và ở một
agent thường: viết hai lần, viết hai lần cách một dòng, mã theo chỗ job-N bên một dòng
ghi đúng mã ấy ở trước hay ở sau, một số và chữ của nó, số 0 là dòng không mã, tên của job
consolidate khi nó bật: boot dừng với câu nêu agent và mã; bảy tệp mỗi lịch một mã khởi
động như trước, cả khi hai mã chỉ khác chữ hoa hay một dòng lấy tên job consolidate đang
tắt; ba agent cùng một mã lịch đều có job của mình trên đồng hồ; tệp bị sửa tay thành
trùng mã lúc server đang chạy: lần lưu không đụng tới lịch và reload đều là 422 với đúng
câu của boot, tệp và agent đang chạy giữ nguyên; PATCH và POST đem tới hai lịch cùng mã là
422 với câu đó, không ghi gì). Kiểm chứng đột biến: tám sửa đổi (bỏ phép so khi đọc tệp,
không bao giờ báo trùng, không ghi lại mã đã gặp, bỏ qua job consolidate, so không phân
biệt hoa thường, bỏ mã khỏi câu, bỏ tên agent khỏi câu, bỏ qua dòng đầu) đều bị test bắt.reload nhận hết hoặc không nhận ai
tests/test_api_agents_install_refused.py (home có một agent.yaml hỏng cú
pháp, sai kiểu một khoá hay hai lịch trùng mã: cài là 422 nêu tệp, không tệp nào và
không thư mục nào đổi, kể cả workspace nêu trong lời gọi chưa được tạo, và sửa tệp xong
thì cài được; tám mã không phải mã agent, có và không force, là 422 và không ghi gì,
kể cả ra ngoài home; năm tên mẫu có đường dẫn là 404; mã của master và mã của agent do
kit đem tới là 409 dù có force; workspace là một tệp, hay nằm dưới một tệp, là 422
trước khi ghi, cả khi force lên agent đang chạy; workspace chưa có thì được tạo; mẫu
ghi xong mà không vào đội được thì thư mục vừa tạo bị gỡ, tệp bị ghi đè trở lại nội
dung cũ, home khởi động được như trước);
tests/test_agent_template_names.py (mọi mẫu đi kèm đều mang tên là một mã agent;
add_template từ chối mã không hợp lệ, mã của master và tên mẫu có đường dẫn mà không
ghi gì; landing liệt kê đúng mọi tệp một lần cài ghi ngoài các đồng đội nó kéo theo;
dòng lệnh agent add … --id ../x trả 1 và in lý do; ba thư mục chép tay, cái ở giữa có
workspace là một tệp: reload là 422 nêu đường dẫn, không ai vào đội, sửa xong thì cả
ba vào). Kiểm chứng đột biến: mười bảy sửa đổi (không trả lại gì, để lại thư mục vừa
tạo, để lại tệp đã ghi đè, để lại tệp mới, để lại thư mục agents vừa tạo, không đọc
home trước khi ghi, coi mã chạy không có tệp riêng là còn trống theo hai cách, không
thử tạo workspace trước, lỗi workspace không thành 422, lỗi thư mục lúc vào đội không
thành 422, không kiểm mã, không kiểm tên mẫu, không chặn mã của master, landing bỏ
sót kỹ năng dùng chung, dòng lệnh không in lý do, cho từng agent vào đội ngay khi dựng
xong) đều bị test bắt.agent.yaml hỏng cú pháp bị từ chối kèm tên tệp; front matter không đọc được chỉ làm mất
đúng tệp đó
tests/test_manifest_that_does_not_parse.py (tệp của master và tệp của một
agent: load_profiles ném ValueError mở đầu bằng câu của tệp hỏng, có đường dẫn tệp
và dòng, cột của trình đọc; reload là 422 nêu tệp, không ai vào đội, sửa xong thì
vào; lưu tuyến model bên cạnh tệp như thế là 409 nêu tệp, config.yaml và tuyến đang
chạy giữ nguyên); tests/test_front_matter_that_does_not_parse.py (tệp mẫu là một danh
sách bỏ ngỏ, thứ không cách đọc nào nhận; trình đọc front
matter ném ValueError; agent của kit, lệnh của kit, skill dạng tệp và skill dạng thư
mục: tệp hỏng bị bỏ ra, log nêu đường dẫn, tệp lành bên cạnh vẫn được nạp; front matter
của skill là một dòng chữ, một danh sách hay một số thì skill không có mô tả, mang tên
tệp; với mỗi loại trong ba loại, đội đang chạy reload được, cài mẫu được và khởi động
lại được; persona mở đầu bằng một khối không đọc được thì được đọc nguyên văn, persona có
front matter thì chỉ giữ phần thân). Kiểm chứng đột biến: mười sửa đổi (đọc tệp không
bắt lỗi, bỏ tên tệp khỏi câu, để lỗi của trình đọc front matter đi qua, không bỏ lệnh,
không ghi log lệnh, persona không được đọc nguyên văn, không bỏ skill, không ghi log
skill, hỏi khoá của một front matter không có khoá, không bỏ agent của kit) đều bị test
bắt.tests/test_front_matter_with_a_second_colon.py (description: Use when: the
tests fail đến nguyên văn ở trình đọc, ở agent của kit, lệnh của kit, skill dạng tệp và
skill dạng thư mục, và không dòng log nào nêu tệp; persona có khối như thế chỉ giữ phần
thân; giá trị kết thúc bằng dấu hai chấm, có tab sau dấu hai chấm, có chữ tiếng Việt và
emoji, có dấu nháy kép và dấu gạch chéo ngược, mở đầu bằng --, có ba dấu hai chấm đều
đến đúng từng ký tự; khoảng trắng cuối dòng không thuộc về chữ; tệp có dấu xuống dòng kiểu
Windows đọc ra cùng chữ ấy; dòng mang 200.000 dấu cách ở giữa được đọc trong thời gian tỷ
lệ với độ dài của nó, không phải bình phương; phần sau dấu # thuộc về
chữ; khối YAML đọc được giữ true, số, danh sách, giá trị trong nháy và bỏ ghi chú đúng
như YAML; dòng có dạng đó nằm trong một giá trị trong nháy trải nhiều dòng không bị đọc
lần hai khi YAML đọc được cả khối; bên cạnh một dòng được lấy làm chữ, các dòng khác giữ
đúng kiểu YAML cho, kể cả dòng có dấu hai chấm chỉ nằm trong ghi chú; khoá lồng, giá trị
hai dòng, mục danh sách, khoá trong nháy, khoá có dấu hai chấm, giá trị mở đầu bằng từng
ký hiệu của YAML và khối còn hỏng ở dòng khác vẫn là ValueError; lỗi nêu dòng còn
vướng chứ không nêu dòng đã đọc được; dòng có dạng đó nằm giữa một giá trị trong nháy,
trong {…} hay […] trải nhiều dòng làm cả khối bị từ chối, kể cả khi chữ của nó, hay cả
khoá lẫn chữ của nó, còn đứng ở một cặp khoá khác, khi khối là một danh sách, và khi dòng
ấy không khai cặp khoá nào; hai dòng cùng một khoá cũng vậy; khối như thế bị từ chối bằng
lỗi YAML nêu ở lần đọc đầu; dòng mang U+0085, U+2028, U+2029, dấu về đầu dòng ở giữa,
chuông, sang trang hay ESC thì để nguyên và khối bị từ chối; khoá mà YAML đọc thành số
hay true vẫn là một cặp khoá). Mười bảy sửa đổi nữa ở phần này (regex dừng trước khoảng
trắng cuối dòng, giữ khoảng trắng cuối dòng trong chữ, không cắt dấu về đầu dòng của tệp
Windows, bỏ phép kiểm ký tự, cho qua từng nhóm ký tự trong sáu nhóm, không hỏi dòng đã lấy
có phải một cặp khoá, cho hai dòng khai một cặp khoá, không đòi khối là các cặp khoá, hỏi
cặp khoá của một dòng không khai gì, tìm chữ ở bất kỳ giá trị nào, chỉ tìm khoá, từ chối
bằng một lỗi riêng) đều bị test bắt. Kiểm chứng đột biến trước đó: mười bảy sửa đổi (đọc lần
hai trước lần YAML, chỉ đọc lần hai, không đọc lần hai, ném lỗi của lần đọc đầu, bỏ kiểm
ký hiệu ở giá trị, bỏ kiểm ký hiệu ở khoá, tính cả dấu hai chấm trong ghi chú, bỏ ghi chú
khỏi chữ, viết chữ thành mã ASCII, bọc nháy mà không thoát ký tự, bọc nháy mọi giá trị,
coi - luôn là ký hiệu, không bao giờ coi - là ký hiệu, đọc cả khoá thụt đầu dòng,
không tính dấu hai chấm cuối dòng, skill chỉ đọc bằng YAML, kit chỉ đọc bằng YAML) đều
bị test bắt.tests/test_api_agents_edit.py
(“test_a_persona_file_outside_the_home_is_refused”,
“test_a_workspace_outside_the_home_is_refused”,
“test_a_skills_dir_outside_the_home_is_refused”: ba lời từ chối gốc);
tests/test_api_agents_edit_outside_paths.py (agent có tệp viết tay giữ workspace ngoài home:
sửa script_calls, max_steps hay description là 200, agent trong đội đổi theo, tệp chỉ thêm
đúng dòng ấy và workspace vẫn là cây chủ đã chọn; gửi lại đúng workspace ấy như tệp đang viết,
kể cả khi tệp viết vòng qua .., cùng một khoá khác là 200; đổi sang một cây khác ngoài home,
sang thư mục cha hay thư mục con là 422, tệp và agent đang chạy không đổi; đổi vào trong home
thì được và không đổi ngược ra được; thư mục skill ngoài home và tệp persona ngoài home mà tệp
đã ghi: khoá khác lưu được, gửi lại được, thêm cái thứ hai ngoài home là 422 và chữ của tệp thứ
hai không vào system prompt; loại này không bảo lãnh cho loại kia: workspace ngoài home không
thành thư mục skill hay tệp persona, thư mục skill không thành workspace, tệp persona không
thành thư mục skill hay workspace; workspace ngoài home mà người viết tay vào tệp sau lần khởi
động chưa phải là cái agent đang chạy với: lưu một khoá khác là 422 nêu đường dẫn ấy, agent đang
chạy giữ workspace cũ; tạo agent mới trỏ tới chỗ một agent khác đang làm việc ngoài home là 422;
tệp persona trong thư mục agent là một liên kết tới tệp ngoài home mà tệp không nêu tên nào:
khoá khác vẫn lưu được và chữ persona vẫn vào system prompt)tests/test_api_agents_edit_path_spelling.py (với cả ba loại workspace,
skills_dirs, persona_files: cách viết mới của đường dẫn agent đang chạy với, qua một liên
kết hay vào một thư mục rồi lùi ra bằng .., là 422, câu lỗi nêu chuỗi đã gửi và đường dẫn nên
ghi, tệp và agent đang chạy không đổi; chuỗi tệp viết tay đã ghi sẵn, gửi lại nguyên văn, là 200
và nằm lại trong tệp đúng như đã gõ; cách viết vòng của chủ không chặn lần lưu khoá khác, tệp
chỉ thêm đúng dòng ấy; đường dẫn thật là 200 dù tệp đang viết cách nào; sau một lần lưu, dời
liên kết hay đổi thư mục thành liên kết không đổi được chỗ tệp đã lưu trỏ tới, dù lần lưu trả
lời gì; cái tên lúc khởi động đọc khi tệp không nêu (workspace, skills, AGENTS.md), dù là
liên kết ra ngoài home, ghi vào tệp là 200; bỏ khoá workspace hay persona_files khi cái tên
đọc thay vào là một liên kết ra ngoài home là 422 nêu đường dẫn thật, và dời liên kết sau đó
không đổi chỗ agent trỏ tới; một cây vừa là workspace vừa là thư mục skill, tệp viết qua liên
kết cho loại này: gửi chuỗi ấy cho loại kia là 422, theo cả hai chiều; cách viết vòng mà vẫn nằm
trong home thì 200 như trước và nằm lại trong tệp như đã gõ). Kiểm chứng đột biến: năm sửa đổi ở
phép xét cách viết (không xét gì, không nhận chuỗi tệp đang ghi, xét cả đường dẫn trong home,
không nhận đường dẫn thật, để chuỗi của loại này bảo lãnh cho loại kia) đều bị test bắt.tests/test_api_agents_edit_path_spelling.py (với cả ba loại: một liên kết nằm trong
cây ngoài home mà dẫn vào home, gửi bằng đường dẫn của nó hay bằng cách lùi ra khỏi home với
.., là 422 nêu chuỗi đã gửi và đường dẫn thật trong home, tệp và agent đang chạy không đổi;
trỏ liên kết ấy đi chỗ khác sau lần lưu thì agent đang chạy và tệp đọc lại đều không theo tới
đó; chính đường dẫn thật là 200; liên kết ấy mà tệp viết tay đã ghi sẵn cho cùng loại, gửi
lại nguyên văn, là 200; ~/… đi qua một liên kết tới cây ngoài home là 422, và ~/… là
liên kết dẫn vào home cũng là 422; tên persona bắt đầu bằng ~ là thư mục cạnh tệp của
agent, không hỏi tới thư mục của người dùng; home đặt qua một liên kết: chuỗi tuyệt đối đi
qua liên kết ấy là 422 nêu đường dẫn thật, chuỗi tương đối và đường dẫn thật là 200);
tests/test_profile_wrong_shapes.py (tệp bị sửa tay dưới đội đang chạy: mỗi hồ sơ một giá
trị sai hình dạng ở một chỗ, 1365 tệp, mỗi tệp gặp một lần sửa bỏ khoá ấy và một lần sửa khoá
khác: 200 hoặc 422, không bao giờ 5xx, sau 422 tệp và đội đang chạy không đổi). Kiểm chứng
đột biến: mười sửa đổi (bỏ nửa “chuỗi nằm trong home”, bỏ nửa “chỗ dẫn tới nằm trong
home”, không đọc ~, đọc ~ cả ở tên persona, không gộp .., không nhận đường dẫn thật,
không nhận chuỗi tệp đang ghi, để giá trị không duyệt qua được ném lỗi, bỏ tên persona và bỏ
thư mục skills mà lúc khởi động đọc khi tệp không nêu) đều bị test bắt.tests/test_api_agents_save_matches_start.py (trên một home có kit của đội và kit
riêng của một agent, mỗi kit một lệnh, một thư mục kỹ năng và một hook, master không ghi
name, cả hai tệp viết tay max_steps: 010 và enabled: no: sau từng kiểu lưu (sửa master,
đặt tên rồi bỏ tên master, sửa agent, tạo agent, tạo rồi sửa, xoá, ghi persona, chép thư mục
vào rồi nạp lại, cài mẫu, lưu khoá, lưu rồi gỡ khoá, lưu tuyến model, và hai chuỗi trộn các
kiểu ấy) đọc home lại từ đầu bằng load_profiles rồi so với đội đang chạy theo mọi trường
lấy từ dataclasses.fields, riêng settings so từng trường; master mang tên tệp ghi, tệp
không ghi thì mang tên mặc định); tests/test_api_agents_edit_path_spelling.py (một từ mà
thư viện ghi tệp đọc khác lúc khởi động, workspace: no, skills_dirs: [no],
persona_files: [0o10], không chặn lần lưu về khoá khác dù cách đọc kia trỏ vào một liên
kết ra ngoài home). Kiểm chứng đột biến: năm sửa đổi (bỏ bước cộng kit ở đường lưu, đường
lưu đọc tệp không qua hàm chung nên master mang tên default, bỏ tên mặc định của master ở
hàm chung, để tên mặc định đè tên tệp ghi, xét cách viết đường dẫn theo cách đọc của thư
viện ghi tệp) đều bị test bắt.
Tệp đầu còn giữ lượt đang dừng chờ người: lượt chờ duyệt hay chờ trả lời, được tiếp tục sau
một lần lưu, hỏi model của tuyến vừa lưu và ghi vào workspace vừa lưu; lời gọi ghi được đồng
ý sau khi lần lưu lấy lại tool của nó thì không chạy. Kiểm chứng đột biến: một sửa đổi (hội
thoại giữ agent nó chạy lần đầu) làm cả ba test đỏ.tests/test_profile_wrong_shapes.py (đi hết mọi khoá của hồ sơ, mọi trường của một
dòng lịch thuộc cả ba loại và mọi trường của telegram, lấy từ chính bảng khoá của mã, nhân
với 21 hình dạng sai: số, công tắc, null, chuỗi, chuỗi rỗng, danh sách, mapping, danh
sách chứa số, null, mapping, danh sách hay chuỗi rỗng, phân số, số vô cùng và NaN (hai thứ
cuối gửi bằng chữ Infinity, NaN mà bộ đọc của route nhận); thêm bảy chuỗi lạ cho từng
khoá đường dẫn, viết trơ trọi và viết trong danh sách: ~ của người không có, ~ trơ trọi,
toàn dấu cách, có ký tự NUL, 5000 ký tự gồm nhiều thư mục, một tên 5000 ký tự và một tên 300
ký tự; 1365 hồ sơ ở bộ đọc, 2825 lời gọi ở PATCH
/api/agents/{id} và POST /api/agents, kể cả thân yêu cầu và agent_id sai hình dạng: mỗi
lời gọi là 200/201 hoặc 422, không bao giờ 5xx; sau 422 từng byte của mọi agent.yaml và đội
đang chạy không đổi; sau lần lưu được thì cả đội vẫn liệt kê được và mọi job giữ một việc là
chuỗi; các ca từng làm 500 nay bị từ chối bằng tên khoá, ở lần sửa, lần tạo và lần sửa
master; tệp viết tay mang hình dạng ấy thì POST /api/agents/reload trả 422; lần sửa đưa
vào một lịch mà prompt hay command không phải chuỗi là 422, còn dòng tệp đã giữ sẵn như
vậy không chặn lần lưu khoá khác; số vô cùng, dương hay âm, ở chỗ cần một số nguyên
(max_steps, tool_output_chars, chat_id của telegram) từng là 500, nay là 422 nêu tên
agent ở lần sửa, lần tạo, lần sửa master và ở reload, không tệp nào đổi). Kiểm chứng đột
biến: ba mươi ba sửa đổi ở phép xét danh sách, ở chỗ bộ đọc gọi nó, ở phép lần theo đường
dẫn, ở phép xét việc của một lịch và ở hai chỗ bắt lỗi của số nguyên vô cùng đều bị
test bắt.tests/test_profile_write_floats.py (mọi khoá của hồ sơ, của một dòng lịch, của
telegram và của một mục mcp_servers, lấy từ chính bảng khoá của mã, nhân với hai mươi
bảy số thực, trong đó có 0.00001, 1e-7, 1e16, 1.5e300, 0.1, 3.0, số âm của
chúng, hai đầu mút của số thực và sáu số mang đủ mọi chữ số: trình đọc lúc khởi động đọc
lại đúng số ấy, là số chứ không là chuỗi, 0.0 khác -0.0; đặt vào tệp người viết tay
thì các dòng khác và chú thích giữ nguyên; ghi lần hai y như lần một; .inf, -.inf,
.nan và số viết tay quá lớn như 1e400 được ghi như trước; mười bốn dạng người viết
tay, 1e-5, 1E-5, 1e16, .5, 5., 12e03, 100e+14, 1.5E+300, giữ nguyên từng ký
tự; số viết tay đủ chữ số ở dạng mà thư viện ghi lại thành số khác thì được ghi thành
chính số ấy; lúc khởi động dựng từ tệp đã ghi đúng thứ nó dựng từ giá trị đã lưu, hoặc từ
chối bằng cùng lời, kể cả với .inf và .nan; timeout của một máy chủ MCP được xét như
nhau trước và sau khi ghi. Qua PATCH /api/agents/{id}: cost_cap_usd cỡ nào cũng là 200
và là trần agent đang chạy; lần lưu khoá khác sau đó không bị chặn, dòng số không đổi; số
người viết tay không chặn lần lưu nào; với mọi khoá của agent, mỗi số hoặc được lưu hoặc
bị chính khoá ấy từ chối, không bao giờ bởi phép đọc lại, và sau mỗi lần đội đang chạy là
đội lần khởi động kế dựng; NaN cho cost_cap_usd là 422, không gì đổi. PUT
/api/connections/routes ghi lại config.yaml mà trần chi phí và timeout giữ nguyên
giá trị). Kiểm chứng đột biến: mười sửa đổi (trả lại cách ghi cũ, bỏ .0, thêm .0 cả
khi đã có dấu chấm, bỏ cách ghi của số một lần sửa đem tới, bỏ cách ghi của số tệp đã giữ,
bỏ phép so giá trị, bỏ phép so bố cục, so bố cục bằng độ dài, so bố cục không kể hoa
thường, lấy chữ Python in ra làm chữ thay thế) đều bị test bắt.tests/test_api_agents_declared.py::test_the_tools_an_agent_holds_are_not_what_its_file_lists,
tests/test_api_agents_declared.py::test_a_save_answers_with_the_tools_the_file_now_lists,
tests/test_api_agents_declared.py::test_a_patch_that_keeps_the_declared_tools_leaves_the_agent_open_handed
(cùng tệp giữ hai mặt còn lại của declared: delegates và schedules)components/agent-editor/tools-section.test.tsx
(“opens on every tool for an agent that lists none, whatever it holds”,
“opens on the list for an agent that writes one, with its names ticked and no other”,
“saves the list with one more tick, never the names the agent holds”,
“has nothing to save after a tick is taken back”); hooks/use-agent-draft.test.ts
(bản nháp lấy tools từ declared.tools); components/tools-matrix.test.tsx
“does not read the tools an agent holds as a list that leaves the others out”. Fake server
của vitest trả declared.tools theo patch (test/schedule-contract.ts), như server thậtmcp-servers.spec.ts
“the tools grid shows the script tool with an agent that reads on its own, with no server at all”
(ô tool agent thiếu mang chú giải chế độ, không mang chú giải “bị loại”; trình sửa của agent
không có danh sách mở ở “Dùng tất cả công cụ hợp lệ” với các ô chọn bị khoá)tools: chỉ gọi là “unknown” khi không tool nào mang tên ấy
tests/test_tool_list_warnings.py, mọi ca dựng cả đội qua build_runtime và đọc
log của lần khởi động, không gọi riêng allowed() (agent có giao việc khai delegate trong
danh sách: agent work, agent khai delegates, master — không dòng cảnh báo nào và vẫn giữ
tool; tên không tool nào mang vẫn ra đúng câu tools lists unknown tool …; image_read
trên máy không có tuyến đọc ảnh vẫn im; delegate ở agent không giao việc cho ai: một dòng
nói nó không giao việc cho ai, không có chữ “unknown”, agent không giữ tool; tool_script,
tool_search, mcp__…: một dòng nói danh sách không quyết tool ấy; tool_script đến với
agent có tool đọc dù danh sách có nêu tên hay không; workspace_edit, workspace_grep,
workspace_glob trên danh sách của assistant: một dòng nói cần mode: work, cùng danh sách
ấy ở agent work thì im và giữ tool; nối lại một agent vừa sửa không nhắc lại cảnh báo của
agent khác); tests/test_agent_profiles.py
(“test_a_tool_that_needs_a_key_is_not_reported_as_an_unknown_name”,
“test_an_allow_list_that_omits_delegate_keeps_a_work_agent_from_delegating”)tools: đem lại được cho từng agent (listable_tools)
tests/test_listable_tools.py, mọi ca dựng cả đội qua build_runtime và hỏi qua
HTTP như trình sửa (đội bốn agent: master, assistant, agent work, agent khai delegates;
đi qua mọi tên tool mà một agent của đội giữ, thêm delegate, tool_script,
tool_search, một mcp__x__y, một tên bịa, ba tool chỉ agent work có và image_read, rồi
dựng lại đội với danh sách nêu hết các tên ấy: tên được kể thì lần khởi động không cảnh báo,
tên không được kể thì có cảnh báo, chạy hai lần là máy có và không có tuyến đọc ảnh; ngoại
lệ duy nhất là image_read trên máy không có tuyến đọc ảnh, không được kể mà cũng không bị
cảnh báo; tên được kể là tên agent thật sự giữ khi danh sách nêu nó; tool_script,
tool_search, mcp__… không bao giờ được kể; danh sách đã xếp theo tên và không co lại
theo danh sách agent đang khai; GET /api/agents kể đúng như GET /api/agents/{id}; một
lần lưu đổi mode, rồi một lần đổi delegates, trả danh sách mới ngay trong câu trả lời và
ở lần đọc kế tiếp, không đụng tới agent khác, đổi lại thì danh sách về như cũ; tool được
trao sau chỉ được kể khi phép xét của cảnh báo nói danh sách được tôn trọng)listable_tools kể, giữ tên lạc cho tới khi người sửa bỏ, và
đặt tên được đánh dấu lại về đúng chỗ của nó
components/agent-editor/tool-allow-list.test.tsx (các dòng là những tên server kể cho
agent này, không phải những gì cả đội giữ; delegate còn dòng, với lời riêng, khi không agent
nào giữ nó nữa, để đánh dấu lại được; bật danh sách thì nó bắt đầu từ những gì agent giữ trong
số các tên ấy, theo thứ tự các dòng, có delegate ở agent giữ nó, và từ mọi dòng khi agent
không giữ dòng nào; sau một vòng qua “mọi công cụ” thì về đúng danh sách tệp viết; tên danh sách
có mà server không kể mở ra đã đánh dấu ở một dòng riêng sau các dòng kia, được nói là không đem
lại gì, và không có gì để lưu; đánh dấu dòng khác không làm nó rơi; bỏ chọn thì nó rời danh sách
và dòng đi theo; nó là tên cuối thì bàn phím sang “mọi công cụ”; bàn phím không ở dòng ấy thì
đứng yên; tệp viết nó hai lần thì có một dòng và cả hai cùng đi; không bỏ chọn được ở agent
không sửa được ở đây; đổi trường khác thì không gửi danh sách nào; delegate ở agent được
server kể là một dòng như mọi dòng; một tên như thế tự làm thành danh sách, và dòng đánh dấu sau
được ghi sau nó); components/agent-editor/tool-allow-list-order.test.tsx (bỏ chọn rồi đánh dấu
lại thì không còn gì để lưu: tên về đúng chỗ danh sách của tệp viết nó, cả khi mọi dòng đi rồi
về theo thứ tự khác, cả quanh một tên server không kể vốn giữ chỗ của nó, và tên tệp viết hai
lần về cả hai chỗ; tên danh sách của tệp chưa từng có được ghi sau các tên của tệp, vẫn sau
chúng khi một tên của tệp được đánh dấu lại muộn hơn, giữa các tên mới thì theo thứ tự các dòng
dù dòng nào được đánh dấu trước, và danh sách bật lên trong phiên này cũng ghi theo thứ tự các
dòng); components/agent-editor/tool-allow-list-unsaved.test.tsx (thay đổi chưa lưu ở chế độ
hay ở những agent được giao việc: dưới danh sách có dòng nhắc lưu trước, và các dòng vẫn theo
agent như đã lưu); components/agent-editor/editor-on-older-server.test.tsx (server không trả
khoá nào của bản này: trình sửa mở trọn vẹn, ở “mọi công cụ”, với hai thiết lập script như đội
đang chạy agent; bật danh sách thì bắt đầu từ những gì agent giữ trong số các dòng của bảng công
cụ, và chỉ danh sách ấy được lưu; dòng đánh dấu sau đó được ghi cùng; agent không giữ dòng nào
thì bắt đầu từ mọi dòng; agent có danh sách thì mở đúng danh sách ấy, và quay lại nó sau “mọi
công cụ” không để lại gì để lưu; bảng công cụ chưa đọc xong thì không có dòng nào và “chọn”
không bật danh sách nào); components/agent-editor/mcp-gone-server-focus.test.tsx (bỏ chọn một
máy chủ MCP mà tệp không còn khai: bàn phím sang dòng thế chỗ nó, sang dòng cuối khi nó đứng
cuối, sang máy chủ cuối mà tệp còn khai khi không còn dòng nào như thế, sang ô đang bật trong
hai ô chọn khi cả danh sách máy chủ đi theo; bàn phím không ở dòng ấy thì đứng yên, và ở ô của
một máy chủ tệp còn khai thì ở lại vì dòng không đi);
components/agent-editor/tools-section.test.tsx (các ca có từ trước nay dựng agent mang
listable_tools); test/schedule-contract.test.ts (nhóm
“the delegates and the names a list has a say over, as the double answers a save”: máy chủ giả
của test ghi delegates ở chỗ form đọc lại, khoá bị xoá là không ai; kể delegate, đúng chỗ
theo thứ tự tên, khi một lần lưu làm agent có giao việc, thôi kể khi lần lưu để agent không giao
cho ai, không bao giờ kể hai lần; giữ nó cho master và agent work dù họ nêu ai; lần lưu khoá
khác không đụng tới các tên)tool-allow-list.spec.ts (bốn luồng, mỗi luồng chạy trên màn rộng và trên điện
thoại 390px cảm ứng, nơi còn kiểm trang không tràn ngang: danh sách bật lên bắt đầu từ những gì
agent giữ và còn đó sau khi tải lại; danh sách của một tệp viết tay giữ mọi tên cho tới khi
người sửa bỏ chọn; master được mời delegate khi không agent nào giữ nó, và đánh dấu thì nó
được ghi; danh sách theo agent như đã lưu: lưu một agent được giao việc trước, rồi mới chọn được
delegate); older-server.spec.ts (mock trả agent như bản phát hành trước, thiếu cả
script_calls, script_llm và listable_tools: trình sửa của một agent mở được, danh sách
công cụ chọn và lưu được; màn Đội và bảng Công cụ mở được); mcp-servers.spec.ts (ca trao máy
chủ cho agent trong trình sửa nay còn kiểm bàn phím nằm ở dòng cuối còn lại sau khi dòng của máy
chủ bị bỏ đi theo dấu tích của nó)components/agent-editor/tool-allow-list.ts (một sửa đổi sống ở lần chạy đầu, danh sách khởi
đầu của agent mới mang tên không dòng nào hiện, và một phép kiểm được thêm để bắt nó); hai mươi
lăm ở components/agent-editor/tools-section.tsx, hai mươi bốn bị bắt và một tương đương (khoá
dòng của một tên lạc trong lúc “mọi công cụ” đang chọn: lúc ấy không có dòng nào như thế); mười
hai ở phần máy chủ giả của test trả lời một lần lưu (applyAgentPatch trong
test/schedule-contract.ts); tám ở chỗ đọc câu trả lời của server bản trước; bảy ở thứ tự của
tên được đánh dấu lại; tám ở chỗ bàn phím sau khi một máy chủ bị bỏ được bỏ chọn.app-connections-reload-crew.test.tsx (trình sửa mở sau một lần lưu ở Kết nối mời tool
mà khoá vừa lưu dựng nên, và danh sách của agent bật lên có nó; không còn dòng cho tool đi theo
một khoá vừa bị gỡ; tuyến vừa lưu cho mọi agent hiện ở agent không nêu tuyến riêng);
screens/manage-screen.test.tsx (nhóm “the keys, on the manage screen”: các khoá chỉ được đọc
một lần dù trang quanh màn hình vẽ lại bao nhiêu lần)components/tool-args-detail.test.tsx; components/components.test.tsx
“opens a tool row’s full arguments from under its summary”legibility-smoke.spec.ts
“a tool call’s full arguments wrap at phone width instead of widening the page”;
phone-smoke.spec.ts
“a request’s full arguments leave its buttons on screen in a short phone view”state/thread-reducer.test.ts nhóm “itemsFromMessages”
(“keeps a call that failed as failed when the thread is read back”: lời đáp mở bằng câu của
registry khi tool báo lỗi, tool sập, không có tool tên đó hay hook của kit chặn thì thẻ giữ
trạng thái failed, “reads a reply that only resembles a failure as a call that finished”,
“reads a reply that only quotes the refusal as a call that finished, read back or as it
arrives”: lời đáp chỉ trích lại câu từ chối ở giữa chữ thì thẻ là done, cả khi dựng lại từ tin
đã lưu lẫn khi lời đáp vừa tới,
“gives a reply the same status read back as it had when it arrived”: cùng một lời đáp cho
cùng một trạng thái ở luồng trực tiếp và khi dựng lại từ tin đã lưu);
state/thread-stored-status.test.tsx nhóm “a call that failed, read back from the stored
thread” (“stays failed when …”: mười hai kiểu server làm hỏng một lời gọi — tool báo lỗi,
không có tool tên đó, hook chặn, tham số không phải JSON hợp lệ, tham số bị cắt, lượt hết số
bước, lượt bị dừng vì lặp, các script của lượt đã dùng hết số lời gọi lồng, id đã được duyệt
cho lời gọi khác, hết thời gian chờ agent con, hội thoại rẽ nhánh trước khi lệnh chạy, lời
gọi bị ngắt giữa chừng — đều cho thẻ failed và dấu lỗi trên thẻ; “has the status it had as
it arrived when …”: mười kiểu có lời đáp tới trong luồng trực tiếp cho cùng một trạng thái ở
cả hai đường; “is not a reply that only holds the words further in, as when …” và “reads a
reply that only resembles one as a call that finished”: lời đáp chỉ chứa câu đó ở phía sau,
câu thiếu ký tự cuối, câu hết lời gọi lồng mà không nêu con số hay thiếu khoảng trắng trước
con số, hay kết quả hết thời gian chờ nằm dưới một tool không phải delegate thì thẻ là
done) và nhóm “a handed-off
task whose wait ran out, read back from the stored thread” (“is failed, and still names its
canvases, with the sentence …”: kết quả delegate hết thời gian chờ là failed mà vẫn hiện
đủ chip canvas, cả khi câu báo đứng sau dòng đếm canvas bị lược lẫn ở dạng đã lưu trước khi
kết quả có dòng canvas; “is not …”: agent con dừng dở, agent con tự viết lại đúng câu đó, kết
quả done hay blocked, câu đứng một mình không có kết quả phía trên, câu bị trích lại ở
giữa một đoạn, hay câu đứng sau một đoạn chỉ chứa (+ ở giữa chữ thì thẻ là done) và nhóm
“a script that did not run to its end, read back from the stored thread” (“is failed, as it
was when it arrived, when it was …”: kết quả tool_script mở bằng dòng “Script không chạy
xong.” là failed như lúc nó tới, dù script bị dừng ở một lời gọi không được phép, dừng sau
khi đã in, chạm giới hạn, gặp lỗi không bắt, lỗi dài nhiều dòng, không đọc được, bị từ chối
trước khi chạy, hết thời gian, hay không còn gì theo sau; “is not a script that …”: script
chạy xong mà in lại câu báo dừng, một số dòng kèm lý do, một lỗi nó đã bắt, hay in dòng mở
đầu ở dưới, ở cuối, có chữ nối tiếp trên cùng dòng, viết hoa hoặc sau một khoảng trắng thì
thẻ là done; “is not a reply of … that opens the same way”: cùng lời đáp dưới tool khác là
done)tests/test_tool_reply_openings.py (mỗi lời mở web dùng trong
web/src/lib/tool-reply.ts đúng là phần chữ cố định của chuỗi server tương ứng, tính tới giá
trị đầu tiên server điền vào, hoặc là cả câu khi câu không điền gì: DENIED_TOOL và
EXPIRED_TOOL, TOOL_FAILED, UNKNOWN_TOOL, TOOL_BLOCKED_BY_HOOK, TOOL_ARGS_INVALID,
TOOL_ARGS_CUT_OFF, STEPS_HALTED_TOOL, LOOP_HALTED_TOOL, NESTED_HALTED_TOOL,
APPROVAL_CALL_MISMATCH, FORK_CALL_NOT_RUN, INTERRUPTED_TOOL
(test_each_opening_the_web_reads_as_a_failure_is_how_the_server_words_one), và registry
chạy thật trả đúng các lời mở đó, nên hai bên không lệch nhau khi một bên đổi chữ);
tests/test_tool_reply_openings_turn.py (qua cả một lượt: tham số không đọc được hoặc bị
cắt, lượt hết số bước, lượt bị dừng vì lặp, id đã được duyệt cho lời gọi khác đều phát ra với
ok=False, mở đúng lời web chờ và được lưu nguyên văn);
tests/test_tool_reply_delegate_wait.py (bốn hằng web dùng để nhận ra delegate hết thời
gian chờ khớp tên tool, giá trị outcome và phần chữ cố định của DELEGATE_TIMEOUT,
DELEGATE_CANVAS_MORE; kết quả timed_out là ok=False với câu báo đứng đầu thân hoặc ngay
sau dòng đếm canvas bị lược; một lượt thật lưu đúng kết quả đó dưới tên tool; agent con dừng
dở thì ok=True và thân mở bằng DELEGATE_UNFINISHED, không phải câu web đọc);
tests/test_tool_reply_script_failed.py (hai hằng web dùng để nhận ra một script không chạy
xong khớp tên tool và SCRIPT_FAILED, câu đó không điền gì, nằm trên một dòng và không mở
giống lời mở nào khác; một lượt thật lưu kết quả của script bị dừng, gặp lỗi, không đọc được
hay bị từ chối dưới tên tool với đúng dòng đó ở đầu rồi mới tới phần còn lại; script chạy
xong thì không có dòng đó)state/thread-reducer.test.ts nhóm
“threadReducer on an event of a kind this build does not know” (server mới hơn bundle gửi một
loại sự kiện thêm về sau: reducer trả lại đúng state đã nhận, vẫn là object đó, ở thread đang
rảnh, đang nhận chữ, đang suy nghĩ và đang chờ duyệt, và sự kiện đã biết tới sau vẫn có tác
dụng); app-unknown-event.test.tsx (một lượt có sự kiện lạ nằm giữa hai mảnh chữ vẫn kết bằng
cả câu trả lời và không có thông báo lỗi nào; bong bóng đang viết có chữ ở cả hai phía của sự
kiện; dòng trạng thái vẫn nói agent đang suy nghĩ)hooks/use-thread.test.ts “Stop on a stream that resumed after a person answered”;
state/thread-reducer.test.ts
“stopping ends the turn at once, drops the spent decision and says it stopped”,
“keeps that note through the loads that follow it, until one brings a new request”,
“opening another conversation starts from nothing, not even that note”;
api/client.test.ts “forwards the abort signal on a decision and on an answer”;
app-thread-refresh.test.tsx; app-thread-refresh-races.test.tsx;
hooks/use-activity.test.ts; app-activity.test.tsx
“learns how a run ended while the stream was down once the stream reconnects”;
components/activity-chrome.test.tsx
“names the stream’s four states in one case, as they take turns in one slot”chat-smoke.spec.ts
(“Stop on an approved call cuts its stream and leaves nothing spinning”,
“a dropped live stream offers a thumb-sized retry that opens it again”);
chat-state-phone.spec.ts
“the retry takes no room of its own, so the composer stays put when the stream drops”components/copy-button.test.tsx
(“shows the text to copy by hand when the browser refuses the write”,
“has the hand-copy box take the focus in the commit that shows it”, “BubbleActions”);
components/markdown-body.test.tsx
(“keeps a fence with no language a block, not a run of inline code”,
“gives each fenced block a copy of its own that takes the code and nothing else”);
components/agent-editor/prompt-section.test.tsx
“hands the prompt over to copy by hand where the browser has no clipboard”;
components/conversation-options.test.tsx; lib/conversation-markdown.test.ts (khối code
hay HTML thô bị cắt giữa chừng được đóng lại để không nuốt các lượt sau);
components/message-thread.test.tsx (“what the person sent through Telegram”,
“the actions under a reply”)phone-smoke.spec.ts (“a copy the phone cannot make opens a box a thumb can close”,
“a code block’s copy button stays in its corner, clear of the code”,
“the export in a conversation’s options is big enough for a thumb”)tests/test_channels_telegram.py::test_the_web_finds_saved_file_lines_by_the_words_the_channel_writescomponents/conversation-list.test.tsx (“the sidebar’s day groups”, “the unread dot”);
lib/relative-time.test.ts
“puts 23:00 local in yesterday although it shares the UTC date with now”;
hooks/use-draft.test.ts; components/composer-draft.test.tsx;
hooks/use-conversations.test.ts “takes its unsent draft with it, and only its own”. Trình
duyệt nhớ được hay từ chối lưu trữ đều dựng bằng test/memory-storage.tsphone-smoke.spec.ts
“a half-written message waits in its conversation across drawer switches”updated_at):
tests/test_store.py::test_background_bookkeeping_can_leave_a_conversation_dated_where_it_was,
tests/test_memory_session_summary.py::test_a_recap_leaves_the_conversation_dated_by_its_last_message,
tests/test_memory_conversation_title.py::test_the_models_name_arrives_without_making_the_conversation_look_new/ trong ô soạn
components/slash-popover.test.tsx; lib/slash-filter.test.ts;
app-slash-commands.test.tsxkeyboard-smoke.spec.ts
(“’/’ lists the agent’s commands and Enter puts the chosen one in the box”,
“a click into the thread puts the command list away, and the box brings it back”);
phone-smoke.spec.ts “the ‘/’ button offers the agent’s commands to a thumb, and the list fits”components/recent-runs-log.test.tsx; components/activity-filters.test.tsx;
hooks/use-run-history.test.ts; components/conversation-activity.test.tsx
“fetches its history on mount and merges it with live runs, each run once” và nhóm
“the focus of whoever pressed the retry” (bấm Thử lại xong, focus quay về nút Thử lại, sang
run đầu tiên vừa tải về, sang nút mở dải trên điện thoại hay sang dòng báo chưa chạy lần nào —
trên điện thoại dải giữ lại dòng đó chứ không ẩn đi mang theo focus; không giành lại focus người dùng đã đặt chỗ khác, không kéo sang cuộc trò chuyện vừa mở — hai test “không đổi” này cho câu trả lời về trong act, nên effect có thể dời focus đã chạy xong trước khi kiểm, chứ không kiểm lúc effect còn chưa tới);
state/activity-reducer.test.ts “runGroups” (run con nằm dưới đúng lượt đã giao nó, kể cả
khi hai lượt chạm cùng một giây hay run con bắt đầu ở giây cuối của lượt)lib/run-order.test.ts nhóm “newestFirst” (kể cả “gives the opposite answer
for a pair compared the other way round”: mỗi quy tắc đúng cả khi so theo chiều ngược lại) và
nhóm “withHeld” (hàng đang giữ quay về chỗ cũ); state/activity-reducer.test.ts
“keeps runs of the same second in the list’s order, behind one it hears start” và trong
“runGroups” “keeps runs that share their start and their end in the order given, a lone child
included”; hooks/use-run-history.test.ts nhóm “mergeRuns” (run luồng trực tiếp đã biết giữ
thứ tự trang nghe thấy, không theo thứ tự kho lưu lần cuối; run chỉ kho có xếp sau);
components/conversation-activity.test.tsx
“names the same run on its line and its first card when two ran in the same second” (dòng
“Lượt chạy gần nhất” và thẻ đầu tiên nói cùng một run, trước và sau khi lịch sử về) và
“keeps the order the page heard runs of the same second in once the history lands”, với run
còn chạy và run đã kết thúc (thẻ và thanh trạng thái của dải không đổi sang run khác khi lịch
sử về theo thứ tự kho lưu)activity-smoke.spec.ts
“the activity log narrows to one agent on the server, then reaches further back”;
phone-smoke.spec.ts
(“the activity log’s chips fold on a phone, then wrap and stay big enough to tap”,
“a chat whose run history cannot be read says so, with a retry big enough to tap”)tests/test_activity.py::test_one_agents_runs_are_not_crowded_out_by_a_busier_agenttests/test_side_calls.py (ghi mục đích và agent, lần gọi rơi tuyến không ghi,
lần gọi bỏ dở sau chunk đầu hay hết giờ ghi giá không rõ, sổ hỏng không làm mất câu trả
lời, mục đích lạ bị từ chối, tổng theo ngày/model/mục đích cộng cả hai bảng, một tên model
sau hai provider vẫn là hai dòng, tool trả phí cộng giá vào run và step);
tests/test_side_calls_wiring.py (ảnh, PDF scan, tóm tắt output, tiêu đề, tóm tắt phiên,
cô đọng, wiki đều ghi đúng mục đích và đúng cuộc trò chuyện; bảo trì không thuộc cuộc
nào và chỉ đếm một lần);
tests/test_script_model_calls.py (lần một script tự hỏi model được ghi dưới mục đích
script, kèm cuộc trò chuyện của lượt, hoặc không kèm cuộc nào khi là script của một
lịch; lần hỏi bị cắt khi model đang trả lời được ghi là một lời gọi không rõ giá);
tests/test_tools_registry.py::test_a_summary_without_a_price_is_charged_as_one_of_unknown_cost;
tests/test_memory_session_summary.py::test_a_recap_that_came_back_blank_is_still_charged;
tests/test_server_agents_activity_jobs.py::test_stats_carry_the_ledger_with_tokens_and_what_each_call_was_forcomponents/stats-panel.test.tsx “splits the whole ledger by what each call was
for, named in words”, “leaves the purpose card out while the ledger is empty or the server
has none”; components/activity-cards.test.tsx “prices a tool that asked a model itself,
and says nothing on one that did not”phone-smoke.spec.ts
“the costs page fits, cache columns and today’s tiles included” (bảng theo mục đích vừa
màn hình điện thoại)tests/test_loop_guard.py (lần thứ ba liên tiếp thì nhắc, lần thứ sáu thì dừng; đổi
tham số, hai lệnh xen kẽ, ghi chú tiến độ, câu hỏi hay câu trả lời xen giữa đều đếm lại từ
đầu; ghi chú đi kèm lệnh lặp không che được lần lặp; lệnh song song đổi thứ tự vẫn là một
lệnh; lời nhắc nằm sau kết quả lần thứ ba, chỉ nêu tên công cụ, không chép tham số hay kết
quả; lệnh thứ sáu không chạy mà nhận kết quả từ chối nên mọi lệnh đều có kết quả; lượt đổi
hướng sau lời nhắc chạy tiếp; “chạy test, sửa, chạy lại” không bị nhắc; lý do dừng đọc bằng
lời); tests/test_channels_telegram.py::test_deliver_reports_a_run_that_stopped_without_a_reply,
tests/test_channels_telegram.py::test_a_reply_from_a_run_that_stopped_early_carries_a_notice
(Telegram nêu lý do dừng bằng lời, không bằng mã)lib/run-summary.test.ts
“reads the halt reasons and the interrupted marker as sentences” (bốn lý do dừng: budget,
max_steps, loop, nested_calls); app.test.tsx
“says in words why a turn halted on %s” (với max_steps và loop)tests/test_agent_loop.py (“test_max_steps_halts_a_tool_loop”,
“test_a_turn_that_answers_on_its_last_allowed_call_is_done”,
“test_a_turn_out_of_steps_closes_the_calls_it_never_ran”: lệnh mà lần gọi cuối xin không
chạy mà nhận kết quả nói lượt đã hết bước, và tin kế tiếp không đóng lại chúng thành lệnh
bị ngắt)components/run-timeline.test.tsx; lib/format-usage.test.ts;
components/stats-panel.test.tsx; state/activity-reducer.test.ts
“keeps the prompt and cached tokens of a finished call, and that it thought first”phone-smoke.spec.ts
“the costs page fits, cache columns and today’s tiles included”tests/test_server_agents_activity_jobs.py::test_stats_add_up_each_agents_cache_over_the_same_runs_as_its_spend,
tests/test_server_agents_activity_jobs.py::test_stats_count_apart_the_calls_that_gave_a_prompt_but_no_cache_figure,
tests/test_server_agents_activity_jobs.py::test_stats_say_when_the_calls_gave_tokens_but_no_cache_figurecomponents/memory-panel.test.tsx “MemoryPanel proposal review”;
components/compile-proposal-view.test.tsx; lib/line-diff.test.tsmemory-phone-smoke.spec.ts
“the history toggle and a compiled page’s disclosure are full touch targets that still look openable”tests/test_memory_proposals_apply.py::test_a_late_approval_of_a_rejected_proposal_writes_nothing,
tests/test_memory_proposals_apply.py::test_a_decision_arriving_during_an_approval_waits_for_it_and_is_refused,
tests/test_memory_proposals_apply.py::test_approving_an_append_twice_writes_the_line_once,
tests/test_server_memory_api.py::test_approving_from_a_stale_tab_after_a_reject_is_a_conflict_that_writes_nothing,
tests/test_server_memory_api.py::test_approving_for_an_agent_that_left_the_crew_is_not_a_conflict,
tests/test_memory_wiki_compile.py::test_an_autonomous_agent_leaves_a_decision_made_first_alone
(agent tự chủ cũng ghi theo cùng luật một-quyết-định-một-lúc, nên đề xuất bị từ chối ngay khi
vừa hiện thì không trang nào được ghi), tests/test_memory_jobs_decide_off_loop.py (việc dọn
bộ nhớ và dựng wiki chờ một quyết định đang ghi ở web mà không chặn event loop)components/memory-panel.test.tsx
(“shows why the model changed or dropped each line, before the person decides”,
“shows nothing extra for a proposal with no reasons to give”,
“drops whichever bullet marker the model used in front of a reason”,
“shows a superseded rewrite in the history, with no undo since nothing of it was applied”,
“flags a fact nobody has confirmed in over ninety days”, “does not flag a fact confirmed recently”)tests/test_memory_hygiene.py (các hàm thuần của fact_dates.py: danh sách xem lại gồm
dòng thiếu ngày và dòng cũ, một ngày không có thật tính như thiếu ngày, mỗi nhóm tối đa 40 dòng
và đếm phần còn lại; lý do được tách trước khi cắt bớt, dấu tách viết thường không dấu hay ở
dạng NFD vẫn nhận; dòng bị bỏ hay bị viết lại khác với chỉ đổi ngày hay chỉ thêm dòng; ngày
bịa ra không khớp nguồn nào), test_consolidate_stores_reasons_and_the_prompt_carries_the_review_list_and_today
(prompt mang danh sách xem lại và đúng ngày hôm nay của lần cô đọng),
test_an_autonomous_agent_with_a_dropped_line_stays_pending_and_leaves_the_file
(một agent autonomous làm rơi một dòng vẫn dừng ở đề xuất chờ duyệt, tệp giữ nguyên),
test_an_autonomous_agent_with_only_an_addition_still_auto_applies,
test_an_autonomous_agent_with_only_a_date_change_still_auto_applies
(hai trường hợp vẫn tự áp dụng vì không dòng nào mất và không ngày nào bịa ra),
test_a_rewrite_with_an_invented_date_stays_pending_and_names_the_date,
test_a_dropped_line_and_an_invented_date_are_both_named (tóm tắt run nêu đủ cả hai lý do bị
giữ), test_a_date_equal_to_the_consolidation_today_is_not_invented;
tests/test_memory_proposal_stale.py (supersede cần ít nhất một bộ lọc và chỉ đổi đề xuất
đang chờ của đúng agent; duyệt một bản viết lại khi MEMORY.md đã đổi là lỗi stale, qua HTTP
là 409 và tệp giữ nguyên; duyệt một đề xuất đã bị thay cũng là 409; lần cô đọng thứ hai thay
chỗ bản viết lại cũ còn chờ; hoàn tác ghi thẳng, không bị cổng chặn; fact cũ hơn 90 ngày được
báo stale trong GET /api/memory/user);
tests/test_memory_fact_review.py (chỉ master mới xem lại fact người dùng, và agent khác
không gọi model lần hai; kể cả khi autonomous vẫn chỉ tạo đề xuất chờ duyệt; mỗi lần xem lại
tính một side call phí consolidate; fact cũ đứng trước, cũ nhất trước, fact không đọc được
ngày tính là cũ nhất, phần còn lại mới nhất trước; chỉ 60 fact được đưa vào và tên nằm ngoài
phần cắt bị bỏ qua; mỗi dòng fact mang mô tả và thân đã gộp dòng, cắt ở 400 ký tự; mảng JSON
đầu tiên được đọc ra dù nằm trong đoạn văn hay khối code; lý do cắt ở 300 ký tự; tên lạ, bản
cập nhật rỗng hay không đổi gì bị bỏ qua; quá 10 mục thì cắt; json hỏng không tạo đề xuất
nào; tóm tắt run đếm số đề xuất fact, nêu câu trả lời hỏng, và nói rõ khi bước xem lại lỗi mà
bản viết lại vẫn còn; bước xem lại đọc cùng khối ghi chú đã cắt theo ngân sách và cùng ngày
hôm nay với bước viết lại; đề xuất giống hệt không bị nhân đôi),
tests/test_memory_fact_review.py::test_a_different_body_supersedes_the_old_pending_hygiene_proposal
(một đề xuất vệ sinh fact mới thay chỗ đề xuất cũ cùng loại còn đang chờ),
tests/test_memory_fact_review.py::test_a_forget_supersedes_a_pending_update_of_the_same_fact
(một đề xuất quên thay chỗ đề xuất cập nhật cùng fact, còn đề xuất tạo từ cuộc trò chuyện giữ
nguyên)components/markdown-body.test.tsx “an image in a reply” (kể cả
“asks again when the image it showed gives way to one at another address”: trang vẽ lại với
ảnh ở địa chỉ khác tại cùng chỗ, dù cùng site, thì nút hỏi quay lại và ảnh mới không tải;
“asks again when the address only adds to the one agreed to, or is that one cut short”: địa
chỉ đang viết dở tới từng mảnh, đồng ý với một mảnh không phải đồng ý với địa chỉ dài hơn hay
ngắn hơn nó, và ảnh được hỏi lần hai cũng nhận focus của nút như ảnh đầu;
“shows an image again unasked when the page comes back to the address agreed to, and takes no
focus for it”: quay về đúng địa chỉ đã đồng ý thì ảnh hiện lại mà không hỏi, vì người dùng đã
đồng ý với chính địa chỉ đó, và focus ở yên chỗ cũ;
nhóm “when the image the keyboard is on gives way to its button”: ảnh đang giữ focus mà đổi địa
chỉ thì nút hỏi lại nhận focus thay vì để focus rơi về trang (“puts the keyboard on the button,
not on the page”), focus người dùng đã chuyển đi chỗ khác thì ở yên (“leaves the keyboard where
the person has since put it”), và ảnh hiện lại không hỏi sau đó không kéo focus về nút khi nó
đổi địa chỉ lần nữa (“takes the keyboard for the image it replaced only, not for one shown
unasked later”);
“never holds the app’s own image, and showing one agrees to nothing from outside”;
“asks about each image of a page on its own”: hai ảnh trong một trang được hỏi riêng, một ảnh
đổi địa chỉ thì chỉ ảnh đó hỏi lại)components/wiki-read-view.test.tsx; lib/wiki-links.test.ts;
components/wiki-section.test.tsx (“WikiSection read mode”, “WikiSection compile tracking”);
components/memory-panel.test.tsx
“follows the consolidation it started, then re-reads the memory and the proposals”manage-smoke.spec.ts
(“a wiki page reads as prose, its link leads to the next page, and one tap marks it fine”,
“a compile that ends while the stream is down lets go of its chip on the reconnect”);
memory-phone-smoke.spec.ts
(“a page followed from the bottom of a long one opens at its title, with focus there”,
“an open question holding a long link wraps on a phone instead of running off the screen”)components/jobs-panel.test.tsx (“a job read at a glance”,
“where the jobs list sends a person to change a schedule”); lib/cron-text.test.ts;
screens/manage-screen.test.tsx
“counts the jobs whose last run failed beside the jobs entry, and nothing otherwise”;
hooks/use-route.test.ts “reads the job a page was opened from out of the query”manage-smoke.spec.ts
“a job reads its schedule in words, shows its last run, and a failure is counted on the nav”;
jobs-list-smoke.spec.ts (“a job off in its profile dims its text but not its buttons”,
“the back link of a job’s editor or run returns to that job’s row”)screens/manage-screen.test.tsx (“brings the current section’s entry into view”,
“brings the current section’s entry back into view when %s change” — mỗi số đếm một ca: run
đang chạy, run lỗi, yêu cầu chờ duyệt, đề xuất bộ nhớ, lịch lỗi;
“leaves the row where the person scrolled it when a count changes”)manage-row-phone.spec.ts
(“a count arriving leaves the row where the person scrolled it”,
“the current pill stays in view as the counts before it widen the row”)hooks/use-version-check.test.ts (kể cả “makes no look of its own on a reconnect when a look since the drop reached the server”, “looks at most every half minute on a stream that keeps dropping, the last look made late”); components/update-bar.test.tsx; app.test.tsx
(“offers a reload once the server runs a newer build than the page”,
“asks the server again when settings open, and names both builds there”)version-refresh.spec.tslib/run-summary.test.ts; components/run-chip.test.tsx;
components/activity-cards.test.tsx “reads a halt or interruption code as the run card does”tests/test_activity_restart.py;
tests/test_memory_conversation_title.py::test_a_conversation_deleted_or_renamed_while_naming_waited_costs_nothing;
tests/test_tools_delegate.py::test_a_child_deleted_while_the_parent_waits_is_reported_in_wordsapp-manage-connection.test.tsx
(“says the pages stopped following the server when the stream drops, and a retry opens a fresh one”,
“says nothing while the stream first connects”,
“says the device is offline instead of offering a retry that cannot work”,
“reads the crew, the schedules and the totals again when the stream comes back, not when it first opens”)touch-targets-phone.spec.ts — quét mọi nút, link, ô nhập, select, summary, công tắc và
tab trên từng trang (chat, mười trang Quản lý) và trong drawer cuộc trò chuyện ở 390×844 cảm ứng;
checkbox đo theo label bọc nó, link nằm trong câu được miễn như WCAG (“every control on %s is big
enough for a finger”, “every control in the phone’s conversation drawer is big enough for a
finger”); phép quét nằm ở small-targets.ts, quét được riêng một vùng, và canvas.spec.ts dùng nó
cho dock canvas, canvas-note.spec.ts cho chip ghi chú dưới tin (cả ở 1000 px cảm ứng, nơi chỉ
touch-targets.css giữ nút chép ghi chú ở 40px)MEDIA: trần vẫn đưa lời khuyên tới người dùng
tests/test_delegate_attachments.py::test_an_answer_written_beside_a_tool_call_is_not_lost_to_a_bare_chart_line;
tests/test_delegate_attachments.py::test_only_a_chart_only_ending_reaches_back_for_the_words
(lời nháp trước một câu trả lời thật vẫn bị bỏ, dòng đính kèm không gửi hai lần);
tests/test_delegate_attachments.py::test_the_words_are_looked_for_only_since_the_task_was_giventests/test_resumed_child_depth.py (cuộc con chạy lại không truyền độ sâu vẫn ở độ
sâu 1, cuộc người mở giữ độ sâu 0, con lấy lại tool delegate qua deps_for_conversation vẫn
nhận lỗi quá sâu và không tạo cuộc cháu nào)tests/test_eval_cases.py (đọc case từ tệp hay thư mục; khoá lạ, thiếu id hay
agent, tin nhắn không phải chuỗi, regex hỏng, id trùng bị từ chối trước khi tốn tiền; từng
loại kỳ vọng: gọi và không gọi tool theo tên, tham số, agent và lượt, xin duyệt, số lần gọi tối đa,
câu đáp chứa và không chứa, giao việc cho agent nào và việc đi tới outcome nào (không nêu
outcome thì outcome nào cũng được, outcome lạ bị từ chối), giá tối đa; “hai phần ba số lần
chơi đạt thì case đạt”; bước canvas đọc đúng chỗ giữa các tin, bước lạ, tin có : không
ngoặc, bước thiếu hay thừa khoá, kind lạ, đoạn chọn rỗng, edit_canvas có new trùng
old, bước chọn không có tin sau và case chỉ có bước bị từ chối, kỳ vọng canvas sai dạng
cũng vậy);
tests/test_eval_canvas.py (tạo canvas trong cuộc trò chuyện và mở nó, sửa lưu trên đúng bản
vừa đọc, chưa mở thì lấy canvas mới đổi gần nhất, không có canvas hay old không có đúng một
lần thì hỏng mà không lưu, server từ chối hay trả lời ở dạng không đọc được (thiếu id hay
bản, không phải JSON) thì hỏng mà eval đi tiếp, mất server thì ném lỗi như
một lượt, vùng chọn mang đúng dòng và chỉ đi với tin kế, mở canvas khác thì bỏ vùng chọn; chấm
canvas: chứa, không chứa, số canvas, dán ở lượt nào hay chia ra hai tin cũng hỏng và nêu tên
canvas bị dán, trích một dòng hay nêu dòng ngắn thì không tính, mỗi canvas chấm riêng; mỗi tin
ghi lại phần ghi chú nợ agent: canvas người tạo hay lưu kể từ tin trước, ở bản server trả về
khi lưu, và đoạn tin mang theo; tin trước khi có canvas không nợ gì, hai canvas mới đều nợ);
tests/test_eval_note.py (ghi chú nêu canvas ở bản người để lại, dạng nào cũng được: canvas
chưa đọc, đoạn diff, đã lên bản, sửa nhiều chỗ; không có ghi chú, ghi chú rỗng hay chỉ nêu
canvas đang mở thì hỏng; bản cũ hơn, canvas có id dài hơn cùng đầu, bản có cùng chữ số đầu,
dòng vùng chọn và dòng trích không tính, kể cả dòng trích trông như một dòng báo thay đổi;
mỗi canvas phải được nêu riêng; đoạn mang theo phải được trích như ghi chú trích, kể cả ngắt
dòng dán từ trình soạn thảo, đoạn quá dài chỉ cần phần ghi chú trích; ghi chú đọc từ tin chứa
chữ case gửi chứ không từ tin loop guard; tin cuộc trò chuyện không giữ để observe báo);
tests/test_eval_paste.py (tỉ lệ cụm ba chữ chat lặp lại: canvas dòng ngắn nói lại nguyên văn,
thành bảng hay dồn một dòng không dấu đều là dán hết; nêu tiêu đề, đề mục mở đầu kể cả sau
dòng trống, dòng chính là tiêu đề dù markup nào, trích dòng vừa sửa dù nó chiếm gần hết
canvas, dòng canvas lặp hai lần vẫn là một dòng, trích hai dòng vừa đổi của canvas dài hơn,
nêu các đề mục hay việc vừa thêm thì không phải dán; nói lại cả danh sách, dàn ý đổi cách
đánh số, dòng dài bỏ nhãn vẫn là dán; canvas quá ngắn thì không bao giờ);
tests/test_eval_observe.py (mỗi lệnh gọi mang số lượt và agent, việc của agent con tính
vào lượt của cha đã giao, outcome của mỗi lần giao việc đọc từ dòng hai kết quả của nó, kết
quả cũ không có dòng đó thì không có outcome; lời đáp cuối, lỗi và giá, tìm ra các cuộc con;
mọi câu agent nói trong cuộc gốc, tiêu đề và nội dung từng canvas; lượt đếm theo tin case đã
gửi: ghi chú loop guard không mở lượt cũng không cắt lời đáp, agent nói trước tin kế của
người không mở lượt đó, cuộc trò chuyện thiếu tin đã gửi thì hỏng trừ khi lần chơi đã hỏng);
tests/test_eval_client.py (duyệt và từ chối được ghi lại, lệnh nêu đường dẫn live bị từ
chối kể cả khi chính sách là duyệt, câu hỏi lấy câu trả lời kế tiếp và hết thì hỏng, một
lượt vẫn bị cắt khi luồng cứ gửi keep-alive, yêu cầu duyệt của agent con được trả lời, được
hỏi lại khi nó xin lần nữa, và hỏng rõ ràng khi không trả lời được; giá các lượt của cuộc
trò chuyện bị xoá vẫn nằm trong sổ cái, lời gọi miễn phí không tính là không báo giá, lần
xoá bị từ chối thì ném lỗi và không cộng gì; mọi cuộc trò chuyện và canvas được liệt kê và
xoá theo id; phần paid cộng đúng bằng phần sổ cái thật mất khi xoá một cuộc);
tests/test_eval_reset.py (bộ nhớ trở lại như cũ dù lần chơi ghi thêm, sửa hay xoá; gốc vốn
không có thì lại không có; tệp hoá thư mục và ngược lại được đặt lại; symlink lần chơi để lại
bị xoá mà không theo; gốc ngoài thư mục chạy, kể cả qua symlink, và symlink có sẵn lúc bắt
đầu bị từ chối; các gốc là bộ nhớ của người và ghi chú của từng agent; đặt lại xoá mọi canvas
rồi mọi cuộc trò chuyện, kiểm lại rồi mới đặt bộ nhớ; còn sót gì trên server hay không đặt
lại được bộ nhớ thì hỏng, nói rõ cái gì, và không đụng gì bên ngoài);
tests/test_eval_home.py (bản sao bỏ bí mật, trạng thái, cơ sở dữ liệu của server, lịch sử,
phiên đăng nhập, symlink, kit ngoài thư mục crew; giữ cấu hình, persona, bộ nhớ, workspace
và cơ sở dữ liệu của workspace; nạp lại được bằng đúng bộ đọc của server; không còn lịch,
Telegram, cô đọng bộ nhớ; không manifest nào nêu đường dẫn home live; không agent nào còn
danh sách cho phép lệnh shell, ở manifest lẫn config.yaml; chuỗi còn nêu đường dẫn live
được báo chứ không viết lại; từ chối đường dẫn ra ngoài, thư mục chạy nằm trong home hay
workspace, một workspace là hay chứa thư mục home của người dùng; cây live không đổi; thư
mục chạy chỉ chủ sở hữu đọc được; chép hỏng thì dọn sạch);
tests/test_eval_report.py (luật đạt, đếm khi chạy thử, tiền là cận dưới khi có lời gọi
không báo giá, dạng dòng, bảng, markdown và json);
tests/test_run_evals.py (một lần chơi đạt hay hỏng và vì kỳ vọng nào, cuộc trò chuyện
không tự động, lỗi ở lượt đầu chặn lượt sau, tiền tính theo mức tăng của sổ cái, hết ngân
sách thì dừng trước lần chơi kế, Ctrl-C để lại số của các lần đã xong, một lượt quá giờ hay
mất server dừng cả cuộc, các từ chối trước khi chép gì, chạy thử qua một server thật; bước
canvas chạy giữa các lượt và mọi tin sau mang canvas đang mở, bước hỏng chặn tin sau mà không
dừng eval, tin trước khi có canvas chỉ có chữ, bước canvas không tới agent thì lần chơi hỏng; khi chạy thử, server đặt vùng chọn đúng dòng eval
đếm trên bản đã sửa, ghi chú thật của server nêu canvas ở bản đã lưu và trích vùng chọn,
người sửa canvas agent vừa tạo thì agent nhận diff của đúng lần sửa ấy, và một lần dán ba
dòng bị bắt từ JSON thật của server; mỗi lần chơi bắt
đầu sau một lần đặt lại, không đặt lại được thì không chơi và dừng eval; mỗi lần chơi để lại
bản ghi tên theo thứ tự case, id và lần, giữ cuộc trò chuyện, cuộc con, canvas và lần xin
duyệt; bộ nhớ không đặt lại được bị từ chối trước lần chơi đầu; khi chạy thử, lần sau không
thấy canvas, cuộc trò chuyện hay ghi chú của lần trước);
tests/test_llm_bench_client.py (server bench và eval chỉ nhận từ môi trường của người
chạy những biến một chương trình cần và model key; home, thiết lập live, danh sách cho phép
lệnh shell, token bot, khoá của dịch vụ khác và socket ssh-agent ở ngoài);
tests/test_serve_flags.py (--no-schedule tắt cả scheduler lẫn kênh, không đụng --port);
tests/test_echo_provider.py (mỗi lệnh gọi của model giả có mã riêng, như provider thật,
nên hai cuộc trò chuyện giao việc ở cùng một chỗ không nhận nhầm agent con của nhau; dòng
cuối cùng mở đầu bằng /tool, khi nó cùng tham số kết thúc tin, là lời gọi dù phía trên có
gì, kể cả ghi chú canvas;
dòng /tool nằm trong ghi chú mà người chỉ viết chữ thường thì không gọi gì; tham số có chữ
/tool được giữ nguyên)scripts/run_evals.py trên các case trong <home>/evals/outcome= ở dòng hai), không chỉ run kết thúc ra sao;
chỉ việc đi tới done mới được chuyển thẳng cho người dùng
tests/test_delegate_outcome.py (dòng Status: theo mọi kiểu model viết,
DONE_WITH_CONCERNS không bị đọc thành DONE, dòng cuối thắng, trạng thái lạ bị bỏ qua, lý do
lấy từ Summary: khi dòng Status: không có, chữ BLOCKED trần chỉ tính khi không có dòng
Status:; dòng Status: trong khối code không lấn dòng khép lại bên ngoài, khối khép lại con
bọc trong code vẫn tính, status: blocked viết thường trong một tệp con cho xem thì không;
runtime thắng lời khai: run không xong là failed, approval tool quyết định gần nhất bị từ chối
hay hết hạn là blocked, câu hỏi không ai trả lời thì không; hết giờ chờ vẫn giữ dòng một);
tests/test_tools_delegate.py::test_the_first_two_lines_are_the_ones_the_web_card_reads
(Python và regex của thẻ web đọc cùng hai dòng),
::test_a_child_that_needs_more_context_is_not_handed_on,
::test_a_child_done_with_concerns_goes_back_to_the_parent_whole,
::test_a_child_that_declares_done_is_handed_on_word_for_word,
::test_a_child_whose_last_approval_was_refused_is_blocked,
::test_a_question_after_a_refused_tool_does_not_hide_the_refusal,
::test_a_child_still_waiting_when_the_wait_runs_out_failed;
tests/test_store_approvals_jobs_usage.py::test_history_can_be_narrowed_to_tool_approvals;
tests/test_delegate_relay.py::test_a_child_that_needs_more_context_goes_back_through_the_boss,
::test_a_child_halted_for_repeating_itself_is_reported_as_failed,
::test_a_status_in_a_code_block_never_lets_a_blocked_child_through;
tests/test_delegation_contract.py::test_every_status_the_skill_offers_is_one_the_parent_readslib/delegate-result.test.ts (“reads what the task came to, and why, from the second
line”, “reads an outcome with no reason, and one with no reply after it”, “leaves the outcome
out for a result written before there was one”, “goes by the outcome when there is one”, “goes
by how the run ended for a result that has no outcome”, và mục delegateReason: mã dừng, hết
giờ chờ khi con còn chạy, tool bị từ chối hay hết hạn thành câu, lời của con giữ nguyên);
components/delegate-cards.test.tsx (“says in words what the task came to, and marks one that
is not done”, “marks a task that failed apart from one that only stopped short”, “says why in
words, not in the codes the delegating agent reads”, “reads a finished task as done, with
nothing more to explain”)delegate-smoke.spec.ts
“a task that came back short of done says so, and why, on the card”a-lookup-is-handed-to-the-researcher trong
<home>/evals/delegation.yaml (delegates_to: {agent: researcher, outcome: done})outcome= và trên một dòng
trống luôn có
tests/test_delegate_canvases.py
(“test_the_canvases_the_child_wrote_stand_right_under_the_outcome”: mỗi canvas một dòng ở phiên
bản lớn nhất con viết, theo thứ tự viết lần đầu, thẻ khớp TAG_RE và cả dòng khớp regex
CANVAS của web, reply vẫn đúng là câu trả lời,
“test_a_child_that_wrote_no_canvas_leaves_only_the_blank_line”,
“test_an_answer_that_opens_like_a_canvas_line_stays_below_the_blank_line”,
“test_a_long_title_is_cut_like_any_field_quoted_in_a_result”,
“test_canvases_past_the_twelfth_are_counted_in_the_first_line_of_the_body”,
“test_a_canvas_deleted_while_the_parent_waited_is_not_named”,
“test_a_task_that_is_not_done_still_names_what_was_written”,
“test_a_child_stopped_short_names_its_canvases_above_the_unfinished_note”,
“test_a_wait_that_runs_out_still_names_what_the_child_had_written”: mười hai dòng đầu và câu
đếm phần còn lại, như mọi kết quả khác,
“test_a_child_deleted_mid_wait_still_names_what_it_had_written”: người dùng xoá cuộc con trong
lúc agent cha còn chờ thì lời gọi vẫn lỗi và vẫn mở đầu bằng câu báo đã xoá, các dòng canvas
nằm ngay dưới câu ấy, câu đếm phần còn lại dưới một dòng trống,
“test_a_child_deleted_mid_wait_that_wrote_nothing_is_the_sentence_alone”: con chưa viết canvas
nào thì kết quả đúng là câu ấy, không thêm gì,
“test_carrying_a_canvas_to_or_from_a_file_unchanged_names_nothing”: xuất và nhập không đổi
không sinh dòng, cũng không chia sẻ canvas sang cuộc gốc,
“test_naming_a_canvas_to_the_master_is_not_the_master_having_read_it”: lượt kế của cuộc gốc vẫn
nhận ghi chú canvas mới đúng một lần, artifact_rewrite của master vẫn bị từ chối,
“test_the_count_of_the_rest_says_how_many_and_which_tool_lists_them”: câu đếm nêu số và tool
artifact_list có thật, và không đọc nhầm thành một dòng canvas,
và canvas_lines gọi thẳng: cuộc không ai viết, mười hai dòng đầu rồi câu đếm phần còn lại,
bản người lưu và bản ghi ở cuộc khác không được nêu);
tests/test_delegate_outcome.py
(“test_the_blank_line_is_there_whether_or_not_a_canvas_was_written”,
“test_a_wait_that_runs_out_names_the_canvases_written_so_far”);
tests/test_tools_delegate.py::test_the_first_two_lines_are_the_ones_the_web_card_reads
(dòng ba là dòng trống khi con không viết canvas nào);
tests/test_delegation_contract.py::test_the_parent_is_told_what_the_canvas_lines_under_the_outcome_arelib/delegate-result.test.ts, mục “the canvases a delegate result names” (khối đọc
tới dòng trống, dòng trống ngay dưới outcome là không canvas nào, kết quả cũ không có dòng
trống hay không có dòng outcome đọc như trước, dòng chỉ có dấu cách không phải dòng trống,
câu trả lời mở đầu bằng dòng giống thẻ, dòng giống thẻ ở dưới hay lẫn với chữ khác, thẻ không
đúng dạng chuẩn, dòng dừng ở dấu cách sau thẻ là canvas không tiêu đề, quá 12 canvas, hết giờ
chờ)tests/test_approval_ttl.py (lịch và khối telegram nhận số giây nguyên từ một phút
tới nửa ngày; số trong ngoặc kép, bool, số lẻ và số ngoài khoảng bị từ chối kèm chỗ sai; khối
telegram không có khoá đọc như trước và khoá mới không nới hai khoá bắt buộc; cuộc trò
chuyện giữ và báo hạn của nó, cơ sở dữ liệu cũ có thêm cột rỗng; sửa lịch khác hay đổi
chat_id qua API không làm mất hạn; danh sách khoá của lịch ở fake web khớp server; câu hết
hạn giữ đoạn mở đầu của câu từ chối);
tests/test_approval_ttl_runs.py (job hỏi thì chờ đúng hạn của lịch, câu hỏi sau khi khởi
động lại vẫn chờ chừng ấy; cuộc mở từ Telegram chờ theo kênh, từ web thì theo cấu hình; con
chép hạn của cha và cha chờ đúng chừng đó cộng biên, không có thì theo agent con, con được tìm
lại giữ hạn lúc mở; cha thôi chờ khi hạn của con đã qua);
tests/test_api_agents_edit.py::test_a_saved_schedule_comes_back_in_the_shape_it_can_be_resent_incomponents/agent-editor/schedules-section.test.tsx (“keeps how long a row’s
approvals wait when another row is edited”); components/agent-editor/channel-section.test.tsx
(“stays when the chat id changes”, “comes back with the channel when it is turned off and on
again”); hooks/use-agent-draft.test.ts (dòng server khai lại mang approval_ttl_seconds)fetch_url báo 202 và trang không còn chữ là lỗi, markdown rỗng
của firecrawl vẫn rơi về tải trực tiếp; shell_run không in gì vẫn nói lệnh kết thúc ra sao
tests/test_tools_web.py::test_a_page_accepted_but_not_ready_is_an_error_not_an_empty_read,
::test_a_page_with_no_readable_text_is_an_error,
::test_a_script_page_with_a_title_still_reads_as_its_title,
::test_a_blank_scrape_falls_back_to_the_page_itself,
::test_a_blank_scrape_of_a_blank_page_is_an_error;
tests/test_tools_shell.py::test_a_command_that_prints_nothing_still_says_how_it_endedtests/test_trajectory.py (mỗi run ghi cuộc trò chuyện đang ở tin nào khi nó bắt đầu
và chỉ lấy tin của mình, run tiếp tục sau khi duyệt giữ mốc cũ, run có từ trước khi có mốc
thì cắt theo giờ và lấy cả tin đúng giây kết thúc, hai run bắt đầu cùng giây được phân theo
mốc tin, run không ghi gì vẫn giữ chỗ khi được lưu lại nên không nhận tin của run sau nó cùng
giây, agent con đi theo còn con của cuộc khác trùng mã lời gọi thì không, chỉ con do chính
run mở mới đi theo, hai run cùng gọi call_0 mỗi run chỉ mang con của mình, lời gọi chưa có
kết quả chỉ mang con mở trong lúc run chạy và do chính cuộc này giao, run không có cuộc trò
chuyện vẫn xuất bản ghi và các bước, Markdown có mọi lời gọi tool kèm tham số và kết quả, kết
quả dài bị cắt và nói dài bao nhiêu trừ khi xin bản đầy đủ, Markdown giữ tiêu đề và tóm tắt
trên một dòng và đóng khối code bị bỏ dở trước tin sau còn khối tự đóng và code trong dòng
thì giữ nguyên, tin đọc sau một ghi chú canvas mang ghi chú đó với bí mật đã che và Markdown
đặt ghi chú trong khối code ngay trước tin, tin không có ghi chú thì không có trường này);
tests/test_trajectory_redact.py (chỉ gom giá trị dài của biến có tên như
bí mật, gom cả dạng gộp khoảng trắng, dạng JSON đã thoát và dạng có cả hai, bí mật chứa bí
mật khác được che trọn, phần đầu bị cắt ở cuối chữ hay trước dấu … và phần đuôi ở đầu chữ
đều được che, chữ có nhiều chỗ cắt thì sau mỗi dấu … một phần đuôi và trước mỗi dấu … một
phần đầu đều được che, chữ chỉ trùng đầu hay đuôi bí mật mà không phải chỗ cắt thì để nguyên,
dạng khoá quen thuộc được che dù không biết giá trị, chữ chỉ hơi giống khoá thì để nguyên; run
không có là 404, hai dạng đến như tệp để lưu với no-store, bí mật trong môi trường và chuỗi
dạng khoá được che ở cả hai dạng, bí mật nằm ở chỗ bị cắt vẫn được che trước khi cắt, bí mật
bị bản ghi cắt ngắn ở tiêu đề, tóm tắt, output và tham số của bước, ở tên và giá trị tham số
lời gọi hay trong tin của agent con đều được che ở cả hai dạng, bí mật có ngoặc kép hay gạch
chéo ngược được che cả khi JSON đã thoát nó, không gì từ môi trường đi theo nếu tin không chứa
nó, bản đầy đủ giữ nguyên kết quả dài)components/run-replay.test.tsx (“offers the run as a JSON and a Markdown file to
save, with a word to check before sharing”, “offers no download while there is no run to
download”)replay-smoke.spec.ts “a run downloads as a JSON file and as a Markdown file from
its own page” (bấm là tải chứ không điều hướng, tới đúng URL với đúng tên tệp; trình duyệt tự
tải a[download] mà không qua route nào của spec, nên thân tệp và header do pytest giữ)/steer và
lệnh kit chèn vào lượt đang chạy, chờ duyệt vẫn từ chối, Stop trả lại chữ
tests/test_busy_queue.py (tin gửi lúc lượt chạy chờ rồi có lượt riêng, còn lượt đang
chạy đi tiếp như chưa có gì; các tin chờ cùng nhau thành một tin theo thứ tự, đường dẫn và lệnh
không ai định nghĩa là tin thường; /steer trơn bị từ chối cả khi rảnh lẫn khi bận; tin quá
trần bị từ chối và hàng giữ nguyên; tin gửi trước khi lượt kịp được đọc vẫn chờ, dù đi cửa
nào; cuộc chờ duyệt từ chối tin mới; tin gửi ngay sau một quyết định chờ lượt nó tiếp tục; tin
đã chờ qua một lần dừng duyệt được trả lời sau quyết định; quyết định thứ hai bị từ chối; lượt
quét hết hạn bỏ qua yêu cầu mà một quyết định đang giữ);
tests/test_steer.py (steer tới lượt đang chạy sau tool nó chờ, model nhận nó ngay sau kết quả
tool, run có step steer; các steer gửi trong cùng một bước tới cùng nhau, một dòng trên
timeline; steer gửi lúc câu trả lời đang stream được chính lượt đó trả lời; lệnh kit gửi khi
bận được expand rồi chèn; lấy steer để nguyên tin thường và đếm lặp lại từ đầu);
tests/test_queue_store.py (vị trí trong hàng và trần; đọc không lấy; chuyển vào một tin người
dùng theo thứ tự, cùng một transaction với nhật ký; chỉ chuyển các dòng được nêu tên; ghi tin
hỏng hay cuộc đã xoá thì dòng còn nguyên; dòng đã bị lấy thì không chuyển gì; chỉ lấy steer; lấy
hết mà không ghi; cuộc chờ lâu nhất đứng đầu; xoá cuộc trò chuyện xoá hàng; hàng sống qua
restart);
tests/test_queue_drain.py (claim chỉ bị gỡ bởi token của chính nó và hết hạn sau thời gian
chờ; tin còn chờ khi drain dừng được trả lời sau lần khởi động kế; khởi động drain mọi hàng trừ
cuộc chờ duyệt và dọn hàng của cuộc đã xoá; claim không ai đọc hết hạn và tin sau nó được trả
lời; chat Telegram nghe câu trả lời từ bot của nó và chờ khi chưa có bot; tin từ web vào chat
Telegram được trả lời ở server; runner lỗi thì nhả cuộc trò chuyện; huỷ dừng lượt do hàng
chạy, không đụng lượt người khác đang đọc);
tests/test_busy_queue_api.py (event queued kèm vị trí, queued trong chi tiết cuộc trò
chuyện và đọc hai lần không đổi; /steer trơn nhận 422, tin quá trần nhận 429 ở cả /messages
lẫn /api/inbound, kèm câu lý do;
stop trả lại chữ và làm rỗng hàng, 404 cho cuộc không có; relay nghe mình đã xếp hàng; quyết
định thứ hai nhận 409; stop huỷ lượt do hàng chạy và lượt do web khởi động; server khởi
động thì trả lời tin đã chờ qua restart, còn server --no-schedule để tin ấy tới sau lượt kế);
tests/test_kit_commands.py::test_steer_text_renders_kit_commands_and_leaves_everything_else_alone,
::test_inbound_expands_a_kit_command_before_the_turn (lúc rảnh /steer X là tin X)state/thread-reducer.test.ts nhóm “threadReducer queue and steer”
(“loaded reads waiting from detail.queued, defaulting to empty”,
“opened clears waiting along with the rest of the thread”,
“queued adds a chip and drops the matching local bubble, but only on a text match”,
“queue_cleared empties waiting”, “elsewhere sets a notice of that kind”,
“a steer event adds one user message and drops the oldest matching count of steer chips”,
“a steer count larger than the number of steer chips only drops the steer chips there are”,
“a queued event reaching the reducer directly is a no-op: the hook intercepts it first”,
“queue_failed sets an error notice without touching busy, streaming or items” — lỗi từ POST
xếp hàng không đụng luồng đang chạy, chỉ để lại một thông báo và giữ chip nguyên tại chỗ);
hooks/use-thread.test.ts
(“goes a second POST instead of runTurn, and the running turn keeps working and can still be
stopped”, “aborts a queueing POST still in flight when the conversation is switched” — và request
bị cắt không để lại thông báo nào ở hội thoại đang hiện, nhóm
“send while the conversation is busy elsewhere (this tab thought it was idle)”
“catches the queued event on the plain send path and turns the temp bubble into a chip”, nhóm
“send while busy hits the queue’s own limits”, và nhóm “Stop, redesigned: server first, then
abort, chip text back in order”
(“calls the server before aborting, returns the cleared texts in queue order and clears the
chips”, “shows no notice when Stop only had chips to clear and nothing was running”,
“says elsewhere on an external run the server could not cancel, and still clears the chips”,
“aborts locally once STOP_WAIT_MS elapses when the server hangs, driven by fake timers”) — hạn
chờ giả bằng fake timers, không chờ thật ba giây;
lib/steer-hint.test.ts “steerHint” (bảng /tmp/x, /Users/a/b, lệnh lạ, /steer,
/steer x, lệnh đã biết, chữ thường — cùng dạng regex với server);
components/queued-chips.test.tsx
(“renders nothing when there is nothing waiting”,
“carries the full text in the DOM even though the row itself is a single truncated line”,
“announces itself with aria-live so a chip appearing is read out without moving focus”,
“labels a follow-up chip and a steer chip differently for someone using a screen reader”,
“offers no button: a chip is confirmation only, nothing here can be cancelled”);
components/components.test.tsx nhóm “Composer”
(“still sends on Enter while busy: the box is never locked, only Stop is offered beside it”,
“labels the send button by what a busy send will do: queue a plain message, steer a /steer
one”, “offers both Stop and send while busy with text typed, but only Stop once the box is
empty”, “hides Stop when the caller says the busy turn cannot be stopped from here”,
“prepends restored text to whatever is already in the box, then focuses it”,
“restores alone with no join when the box was empty” — hai test cuối waitFor việc focus
dời sang ô soạn sau khi restore đổi nonce);
lib/run-steer-step.test.ts nhóm “a steer event on a live run”
(“becomes a steer step, shortened exactly as the server shortens it”,
“cuts a long steer text at the same length the server cuts at”) — cùng độ dài cắt với
note_text phía server; nhóm “a queued event on a live run”
“never reaches the activity stream, but a run read from a stale tab is not thrown”; nhóm
“a steer step in the timeline”
(“is labelled with its own sentence and painted done”,
“never merges with the steer step beside it: two turns of steering differ even when they read
alike”, “is left out of the progress counter, like a note: it is what a person said, not a
piece of work”);
hooks/use-external-run-refresh.test.ts nhóm “a run first seen running while a drain’s chip is
still on screen”
(“reloads the thread right away instead of waiting for the run to change status”,
“does not reload when there is no chip waiting: a plain external run keeps its old, quieter
behaviour”,
“reloads even when the run is first seen already finished: an echo reply or a turn halted on
budget never renders as running at all”,
“reloads when the run is first seen halted: the same instant turn can also stop on its own
budget cap”,
“does not reload while the stream is behind: the reload on catching up already covers it”)queue.spec.ts
(“a plain message queues as a chip, clears the box, and leaves Stop and send both showing”,
“a /steer message queues as a chip that will cut in, then becomes a user message once the held
turn steers it”,
“Stop hands both queued texts back to the box in order and the chips go”,
“a reload still shows the chip: the conversation’s own queued field carries it”,
“chips wrap instead of forcing the page wider, and Stop and send stay tappable”)send báo kết quả ngay khi server nói gì đó: tin server giữ trong hàng không quay về ô soạn
tin, tin server không nhận thì chữ ở lại nơi người gõ
app-queued-send.test.tsx nhóm “a message sent while this tab thought the conversation
idle” (“stays out of the box once the server queued it: the chip stands for it” — tab không
thấy lượt đang chạy ở tab khác nên gửi theo đường rảnh và chỉ nhận một event queued: chip
đứng thay tin, ô soạn tin trống, không có bong bóng thứ hai; “stays out of the box too when it
is the first message of a new conversation, and the server queued it” — lượt ở tab khác chiếm
cuộc trò chuyện mới trước khi tin đầu tới: chip cũng đứng thay tin và ô soạn tin trống; “takes
its bubble back and leaves the words in the box when the server refuses it before saying
anything” — 429 trước event đầu:
bong bóng tạm biến mất, chữ ở lại ô và thông báo giữ câu của server; “hands the first message
of a new conversation back to the box when it cannot be sent” — cuộc trò chuyện mới đã tạo,
chữ về ô của nó);
hooks/use-thread.test.ts nhóm “send settles at the first thing the server says”
(“answers sent at the first event while the turn goes on”, bảng “%s before any event takes the
bubble back and reports the send as failed” cho hàng đầy, chờ duyệt, server hỏng, hội thoại
mất và mất kết nối, “keeps the bubble and its error once the server has said anything: the
text is not given back”, “answers sent, not failed, when the person leaves before the server
says anything” — rời đi hay bấm Stop không phải lỗi nên không trả chữ về ô của hội thoại khác,
“answers sent when the stream ends with nothing to say: the server took the message”,
“has nowhere to send without a conversation”); nhóm “send while busy hits the queue’s own
limits” (“a %d from the queueing POST shows the server’s own text in the notice and fails the
send in our words” — thông báo giữ câu của server, error là câu của ta) và ba test xếp hàng
cũ nay kiểm cả kết quả (queued, sent, queued);
hooks/use-thread-send.test.ts nhóm “a message queued behind this tab’s own turn” (“is held
among the queueing requests only while its request is in flight” — request xếp hàng nằm trong
tập để rời hội thoại thì cắt được, và rời tập khi xong; “answers sent when its request closes
having said nothing: the server took the message”; “says nothing of a request it was cut off
from: the person left, nothing was refused” — bị cắt vì người rời đi thì không báo lỗi và không
đặt thông báo), nhóm “a message sent on this tab’s own stream that the server queues instead”
(“is turned into its chip by the send itself: the turn’s stream is handed no queued event” —
đường gửi thường tự đổi bong bóng tạm thành chip, luồng của lượt không nhận event queued lần
hai; “hands every other event on to the turn’s stream”) và nhóm “a message queued behind this
tab’s own turn that the server refuses”
(“is worded as any refused message is: a validation dump never reaches the notice” — tin xếp
hàng sau lượt đang chạy ở tab này mà server từ chối bằng danh sách lỗi kiểm tra dữ liệu, như
tin dài quá 20000 ký tự, thì thông báo nói câu của ta giống đường gửi thường, không in nguyên
danh sách JSON);
state/thread-reducer.test.ts “user_unsent takes back the local bubble for that text, and
only that one” (chỉ bong bóng local- cuối cùng đúng chữ; tin server đã lưu hay có thứ gì
đứng sau thì ở lại);
lib/send-result.test.ts nhóm “settlement” (chốt một lần, ở việc đến trước trong ba: nghe
event, lỗi, kết thúc; queued chỉ cho event queued) và “sendErrorText” (đổi trạng thái
thành câu tiếng Việt của ta, kể cả khi server trả câu tiếng Anh)tests/test_telegram_background.py (/status trả lời giữa lượt, tin thứ hai xếp
hàng không kèm “đang gõ…” rồi được trả lời riêng, /new bị từ chối khi đang chạy; /steer
tới lượt đang chạy, /steer trơn bị từ chối và không vào hàng; lượt qua nửa đêm giữ cuộc trò
chuyện; dừng bot chờ lượt xong trong hạn, quá hạn thì huỷ và báo một lần; lượt hỏng chỉ nêu tên
loại lỗi; Telegram từ chối cả câu báo lượt hỏng thì lượt vẫn kết thúc, không lỗi nào thoát ra
và log ghi một lần kèm nguyên nhân; bot trả lời tin chờ từ lúc start tới lúc stop; “đang gõ…” treo không giữ câu trả
lời; /approve tìm cuộc chờ duyệt nằm sau cuộc mới hơn, gửi hai lần chỉ tiếp tục một lần);
tests/test_queue_drain.py::test_a_telegram_chat_hears_its_answer_from_its_bot_and_waits_while_there_is_none
(claim của drain giữ cuộc trò chuyện tới khi run nhận);
tests/test_telegram_drain_wiring.py (runtime giao drain cho bot dựng lúc khởi động và cho
bot dựng lại khi đổi kết nối, nên tin chờ trong chat được bot đang chạy trả lời); các test
Telegram cũ gửi từng tin một bằng poll_each (tests/telegram_fake.py)tests/test_telegram_call_limits.py (lời gọi không tự nêu hạn mang hạn của chính
client, không phần nào trong bốn phần của hạn bị bỏ trống: sendMessage, sendChatAction,
setMyCommands, getFile, ảnh và tệp gửi từ đĩa; tin dài gửi thành nhiều khúc thì khúc nào
cũng theo hạn ấy; long poll giữ hạn nó tự nêu, dài hơn thời gian chờ tin; tệp gửi từ bộ nhớ giữ
hạn 60 giây của nó);
tests/test_app_wiring.py::test_default_http_client_waits_as_long_as_the_provider_would (hạn
của client mà server dựng lúc khởi động);
tests/test_script_job_failure_notice.py::test_a_notice_that_never_returns_is_given_up_on_and_the_next_job_runs
(tin báo job script hỏng không gửi xong trong hạn thì bị bỏ và job kế tiếp vẫn chạy). Kiểm chứng
đột biến: hai sửa đổi (đưa thẳng None cho client như trước; bỏ hạn chờ của tin báo) đều bị
test bắt.tests/test_interrupted_calls.py (call không có kết quả nhận kết quả thay thế nằm
trước tin mới và không chạy; call đã duyệt mà chưa báo lại không chạy lần nữa; call bị từ chối
hay hết hạn đóng bằng lời từ chối của nó; câu hỏi đóng bằng câu trả lời hoặc mặc định; call còn
chờ duyệt để nguyên; không làm gì khi mọi call đã có kết quả);
tests/test_queue_drain.py::test_what_waited_through_a_restart_comes_after_the_calls_it_cut_short/steer
tests/test_telegram_voice.py (find_voice đọc voice note và audio file, bỏ qua ảnh
hay message trống; voice hợp lệ được chép, “Đã nghe: …” gửi trước, rồi lượt tới master mang
cả đường dẫn tệp lẫn lời chép; chưa cấu hình audio_routes thì báo cách bật, không tải về;
quá 300 giây hay định dạng không nhận ra bị từ chối trước khi tải; quá 10 MB chỉ lộ ra sau
khi tải thì vẫn bị chặn trước lúc chép lời; tải hỏng báo lỗi và không mở cuộc trò chuyện nào;
tuyến chép lời lỗi báo bằng lý do cố định, không lộ lỗi gốc của provider; lời chép trống hay
“không nghe rõ” vẫn tính tiền vào sổ chi phí; chép lời hết giờ báo timeout và không mở lượt;
voice tới lúc agent đang bận thì xếp hàng, không bao giờ chen ngang thành /steer; hai voice
trong một nhóm mỗi cái có lượt chép và lượt riêng của nó; message vừa có voice vừa có document
thì đi theo đường voice; tuyến audio đọc thẳng từ deps của kênh, thêm route sau khi khởi động
vẫn có tác dụng ngay); tests/test_side_calls.py ghi nhận purpose transcribe trong
PURPOSEScomponents/connections-panel.test.tsx
(“shows the providers that were built and the routes in order” — cũng khẳng định
noAudioRoutes hiện khi chưa cấu hình, “lists the audio route used for voice note
transcription”); components/stats-panel.test.tsx
“names a voice note transcription row by its own purpose”; components/global-routes-editor.test.tsx,
test/fake-backend.ts, e2e/mock-api.ts mang trường audio_routes trong mọi fixture
ConnectionsInfo để không mock nào lệch kiểu thậttests/test_store_fork.py
(test_copies_every_message_before_the_cut_with_its_columns,
test_cutting_at_the_first_user_message_makes_an_empty_fork_with_the_right_draft,
test_tool_calls_and_tool_messages_survive_the_copy,
test_provider_cost_and_token_columns_are_null_on_the_copy,
test_the_fork_has_zero_spend_and_zero_unknown_cost_calls,
test_usage_totals_are_identical_before_and_after_forking (by_model, by_purpose, by_day
và /api/stats không đổi vì bản chép không mang provider, nên không lượt gọi model nào bị
đếm hai lần), test_agent_id_cost_cap_and_skills_are_copied_others_are_reset,
test_autonomous_follows_the_agent_default_not_the_source (nhánh không thừa hưởng
autonomous hay auto_approve của gốc dù gốc đã bật),
test_the_suffix_is_added_once_and_not_doubled_on_a_fork_of_a_fork,
test_a_default_titled_source_stays_default_on_the_fork,
test_cutting_at_an_assistant_or_tool_message_is_a_value_error,
test_a_message_of_another_conversation_is_a_key_error,
test_an_unknown_message_id_is_a_key_error, test_an_unknown_conversation_is_a_key_error,
test_a_delegated_child_conversation_cannot_be_forked,
test_the_source_conversation_is_unchanged_after_forking,
test_a_pending_approval_on_the_source_survives_forking_at_an_earlier_point,
test_forking_a_telegram_conversation_makes_a_web_fork_and_keeps_the_channel,
test_an_open_call_is_copied_still_open_ready_for_the_route_to_close_it (lịch sử thật
[assistant(A,B), tool(A), user] khi một lượt bị ngắt giữa chừng rồi người nhắn tiếp; call B
còn mở sau khi chép, chờ route đóng lại),
test_a_failed_copy_leaves_no_fork_behind (chép tin hay chép liên kết canvas lỗi đều không
để lại nhánh hay hàng nào),
test_only_the_source_conversations_own_messages_are_copied (tin của hội thoại khác có
seq nhỏ hơn điểm cắt không lọt vào nhánh),
test_forked_from_marks_the_conversation_so_the_recap_can_be_skipped,
test_deleting_the_source_clears_forked_from_and_the_fork_keeps_its_messages,
test_deleting_the_fork_does_not_touch_the_sources_delegate_children,
test_a_shared_line_finds_a_single_hit_belonging_to_the_fork (chỉ mục FTS của tính năng tìm hội thoại gộp
phần chữ chung của gốc và nhánh về một hit, thuộc về nhánh vì id lớn hơn));
tests/test_api_fork.py
(test_forking_returns_the_new_conversation_and_the_cut_messages_own_text,
test_unknown_conversation_is_404, test_unknown_message_id_is_404,
test_a_message_belonging_to_another_conversation_is_404,
test_cutting_at_a_non_user_message_is_400, test_forking_a_delegated_child_is_400,
test_before_message_id_must_be_a_positive_integer,
test_an_open_call_is_closed_and_never_run_again (chạy một lượt ở nhánh với tool đếm số lần
gọi: tool bị đóng ở điểm cắt không được gọi lại),
test_a_refusal_that_fails_to_write_leaves_no_fork_behind (refuse_unanswered ném lỗi thì
route trả 500 và không còn nhánh),
test_the_fork_runs_on_its_own_only_when_the_agent_default_says_so (cả hai giá trị của
autonomous_default, gốc luôn mang giá trị ngược lại));
tests/test_memory_session_summary.py::test_a_fork_is_not_told_about_the_conversation_it_was_cut_from
(hội thoại liền trước nhánh thường là chính gốc của nó; tóm tắt của gốc kể phần sau điểm cắt
mà nhánh sinh ra để bỏ, nên không vào prompt của nhánh)hooks/use-fork.test.ts
(“uses a numeric id straight away, with no extra read of the conversation”,
“resolves a local-N id by reading the conversation once and counting from the end” (bubble vừa
gửi chưa nạp lại nên còn giữ id local-N; khớp theo vị trí tin user tính từ cuối và đúng
chữ của nó),
“gives up without ever calling the fork API when the text at that position no longer matches”,
“saves the fork’s draft, refreshes the list, and selects the fork”,
“shows forkFailed and stays put when the server refuses the fork”,
“only on the conversation it failed in, and not again on the way back to it”,
“never, when it fails after the person has already moved to another conversation” — lỗi rẽ
nhánh chỉ hiện ở đúng cuộc trò chuyện vừa lỗi, không theo người sang cuộc khác);
app-fork.test.tsx (cả app trên FakeBackend: “opens the branch with the old words ready to
edit, and links back to an untouched source” — ô soạn tin mang chữ cũ và có focus, nhánh được
chọn trong sidebar, dòng “Rẽ nhánh từ” dẫn về gốc còn đủ bốn tin và gốc không có dòng đó;
“reports a refused fork on its own conversation only”);
components/message-thread.test.tsx
(“sits under the user bubble but not under the assistant’s reply”,
“is absent without an onFork handler, even while idle”, “is hidden while the thread is busy”,
“calls onFork with the item it sits under, not the conversation as a whole”);
components/conversation-header.test.tsx
(“is absent when the conversation is not a fork”,
“is absent when the conversation carries no forked_from field at all”,
“names the source conversation and opens it on click”,
“falls back to the generic label when the source is not in the loaded list”);
hooks/use-draft.test.ts
(“is read back by useDraft once it mounts on that key”,
“is picked up by a useDraft already mounted, the moment its key switches to it”,
“forgetDraft still empties what saveDraft wrote”)e2e/fork.spec.ts
(“forking at the second saved message keeps the first turn, prefills the composer, and links
back” — gốc không có dòng “Rẽ nhánh từ” cả trước khi rẽ lẫn lúc quay về, “forking right after sending, before any reload, still resolves to the real message” —
ca local-N trên mock, riêng với self-test thủ công trên server thật)conversation_search cho model, “Trong nội
dung” trong sidebar cho người
tests/test_search_index.py (thiếu module fts5 báo lỗi rõ; tin user/assistant
vào chỉ mục ngay, tin tool thì không; xoá cuộc trò chuyện dọn đúng theo nó; backfill lập
chỉ mục cho tin ghi trước khi có bảng; mở lại một CSDL hai lần không nhân đôi chỉ mục;
backfill lỗi giữa chừng không để lại bảng hay trigger mồ côi, lần chạy sau thành công; không
có chỗ nào UPDATE messages — chỉ mục chỉ có trigger insert/delete);
tests/test_conversation_search.py (gõ “doc” khớp “đọc”; dấu nặng tiếng Nhật không bị bỏ
như dấu tiếng Việt; dạng NFD vẫn tìm được và đoạn trích giữ nguyên ký tự gốc; đoạn trích chỉ
đánh dấu đầu/cuối thật sự bị cắt; ký tự đặc biệt của FTS5 không làm câu truy vấn vỡ; nội dung
trùng ở nhiều cuộc trò chuyện của cùng một agent gộp về bản mới nhất, khác agent thì giữ cả
hai; một cuộc trò chuyện không góp quá ba kết quả; limit chặn tổng số; since lọc theo
ngày; agent_ids lọc đúng các agent được nêu; exclude_conversation bỏ đúng cuộc đang chạy
khỏi kết quả);
tests/test_conversation_search_api.py (kết quả trả về 200 dù conversation_id không có
thật, không phải 404 của route cuộc trò chuyện; agent_id thu hẹp về đúng một agent; agent
không tồn tại trả kết quả rỗng, không lỗi; câu rỗng cũng vậy; limit dưới 1 hay trên 50 là
422; không truyền agent_id thì tìm khắp cả đội vì người gọi route là chủ của cả đội)hooks/use-content-search.test.ts
(“waits 250ms of no typing before calling the API, and skips a call the next keystroke
cancels”, “never calls the API for a query under 2 characters, and clears any earlier
hits”, “aborts the in-flight request when the query changes again before it answers”,
“reports a real failure as an error, but a cancelled request as no error at all”,
“does not call the API at all while disabled, even for a long query”, “retries the same
query immediately, without waiting out another debounce window”);
components/content-hits.test.tsx
(“shows a loading state while the search is in flight”, “shows an error state, distinct
from an empty result, with a retry button”, “shows nothing at all once settled with no
hits, not an error”, “names each row’s agent with a badge, and gives the button an
accessible name naming the conversation”, “shows when each hit happened, relative to now”,
“calls onSelect with the hit’s conversation_id, not its message_id, when clicked”);
components/conversation-list.test.tsx
(“shows a ‘Trong nội dung’ section once the debounced content search answers”, “never
calls the content search API without an agentName”, “keeps the row list’s own ‘no
matches’ message off when a title misses but content still hits”)schedule_create, người duyệt nguyên văn prompt
tests/test_cron_next_after.py (next_after nhảy so với một bản quét từng phút viết
riêng trong test, trên khoảng 30 biểu thức cron × 9 mốc bắt đầu; không có lần chạy nào trong 366
ngày trả None; ngày-trong-tháng và thứ vẫn là AND qua một ca 29/2 rơi vào thứ Hai; kết quả
luôn sau mốc bắt đầu kể cả khi mốc đó đã tròn phút); tests/test_schedule_words.py (đọc lịch
bằng lời y hệt từng ca của lib/cron-text.test.ts: hằng ngày, một khoảng thứ, cuối tuần, một
danh sách thứ, ngày-trong-tháng, mỗi phút/giờ và bước */N chia hết chu kỳ; một dạng không nằm
trong các ca trên, kể cả bước không chia hết giờ hay ngày, trả về nguyên văn cron hoặc every);
tests/test_schedule_proposal.py::TestRejections (mỗi phút, mỗi 5 phút, cron không có lần chạy
trong hạn, every dưới 15 phút hay dài hơn 366 ngày, một every khổng lồ bị từ chối bằng lời
thay vì tràn timedelta, every sai dạng báo lỗi bằng tiếng Việt, thiếu hoặc thừa cả cron
lẫn every, cron sai hay dài quá 100 ký tự, tên hoặc prompt rỗng hay quá dài, skills không
phải danh sách, skill không có thật đều bị từ chối); ::TestAcceptance (*/15, cron hằng ngày,
every 15m, every 30d, cron hằng năm đều được chấp nhận; một tên skill đứng riêng là một
skill chứ không phải từng chữ cái; skill lặp chỉ giữ một, đúng thứ tự; đúng bằng từng giới hạn —
cron dài đúng 100 ký tự, every 366d, tên 60 và prompt 2000 ký tự — vẫn được chấp nhận);
::TestUpcoming (ba lần chạy kế tiếp của cron, dừng sau một lần khi lần đầu đã xa hơn hạn,
every là now + k×chu kỳ); ::TestReasonLine (reason_line là đúng từng dòng: tên, lịch
bằng lời, ba lần chạy, nguyên văn prompt, lời cảnh báo; thẻ every ghi giờ là “khoảng”, thẻ
cron thì không; cron dài nhất hợp lệ vẫn chỉ liệt kê ba lần chạy; đề xuất sai có reason đúng
bằng lời báo lỗi; không bao giờ rỗng và không vượt trần ký tự Telegram dù là cron hằng năm);
test_min_gap_minutes_documented_cases và
test_min_gap_minutes_is_a_lower_bound_that_only_under_rejects (khoảng cách hai lần chạy liền
nhau của cron là cận dưới, chỉ có thể từ chối thừa);
tests/test_schedule_create.py::TestAlwaysAsks (ask_reason không bao giờ rỗng cho tool này;
needs_decision vẫn đúng dù cuộc trò chuyện autonomous, dù tool nằm trong auto_approve; một
lượt không người trông vẫn dừng dù mọi cổng khác đã cho qua; đề xuất sai dạng có reason đúng
bằng lời báo lỗi, không phải tên tool trơ trọi; những tham số từng làm đổ thẻ — skills là số,
every khổng lồ, tên rỗng — vẫn dừng chờ người kèm reason; một tool trả reason rỗng hoặc lỗi
khi dựng reason (lỗi số học hay lỗi tra khoá) vẫn dừng chờ người, không đổ lượt; reason không bị
bọc bởi câu dành cho lệnh shell); ::TestUnattendedProposalExpires (job prompt tự đề xuất lịch
mà không ai duyệt thì approval hết hạn và không để lại dòng DB); ::TestApprovalToRun (duyệt
xong có dòng, describe() hiện origin=chat, đồng hồ giả tiến tới hạn thì run_job chạy mà
không cần khởi động lại Scheduler, jobs() cũng có job đó; một Scheduler mới trên cùng CSDL
vẫn thấy dòng đó; tạm dừng qua job_state khiến due không chọn job chat);
::TestDenialAndLimits (từ chối thì không có dòng, vượt trần 20 thì lỗi, hai lần add chạy đua
ở biên trần chỉ một cái qua; trần và số đếm tính riêng từng agent; bị từ chối ở trần không để
transaction treo, CSDL vẫn ghi tiếp được; các dòng trả về đúng thứ tự thêm dù cùng một giây;
agent đã đủ trần thì thẻ duyệt báo ngay điều đó mà vẫn dừng chờ người, và trần đã đủ của agent
khác không tính; thiếu một lịch mới tới trần vẫn ra thẻ đầy đủ; tool báo đủ trần bằng lời và
không lưu gì); ::TestOwnershipAndSkills (tool không có tham số agent, dòng luôn mang
agent_id của người gọi; skill không có bị từ chối; skill bị xoá sau khi lịch đã tạo không chặn
job chạy, dòng chat thành job prompt mang đúng tên, cron, prompt; run lưu dòng dưới agent_id
của người gọi — tham số agent lạc bị bỏ qua — đúng cuộc trò chuyện của lượt, đúng skill, và
trả lời kèm lần chạy kế tiếp); ::TestAssembly (agent nào cũng có tool này, gắn đúng id và danh
sách skill của chính agent đó); ::TestOrphanRowsAndConcurrentDescribe (dòng của agent đã bị gỡ
không xuất hiện trong describe(); gọi describe() từ thread pool trong lúc luồng khác
add/remove không lỗi và cho kết quả nhất quán); ::TestDeleteRoute (xoá bỏ đúng dòng
created_schedules và job_state, giữ nguyên lịch sử run; id không có hay id của agent khác
báo không xoá gì; qua HTTP trả 204 và xoá luôn trạng thái tạm dừng của job, 409 cho job
origin=profile, 404 khi không có; xoá được cả dòng mồ côi); ::TestTelegramCardLength (thẻ
duyệt Telegram mang nguyên văn prompt và không vượt 4096 ký tự kể cả ở ca prompt dài nhất với
cron hằng năm); test_existing_shell_ask_pattern_gate_is_unaffected (cổng hỏi của lệnh shell
hiện có không đổi kết quả sau khi thêm ask_reason)components/jobs-panel.test.tsx (“labels a chat-origin row and leaves a profile row
unlabelled”, “offers no delete button for a profile job even when onDelete is supplied”,
“deletes a chat job once the confirm dialog is accepted”, “keeps the job when the confirm dialog
is declined”, “offers no edit button on a chat job, whose schedule is not in the profile
editor”); app.test.tsx (“withholds always-allow on a proposed schedule / a shell command
on the ask list, which stopped for a reason, and shows that reason”: thẻ có reason hiện đủ từng
dòng reason, có nút Duyệt nhưng không có “Luôn cho phép”; thẻ không reason vẫn có nút đó, theo
“always-allows a tool from the approval bar, shows it in the header and revokes it”);
app-activity.test.tsx (“deletes a chat-created job from the jobs tab and keeps the profile
one”: trên toàn app, nút xoá chỉ có ở job chat, bấm xác nhận thì gửi đúng một DELETE, dòng biến
mất, job profile còn nguyên)e2e/manage-smoke.spec.ts
“a schedule is added from the jobs list through the agent’s editor” (không đổi assertion,
chỉ sửa e2e/mock-api.ts để một spec giữ tham chiếu tới cùng mảng jobs nó truyền vào
mockApi thấy được cả việc thêm bằng push lẫn việc xoá qua route DELETE mới)tool_output_read), spill và dọn dẹp
tests/test_output_spill.py (ghi và đọc tệp, trần 5 MB cắt đúng ranh giới ký tự
nên tệp vẫn đọc được UTF-8, tên tệp băm từ id, id hội thoại lạ bị từ chối, quét không theo
và không xoá symlink, sao chép bỏ qua symlink, shape_with_spill);
tests/test_registry_spill.py (registry ghi tệp và nối dòng trỏ vừa trần, không ghi khi
đầu ra ngắn, ghi lỗi thì vẫn trả kết quả, tool_output_read không tự spill dù trần rộng
hay hẹp, trần quá hẹp cho dòng trỏ thì chỉ rút gọn, ghi chú của hook sau không đổi); tests/test_tool_output_read.py (đọc từng đoạn, nhãn nguồn, tệp thắng
bản DB, hết thì báo, offset âm hoặc quá cuối bị kẹp vào văn bản, id của cuộc khác, id trùng
bị từ chối, vừa trần registry; model không nêu độ dài thì nhận một đoạn 8.000 ký tự dù trần
rộng; người gọi cầm được nhiều hơn thì trong lúc nó đọc đoạn dài vừa đúng phần nó cầm, kể cả
hai dòng chỉ đường, xin dài hơn cũng chỉ được vậy, xin ngắn hơn thì ngắn hơn, và lần đọc
sau đó lại theo trần của agent);
tests/test_message_tool_results.py (MessageStore.tool_results);
tests/test_output_read_wiring.py (agent có hoặc không có tool trong tools: thì có hoặc
không có spill, stub trong prompt nêu id đúng khi có tool, vòng lặp thật đọc lại đoạn giữa
của đầu ra 22 000 ký tự); tests/test_agent_context_trim.py
(“test_a_stub_names_the_id_to_reread_only_for_an_agent_that_can_reread”,
“test_rereading_changes_only_the_wording_of_the_stubs”,
“test_a_stub_for_a_call_with_no_id_falls_back_to_the_plain_wording”);
tests/test_agent_templates.py (“test_every_tool_a_template_lists_is_a_real_tool”,
“test_the_counsel_and_the_researcher_can_read_a_long_output_back”);
tests/test_housekeeping.py (quét ngay khi khởi động, quét lại mỗi chu kỳ, lần quét lỗi
không giết vòng, huỷ được, lifespan chỉ quét khi schedule=True); tests/test_api_fork.py
(“test_deleting_a_conversation_removes_its_spill_folder”,
“test_deleting_an_unknown_conversation_is_404_and_touches_no_spill_folder”,
“test_forking_copies_the_spill_files_to_the_fork”,
“test_a_failed_spill_copy_does_not_break_the_fork”,
“test_a_fork_still_reads_the_original_after_its_source_is_deleted_and_swept”)evals/ của home, một ca chạy lệnh in khoảng 24 000 ký tự rồi hỏi một giá
trị ở giữa (kỳ vọng tool_output_read, tối đa một shell_run)tests/test_tool_call_ids.py (id provider bỏ trống là uuid duy nhất);
tests/test_approval_id_reuse.py (id dùng lại mà approval đã lưu ghi lời gọi khác thì bị
từ chối, kể cả khi chỉ khác tên tool); tests/test_delegate_child_scope.py (agent con chỉ
tìm được qua lời gọi của chính cha nó); tests/test_tools_delegate.py
(“test_another_parents_child_with_the_same_call_id_is_not_reused”)tests/test_route_retry.py (chunk lỗi provider_unavailable giữa stream được hỏi
lại và trả lời, 503/429 và chunk lỗi mã 500/429 được thử lại, 402/400, chunk lỗi không mã
hay mã 400 và stream hỏng không thử lại, lỗi kết nối được thử lại, đã hiện chữ thì không thử
lại, hỏng lần hai thì rơi tuyến kế hoặc báo mọi tuyến hỏng, lần hỏi đã được phục vụ ghi sổ
giá không rõ còn lần bị từ chối trước chunk đầu thì không, lượt chỉ giữ câu trả lời của lần
hỏi lại và không phát route_fallback); tests/test_provider_chain.py (lỗi không tạm thời
vẫn rơi tuyến ngay như trước)tests/test_tool_call_ids.py
(“test_each_piece_of_arguments_comes_back_named_with_the_call_it_belongs_to”: mỗi mảnh mang
vị trí và tên đã ghép của lời gọi, lời gọi ghép ở cuối không đổi,
“test_a_delta_that_brings_no_arguments_is_not_a_piece”,
“test_arguments_sent_ahead_of_the_name_come_back_with_no_name_yet”,
“test_pieces_of_two_calls_keep_their_own_index_and_name_in_the_order_they_came”,
“test_a_delta_that_states_no_index_is_a_piece_of_the_first_call”);
tests/test_tool_call_position.py (mỗi mảnh mang vị trí của lời gọi trong câu trả lời chứ
không mang số provider gửi: stream đánh số từ 1, bỏ cách số hay cả hai vẫn cho vị trí 0 rồi
1, trùng chỗ của lời gọi trong calls(); lời gọi giữ vị trí khi lời gọi sau mở ra; lời gọi
mở trước một lời gọi mang số nhỏ hơn thì từ lúc lời gọi kia mở, mảnh của nó mang đúng chỗ
theo thứ hạng của số, là chỗ nó đứng trong calls(), chứ không theo thứ tự tới; mảnh
tới trước tên cũng được xếp như vậy; lời gọi mới chỉ có id và tên vẫn giữ chỗ của nó;
stream ghi "index": null cho lời gọi duy nhất vẫn được vị trí 0;
“test_the_piece_a_turn_shows_names_the_place_of_its_call_in_the_answer”: qua cả một lượt
với stream đánh số 1 và 2, event tool_call_delta mang index đúng bằng chỗ của lời gọi
trong tool_calls của assistant_message);
tests/test_openrouter.py::test_each_fragment_of_a_tool_call_is_passed_on_as_it_arrives
(mảnh xen với chữ theo đúng thứ tự gửi, Completion cuối y như cũ) và
“test_a_delta_that_brings_words_and_a_fragment_passes_both_on_the_words_first” (một delta
mang cả chữ lẫn mảnh thì mảnh không bị bỏ và đi sau chữ);
tests/test_ollama.py::test_a_local_model_passes_on_the_pieces_of_a_tool_call_too;
tests/test_route_retry.py
(“test_a_route_is_still_asked_again_after_part_of_a_tool_call_arrived”,
“test_a_tool_call_piece_does_not_use_up_the_retry_that_text_would”: mảnh lời gọi không phải
chữ đã hiện, nên tuyến vẫn được hỏi lại); tests/test_provider_chain.py
(“test_scripted_provider_streams_each_tool_call_in_pieces_before_the_completion”,
“test_scripted_provider_streams_no_piece_for_an_answer_that_calls_nothing”);
tests/test_echo_provider.py (model giả phát mảnh 12 ký tự mang tên lời gọi, chữ có dấu
không bị đổi thành mã escape, lượt chỉ gọi tool không còn mảnh chữ rỗng, fake:slow chờ
giữa hai mảnh chữ hay hai mảnh lời gọi và không chờ quanh câu trả lời một mảnh, fake:echo
không bao giờ chờ, mặc định chờ bằng asyncio.sleep)tests/test_draft_preview.py (chỉ artifact_create và artifact_rewrite được xem
trước; mảnh đầu phát ngay dù đồng hồ đọc số nào; mảnh tới trong 3 giây được gom, mảnh tới
đúng 3 giây sau event trước mang ra mọi thứ đã gom, và khoảng chờ tính lại từ event ấy; mảnh
tới trước tên thì chờ tên rồi đi cùng; lời gọi mang tên tool khác, kể cả artifact_import,
bị bỏ hẳn dù tên đổi về sau; hai lời gọi trong một câu trả lời được tính giờ và gom riêng;
phần còn giữ khi lần thử kết thúc bị bỏ chứ không gửi; reset quên cả lời gọi đã bỏ; đồng hồ
mặc định là time.monotonic); tests/test_model_stream.py
(“test_a_canvas_write_is_shown_ahead_of_the_answer_that_carries_it”; phần còn giữ không được
xả sau câu trả lời; bước gọi model đi theo đồng hồ được tiêm; workspace_write với đối số
dài không cho event nào;
“test_an_attempt_cut_off_mid_document_is_called_off_before_the_next_one_shows”: tuyến bị cắt
giữa chừng đối số cho một event tên rỗng mang attempt=1, rồi lần thử mới bắt đầu lại từ
mảnh đầu và mọi event của nó mang attempt=1; lần thử chưa hiện gì thì không có event tên
rỗng nhưng vẫn được đếm; tuyến hỏng hẳn vẫn cho route_fallback và cũng được đếm;
“test_on_the_slow_model_a_document_of_a_few_kilobytes_is_seen_filling_in”: trên fake:slow
một tài liệu vài KB được phát nhiều lần chứ không phải một, và tổng thời gian model chờ
cho cả tài liệu ấy dưới một phút);
tests/test_tool_call_delta_event.py (event tool_call_delta chỉ có type, index, name,
chunk, attempt; “test_the_activity_hub_neither_writes_nor_broadcasts_it”;
“test_it_leaves_a_run_exactly_as_it_was”: apply_event không mở step và không đổi số nào;
“test_a_reply_reads_the_same_with_the_pieces_in_the_chain_or_without_them”: Telegram và job
đọc cùng một câu trả lời; qua API nó là một khối SSE riêng đứng trước assistant_message,
còn lượt gọi tool khác không có khối ấy)web/src/state/writing-previews.test.ts (mảnh đầu mở một bản với khoá riêng, mảnh sau
nối vào và đếm thêm một lần; hai lời gọi trong một câu trả lời tách nhau theo vị trí; event tên
rỗng hay mảnh mang số lần thử khác, lớn hơn hay nhỏ hơn, bỏ mọi bản chưa có lời gọi, lần thử
mới nhận khoá mới;
event tên rỗng của chính lần thử ấy không đổi gì; bindCalls gắn bản với lời gọi đứng đúng vị
trí khi cùng tên tool, bỏ bản mà lời gọi là tool khác, kể cả artifact_rewrite thay cho
artifact_create, bỏ bản không có lời gọi ở vị trí ấy và bỏ bản của bước trước; bước sau mở
bản mới bên cạnh bản đã gắn dù cùng vị trí và cùng số lần thử, vẫn nối tiếp vào bản của chính
nó khi bản đã gắn được viết ở lần thử khác, và lần thử lại của bước sau không đụng tới bản đã
gắn; “what the person put away, where the stream of a turn stops or starts”: việc người đã
cất một bản chỉ còn tính khi lượt đang chờ họ trả lời, không tự có ở nơi chưa ai cất gì, và
qua một lần tải lại chỉ còn tính khi đúng yêu cầu đã chờ ấy vẫn chờ; lượt đã xong thì không
còn bản nào và không còn gì đã cất);
web/src/state/thread-reducer-previews.test.ts (luồng theo từng mảnh mà không đổi thinking
hay chữ đang tới; route_fallback rồi event tên rỗng bỏ bản và giữ dòng báo đổi tuyến;
assistant_message gắn lời gọi và bản ở lại qua tool_call lẫn tool_result;
“drops a preview whose call turned out to be another tool, with all it had read of it”: vị trí
từng mang tên artifact_create mà lời gọi là workspace_write thì không còn bản nào; tám cách
một lượt kết thúc, mỗi cách không để lại bản nào dù bản đã gắn hay chưa; lượt sau, cuộc khác
và lần tải lại không dùng lại khoá cũ);
web/src/state/thread-reducer-put-away.test.ts (luồng nhớ việc người đã cất một bản đang viết
và không đổi gì khác, thẻ vẫn còn; dấu ấy ở lại qua mọi thứ lượt nói tiếp, kể cả khi model bắt
đầu lại; “a canvas put away in a turn that then waits for the person”: dấu còn khi yêu cầu
duyệt đang chờ và stream đã đóng sau nó, còn khi người trả lời rồi lượt viết canvas khác, còn
khi câu trả lời không gửi được, còn sau lần tải lại thấy đúng yêu cầu ấy; mất khi lần tải lại
thấy yêu cầu đã đóng hay thấy một yêu cầu khác, khi người bấm dừng, và khi server báo lượt
xong, bị chặn hay lỗi dù trang còn hiện yêu cầu; tám cách một lượt kết thúc đều xoá dấu, mở
cuộc khác trong lúc đang chờ cũng vậy, và lượt bắt đầu sau đó không nợ gì dấu được đặt khi
không có lượt nào chạy);
web/src/lib/partial-json-content.test.ts (đọc title, kind, id, content ở tầng ngoài
cùng của object chưa đóng: content có ngay khi chuỗi mở, ba khoá kia chỉ có khi chuỗi đóng;
mọi escape của JSON cho đúng chữ JSON.parse cho; ký tự viết bằng hai escape đọc thành một ký
tự, đúng ở cả hai đầu dải của mỗi nửa; escape bị cắt ở cuối thì bỏ lại chờ mảnh sau, kể cả nửa
đầu của ký tự viết bằng hai escape; nửa ký tự không bao giờ đủ thành U+FFFD và đọc tiếp, kể cả
khi chữ theo sau chỉ trông giống đuôi một escape mà không có dấu gạch chéo ngược mở đầu; escape
sai không làm mất phần sau; giá trị không phải chữ bị bỏ qua dù bên trong có ngoặc, nháy hay
khoá trùng tên; khoá lặp lấy lần cuối như server; khoá lạ, kể cả __proto__ và constructor,
không được đọc; thứ không mở bằng { thì không đọc gì dù theo sau là khoá và chữ; object hỏng
chỉ đọc tới chỗ hỏng, và dấu khác đứng ở chỗ của : hay , cũng là chỗ hỏng; chuỗi một
megabyte đọc trong một lượt);
web/src/lib/canvas-writing.test.ts (tiêu đề đi qua đúng phép làm sạch của server, tiêu đề
trống hay quá 200 ký tự thì không có; kind chỉ nhận tên một loại canvas, constructor và
toString không phải; lời viết lại chỉ nêu canvas khi id đủ 12 ký tự hex, lấy tiêu đề và loại
của canvas ấy, và không hỏi tab về id sai dạng; lời tạo mới không nêu canvas nào dù đối số
mang id; kích thước tính theo byte UTF-8);
web/src/state/activity-reducer.test.ts (“takes nothing from the pieces of a canvas being
written: they are no step of the run”)web/src/hooks/use-canvas-writing.test.tsx (thẻ chỉ có cho bản chưa gắn lời gọi; mỗi
mảnh được đọc một lần dù màn hình vẽ lại bao nhiêu lần và danh sách canvas của dock là object
mới ở mỗi lần vẽ; tắt xem trước thì hết thẻ ngay, bật lại thì có lại, không chờ mảnh kế; canvas
mới tự hiện từ mảnh thứ hai có
chữ, không trên màn hẹp kể cả khi màn rộng ra sau, không khi người đang gõ trong một canvas kể
cả khi họ ngừng, không cho lời gọi đã gắn, không thay bản đang hiện, và hai bản sẵn sàng cùng
lúc thì hiện bản đầu; lời viết lại chỉ tự hiện trên chính canvas nó viết lại đang mở, và lời
chưa nêu canvas nào thì không tự hiện dù dock đang giữ canvas, đang ở danh sách hay đang đóng;
bấm xem thì hiện ngay từ mảnh đầu, trên màn hẹp và cả khi đang gõ, thay bản tự hiện, đếm số lần
hỏi và không tự hiện lại về sau; “shows the one asked for in the very drawing that dropped the
one on show”: lời bấm xem tới đúng lần vẽ làm mất bản đang hiện thì bản được hỏi vẫn hiện; rời
khung thì hook báo cho luồng đúng một lần và phần còn lại của lượt im, luồng quên thì bản kế
lại tự hiện; “holds for a screen drawn anew in that turn, which has seen none of it: the thread
remembers”: màn chat dựng lại giữa lượt mà luồng còn nhớ thì bản mới chỉ là thẻ; mỗi lần cất
một bản được bấm xem đều báo lại, còn khung tự đi thì không báo gì; khung
tự đi khi model bắt đầu lại, khi dock chuyển chỗ và không quay lại khi dock trở về, khi tắt xem
trước; nó ở lại suốt lời gọi, rồi mở lặng lẽ đúng một lần canvas mà kết quả nêu dù đối số nói
gì; lời gọi hỏng, bị từ chối hay bị dừng, và kết quả không nêu canvas, chỉ cất khung; không mở
đè lên chữ người vừa gõ trong canvas phía sau, nhưng vẫn mở khi bàn phím chỉ đang ở trong dock
mà người không gõ gì ở đó, vì bàn phím ấy đang ở chính khung sắp nhường chỗ; canvas viết lại
vốn đang mở thì không mở lại; lời gọi hỏng không làm im canvas sau);
web/src/components/canvas/canvas-writing-card.test.tsx (thẻ nói đang viết hay viết lại dưới
tiêu đề tới lúc này hoặc một chữ thay, ký tự ẩn trong tiêu đề hiện thành dấu, loại chỉ có khi
đã biết, kích thước đã viết nằm ở dòng không bị đọc lên mỗi lần đổi và nêu đúng con số, ba
chấm ẩn với trình đọc màn hình, nhãn của nút xem có tên canvas và nút hỏi đúng bản ấy, thẻ
không hiện chữ đang viết);
web/src/components/canvas/canvas-writing-view.test.tsx (đầu khung nêu tên và loại, nói chưa
lưu khi agent còn viết và đang lưu khi lời gọi đã có, ở cùng một dòng status để trình đọc màn
hình đọc lên lúc nó đổi; đầu khung chỉ có nút đóng, và bản không có link, ảnh hay khối mã thì
cả khung chỉ có nút ấy, không ô gõ, không link;
markdown đi qua cùng view của canvas đã lưu, HTML viết trong markdown hiện thành chữ; html,
svg, mermaid hiện mã nguồn kèm dòng nói vì sao và không dựng phần tử nào; loại chưa biết,
code và image là chữ thường; ký tự ẩn hiện thành dấu; bàn phím ở yên khi khung tự hiện,
tới nút đóng khi người bấm xem, không bị kéo lại khi chữ tới thêm, quay lại khi bấm lần nữa,
và không bị lấy khỏi link trong khung; khung theo dòng cuối cho tới khi người cuộn lên);
web/src/components/canvas/canvas-writing-view-offers.test.tsx (bản markdown đang viết có
đúng những thứ canvas đã lưu có trong phần chữ: link mở ở tab khác với noopener noreferrer,
nút giữ ảnh của trang ngoài và nút “Sao chép mã”, ngoài nút đóng; “draws that text exactly as a
saved canvas draws it”: phần chữ là đúng HTML mà view của canvas đã lưu vẽ ra; không có gì để
gõ vào dù bản chứa gì; bản thuộc loại hiện mã nguồn, và bản markdown đã quá dài, chỉ có nút
đóng vì link, ảnh và mã trong chúng là chữ; “is asked about again when a later piece lengthens
the address the person agreed to”: người đã đồng ý với http://a.test/x, mảnh sau nối địa chỉ
thành http://a.test/xy thì ảnh không được tải và nút hỏi lại với địa chỉ mới; mảnh sau không
đụng tới địa chỉ thì ảnh ở yên);
web/src/components/canvas/canvas-writing-view-long.test.tsx (bản markdown dài 99 999 ký tự
vẫn đọc như markdown; từ 100 000 ký tự trở lên, ở đúng ngưỡng, hơn một ký tự hay gấp mấy lần,
khung hiện mã nguồn kèm một dòng nói vì sao và không kèm dòng của trang hay hình vẽ; ký tự ẩn
trong bản dài vẫn hiện thành dấu; code, image và loại chưa biết dài cỡ ấy không có thêm
dòng nào, vì chúng vốn là chữ thường; html, svg, mermaid giữ đúng một dòng như bản
ngắn; “keeps its frame, its place to scroll and the keyboard on its close button”: bản vượt
ngưỡng trong lúc đang hiện thì khung, vùng cuộn và nút đóng vẫn là các phần tử cũ, bàn phím ở
yên trên nút đóng; mảnh sau không đưa bản trở lại markdown và dòng báo không lặp);
web/src/components/canvas/canvas-dock-writing.test.tsx (dock hiện khung trên tab canvas dù
chưa mở gì và trả cột hoạt động lại sau đó; canvas dock đang giữ chờ phía sau cùng chữ người
đã gõ; danh sách bị che; trên màn hẹp khung phủ lên chat với đường quay lại đứng đầu; đường
quay lại, nút Canvas và nút đóng đều rời khung trước khi làm việc cũ; bàn phím ở yên trong ô
chat khi khung tới và khi nó đi, về nút Canvas khi dock đóng lại, ở lại trong cột hoạt động,
và tới control đầu của dock khi canvas thật thế chỗ khung; canvas thứ hai theo dòng cuối của
chính nó); web/src/lib/canvas-dock-state.test.ts (dock đóng vẫn là đang hiện khi có khung);
web/src/components/message-thread-writing.test.tsx (mỗi canvas một thẻ theo thứ tự bắt đầu,
đứng sau chữ đang tới; thẻ thay dòng “đang nghĩ”; không thẻ ở màn không theo việc viết);
web/src/hooks/use-canvas-auto-open-writing.test.ts (lời gọi mà dock đã hiện khi còn viết
không được mở lần nữa ở hook tự mở, các canvas khác của lượt vẫn mở);
web/src/hooks/use-canvas-list-verify.test.ts (kindOf lấy loại từ danh sách rồi từ lần đọc
gần nhất)web/src/app-canvas-writing.test.tsx (mảnh đầu chỉ là thẻ; từ mảnh thứ hai
khung hiện cạnh luồng, ô chat giữ bàn phím; “gives way to the canvas the agent made, in the
same column, having written nothing to the server”: không request ghi nào tới /artifacts,
không ghi canvas đang mở; tin gửi khi khung đang hiện không mang gì của nó; model bắt đầu lại
thì khung đi ngay và bản sau dựng từ đầu; dừng lượt thì không còn gì; nút Canvas cất khung
trước và phần còn lại của lượt im; Escape trên màn rộng không cất khung, vì ở đó khung không
che chat; thẻ gọi khung lại; canvas thật vẫn tự mở sau khi cất;
người đang gõ trong canvas khác thì khung không tự hiện, và bấm thẻ lưu chữ của họ như mọi lần
rời ô soạn: đúng một PUT mang chữ của họ; lưu được thì canvas của agent mở khi xong, lưu
hỏng thì canvas của họ trở lại cùng chữ đã gõ);
web/src/app-canvas-writing-put-away.test.tsx (người cất khung, lượt dừng lại hỏi duyệt một
lời ghi tệp, người duyệt, rồi canvas kế của lượt ấy chỉ là thẻ và thẻ vẫn gọi được khung;
không cất gì thì canvas kế tự hiện như trước; người cất khung, sang Quản lý rồi quay lại chat
giữa lượt: bản ấy vẫn là thẻ ở lúc quay lại và ở mọi mảnh sau, còn không cất thì nó hiện lại;
lượt xong hay bị người dừng thì canvas của lượt sau lại tự hiện);
web/src/app-canvas-writing-safety.test.tsx (lời viết lại hiện trên canvas nó viết lại, canvas
ấy chờ phía sau rồi hiện lại; bấm thẻ của chính canvas ấy trong luồng thì khung được cất và
canvas dock đang giữ hiện lại; canvas khác đang mở hay không mở gì thì chỉ là thẻ; id trong bản
đang viết không vào request nào; bộ nhớ trình duyệt giữ chữ người gõ cho canvas ấy và không có
chữ nào của agent; bản hoá ra là tool khác bị gỡ, đối số của nó không ở lại trong dock; trang
html hiện mã nguồn và không gì trong nó chạy; markdown vẽ như canvas đã lưu; loại lạ là chữ;
tiêu đề theo đúng giới hạn của canvas đã lưu);
web/src/app-canvas-writing-narrow.test.tsx (màn hẹp chỉ có thẻ; bấm xem thì khung phủ chat
và bàn phím ở nút đóng; Escape, nút đóng hoặc đường quay lại trả chat, bàn phím về đúng nút
của thẻ đã mở khung chứ không về nút Canvas; Escape rời khung trước rồi mới cất canvas dock
đang giữ; canvas thật phủ chat khi người đang xem, và
không phủ khi họ không xem);
web/src/components/canvas/canvas-dock-opener.test.tsx (dock ở cả hai dạng, lớp phủ và cột:
lúc dock mở, hook nhớ bàn phím đang ở đâu ngoài dock, và lúc đóng trả về đó nếu chỗ ấy còn
trong trang: nút của thẻ trong luồng, với bản đang viết lẫn canvas đã lưu; ô chat khi một lần
chạm không lấy bàn phím đã mở dock; nút ấy không còn thì về nút Canvas; bàn phím không ở đâu
lúc mở thì về nút Canvas, không về nút của một lần mở trước; người đã tự đưa bàn phím về ô
chat trước khi đóng thì nó ở yên đó);
web/src/app-canvas-writing-keyboard.test.tsx (luật tự hiện hỏi typing() của dock chứ không
hỏi editing(): bàn phím ở một nút của canvas đang mở, ở một tab của cột, trong danh sách
canvas hay trong cột hoạt động khi chưa mở gì, không gõ chữ nào, thì bản đang viết vẫn chỉ là
thẻ, bàn phím ở yên và không có PUT nào; bàn phím về ô chat rồi thì bản ấy vẫn không tự
hiện, thẻ vẫn gọi được khung; bàn phím ở ô chat và canvas đang mở không có chữ gõ dở thì
khung hiện đè lên như trước; màn 1000 px nới ra 1440 px khi bàn phím còn ở canvas vừa phủ
chat thì mảnh kế không làm khung hiện, còn bàn phím ở ô chat thì khung hiện ở mảnh kế);
web/src/app-canvas-writing-handover.test.tsx (lúc lời gọi xong, khung nhường chỗ theo
editing() chứ không theo typing(), và mỗi lần dock được bảo mở đều được đếm qua một lớp
bọc quanh useCanvasDock thật: màn rộng, canvas của agent được mở đúng một lần, lặng lẽ, bởi
hook đã hiện nó, và hook tự mở không mở lại ở lúc ấy hay ở phần còn lại của lượt; bàn phím ở
nút đóng của khung hay ở một tab của cột thì canvas vẫn mở; chữ người gõ mà lần lưu bị từ
chối thì không gì mở canvas của agent, kể cả sau khi lần lưu lại thành công và bàn phím đã về
ô chat; màn hẹp, người đang xem thì canvas mở đúng một lần phủ chat và bàn phím tới đường
quay lại, người chỉ có thẻ thì không gì được mở, và chữ chưa lưu được thì canvas của họ vẫn
phủ chat cùng chữ ấy)web/e2e/canvas-writing.spec.ts (màn rộng: khung hiện dần cạnh luồng, ô chat giữ bàn
phím, rồi canvas thật thế chỗ; trang html là mã nguồn và window.pwned không được đặt; “a
page being written as its source keeps its newest line in sight until the person scrolls up”:
bản hiện dưới dạng mã nguồn dài 300 rồi 450 dòng thì dòng mới nhất nằm trong màn hình và dòng
đầu thì không, vì vùng cuộn là thân khung như với markdown được vẽ chứ không phải khối mã;
người cuộn lên rồi thì 150 dòng kế không kéo khung xuống nữa; điện
thoại 390×844: chỉ có thẻ cho tới khi chạm, khung phủ cả màn, nút cao ít nhất 40 px, Escape
rời khung và bàn phím về nút của thẻ, mở bằng Enter rồi đóng bằng nút đóng cũng về nút ấy);
web/e2e/canvas-writing-card.spec.ts (dòng trạng thái của thẻ: ở 390 px kích
thước xuống hàng riêng và hàng ấy không mở đầu bằng dấu chấm ngăn cách, dấu chấm nằm ngoài
vùng dòng được vẽ; ở 1440 px chữ và kích thước cùng một hàng, dấu chấm hiện trọn giữa hai
phần). Luồng còn mở của các test này dựng bằng web/src/test/canvas-writing-turn.ts
(vitest), web/e2e/writing-turn.ts và web/e2e/live-stream.ts (e2e, vì route của Playwright
trả cả thân một lần); yêu
cầu duyệt đẩy vào một luồng còn mở được web/src/test/fake-backend.ts ghi lại trên cuộc trò
chuyện như server làm, để lời duyệt theo sau tìm thấy nóoff, và trình duyệt không cho lưu thì lựa chọn vẫn có hiệu lực
trong tab kèm dòng nói rõ
web/src/hooks/use-live-preview.test.tsx (mặc định bật và không lưu gì; tắt thì lưu
đúng một chữ off dưới canvas.livePreview, bật lại thì xoá; chữ khác off vẫn là bật; mọi
nơi đọc trong tab thấy cùng một lựa chọn; lựa chọn ở tab khác tới qua event storage, và hook
thôi nghe khi không còn ai đọc; “asks a reader that is gone nothing more”: nơi đọc đã rời đi
không còn được báo, lựa chọn sau đó vẫn được lưu mà không lần đọc bộ nhớ nào xảy ra thay cho
nó, và bao nhiêu nơi đọc đến rồi đi thì số lần đọc cho nơi còn lại vẫn như khi chỉ có một
mình nó; trình duyệt từ chối lưu thì lựa chọn vẫn được nhận, tabOnly
bật, nơi đọc kế tiếp trong tab vẫn thấy nó, nó đứng trên thứ trình duyệt đang lưu, và được bỏ
khi trình duyệt lại lưu được);
web/src/components/canvas/live-preview-setting.test.tsx (ô chọn gọi theo tên, mặc định bật,
có dòng “chỉ trên thiết bị này”, nằm trong thẻ Canvas; bấm thì ghi rồi xoá chữ off; thiết bị
đã tắt thì hiện là tắt; trình duyệt từ chối lưu thì ô vẫn đổi và có dòng báo chỉ giữ trong tab);
web/src/screens/manage-screen.test.tsx (“offers the switch for watching a canvas be written
ahead of the machine’s settings”, “keeps that switch off the … section” cho chín mục còn lại)web/src/app-canvas-writing-off.test.tsx (thiết bị đã tắt: không thẻ, không
khung, dòng “đang nghĩ” và dòng trạng thái như trước mảnh đầu, ô chat giữ bàn phím; canvas agent
viết xong vẫn tự mở cạnh chat trên màn rộng và không request ghi nào; màn hẹp cũng không có
thẻ; tắt bằng công tắc trong Cài đặt thì chỉ chữ off được lưu và canvas viết sau đó không
hiện; bật lại thì canvas kế tiếp hiện dần)tests/test_tool_args_invalid.py (tham số bị cắt giữa chừng báo vị trí chỗ dừng
chứ không phải chỗ chuỗi bắt đầu, lỗi giữa chừng báo vị trí và vài chục ký tự quanh đó với
ký tự điều khiển viết thành <U+000A>, escape \u sai gần cuối không bị coi là bị cắt,
JSON không phải object được gọi theo kiểu, tham số đúng hay rỗng không bị đánh dấu, chi
tiết không bao giờ lên wire, lời gọi hỏng lưu và đọc lại được còn lời gọi thường giữ đúng
dạng hàng cũ, lời gọi hỏng không chạy mà trả lỗi rồi lượt chạy tiếp, ask_user và tool cần
duyệt có tham số hỏng không hỏi ai); tests/test_openrouter.py
(“test_malformed_tool_arguments_complete_as_an_invalid_call”)tests/test_openrouter.py
(“test_a_tool_call_field_that_is_not_text_is_a_malformed_stream”, hai ca: đối số là object,
tên là mảng)tests/test_tool_args_cut_off.py (stream dừng với finish_reason length chỉ
đánh dấu lời gọi cuối, lời gọi hỏng trước đó hay câu trả lời dừng bình thường không bị đánh
dấu, lời gọi bị cắt nhận lời dặn chia nhỏ chứ không phải “gửi lại”, sự kiện câu trả lời mang
chi tiết chỗ hỏng của từng lời gọi, sáu lần hỏng khác chỗ không bị nhắc lặp hay dừng lượt,
cùng một lời gọi hỏng ba lần liền vẫn bị nhắc, lỗi tool dài hơn trần bị cắt như mọi kết quả)/api/inbound không nhận tên kênh nội bộ làm source
tests/test_api_inbound.py
(“test_a_relay_cannot_borrow_the_name_of_one_of_the_servers_own_channels”: chat, telegram,
job, job:<id>, delegate:<…>, web, memory:<…> bị 422 và không có run hay cuộc trò
chuyện nào được tạo; “test_any_other_source_is_recorded_on_the_run”: api, selftest,
slack:team, api:zalo vẫn chạy và run ghi đúng nhãn)tests/test_untrusted_content.py (mỗi nhóm đuôi của bảng ghim sẵn có đúng
Content-Type, inline hay attachment, CSP sandbox trừ PDF; .PNG viết hoa như .png;
HTML, .xht, .rss, .atom, .ts, tệp không đuôi đều là application/octet-stream tải về;
SVG tải về khi mở; mọi response có nosniff và CORP same-origin; tên tệp chữ Việt, Nhật, có
ngoặc kép, xuống dòng, / hay chỉ có emoji ra header mã hoá được bằng latin-1, có tên ASCII
dự phòng và filename* giải mã lại đúng tên; bảng của canvas (canvas_shown): canvas chữ là
text/plain mở tại chỗ và tải về khi yêu cầu, svg khi xem là image/svg+xml tải về còn khi
lưu là chữ, ảnh theo kiểu mà byte của nó cho, byte không nhận ra là application/octet-stream
không bao giờ mở tại chỗ);
tests/test_server_agents_activity_jobs.py::test_agent_files_are_served_only_from_the_workspace
(chỉ tệp trong workspace, kể cả qua symlink)tests/test_artifact_kinds.py (mỗi loại là chữ hoặc nhị phân, mỗi loại một trần,
loại lạ bị từ chối theo tên, lỗi kích thước là ValueError để một chỗ trả lời được mọi đầu
vào sai, loại tạo bằng tay là các loại chữ theo thứ tự của bảng chứ không theo tập,
sniff_image nhận PNG, JPEG, GIF và WebP theo vài byte đầu rồi trả đuôi, còn RIFF không có
WEBP, chữ cắt ngắn, trang hay PDF thì không, prepare kiểm byte của ảnh sau khi kiểm cỡ nên
ảnh quá trần vẫn bị từ chối vì cỡ,
“test_a_title_is_one_line_of_visible_text”: xuống dòng thành dấu cách, bỏ ký tự
điều khiển, bidi và tag nhưng giữ joiner của emoji, tiêu đề rỗng bị từ chối, giới hạn 200 ký
tự đo sau khi làm sạch, ngôn ngữ là một tên ngắn viết thường tối đa 40 ký tự, ngôn ngữ có
dấu cách, xuống dòng hay ký tự ẩn bị từ chối); tests/test_artifact_guards.py (mọi đường đặt
tiêu đề đều lưu bản đã làm sạch, tiêu đề sai làm hỏng cả lần ghi, ngôn ngữ sai không tạo
canvas, tác giả không phải user hay agent:<id> với mã agent hợp lệ bị từ chối, người tạo
phải khớp tác giả: "" cho người, <id> cho agent:<id>, lần ghi vượt trần của tác giả bị
từ chối và không lưu gì, người được cả 1 GiB còn agent dừng trước một phần mười
(“test_agents_stop_short_of_the_ceiling_so_the_person_can_still_save”), chạm đúng trần vẫn
được, lần lưu gộp vào loạt chỉ tính phần thêm vào); tests/test_store.py
(“test_a_delete_that_fails_halfway_leaves_the_conversation_whole”);
tests/test_artifact_schema.py (DB mới có đủ bảng và index, payload là các cột cuối, tổng
kích thước cộng từ index artifact_versions_by_size mà không đọc trang chứa nội dung, DB cũ
có thêm bảng canvas mà giữ nguyên tin nhắn, mở lần hai không đổi gì);
tests/test_artifact_links.py (liên kết lại giữ seen_version, seen_version không lùi,
không lưu liên kết hay focus nào cho cuộc trò chuyện hoặc canvas không tồn tại, kể cả lần
ghi muộn sau khi xoá, một phiên bản được ghim khi có cuộc trò chuyện đã thấy hoặc đang đọc
nó, đánh dấu chưa commit được huỷ cùng giao dịch của bên gọi, focus giữ canvas đang mở và
đoạn được chọn, xoá cuộc trò chuyện bỏ liên kết và focus mà giữ canvas);
tests/test_artifact_read_cursor.py (trang từ đầu một bản mới hơn bắt đầu con trỏ lại,
trang bắt đầu trong phần đã đọc thì nối dài, trang nhảy cóc hay bản cũ không đổi gì, chỉ đọc
liền tới cuối mới tính là đã thấy,
“test_pages_taken_from_two_versions_never_count_as_one_whole_read”, bản rỗng tính là đã thấy
sau một lần đọc, đọc trọn bản cũ không kéo seen_version lùi);
tests/test_artifact_versions.py
(“test_thirty_saves_in_a_minute_that_nobody_has_seen_keep_one_version”,
“test_a_burst_that_never_pauses_still_closes_its_row_after_the_window”,
“test_a_clock_set_back_does_not_hold_a_burst_open”,
“test_a_version_an_agent_has_seen_is_never_folded_into_the_next_save”,
“test_a_version_an_agent_is_reading_page_by_page_is_never_folded_away”,
“test_a_save_that_fails_halfway_through_folding_loses_nothing”; số phiên bản không bao giờ
dùng lại, base cũ bị từ chối kèm bản mới nhất, apply đọc, sửa và ghi trong một bước, CR và
CRLF lưu thành LF trên mọi đường ghi, các ký tự ngắt dòng khác giữ nguyên trong dòng để dòng
chỉ tách theo LF, khôi phục giữ các bản sau);
tests/test_artifact_store.py (kích thước tính bằng byte UTF-8, payload sai loại hay vượt
trần không lưu gì, byte không phải ảnh không vào được qua tạo hay ghi, trần đo trên chữ đã
đổi xuống dòng, thứ không tồn tại là KeyError,
“test_a_version_gone_from_a_canvas_that_is_there_names_the_newest”: bản đã bị gộp hay chưa
từng có là VersionGone kèm số bản mới nhất, xoá canvas bỏ phiên bản, liên kết và focus của
nó, “test_list_matches_the_title_regardless_of_case_and_accents”, hai canvas cùng giây xếp
theo thứ tự tạo, “test_on_change_hears_every_write_after_commit_outside_the_lock”,
“test_a_failing_on_change_is_logged_and_the_write_stands”)tests/test_artifact_source_store.py
(“test_a_write_records_where_its_version_came_from_in_the_one_change”: write(source=…) đổi
nguồn cùng lúc với phiên bản mới, một event mang cả hai,
“test_a_write_that_names_no_source_keeps_the_one_recorded”: ghi, apply, khôi phục không nêu
nguồn thì nguồn giữ nguyên, “test_a_refused_write_leaves_the_source_as_it_was”,
“test_setting_the_source_adds_no_version_and_keeps_the_time_of_the_last_change”: set_source
không thêm bản, không đổi updated_at, có event sau commit và ngoài khoá,
“test_setting_the_source_of_a_canvas_that_is_gone_raises_key_error”,
“test_typing_right_after_a_reimport_keeps_the_imported_version” và
“test_a_reimport_right_after_a_burst_of_typing_keeps_what_was_typed”: dòng import không bị
tự lưu của người gộp mất và cũng không gộp mất loạt tự lưu,
“test_a_picture_canvas_takes_new_bytes_and_keeps_the_old_ones”,
“test_a_write_whose_payload_does_not_fit_the_kind_adds_no_version”: chữ cho canvas ảnh hay
byte cho canvas chữ là PayloadMismatch,
“test_restoring_a_picture_brings_back_its_exact_bytes”);
tests/test_artifact_authors.py
(“test_a_version_imported_from_a_file_is_named_as_one_whoever_imported_it”: bản nhập của
người lẫn của agent đứng riêng khỏi các bản tự viết của cùng tác giả, các bản nhập liền nhau
là một nhóm);
tests/test_canvas_note_one_line.py
(“test_a_picture_that_changed_is_told_in_one_line_and_stays_unseen”: canvas ảnh đổi thì ghi
chú là một dòng tác giả, không diff, seen đứng yên,
“test_a_reimport_from_the_web_is_told_in_one_line_and_stays_unseen”: bản người nhập lại từ
tệp cũng một dòng, artifact_rewrite sau đó vẫn bị từ chối);
tests/test_api_artifact_pictures.py
(“test_saving_text_onto_a_picture_canvas_is_422_and_the_picture_stays”)tests/test_artifact_store_written_in.py
(“test_a_canvas_the_agent_began_and_one_it_only_changed_are_both_listed”: written_in nêu cả
canvas agent tạo lẫn canvas có sẵn mà agent chỉ sửa, kèm tiêu đề hiện tại, và cuộc trò chuyện
gốc không có dòng nào,
“test_each_conversation_lists_what_was_written_in_it_alone”: canvas được ghi ở hai cuộc trò
chuyện thì mỗi bên nêu phiên bản lớn nhất ghi ở bên đó, không phải bản mới nhất của canvas,
“test_what_a_person_saved_in_the_conversation_is_not_the_agents_writing”: bản người lưu trong
cùng cuộc trò chuyện bị bỏ, canvas chỉ có bản của người không có dòng, phiên bản nêu là bản
lớn nhất agent ghi dù người đã sửa sau,
“test_a_deleted_canvas_is_no_longer_listed”,
“test_canvases_come_in_the_order_each_was_first_written_within_one_second”: cùng một giây vẫn
xếp theo lần ghi đầu, không theo mã, tiêu đề hay lần ghi mới nhất,
“test_a_file_the_agent_imports_is_its_writing_as_a_new_canvas_or_a_new_version”: bản agent
nhập bằng artifact_import được tính, canvas mới lẫn phiên bản mới,
“test_carrying_a_canvas_to_or_from_a_file_unchanged_writes_nothing”: xuất, nhập không đổi và
ghi nguồn không thêm phiên bản nên không sinh dòng nào,
“test_a_file_the_person_reads_again_on_the_web_is_not_the_agents_writing”: “Nhập lại” trên
web là bản của người, không thuộc cuộc trò chuyện nào);
tests/test_artifact_store_usage.py
(“test_the_canvases_are_counted_with_the_stores_lock_held”: sizes và written_in chạy câu
truy vấn khi đang giữ khoá của store, nên không đếm phiên bản của một lần ghi còn dang dở,
“test_a_canvas_named_as_written_cannot_be_changed_by_its_reader”)tests/test_artifact_version_range.py
(“test_a_version_number_at_or_beyond_the_64_bit_edge_is_gone_and_names_the_newest”: mọi số từ
2**63 trở lên hay từ -(2**63) - 1 trở xuống, cùng hai số sát mép còn nằm trong 64 bit, đều
là VersionGone kèm số bản mới nhất chứ không phải OverflowError của driver,
“test_a_canvas_that_is_not_there_stays_a_missing_canvas_whatever_version_is_asked”: canvas
không có vẫn là KeyError gọi đúng mã canvas chứ không thành “bản không còn”,
“test_a_read_of_a_version_too_large_to_exist_is_404_with_the_newest_number”: xem một bản,
raw và trang chạy với số đó đều 404 kèm head_version,
“test_a_restore_of_a_version_too_large_to_exist_is_404_and_writes_nothing”: khôi phục bị 404
và không thêm bản nào,
“test_a_save_on_a_version_too_large_to_exist_is_a_conflict_and_writes_nothing”: lưu với
base_version đó là 409 kèm bản mới nhất, canvas không đổi (số này không bao giờ xuống SQL
nên test chỉ ghim điều đó),
“test_the_tool_reads_a_version_too_large_to_exist_as_one_that_is_gone”: artifact_read trả
đúng câu “bản đó không còn, bản mới nhất là …” cho cả số lẫn chuỗi số, không phải “tool
lỗi”). Việc chặn nằm ở một chỗ là ArtifactHistory.version, nơi mọi đường gọi tên một phiên
bản đều đi qua, nên không route hay tool nào cần giới hạn riêngtests/test_artifact_unstorable_text.py
(“test_the_store_refuses_half_a_character_saying_which_part_and_writes_nothing”: nửa ký tự
(surrogate lẻ, ví dụ nửa đầu của một emoji) trong nội dung hay tiêu đề, qua tạo, lưu, apply
và đổi tên, đều là UnstorableText kèm câu nói rõ phần nào sai và kho không đổi gì,
“test_a_request_with_half_a_character_is_422_saying_which_part_and_writes_nothing”: tạo, lưu
và đổi tên qua REST đều 422 kèm đúng câu đó, không ghi gì,
“test_a_whole_emoji_sent_as_an_escaped_pair_is_kept_by_every_write”: emoji đủ cặp, mà JSON viết
thành hai \u escape, vẫn được lưu ở cả ba đường,
“test_the_tool_refuses_half_a_character_saying_which_part_and_writes_nothing”: artifact_create,
artifact_edit (khớp đúng, khớp nới, tiêu đề, và lần sửa không đổi gì mà vẫn gửi tiêu đề) và
artifact_rewrite trả đúng câu đó chứ không phải “tool lỗi”, canvas không đổi và khoá của kho
được nhả, language có nửa ký tự vẫn nhận lời từ chối language có sẵn,
“test_a_whole_emoji_is_written_by_every_tool”,
“test_an_old_passage_with_half_a_character_is_one_the_canvas_does_not_hold”: old có nửa ký
tự chỉ là đoạn không tìm thấy, vì canvas không bao giờ chứa nó). Việc chặn nằm ở utf8_size,
một chỗ tính cỡ UTF-8 cho nội dung, tiêu đề và new của lần sửa, nên không đường ghi nào cần
kiểm riêng; hai hàng của tests/test_artifact_errors.py và tests/test_artifact_scope.py ghim
chỗ bảng 422 và canvas_errors nhận ra UnstorableTexttests/test_artifact_tools.py (build_artifact_tools trả năm tool, không tool nào
hỏi duyệt; hai tool mang tệp vào và ra nằm ở mục nhập, xuất bên dưới; mô tả đưa tài
liệu người sẽ sửa dần vào canvas dù chỉ vài dòng và nói cách thêm chữ bằng một lần sửa, tạo lưu canvas
dưới tên agent và không trả lại nội dung, con được giao việc chia sẻ canvas nó tạo với gốc,
loại agent không ghi được (ảnh) bị từ chối, enum của tool là năm loại chữ, tạo html, svg hay
mermaid như mọi canvas và html được lớn hơn tài liệu nhưng có trần riêng, mô tả của tool tạo
(và chỉ nó) nói cho trang html biết khung cách ly không cho gì: không localStorage, không
mạng ngoài ba CDN, không alert, không mở cửa sổ,
“test_a_turn_from_the_web_chat_telegram_or_a_job_writes_and_reads_canvases”: lượt web chat,
Telegram và job tạo, đọc, sửa, viết lại và liệt kê được,
“test_a_turn_from_the_inbound_api_writes_no_canvas_yet_reads_one”: lượt API chỉ đọc và liệt
kê, “test_a_delegated_child_writes_when_the_turn_its_chain_began_from_could”: con ghi được
khi chuỗi bắt đầu từ web chat, Telegram hay job, không ghi được khi bắt đầu từ API hay không
có gốc, canvas master đã đọc không vào tầm của con, lần đọc của một con
không mở rộng tầm của con kế tiếp, canvas do người tạo chỉ vào tầm qua liên kết, canvas không
tồn tại trả lời như canvas ngoài tầm,
“test_a_turn_writes_at_most_thirty_versions_of_one_canvas”: lần ghi không đổi gì không tính
và canvas khác có bộ đếm riêng, một lượt tạo tối đa 30 canvas, danh sách mới nhất trước theo
giờ của chủ và đánh dấu canvas có bản cuộc trò chuyện chưa thấy, lọc theo tiêu đề và master
liệt kê mọi canvas, mọi lời từ chối của kho nói phải làm gì thay vào đó, đối số không phải
chuỗi bị từ chối trước khi chạm vào gì, mọi tool cần cuộc trò chuyện của lượt,
“test_each_write_opens_with_its_tag_and_a_read_or_a_list_never_does”);
tests/test_artifact_read.py
(“test_the_pages_of_a_canvas_fit_the_cap_and_join_back_into_its_text”, ghi chú của hook bị cắt
trước bất kỳ dòng nào của trang, dòng dài hơn một trang bị cắt và trang sau bắt đầu ở dòng kế,
phần bị cắt chưa ai đọc nên bản đó không tính là đã thấy,
“test_only_a_read_from_the_first_line_to_the_last_makes_a_version_seen”, người lưu giữa hai
trang thì lần đọc vẫn ở bản nó bắt đầu, bản đã bị gộp mất thì báo bản mới nhất, mỗi trang nêu
ai viết các bản chưa thấy kể cả bản người khôi phục, trang theo số dòng đặt trước và bản mới
nhất khi không nói bản nào,
dòng quá cuối và loại không đọc được thành chữ bị từ chối, canvas code hiện ngôn ngữ cạnh loại);
tests/test_artifact_edit_tools.py (sửa trích lại các dòng đã đổi và tính bản mới là đã thấy,
“test_an_edit_says_how_long_the_canvas_now_is_as_a_read_counts_it”: kết quả sửa nêu cỡ mới
của canvas, số dòng đúng như trang đọc đếm,
sửa đè lên lần lưu của người nêu người và để bản đó chưa thấy,
“test_the_nearest_passage_to_a_missed_edit_is_looked_for_outside_the_lock”, diff của sửa vừa
mọi trần và để ghi chú của hook bị cắt trước, sửa không đổi gì không thêm bản mà vẫn đổi tiêu
đề, “test_a_rewrite_needs_the_canvas_read_whole_first”: liệt kê, đọc trang đầu hay nhảy tới
trang cuối đều không đủ, viết lại đè lên lần lưu của người bị từ chối kèm phần họ đổi,
“test_a_conflict_over_many_writers_names_the_newest_six_and_any_restore”, xung đột vừa mọi
trần, viết lại từ bản mới nhất đã đọc trọn thì ghi,
“test_what_the_agent_created_or_rewrote_it_rewrites_without_reading_back”: bản agent vừa tạo
hay viết lại thì viết lại được ngay mà không đọc lại, đúng như mô tả tool nói, và mô tả tạo
dặn sửa chính canvas đã có, viết lại chỉ đổi kiểu xuống
dòng là không đổi gì, con sửa hay viết lại thì chia sẻ canvas với gốc,
“test_a_kind_agents_do_not_write_is_refused_only_once_the_canvas_is_in_reach”: canvas ảnh của
người, trong tầm thì bị từ chối là loại đóng, ngoài tầm thì như không tồn tại, sửa và viết lại
html, svg, mermaid như mọi canvas, trang html 1 MB viết liền một dòng thì sửa theo đoạn được
còn viết lại bị từ chối kèm lời dặn dùng artifact_edit, diff trích
dãy backtick có rào dài hơn, sửa khớp hai chỗ bị từ chối trừ khi thay mọi chỗ);
tests/test_artifact_unchanged_rename.py
(“test_a_canvas_deleted_before_an_unchanged_write_renames_it_reads_as_not_found”: canvas bị
xoá giữa lần ghi không đổi gì và lần đổi tiêu đề đi kèm thì artifact_edit lẫn
artifact_rewrite trả đúng câu không tìm thấy canvas chứ không phải “tool lỗi”);
tests/test_canvas_tool_wiring.py
(“test_an_agent_with_no_allow_list_gets_the_seven_canvas_tools”: agent không có allow-list,
master hay không, nhận đủ bảy tool theo thứ tự, năm tool cũ rồi artifact_import,
artifact_export, và chỉ artifact_export hỏi duyệt,
“test_an_allow_list_keeps_out_the_canvas_tools_it_does_not_name”, master
liệt kê mọi canvas theo giờ của chủ còn agent khác thì không,
“test_an_export_lands_only_in_the_agents_own_workspace_and_write_paths”: artifact_export
lắp từ profile chỉ ghi trong workspace và write_paths của chính agent đó, trang đọc vừa
trần đầu ra của chính agent)artifact_import, canvas ra tệp bằng
artifact_export, chữ không đi qua model
tests/test_artifact_source.py (đọc tệp nguồn: chữ về đúng dạng kho sẽ giữ, bỏ BOM,
xuống dòng LF, mã băm của đúng chữ đó, ảnh về nguyên từng byte, đường dẫn thoát workspace
bằng .., bằng đường dẫn tuyệt đối hay ~người-không-có bị từ chối, đường dẫn tuyệt đối
trong workspace và symlink người đặt trong workspace thì đọc được như workspace_read, tệp
không có là không tìm thấy, thư mục bị từ chối,
“test_a_pipe_is_refused_without_waiting_for_a_writer”: FIFO bị từ chối ngay vì tệp được mở
không chặn, test giữ thời hạn và tự mở đầu ghi ở finally nên không treo được,
“test_a_device_behind_a_link_is_refused”,
“test_a_file_over_the_cap_is_refused_before_a_byte_is_read”,
“test_a_file_that_grew_after_it_was_measured_is_refused_one_byte_past_the_cap”: đọc nhiều
nhất trần cộng một byte, tệp đúng bằng trần thì đọc trọn,
“test_bytes_that_are_not_utf8_text_are_refused_rather_than_patched”: byte hỏng hay byte NUL
không bị thay bằng ký tự thế chỗ, byte không mở đầu như ảnh nào thì canvas ảnh từ chối);
tests/test_artifact_source_kinds.py (đuôi cuối cùng định loại, không kể hoa thường, đuôi lạ
bị từ chối kèm lời dặn truyền kind, code lấy ngôn ngữ theo đuôi và để trống khi không biết);
tests/test_artifact_source_ref.py (đường dẫn có ký tự điều khiển, ký tự đổi chiều chữ,
U+2028, nửa cặp surrogate đứng lẻ, quá 1.024 ký tự hay rỗng bị từ chối, đường dẫn thường đi
qua nguyên vẹn,
“test_a_source_names_the_agent_and_the_path_under_its_workspace”: source_for và
parse_source đi vòng đúng, tách ở dấu / đầu tiên, nguồn tính từ gốc workspace chứ không từ
gốc máy, “test_a_source_is_never_built_from_a_name_that_hides_something”: tên thật trên đĩa
cũng được kiểm, URL, chuỗi rỗng và nguồn thiếu tiền tố workspace: parse ra không có gì,
web_url chỉ nhận http và https có
host, không khoảng trắng, không nửa surrogate lẻ, không quá 2.000 ký tự,
“test_a_link_that_carries_a_login_is_refused_in_words_that_say_why”: link có tên đăng nhập hay
mật khẩu trước dấu @ của tên máy chủ bị từ chối bằng câu riêng, không chép lại link, còn
@ trong đường dẫn, câu truy vấn hay phần sau # thì vẫn qua,
“test_each_file_a_page_points_at_is_counted_once_and_a_link_that_loads_none_is_not”);
tests/test_artifact_import.py
(“test_a_file_becomes_a_canvas_that_remembers_where_it_came_from”: canvas có source, tác giả
agent:<id>, cuộc trò chuyện đã thấy bản 1, kết quả là thẻ, tên tệp, cỡ, 12 ký tự mã băm và
dòng nguồn, không có đường dẫn của máy, con chia sẻ canvas nó nhập với gốc của chuỗi, tiêu đề
mặc định lấy tên tệp, trang index lấy tên thư mục chứa nó, tên dài được ghép dấu trước rồi
mới cắt ở 200 ký tự, title trắng tính như không gửi,
“test_the_result_tells_of_a_page_without_a_word_the_file_holds”: chuỗi đánh dấu trong chữ,
trong src= và trong href= của tệp đều không xuất hiện ở kết quả, các tệp trang trỏ tới chỉ
được đếm, cảnh báo đó chỉ có với html và svg, kind gửi kèm thắng đuôi tệp và code lấy ngôn
ngữ theo đuôi, đối số tuỳ chọn gửi trắng tính như bỏ trống, ảnh vào nguyên từng byte và dòng
nguồn không mời đọc, source_url được giữ làm nguồn,
“test_an_absolute_path_inside_the_workspace_is_stored_relative_to_it”);
tests/test_artifact_import_guards.py
(“test_a_turn_on_a_channel_with_no_canvas_is_refused_before_the_file_is_read”: lượt API bị
từ chối, tệp không được đọc, không canvas nào được tạo,
“test_a_telegram_turn_and_a_job_bring_a_file_in_as_a_canvas”: lượt Telegram và job mang tệp
vào thành canvas gắn với cuộc trò chuyện của lượt,
“test_a_turn_that_made_its_share_of_canvases_imports_no_more” và
“test_a_turn_that_wrote_its_share_of_one_canvas_imports_into_it_no_more”: lần thứ 31 bị từ
chối mà không đọc tệp, kind lạ, URL xấu, đường dẫn giấu ký tự hay chỉ có khoảng trắng, đường
dẫn hay link mang nửa surrogate lẻ bị từ chối trước khi đọc, source_url mang tên đăng nhập
hay mật khẩu cũng vậy và không thành nguồn của canvas có sẵn
(“test_a_link_with_a_login_never_becomes_the_source_of_a_canvas_that_is_there”), tệp không đọc được thì không tạo canvas và không trừ ngân sách, tệp không
đổi được loại của canvas đã có, canvas ngoài tầm trả lời như canvas không tồn tại kể cả khi
kind gửi kèm lệch); tests/test_artifact_import_outside.py
(“test_a_path_out_of_the_workspace_is_told_so_whatever_its_suffix_or_kind”: đường dẫn ra ngoài
workspace (.., tuyệt đối, ~tên) nhận câu ra ngoài workspace chứ không phải câu về đuôi
tệp, có hay không có kind, kể cả khi kind gửi kèm không phải loại canvas nào (không
nhận câu về loại lạ), và không tệp nào bị mở,
“test_a_path_out_of_the_workspace_is_told_so_before_the_canvas_it_was_to_go_into”: cả khi
nhập vào canvas có sẵn, kind lệch hay canvas ngoài tầm, không trừ ngân sách,
“test_a_link_inside_the_workspace_is_still_followed_to_where_it_leads”: liên kết người đặt
trong workspace vẫn được đi theo như workspace_read, đường dẫn tuyệt đối rơi vào trong
workspace vẫn đọc được); tests/test_artifact_import_replace.py
(“test_a_file_replaces_the_newest_version_the_conversation_has_seen”: bản mới của agent mang
ghi chú import, seen lên bản mới, language gửi kèm không đổi gì,
“test_a_file_that_changes_nothing_adds_no_version_and_opens_no_gate”: thẻ có unchanged,
không trừ ngân sách, seen đứng yên, canvas vẫn gắn với cuộc trò chuyện mà không được chia
sẻ, title mới vẫn đổi tên, nguồn đổi thì được ghi lại bằng một dòng, canvas ảnh chỉ thêm
bản khi byte khác,
“test_a_file_does_not_go_over_versions_the_conversation_has_not_seen”: lời từ chối nêu ai
viết các bản đó và không ghi, không đếm, không đánh dấu đã thấy gì,
“test_replace_goes_over_unseen_versions_and_leaves_them_unseen”: replace: true ghi đè, nêu
bản của ai bị thay và rằng chúng còn trong lịch sử, seen đứng yên nên ghi chú lượt sau vẫn kể
các bản đó, “test_a_change_made_while_the_file_was_read_is_not_written_over”: bản mới nhất
được tra sau khi đã có tệp trong tay); tests/test_artifact_import_args.py
(“test_only_a_code_canvas_takes_a_language_from_the_suffix”: tệp .py nhập với
kind: markdown không mang ngôn ngữ nào,
“test_a_title_and_a_link_sent_with_a_file_are_recorded_in_the_write_that_replaces”: title
và source_url gửi kèm lần nhập đè được ghi trong chính lần ghi đó, một bản duy nhất, bản
ấy thuộc cuộc trò chuyện đã nhập, dòng nguồn nêu đúng link,
“test_a_link_sent_with_a_file_that_changes_nothing_becomes_the_source”: tệp không đổi gì thì
link vẫn thành nguồn và chỉ được báo một lần,
“test_a_save_that_lands_as_the_file_is_written_is_refused_not_written_over”: lần ghi nêu
đúng bản đã đem so, nên bản người lưu chen vào sát lúc ghi bị báo xung đột chứ không bị ghi
đè, không trừ ngân sách, không đổi nguồn); tests/test_artifact_export.py
(“test_the_two_file_tools_come_together_and_only_the_export_asks_first”,
“test_text_goes_out_as_utf8_and_the_canvas_stays_as_it_was”: kết quả không mở đầu bằng thẻ,
chỉ nói đã thay tệp khi tệp có sẵn, canvas không thêm bản và lượt không bị trừ ngân sách, ảnh
ra nguyên từng byte, canvas html ra x.html vẫn là x.html và đường dẫn tuyệt đối được gọi
lại từ gốc workspace, tham số version và bản không còn, canvas ngoài tầm thì không tạo tệp
và hỏi một bản nó không có cũng chỉ nhận câu không tìm thấy canvas,
“test_an_export_makes_nothing_seen_so_an_unread_canvas_still_cannot_be_rewritten”: xuất
không phải đọc cũng không phải ghi, canvas vẫn gắn với cuộc trò chuyện mà không được chia sẻ);
tests/test_artifact_export_paths.py (thoát workspace bằng .., bằng đường dẫn tuyệt đối
hay ~người-không-có, đích là thư mục, đường dẫn giấu ký tự, mang nửa surrogate lẻ hoặc
rỗng đều bị từ chối và cây thư mục không đổi, “test_an_agent_held_to_its_write_paths_exports_only_under_them”,
“test_a_folder_linked_out_of_the_workspace_takes_no_export”,
“test_a_folder_linked_out_of_the_write_paths_takes_no_export”,
“test_a_link_is_never_written_through”: đích là symlink, kể cả symlink trỏ tới chỗ chưa có,
bị từ chối và tệp nó trỏ tới không đổi; symlink dẫn tới một thư mục được báo là symlink, không
phải là thư mục,
“test_a_write_that_fails_leaves_the_old_file_whole_and_no_other_behind”: os.replace ném
OSError thì tệp cũ nguyên vẹn, không sót tệp tạm, lời báo không có đường dẫn của máy,
“test_a_file_marked_read_only_is_not_replaced”: tệp chỉ đọc không bị thay, và dòng warning
trong log mang số lỗi của “permission denied” như một lần ghi thường, không phải None,
“test_a_new_file_gets_the_mode_of_any_written_file_and_an_old_one_keeps_its_own”);
tests/test_artifact_export_disk.py
(“test_a_name_longer_than_the_disk_takes_is_an_export_that_failed”: tên tệp hay tên thư mục
dài hơn hệ tệp nhận, ở thư mục có sẵn hay thư mục lần ghi sẽ tạo, đều cho agent câu không ghi
được chứ không phải tên lớp lỗi, cây thư mục không đổi và log có đúng một dòng warning nêu
chỗ thật tệp định tới, lớp lỗi và errno, không có dòng error của một tool sập,
“test_a_folder_the_process_may_not_enter_is_an_export_that_failed”: thư mục mode 000, tệp
trong đó có sẵn hay chưa, cũng vậy; bỏ qua khi chạy bằng root,
“test_a_folder_that_is_a_link_back_to_itself_is_an_export_that_failed”: thư mục là symlink
trỏ về chính nó, có hay không có write_paths, cho cùng một kết quả trên Python 3.12 (nơi
Path.resolve ném RuntimeError) và 3.13,
“test_a_write_path_that_is_a_link_back_to_itself_holds_no_folder”: write_paths có một
symlink như thế thì tệp rơi ngoài các đường dẫn còn lại vẫn bị từ chối bằng câu cũ,
“test_a_write_path_that_is_a_link_to_a_folder_holds_what_that_folder_holds”: write_paths
là symlink tới một thư mục khác trong workspace thì tệp gửi dưới nó vẫn được ghi, vào đúng
thư mục symlink dẫn tới,
“test_where_a_path_leads_is_judged_before_the_disk_is_asked_about_its_name”: tên quá dài nằm
ngoài workspace, sau một thư mục liên kết ra ngoài hay ngoài write_paths vẫn nhận lời từ
chối về vị trí như trước, không ghi gì và không để lại dòng log nào);
tests/test_artifact_export_write.py
(“test_a_failure_no_disk_reports_is_worded_like_any_other_and_leaves_nothing_behind”:
os.replace ném ValueError thì agent vẫn nhận câu không ghi được, tệp cũ nguyên vẹn, không
sót tệp tạm hay thư mục, nguyên nhân nằm trong log cùng chỗ thật trên máy mà tệp định tới,
viết ở dạng repr,
“test_a_write_cut_short_takes_its_half_written_file_away”: lần ghi đứt giữa chừng vì
OSError, MemoryError hay một BaseException đều dọn tệp ghi dở,
“test_the_folders_made_for_a_file_that_was_not_written_are_taken_away_again”: chỉ những thư
mục chính lần ghi đó tạo, từ sâu ra nông, thư mục có sẵn thì ở lại,
“test_a_folder_that_holds_something_by_then_is_left_with_what_it_holds”: lỗi tới tay nơi
gọi vẫn là lỗi đã chặn lần ghi, không phải việc thư mục không xoá được,
“test_a_name_someone_put_a_link_at_first_is_never_written_through”: tên tệp tạm đã có sẵn
một symlink thì lần ghi dừng, tệp symlink trỏ tới không đổi và symlink không bị dọn,
“test_a_folder_another_write_made_first_is_used_and_is_not_this_ones_to_take_away”);
tests/test_artifact_export_log.py
(“test_a_disk_that_refuses_is_worded_for_the_agent_and_leaves_one_warning_in_the_log”: đĩa
đầy, mất quyền ghi hay OSError không có số đều cho agent câu không ghi được với đường dẫn
tương đối, và để lại đúng một dòng warning trong log nêu chỗ thật tệp định tới ở dạng
repr, tên lớp lỗi và errno, không kèm lời của hệ điều hành,
“test_the_letters_of_a_name_are_logged_as_letters_whatever_the_alphabet”: tên tệp tiếng
Việt vào log vẫn là chữ đọc được chứ không thành mã \u…, ở nhánh lỗi do đĩa lẫn nhánh
lỗi không do đĩa,
“test_a_folder_whose_name_holds_a_line_break_starts_no_line_of_its_own_in_the_log”: thư mục
thật mode 555 có tên chứa \n, \r hay U+2028 rồi tới một dòng trông như bản ghi log, đi
tới qua một symlink tên thường, vẫn chỉ để lại một bản ghi nằm trên một dòng khi ghi ra theo
đúng định dạng log của server; bỏ qua khi chạy bằng root,
“test_a_failure_no_disk_reports_names_such_a_folder_on_the_line_it_began_too”: nhánh lỗi
không do đĩa, ghi kèm traceback, cũng không để dòng nào của log mở đầu bằng chữ trong tên
thư mục);
tests/test_artifact_export_modes.py
(“test_the_file_being_written_is_open_to_no_reader_the_finished_one_is_closed_to”: dưới umask
022 và 077, từ lúc được tạo tới lúc chữ đi vào, tệp tạm không mở cho ai mà tệp cuối đóng, tệp
thay tệp cũ nhận mode của tệp cũ trước khi byte nào được ghi, tệp mới có mode như mọi tệp ghi
thường);
tests/test_artifact_number_args.py
(“test_a_version_that_is_no_finite_number_exports_nothing”: version là vô cực hay NaN, kể
cả khi JSON gửi 1e999, bị từ chối bằng lời và không tệp nào được ghi,
“test_a_read_sent_one_shows_no_page_and_moves_no_cursor”: version, from_line và lines
của artifact_read cũng vậy, con trỏ đọc đứng yên,
“test_a_number_as_text_is_read_and_what_is_no_number_reads_as_left_out”: số viết thành chuỗi
vẫn được đọc, thứ không phải số vẫn tính như bỏ trống);
tests/test_artifact_source_unreadable.py (tệp, hay thư mục chứa nó, mà tiến trình không có
quyền đọc: lời từ chối nói đúng là thiếu quyền chứ không gọi đó là thứ không phải tệp thường,
tool nhập không tạo canvas và không trừ ngân sách, lần nhập lại từ web trả 422 cùng câu đó
và canvas đứng yên);
tests/test_artifact_file_reach.py (đem canvas ra tệp hay đem tệp vào mà không đổi gì thì
không nới tầm với của ai:
“test_what_a_child_carried_to_or_from_a_file_stays_out_of_the_next_childs_reach”: con xuất
hay nhập lại không đổi canvas của chính nó thì canvas chỉ gắn với cuộc trò chuyện của con,
không chia sẻ, gốc của chuỗi giữ nguyên dù đã gắn sẵn hay chưa, con kế tiếp đọc, xuất hay
nhập đều nhận câu không tìm thấy canvas và không tệp nào được ghi,
“test_what_the_master_carried_does_not_reach_its_delegated_child”: master xuất hay nhập lại
canvas của người thì con được giao việc sau đó vẫn không với tới,
“test_an_import_that_writes_a_version_shares_the_canvas_as_any_write_does”: lần nhập thêm
bản vẫn chia sẻ với cả chuỗi, và xuất hay nhập không đổi sau đó không rút lại phần chia sẻ);
tests/test_kit_hooks_canvas_files.py (hook của kit và hai tool canvas chạm tệp:
“test_a_hook_that_guards_files_is_asked_about_the_canvas_tools_that_carry_them”: hook có
matcher Read|Write|Edit|Bash hay workspace_read|workspace_write chặn cả workspace_read,
workspace_write, artifact_import và artifact_export,
“test_a_hook_is_asked_only_about_the_canvas_tool_that_does_what_it_guards”: hook chỉ nêu
Write không được hỏi về lần nhập, hook chỉ nêu Read không được hỏi về lần xuất, cả trước
lẫn sau lời gọi, hook nêu tên tool canvas chỉ được hỏi về đúng tool đó,
“test_front_matter_still_reads_a_harness_name_as_the_plain_file_tool”: Read trong front
matter vẫn là workspace_read, payload giữ tool_name thật và mang tool_alias là Write /
Read, “test_a_hook_that_refuses_writes_stops_an_export_before_any_file_is_made” và
“test_a_hook_that_refuses_reads_stops_an_import_before_any_canvas_is_made”: qua sổ đăng ký
tool thật, hook chặn thì không tệp nào được ghi và không canvas nào được tạo,
“test_the_hooks_of_an_agents_kit_stand_before_its_canvas_file_tools”: hook trong profile của
agent đứng trước hai tool đó trong sổ đăng ký lắp từ profile)tests/test_artifact_scope.py
(“test_a_turn_from_the_web_chat_telegram_or_a_job_writes_a_canvas”: lượt API thì không,
“test_a_delegated_child_writes_when_the_turn_its_chain_began_from_could”: con theo nguồn của
lượt mở đầu chuỗi, con mở trước khi chuỗi ghi gốc thì không bao giờ ghi, người gõ thẳng vào
cuộc trò chuyện của con thì ghi được từ đó,
“test_an_api_turn_in_a_childs_own_conversation_writes_none”: chiều ngược lại, lượt API trong
cuộc trò chuyện của con thì không ghi và không tính là vắng, dù chuỗi bắt đầu từ web chat,
Telegram hay job,
“test_the_reader_of_a_telegram_turn_or_a_job_is_away_from_the_web”,
“test_a_delegated_childs_reader_is_where_its_chain_began” và
“test_a_person_typing_in_a_childs_own_conversation_reads_from_there”: người đọc của lượt
Telegram, của job và của con trong chuỗi bắt đầu từ đó không ngồi ở web; lượt web chat, lượt
API và chuỗi không có gốc thì không tính là vắng, agent ghi được năm loại chữ (markdown, code, html, svg, mermaid) chứ không ghi ảnh, lời từ
chối kể đủ năm loại, lần ghi thứ 31 cho một canvas trong lượt bị từ chối, một
lượt tạo tối đa 30 canvas,
“test_a_batched_call_counts_toward_its_turn_and_a_child_turn_counts_apart”, master với tới mọi
canvas còn agent khác chỉ canvas của mình, canvas một lượt trong chuỗi chỉ đọc không vào tầm
của con kế tiếp, mỗi lời từ chối của kho thành lời dặn (kể cả chữ có nửa ký tự Unicode),
“test_a_canvas_out_of_reach_reads_exactly_like_one_that_does_not_exist”, lỗi khác đi qua
nguyên vẹn); tests/test_artifact_authors.py (dòng tác giả gom từng đoạn liền của một tác
giả, nhóm phủ cả những số bản một loạt gộp đã bỏ,
“test_the_authors_line_names_the_six_newest_groups_at_most”: giữ sáu nhóm mới nhất và dấu …
đứng đầu, “test_a_restore_is_a_group_of_its_own_naming_the_version_it_brought_back”);
tests/test_artifact_reach.py
(“test_a_quiet_canvas_linked_here_is_found_past_newer_ones_out_of_reach”,
“test_reachable_and_is_reachable_agree_on_every_kind_of_canvas”,
“test_a_persons_canvas_is_reached_only_through_a_link_whatever_the_agent_is_called”, khớp tiêu
đề bỏ qua hoa thường và dấu trước khi giới hạn, mới sửa trước); tests/test_store.py
(“test_a_child_keeps_the_root_of_its_delegation_and_nothing_can_change_it”);
tests/test_tools_delegate.py
(“test_the_child_records_its_root_and_where_the_root_turn_came_from”,
“test_a_child_further_down_keeps_the_root_its_parent_was_given”)tests/test_text_edit.py (khớp chính xác thay tại chỗ, nháy cong và dấu cách đặc biệt
khớp dạng thường theo cả hai chiều, khớp chính xác thắng khớp nới lỏng, khớp nới lỏng nhiều chỗ
bị từ chối kèm số chỗ, khớp chính xác nhiều chỗ bị từ chối trừ khi thay tất cả, old rỗng bị
từ chối, “test_no_match_raises_a_bare_not_found_without_searching”: phần chạy trong khoá của
kho không tìm gì, trần tính số byte của đúng chữ đã khớp, vừa đúng trần thì giữ còn hơn một
byte thì không, replace_all quá trần bị từ chối trước khi dựng chuỗi, chuẩn hoá giữ nguyên vị
trí từng ký tự); tests/test_text_nearest.py
(“test_a_miss_names_the_closest_lines_and_quotes_them_verbatim”, lời báo rào vùng sau câu không
tìm thấy, không gì giống hay một đoạn giống quá ít thì không có vùng,
“test_two_equally_close_places_give_no_region”,
“test_a_long_old_on_a_long_text_compares_at_most_three_windows”, dòng rào lặp khắp nơi không
kéo vùng đi chỗ khác, vùng quanh một dòng khổng lồ vẫn trong cỡ của nó, đoạn khớp dài bị cắt ở
giới hạn dòng, old quá dài thì không tìm, văn bản dài hơn MISS_MAX_TEXT cũng không tìm
(trần theo số đo bộ nhớ, 1 MiB ký tự): đúng trần vẫn trích đoạn gần nhất, hơn một ký tự thì chỉ
có lời không tìm thấy thường); tests/test_artifact_diff.py (hai dạng thẻ đọc lại
qua cùng một mẫu, thẻ chỉ tính khi đứng ngay đầu kết quả, rào dài hơn mọi dãy backtick bên
trong, dòng vừa chỗ giữ nguyên, dòng dài hiện quanh phần cần xem,
“test_removed_lines_take_at_most_half_and_what_was_written_always_shows”, dòng dài bị đổi cắt
quanh chỗ đổi ở cả hai bên, “test_the_diff_never_outgrows_its_budget”, số đếm cuối luôn vừa
ngân sách, dòng thêm dài bị cắt cho vừa chỗ chứ không bị bỏ, tiêu đề đoạn đánh số dòng chỉ theo
LF và theo chỗ trong chữ mới, không khác gì thì diff rỗng, chỉ phần giữa đã đổi được so,
“test_a_diff_quoting_a_backtick_run_is_drawn_again_to_fit_a_longer_fence”, chỗ quá nhỏ vẫn kết
thúc bằng số đếm)tests/test_artifact_quote.py
(“test_an_edit_spread_over_too_many_lines_is_written_and_quotes_no_diff”: sửa vẫn được ghi,
kết quả kết thúc bằng lời bảo đọc lại bằng artifact_read chứ không có khung diff, và việc so
từng dòng không chạy lần nào (SequenceMatcher bị thay bằng hàm báo lỗi),
“test_a_rewrite_refused_over_a_wide_change_sends_the_agent_to_read_instead”: lời từ chối vẫn
nêu bản mới nhất và ai viết, rồi bảo đọc bản mới nhất thay vì xem phần khác biệt, cũng không so
dòng nào, “test_a_change_just_inside_the_limit_is_still_compared_and_quoted”: chỗ đổi trải
đúng MIDDLE_LINES dòng vẫn được so và trích, cả khi sửa lẫn khi bị từ chối viết lại,
“test_a_diff_is_drawn_off_the_thread_that_serves_every_conversation”: diff được vẽ đúng một
lần, trên một luồng khác luồng của vòng lặp sự kiện, ở cả artifact_edit lẫn
artifact_rewrite). Số dòng của các ca lấy từ chính MIDDLE_LINES chứ không chép con số 600,
và luồng được đo bằng định danh luồng chứ không bằng thời gian, nên các test neo vào việc “quá
rộng thì không so” và “so thì không chiếm vòng lặp”, không neo vào một ngưỡng hay một tốc độ.
Ghi chú canvas dùng chung ngưỡng này (tests/test_canvas_note.py)tests/test_canvas_payload_trim.py
(“test_a_steer_mid_turn_leaves_the_document_the_turn_wrote_whole”,
“test_a_child_told_to_conclude_still_sees_the_document_it_wrote”,
“test_an_earlier_turns_frame_and_edits_leave_notes_of_where_their_text_went”: id, tiêu đề, loại
và old ngắn giữ nguyên còn kho giữ mọi chữ, lần ghi thất bại để lại ghi chú chưa lưu gì,
“test_a_write_cut_off_mid_call_leaves_a_note_to_look_before_writing_again”,
“test_a_turn_resumed_after_an_approval_keeps_the_document_it_wrote_before”, lời gọi chưa có kết
quả và mọi tin khác đi qua nguyên vẹn, đúng là object cũ, kết quả lạc hay content không phải
chuỗi không làm hỏng lượt nào, ranh giới lượt nằm ở tin cuối cùng trước lượt, id dùng lại được ghép với kết quả theo sau nó, lời gọi của lượt này dưới id cũ không
nhận kết quả của lời gọi trước, lượt bắt đầu ở chỗ run đang chạy bắt đầu hoặc sau tin cuối,
“test_a_file_carried_in_or_out_by_an_earlier_turn_passes_on_as_the_call_was_made”: lời gọi
artifact_import và artifact_export của lượt trước đi qua nguyên vẹn, đúng là object cũ, dù
đường dẫn hay tiêu đề dài, vì chúng không mang chữ của tài liệu,
“test_every_canvas_tool_argument_that_carries_document_text_is_trimmed”: xét cả bảy tool,
“test_a_note_is_always_shorter_than_the_text_it_stands_for”)tests/test_canvas_prompt_tail.py
(“test_a_turn_that_cannot_write_a_canvas_is_told_so_with_the_same_tools”: lượt API, con có
chuỗi bắt đầu từ API và con của chuỗi không có gốc nghe mục này, sau ghi chú ngày và ngay
trước dòng ngày, cùng bộ tool; web chat và con của nó thì không,
“test_an_api_turn_in_a_childs_own_conversation_is_told_the_channel_is_closed”: lượt API trong
cuộc trò chuyện của con nghe mục này đúng một lần và không nghe lời dặn của lượt vắng mặt, dù
chuỗi bắt đầu từ web chat, Telegram hay job,
“test_the_note_names_only_the_canvas_writes_the_agent_holds”: agent chỉ có artifact_import
và artifact_export được nhắc đúng một tên artifact_import, agent chỉ xuất và đọc thì không
có mục này, “test_the_standing_prompt_has_no_canvas_note”: prompt thường trực không có mục
này, “test_the_note_lists_exactly_the_tools_a_closed_channel_refuses”: ở lượt API
artifact_import bị từ chối và có tên trong mục, artifact_export vẫn ghi được tệp và không
có tên, “test_the_refusal_names_no_channel_as_the_only_one_that_opens_a_canvas”: lời từ chối
không còn nói chỉ web chat mở được canvas)tests/test_canvas_prompt_tail.py
(“test_a_turn_whose_reader_is_away_from_the_web_chat_is_told_how_a_canvas_reaches_them”: lượt
Telegram, job và con trong chuỗi bắt đầu từ đó nghe mục Canvas với lời dặn này, ở cùng
chỗ với mục của kênh đóng, sau ghi chú trong ngày và ngay trước dòng ngày; prompt của lượt
web chat là prompt đó bỏ đi mục này, đúng từng ký tự, cùng bộ tool; lượt job nghe đúng từng
chữ lượt Telegram nghe; con của chuỗi API hay chuỗi không có gốc không nghe lời dặn này,
“test_an_agent_that_writes_no_canvas_is_told_nothing_about_making_one”: agent không giữ tool
ghi canvas nào thì không có mục,
“test_the_line_a_telegram_turn_is_taught_is_one_the_chat_sends_a_canvas_for”: dòng
FILE: artifact:<id> trong lời dặn, thay mã canvas vào, được đọc thành một canvas để gửi,
“test_the_note_says_who_reads_then_when_a_canvas_is_worth_making_then_how_one_is_sent”: lời
dặn nói, theo đúng thứ tự, người nhận không ngồi ở web chat nên không thấy canvas, chỉ tạo
canvas khi được dặn hay khi tài liệu dài và sẽ còn sửa tiếp, còn lại trả lời thẳng trong tin
nhắn, rồi dòng FILE: artifact:<id> gửi canvas kèm tin nhắn,
“test_the_note_never_says_its_reader_is_on_telegram”: Telegram chỉ được nhắc hai lần, là một
trong hai cách lượt được đọc và là điều kiện để dòng đó gửi canvas; job trên máy không có
bot cũng nghe lời này nên nó không khẳng định người đọc ở Telegram)tests/test_telegram_canvas_turn.py, qua vòng lặp và tool thật
(“test_a_telegram_turn_writes_a_canvas_and_sends_it_as_the_file_its_reply_names”: chat nhận
lời của lượt, rồi tệp canvas, rồi danh sách canvas; canvas nằm trong kho dưới tên agent và
gắn với cuộc trò chuyện của lượt,
“test_a_job_writes_a_canvas_and_its_brief_names_it”: brief mang danh sách canvas job đã ghi)tests/test_scheduler_reporting.py
(“test_a_job_that_wrote_a_canvas_and_answered_ok_is_not_pushed_and_keeps_the_canvas”: job trả
lời đúng OK sau khi ghi canvas thì Telegram không nhận gì và canvas vẫn ở trong kho)tests/test_canvas_note.py (hội thoại không có canvas không lưu ghi chú, canvas agent
chưa từng thấy được nêu một lần mà vẫn không viết lại được,
“test_a_persons_edit_is_shown_as_a_diff_that_makes_the_new_version_seen”, ghi chú không hiện
trọn mọi thay đổi (bản của agent khác, dòng bị cắt, bản khôi phục) thì artifact_rewrite vẫn
bị từ chối, người sửa tiếp sau một thay đổi đã báo thì diff từ bản đã báo còn seen đứng yên,
bản đã báo không bị lần tự lưu kế tiếp gộp mất, sửa rồi hoàn tác thì không báo gì mà tính
canvas là đã thấy, phần giữa đổi quá 600 dòng báo một dòng, tối đa ba diff và mới nhất trước,
canvas không vừa được đếm và báo ở ghi chú sau,
“test_a_note_never_outgrows_its_ceiling_and_counts_what_it_left_out”, canvas dựng lỗi được báo
một dòng còn canvas khác vẫn hiện, dựng ghi chú không ghi gì);
tests/test_canvas_note_focus.py (canvas đang mở được nêu một lần cho mỗi lần mở, đoạn được
chọn trích một lần rồi xoá, đặt theo dòng chỉ khi các dòng đó chứa đúng đoạn, đoạn chọn trên
bản cũ nói bản nào mới nhất, trên bản đã gộp thì tìm trong bản mới nhất, vùng chọn sai dạng,
rỗng, chỉ có khoảng trắng hay không có trong bản thì bị bỏ, canvas không phải chữ chỉ được nêu
tên, canvas đang mở mà agent chưa thấy được nêu và báo là mới, tin từ Telegram, API, job hay
agent con không nghe gì về canvas đang mở, canvas đang mở đứng đầu còn lại mới nhất trước);
tests/test_canvas_note_frame.py (dấu mở, dấu đóng và ký tự xuống dòng trong canvas không đổi
được khung ghi chú hay giả làm stub, chữ trích thoát mọi ký tự xuống dòng trừ LF, tiêu đề dài
bị cắt và không đóng được ngoặc quanh nó); tests/test_message_context.py (tin, ghi chú và
các dấu đã báo vào cùng một giao dịch, lỗi ở lúc chèn tin, cập nhật liên kết, cập nhật cuộc
trò chuyện hay commit không để lại gì kể cả sau một commit khác, tin cho cuộc trò chuyện không
tồn tại không đánh dấu gì, ghi chú dựng lỗi thì tin vẫn lưu không ghi chú và tin sau báo lại,
giao từ hàng đợi lỗi thì hàng chờ còn nguyên, tin xếp hàng hay steer từ Telegram nghe thay đổi
mà không nghe canvas đang mở còn từ web chat thì nghe cả hai, chỉ tin của người từ một nguồn
mới có ghi chú); tests/test_artifact_links.py (ghi chú chỉ đánh dấu liên kết đã có, bản đã
báo chỉ tiến và được giữ khỏi bị gộp, seen chỉ tiến từ đúng bản ghi chú diff từ đó, báo
canvas đang mở thì đánh dấu đã báo và có thể xoá vùng chọn, canvas đang mở vẫn là đã báo tới
khi canvas khác mở hay có đoạn mới được chọn); tests/test_artifact_diff.py (diff hiện đủ mọi
dòng đổi mới là trọn; dòng bị cắt quanh chỗ đổi, dòng bỏ chỉ được đếm, hết chỗ cho thay đổi
sau hay dòng thêm bị cắt cho vừa thì không trọn; phần giữa đã đổi đếm các dòng giữa hai đầu
chung); tests/test_canvas_note_prompt.py (mọi lời gọi model của lượt đọc tin sau ghi chú
trọn vẹn còn kho giữ nguyên chữ người viết, ghi chú của lượt trước thành cùng một stub, tin
hàng đợi giao đọc ghi chú trọn trong lượt của nó, system prompt giống hệt dù ghi chú khác
nhau, hội thoại không có canvas tới model đúng như đã lưu và cùng object, khung prompt gọi
những gì canvas chứa, kể cả chữ ghi chú trích, là dữ liệu ở cả hai ngôn ngữ);
tests/test_canvas_fork.py (mỗi tin chép giữ ghi chú của nó, nhánh liên kết những canvas gốc
đã liên kết tới điểm rẽ mà chưa thấy, chưa đọc, chưa báo gì, canvas liên kết sau điểm rẽ bị bỏ
còn cùng giây thì giữ, canvas đang mở ở gốc không mở ở nhánh, ghi chú đầu ở nhánh báo mỗi
canvas là mới, nhánh chỉ đọc trang cuối vẫn không viết lại được);
tests/test_canvas_note_imports.py (mỗi phần dựng ghi chú import được đầu tiên trong một
trình thông dịch mới, phần ở tầng lưu trữ không kéo gói agent, tên nguồn web chat của ghi chú
trùng tên nguồn của agent); tests/test_canvas_note_app.py (qua app với fake:echo: tin từ
web lưu cùng canvas đang mở và diff, model đọc ghi chú trước tin, /tool artifact_read vẫn gọi
được trong hội thoại có ghi chú)tests/test_api_artifacts.py
(“test_a_canvas_made_on_the_web_is_the_persons_and_reads_back_the_same”, canvas tạo trong một
hội thoại được chia sẻ và mở ở đó, hội thoại không có thì 404 và không tạo gì, web tạo được
năm loại chữ chứ không tạo ảnh (image, pdf, rỗng hay viết hoa đều 422 và kho còn trống),
schema của kind khớp CREATABLE_KINDS, tiêu đề kho từ chối là 422, quá trần là 413 nêu đúng
trần của loại đó (trang html lớn hơn tài liệu) và không ghi gì, kho đầy là 507 nêu các canvas
lớn nhất,
“test_the_detail_is_one_version_whole_though_a_write_lands_between_its_reads”, canvas không có
là 404 ở mọi route, “test_a_save_without_a_real_base_version_is_422_and_writes_nothing”: thiếu,
true hay 0 đều bị từ chối, lưu trên bản cũ là 409 kèm bản mới nhất và không ghi gì, hai lần
lưu trong một đợt gộp mà số bản vẫn tăng, đổi tên không thêm bản, danh sách mới nhất trước và
lọc theo hội thoại hay tiêu đề, limit từ 1 tới 200); tests/test_artifact_errors.py (mỗi lời
từ chối của kho ra đúng mã:
“test_a_version_folded_away_is_404_with_the_newest_number_though_it_is_a_key_error”, KeyError
của id khác và ValueError bảng không nêu đi tiếp như lỗi thật, 409 đọc lại bản mới nhất chứ
không dùng bản lúc xung đột, canvas bị xoá giữa chừng là 404, 413 nêu cỡ và trần,
“test_full_storage_is_507_naming_the_three_largest_canvases_biggest_first”, 422 mang lời của
kho, kể cả khi byte gửi lên không phải ảnh hay chữ có nửa ký tự Unicode)GET /api/artifacts/usage)
tests/test_api_artifacts_usage.py
(“test_usage_adds_up_every_version_of_every_canvas”: bytes và by_artifact cộng mọi phiên
bản chứ không chỉ bản mới nhất, chữ tính theo byte chứ không theo ký tự, ảnh tính cùng chữ,
cap là trần chung của kho,
“test_a_deleted_canvas_gives_its_room_back”,
“test_usage_with_no_canvas_is_nothing_under_the_same_ceiling”: kho rỗng trả count 0, bytes
0, by_artifact rỗng và vẫn nêu trần,
“test_usage_is_not_read_as_the_id_of_a_canvas”: route khai báo trước /artifacts/{id} nên
“usage” không bị đọc thành mã canvas; từ khác ở chỗ đó vẫn là 404 và canvas thật vẫn đọc được,
“test_usage_counts_the_canvases_a_list_is_too_short_to_return”: 201 canvas thì danh sách dừng
ở 200 còn count là 201)tests/test_api_artifact_history.py (phiên bản mới nhất trước và không kèm chữ, một bản
đọc trọn, bản đã gộp là 404 kèm số bản mới nhất, canvas không có là 404 ở mọi route lịch sử,
“test_a_restore_writes_the_old_version_as_the_persons_newest”: ghi chú restore:<n>, số bản
khôi phục phải là số nguyên thật, “test_the_raw_text_is_plain_text_that_runs_nothing”: raw của
canvas chữ là text/plain sandbox kèm nosniff, CORP và một chính sách frame-ancestors
riêng dù canvas chứa HTML, chữ thô của một bản cũ,
“test_a_page_is_shown_and_saved_as_text_and_never_as_a_page” (khi xem lẫn khi tải về),
“test_a_drawing_is_shown_as_an_image_that_downloads_and_is_saved_as_text”: svg khi xem là
image/svg+xml tải về (tên .svg.txt) còn khi lưu là chữ, mermaid là chữ .mmd,
“test_a_picture_goes_out_as_its_own_bytes_under_the_type_they_give”: ảnh ra đúng từng byte
dưới kiểu PNG, JPEG, GIF hay WebP mà byte cho, chi tiết của ảnh có content: null, bản cũ của
ảnh kèm byte và kiểu của chính nó,
“test_a_download_is_named_after_the_title_in_any_script”); tests/test_artifact_filenames.py
(“test_a_file_name_keeps_the_title_in_any_script_without_what_a_file_system_refuses”,
“test_a_file_name_ends_in_the_extension_of_its_kind”: markdown là .md, code theo ngôn ngữ, ngôn
ngữ lạ là .txt, code html, canvas html và svg là .html.txt, .svg.txt để mở tệp tải về
chỉ thấy chữ, không chạy script, mermaid là .mmd; ảnh lấy đuôi từ byte của nó và .bin khi
byte không cho đuôi nào, còn byte không bao giờ đổi tên của loại chữ,
“test_an_extension_names_the_first_language_that_saves_under_it”: bảng ngược
LANGUAGE_BY_EXTENSION phủ đúng các đuôi của CODE_EXTENSIONS và mỗi đuôi trả về ngôn ngữ
lưu ra chính đuôi đó)POST /api/artifacts/{id}/reimport)
tests/test_api_artifact_import.py
(“test_a_changed_file_becomes_the_persons_newest_version_marked_as_an_import”: tác giả user,
ghi chú import, conversation_id rỗng, nguồn giữ nguyên, trả {changed, artifact} không kèm
chữ, “test_a_file_that_changes_nothing_adds_no_version”: tệp chỉ khác BOM và kiểu xuống dòng
vẫn là không đổi, “test_a_reimport_must_say_which_version_the_panel_loaded”: thiếu
base_version, 0 hay chuỗi là 422,
“test_a_file_never_goes_over_a_version_saved_since_the_panel_loaded”: base_version cũ mà tệp
khác bản mới nhất là 409 kèm head_version, chữ và tác giả của bản đó; cũ mà tệp bằng bản mới
nhất là 200 changed: false,
“test_a_save_made_while_the_file_was_read_is_what_the_file_is_compared_with”: bản mới nhất được
đọc sau khi tệp đã trong tay,
“test_a_picture_is_compared_and_replaced_byte_for_byte”);
tests/test_api_artifact_import_scope.py
(“test_a_reimport_links_no_conversation_and_makes_nothing_seen”: bản mới không thuộc cuộc
trò chuyện nào dù canvas đã liên kết,
“test_the_request_picks_neither_the_file_nor_whose_version_it_becomes”: path, source,
conversation_id, kind, author, note, content trong body đều không có tác dụng,
“test_no_request_of_the_web_gives_a_canvas_a_source”: tạo, lưu, đổi tên kèm source vẫn để
nguồn rỗng nên nhập lại là 422,
“test_a_reimport_is_announced_only_when_it_changed_the_canvas”: không đổi thì không có event,
đổi thì một event artifact tới các hội thoại đã liên kết);
tests/test_api_artifact_import_refusals.py (lời từ chối nào cũng không ghi gì và không lộ
đường dẫn của máy: “test_a_canvas_that_names_no_workspace_file_is_422”: không nguồn, nguồn là
URL, workspace: thiếu agent hay thiếu đường dẫn, “test_a_canvas_that_is_not_there_is_404”,
“test_a_canvas_deleted_while_its_file_was_read_is_404”,
“test_a_source_whose_agent_left_the_crew_is_410”, “test_a_source_file_that_is_gone_is_410”,
“test_a_source_outside_the_workspace_is_403_in_words_for_a_person”: .., đường dẫn tuyệt đối
và ~ ra ngoài workspace nhận FILE_OUTSIDE_WORKSPACE,
“test_a_file_that_cannot_be_a_version_of_the_canvas_is_refused_by_the_canvas_kind”: quá trần
là 413, không phải UTF-8 hay có byte NUL là 422, canvas ảnh mà tệp không phải ảnh là 422, thư
mục là 422, “test_a_source_that_became_a_pipe_is_422_without_waiting_for_a_writer”: request
không treo, “test_a_page_of_another_site_cannot_ask_for_a_reimport”: Sec-Fetch-Site
cross-site hay same-site là 403, “test_full_storage_is_507_and_the_canvas_stays”)tests/test_artifact_render.py (chính sách viết ra nguyên văn trong test nên đổi nó là
đổi cả tệp này; sandbox chỉ có allow-scripts, không bao giờ allow-same-origin; chỉ
frame-ancestors nhắc 'self'; default-src, connect-src, form-action và base-uri là
'none', img-src và media-src chỉ có data: và blob:, không nguồn nào là *, http: hay
https: trần, webrtc bị chặn; thư viện của trang mermaid nằm trong script-src;
“test_a_page_goes_out_with_the_headers_of_a_page_that_runs_and_no_others”: hai header
Content-Security-Policy (chính sách rồi frame-ancestors), text/html, nosniff,
no-referrer, no-store và không header nào khác ngoài content-length và vary của lớp nén;
canvas html ra đúng trang của nó kèm reporter, mermaid ra trang vẽ nguồn của nó, bản mới nhất
khi không hỏi bản nào, version bằng 0, âm hay không phải số là 422; markdown, code, svg và ảnh
là 404 NOT_A_PAGE kể cả khi kèm version, canvas không có là 404, bản đã gộp hay chưa có là
404 kèm số bản mới nhất; địa chỉ chỉ nhận GET, các method khác là 405 và không ghi gì);
tests/test_render_pages.py (reporter đứng ngay sau doctype mở đầu trang, sau cả BOM, khoảng
trắng và chú thích, và đứng trên cùng khi trang không có doctype, bỏ nó đi thì ra đúng canvas;
trình duyệt gặp doctype trước và reporter trước mọi script của trang; reporter không tự đóng
được thẻ script của nó và là JavaScript cổ trong một hàm không để lại biến toàn cục, vì một
trang không parse được thì không báo gì; một lời từ chối không phải Error (Mermaid từ chối sơ
đồ không parse được bằng object thường {str, message, hash}) được báo bằng message của nó
chứ không phải [object Object];
“test_the_reporter_hands_the_app_a_port_before_it_listens_for_anything”: reporter mở một
MessageChannel, giữ postMessage của đầu kênh nó giữ trong một biến cục bộ đã bind, gửi
canvas-hello kèm đầu kia trong danh sách chuyển giao đúng một lần và trước mọi
addEventListener, sau lời chào không còn chỗ nào nhắc tới port1;
“test_a_browser_without_message_channels_still_reports_what_goes_wrong”: mọi thứ về kênh nằm
trong một try bốn dòng, phần báo lỗi nằm ngoài nó;
“test_the_reporter_tells_of_a_press_only_when_the_browser_says_a_person_made_it”: reporter nói
press ở đúng một chỗ và chỉ khi event.isTrusted === true (đúng một lần đọc isTrusted), và
không đọc type hay detail của sự kiện nào: hai thứ ấy là accessor trên prototype, trang viết
lại được, nên cú bấm được nhận ra bằng listener nó tới chứ không bằng điều sự kiện tự nói;
listener của nó trên window đúng là pointerdown, mousedown, pointerup, mouseup (cùng
hàm ấy, pha capture) rồi error, unhandledrejection, securitypolicyviolation, không có
focus, pointermove, wheel hay keydown là những sự kiện trang tự khiến trình duyệt bắn ra
được, và không có click, cái mà Enter hay phím cách người gõ cho app tạo ra trên một nút của
trang đã giành bàn phím;
“test_the_hello_is_no_report_and_counts_against_none”: lời chào không tính vào hai mươi lời báo;
“test_the_work_on_a_page_does_not_grow_with_the_square_of_what_it_holds”: hai đầu vào mà bản cũ
tốn hàng giây hoặc vô tận (28 chú thích liền nhau trước một doctype không có, 64.000 khoảng
trắng trong sơ đồ có rào) phải xong dưới 0,5 s, vì cả hai chạy trên event loop; sơ đồ đóng
</pre> rồi mở <script> và tiêu đề đóng </title> đều ra thành chữ; một rào ba dấu huyền bọc
cả sơ đồ thì bỏ, mọi trường hợp khác giữ nguyên; thư viện là một phiên bản ghim kèm băm và
crossorigin, securityLevel: "strict" và startOnLoad: false; sơ đồ không parse được không
bị trang nuốt: run() đứng một mình, không try, catch, finally, await hay .then, nên
lời từ chối của nó tới reporter, và bỏ lời gọi đó thì không sơ đồ nào được vẽ; không có thư viện
thì có dòng báo ở trên nguồn; trang là tài liệu đủ bộ và reporter đứng trước mọi thứ nó tải);
tests/test_render_fixtures.py (web/e2e/render-policy.txt và web/e2e/frame-reporter.js, hai
tệp mà test trình duyệt đọc, bằng render_csp() và REPORTER_JS từng byte; sửa một bên mà quên
tệp thì test đỏ); tests/test_local_guard.py
(“test_only_the_render_page_of_a_canvas_is_open_to_a_request_from_elsewhere”: với
Sec-Fetch-Site là cross-site hay same-site, địa chỉ render trả 200 còn canvas, raw,
versions, danh sách, /render/, /render/x, /render%0A và a%2Fb/render đều 403; Origin
lạ và host lạ vẫn bị từ chối)tests/test_api_canvas_focus.py (đặt rồi đọc lại cùng vùng chọn, null là đóng, vùng
chọn không kèm canvas là 422, mở canvas agent chỉ đọc thì chia sẻ nó, canvas chưa liên kết được
liên kết chia sẻ và chưa thấy,
“test_a_canvas_opened_on_the_web_reaches_the_agents_the_conversation_delegates_to”,
“test_a_selection_the_note_would_drop_is_422_and_nothing_changes”: true, chỉ khoảng trắng, bản
sau bản mới nhất, dòng cuối trước dòng đầu, chữ quá 20.000 ký tự,
“test_a_selection_may_reach_twenty_thousand_characters”, hội thoại hay canvas không có là 404)tests/test_api_chat_canvas.py
(“test_a_message_names_the_canvas_open_in_the_tab_that_sent_it”: thiết bị khác vừa mở canvas
khác cũng không đổi điều tin nêu, tin trích đoạn được chọn, tin không mang canvas giữ canvas
đang mở, tin từ tab không mở canvas thì đóng nó,
“test_a_message_with_a_selection_the_note_would_drop_is_422_and_goes_nowhere”, cổng từ chối (chờ
duyệt, hội thoại không có) thì canvas đang mở không đổi, tab hiện canvas đã xoá vẫn gửi được và
đóng nó, “test_a_queued_message_names_its_canvas_when_it_is_delivered”,
“test_the_open_canvas_is_named_once_while_messages_keep_carrying_it”,
“test_a_queued_ask_keeps_its_passage_until_it_is_delivered”: tin hỏi về một đoạn mà bị xếp hàng
giữ vùng chọn tới lúc giao, ghi chú giao đi trích đúng đoạn ấy,
“test_a_queued_ask_loses_its_passage_to_a_later_message_from_the_same_tab”: giới hạn đã biết, ghi
chú của tin xếp hàng dựng lúc giao theo canvas đang mở khi ấy, nên tin sau từ cùng tab, đã xoá
vùng chọn, làm tin trước mất đoạn)web/src/api/client.test.ts (“carries the tab’s canvas only
when it is given, and a null one as it is”: thân POST là {text} khi không có canvas, {text,
canvas} khi có, kể cả {artifact_id: null}); web/src/hooks/use-canvas-dock-focus.test.ts nhóm
“the canvas a message names” (chưa mở canvas nào ở tab này thì không nêu gì, mở rồi thì nêu canvas
ấy không kèm đoạn chọn, canvas mới nhất vừa mở và canvas tạo tại đây ngay từ lúc tạo; đóng trên
màn rộng là null dù đóng bằng close, forceClose hay toggle, còn lớp phủ của màn hẹp dẹp đi
thì vẫn nêu canvas; theo bề rộng lúc đóng chứ không lúc mở; về danh sách vẫn nêu canvas, việc đóng
đang chờ một lần lưu chưa xong cũng vậy; sang hội thoại khác thì trống và không quay lại theo
người; đọc qua tham chiếu lấy từ trước vẫn thấy giá trị lúc hỏi);
web/src/hooks/use-thread.test.ts nhóm “the canvas a send carries” (canvas đi theo POST của tin
gửi thường và của tin xếp hàng sau lượt đang chạy ở tab này; bảng “%s on a plain send is told in
Vietnamese, in the result and in the notice”, gửi tin mang một đoạn chọn, cho 422 là đoạn chọn
không còn khớp, 409, 429, 500 và mất kết nối: error của kết quả luôn là câu của ta, thông báo
giữ câu của server ở 429 và nói máy chủ lỗi ở 5xx, bong bóng tạm biến mất; ba lỗi 422, 429 và mất
kết nối của tin xếp hàng cũng vậy; hai bảng “a 422 for a plain send with %s is not told as a
passage that no longer fits” và “a 422 for a send queued behind this tab’s turn with %s is not
told as a passage either” cho tin nêu canvas mà không mang đoạn chọn nào — selection: null,
không có trường selection, và canvas đang đóng: kết quả là câu chung “chưa gửi được” còn thông
báo giữ lời của server, vì không đoạn nào của người bị từ chối;
“keeps the old wording of a 422 for a message that carried no canvas”);
web/src/lib/error-text.test.ts nhóm “turnErrorText” (409 là hội thoại chờ quyết định dù server
viết gì, 422 của tin mang đoạn chọn là đoạn chọn không còn khớp, lỗi khác để errorText lo dù
tin có đoạn chọn hay không); web/src/lib/send-result.test.ts (“words a 422 as the selection only
when the message carried a selection of the canvas”; sendErrorText đổi nghĩa chỉ của 422 và chỉ
khi tin mang đoạn chọn, lời server bằng tiếng Anh không bao giờ tới người dùng)tests/test_user_context_event.py
(“test_the_first_event_of_a_turn_is_the_note_its_message_was_stored_with”, event chỉ có type
và context, không mang id tin; tin lưu không có ghi chú, lượt không nhận tin mới và tin server
xếp hàng (chỉ nhận queued) đều không có event này;
“test_the_activity_hub_neither_writes_nor_broadcasts_it”: tab khác đang xem không thấy ghi chú
của người gửi; “test_a_reply_reads_the_same_with_the_note_in_the_chain_or_without_it”;
“test_it_cannot_be_changed_once_it_is_made”: một event đi qua luồng, bộ lọc của hub và câu trả
lời kênh chat gom lại, nên không chỗ nào được sửa ghi chú của chỗ sau; qua API thì là khối SSE
đầu tiên và mang đúng đoạn người đã chọn)web/src/state/thread-reducer.test.ts (tin lưu cùng ghi chú mang ghi chú, tin lưu không
có hay có ghi chú rỗng thì không có khoá context; event user_context gắn ghi chú vào tin người
mới nhất dù nó đứng đâu trong thread, và để yên thread khi chưa có tin người nào);
web/src/state/activity-reducer.test.ts (luồng hoạt động của tab khác không nhận gì từ ghi chú
của tin); web/src/hooks/use-thread.test.ts (“reaches the bubble the send drew, off the stream
of that send”; tin server không lưu ghi chú thì bong bóng giữ nguyên chữ người gõ);
web/src/components/canvas/canvas-note-chip.test.tsx (chip gập lại và chưa có chữ nào của ghi
chú trên màn hình, aria-controls chỉ có khi thân đang mở, mở rồi đóng lại, chữ trong ghi chú
là chữ chứ không là markup, ký tự ẩn hiện thành dấu thấy được còn nút chép chép đúng bản gốc);
web/src/components/message-thread.test.tsx (“the canvas note under a user message”: chip nằm
ngay dưới bong bóng của tin mang ghi chú và ngoài bong bóng, không có dưới tin không mang ghi
chú hay dưới câu trả lời của agent, và chỉ dưới đúng tin của nó)canvas-note.spec.ts (“a message sent with a canvas note shows it as a chip, and
still does once the page is loaded again”: chip ngay sau bong bóng, mở ra thấy đúng ghi chú,
tải lại trang vẫn còn; tin gửi không ghi chú thì không có chip; ở 390×844 và 1000×800 cảm ứng
chip và nút chép đều ≥40px kể cả lúc đang mở, không cuộn ngang; ghi chú dài — 60 dòng, một dòng
nhiều từ và một dòng 400 ký tự liền — mở ra vẫn xuống dòng trong bề rộng, cao dưới nửa màn hình
và cuộn được bên trong)tests/test_artifact_events.py (“test_every_change_made_over_rest_is_announced”: tạo,
lưu, đổi tên, khôi phục và xoá, mỗi lần một event artifact kèm hội thoại liên kết,
“test_a_canvas_an_agent_writes_with_its_tool_is_announced”,
“test_a_write_from_another_thread_reaches_the_watchers_loop”: loop chạy ở chế độ debug, lần ghi
từ luồng khác tới watcher trong 0,5 s mà không có lỗi nào; thiếu bước chuyển về loop thì test
đỏ chứ không treo); tests/test_api_artifact_invariants.py
(“test_every_canvas_route_and_the_chat_message_run_on_the_event_loop”,
“test_the_routers_checked_here_are_every_canvas_router_the_app_serves”: router canvas nào
của app.ROUTERS cũng nằm trong bộ được kiểm, kể cả router nhập lại,
“test_no_read_changes_what_a_conversation_knows_or_has_open”: mọi GET canvas, kể cả
usage và trang render của canvas html và mermaid ở bản mới nhất lẫn một bản cũ, giữ
nguyên liên kết, con trỏ đọc, canvas đang mở, phiên bản và tin)web/src/lib/diff-lines.test.ts (dòng trống, khoảng trắng cuối dòng và xuống dòng cuối là
dòng thật, đầu đuôi chung được cắt trước khi áp trần, quá trần là null);
web/src/lib/merge3.test.ts (“merges two edits far apart in a long text under the default cap”,
viết lại các dòng sát một lần chèn là xung đột, hai bên cùng thêm cuối tệp là xung đột, sửa hai dòng
liền nhau là xung đột, xuống dòng cuối của mỗi bên được giữ, sửa giống nhau không nhân đôi, xoá với
sửa cùng dòng là xung đột, một bên quá trần là xung đột, “gives the server’s changes in the person’s
line numbers”); web/src/lib/line-edits.test.ts (sửa ở hai phía con trỏ giữ đúng ký tự, thêm dòng
cuối tệp không dời con trỏ ở dòng cuối, vị trí trong vùng thay về cuối đoạn thay, trong vùng xoá về
chỗ vùng đó, không so được thì chỉ kẹp); web/src/lib/canvas-diff.test.ts (dòng bớt trước dòng
thêm, đoạn không đổi dài thu còn ba dòng mỗi bên, kể cả sau một lần chèn ở dòng đầu, quá trần là
null)web/src/api/artifact-client.test.ts (danh sách mọi canvas, của một hội thoại hay theo
tiêu đề, tạo trong hội thoại trả bản đầy đủ, mọi route dưới id đã mã hoá, signal và keepalive
của lần lưu tới fetch, link chữ thô của bản mới nhất, của một bản và để tải về,
“links the page of a canvas under an encoded id, with no version: the newest is the one shown”,
“sends the kind a canvas is made as, with the text it starts from”, chi tiết của ảnh có
content: null,
“reads the server’s version from a 409 that carries one, and from nothing else”, 507 nêu dung
lượng và các canvas lớn nhất, “tells a version that was folded away from a canvas that is
gone”); web/src/api/client.test.ts (“keeps a structured error detail beside the message older
callers read”, detail dạng chữ vẫn là lời báo, luồng hoạt động chuyển cả event artifact);
web/src/hooks/use-activity.test.ts (“hands an artifact payload to the canvas listeners and
leaves the run state as it was”); web/src/lib/artifact-events.test.ts (một hàm đăng ký hai lần
bỏ một lần vẫn nghe, “skips a listener removed during an event, and starts one added during it
from the next”)web/src/test/fake-canvas.test.ts (số bản nối sau bản mới
nhất, lưu trên bản cũ trả bản mới nhất, khe và bản đã gộp, khôi phục ghi restore:<n>,
“checks a save the way the store does: the canvas, the body, the base, the size, then the
room”, kho đầy nêu ba canvas lớn nhất, route và method không có trả như router, danh sách gập
tiêu đề như server và cùng giây thì canvas tạo sau đứng trước, từ chối, mất reply và giữ request
hay reply đúng một lần, event mang hội thoại liên kết lúc đó và không bao giờ mang chữ,
“refuses a keepalive body that would take the bytes in flight past 64 KiB, without sending it”,
request bị huỷ vẫn ghi phần việc đã bắt đầu, canvas chỉ liên kết với hội thoại có thật và event
tới mọi luồng đang mở, “makes a canvas of every kind a person can make, with the text it is
given”, “keeps a picture as bytes with no text: its detail and versions say so, and a write of
text is refused”). Mẫu canvas html, svg, mermaid và ảnh cho các test về loại nằm ở
web/src/test/fake-canvas-kinds.ts (SAMPLES; ảnh không có chữ, server giả chỉ giữ cân nặng
của nó và trả content: null như server thật)web/src/lib/canvas-machine-save.test.ts
(“never has two saves in flight, whatever falls due and whatever comes back”, gửi chữ kèm phiên bản
nó được sửa từ đó rồi lấy reply làm base, chữ gõ trong lúc lưu được gửi khi lần lưu kia về, trần
kích thước đo bằng byte UTF-8, thử lại sau 2, 5, 15, 30 giây rồi mỗi phút, 5xx như mất reply, “still
sends the text when it went back to its base after a lost save, which may have landed”, mỗi mã từ
chối một test: 404 là đã xoá, 413 dừng tới khi chữ đổi, 507 giữ các canvas lớn nhất và dừng tới lần
lưu tay, 422 và 4xx khác dừng tới lần lưu tay, 409 không kèm bản mới nhất không phải xung đột);
web/src/lib/canvas-machine-flush.test.ts (flush trả phiên bản giữ chữ lúc gọi, không chờ phím gõ
sau đó, chờ qua lần lưu đang bay mang chữ cũ, null khi xung đột, đã xoá, bị từ chối, lần lưu đầu
mất, quá trần hay đang dừng); web/src/lib/canvas-machine-size.test.ts (trần theo loại canvas:
markdown, code và mermaid 512 KB, html 4 MB, svg 2 MB; đúng trần thì gửi, hơn một byte thì dừng và
nêu trần; html nhận 1 MB mà markdown thì không; loại lạ theo trần nhỏ nhất; đếm byte UTF-8 chứ
không đếm ký tự; dừng vì cỡ được gỡ và quên trần khi chữ vừa lại, vẫn dừng khi chưa vừa, lưu tay hay
khôi phục một bản cũng gỡ nó và quên trần; 413 nêu
trần server nói và trần ấy thắng bảng, 413 không kèm trần thì dùng trần của loại, lần lưu xong
thì không còn trần nào); web/src/lib/canvas-machine-slow.test.ts (lần lưu hết hạn mà không reply
là mạng chậm chứ không phải mất: thử lại sau hai giây, chữ ấy còn tính là có thể đã tới server,
trạng thái “slow” khi máy có mạng và “offline” khi không, lần thử lại gửi đúng chữ và trạng thái
vẫn là “slow” tới khi một lần lưu về, bỏ qua lần lưu theo giờ hay lúc mất focus trong lúc chờ,
chờ lâu dần như lần lưu mất, phân biệt với lần lưu mất ở lần kế, một lần lưu về thì thôi nói mạng
chậm, flush đang chờ được trả null, 409 mang chữ của lần hết hạn là của chính mình, lần thử lại
thấy đúng chữ ấy trên server thì coi là đã lưu, canvas bị xoá lúc lần lưu đang bay thì hết hạn cũng
không đặt lần thử lại nào, không có lần lưu nào bay thì bỏ qua);
web/src/lib/canvas-caps.test.ts (trần từng loại, tên lạ hay tên của prototype theo trần nhỏ
nhất, độ dài tin chat là 20000, đếm byte UTF-8 của chữ một, hai, ba và bốn byte, fits đúng trần
và hơn một byte của từng loại, chữ hai byte có số ký tự nhân ba vượt trần mà đo byte vẫn vừa, mỗi
loại một trần riêng)tests/test_web_caps.py (bảng trần của web khớp bảng của server theo từng loại, và giới
hạn độ dài tin của web khớp route chat; một bên đổi một mình thì hỏng ở đây)web/src/lib/canvas-machine-sync.test.ts (“reads nothing when its own save’s event comes
before or after the reply”, “does not call text saved when the agent wrote a newer version during
the save”, đổi tên và xoá khi đang lưu, “puts off a read asked for during a save, and never sends
older text after it”, hai bản nối nhau mỗi bản một lần đọc, lần lưu mất reply mà đã tới server được
nhận làm base; mở với nháp: nháp bằng bản mới nhất thì bỏ, nháp trên bản mới nhất thành chữ chưa
lưu, nháp trên bản cũ được trộn, sửa cùng dòng là xung đột, “takes the person’s own save that was
out when the page closed, and keeps what they typed after it”, “does not take a version the agent
wrote with the same words for the person’s own save”); web/src/lib/canvas-machine-conflict.test.ts
(409 của chính lần lưu mất reply là base chứ không phải xung đột, phím gõ trong lúc lưu được trộn,
“merges again when the merged save meets yet another version, and loses nothing”, keepMine và
loadTheirs rồi lấy lại chữ của mình được cho tới khi gõ, 409 giữa lúc soạn IME chờ
compositionend, khôi phục đặt chữ và base không chờ luồng; “ends on the newest version when it
loads theirs while a newer one is being read”: bấm “Nạp bản mới” lúc lần đọc bản mới hơn nữa
đang bay thì lần đọc đó không được áp vào chữ vừa thay, máy hỏi đúng một lần đọc nữa, không có
lần thứ ba, và canvas dừng ở bản mới nhất với trạng thái đã lưu; “reads at once when it loads
theirs knowing of a newer version that no read is out for”: lần đọc trước đó hỏng thì “Nạp bản
mới” hỏi đọc ngay; nạp đúng bản mới nhất đã nghe tới thì không đọc gì; “ends on a version
written over the restore, whose read was out when the restore’s reply came” và “reads at once
after a restore under a version it has heard of, when no read is out”: khôi phục cũng vậy, còn
khôi phục thành bản mới nhất thì không đọc gì); web/src/lib/canvas-draft.test.ts
(nháp chỉ của đúng canvas, xoá khoá cũ trước khi ghi nên kho đầy không để lại nháp, trình duyệt từ
chối thì báo và tab giữ nháp, “clears a draft only while it still holds the text that was saved”,
giữ mười nháp mới nhất, bỏ nháp quá ba mươi ngày và nháp hỏng; hết quota thì bỏ nháp cũ nhất từng
cái một cho tới khi vừa, mục không phải nháp đi trước, và dọn hết vẫn không vừa thì mọi nháp khác
được trả lại nguyên byte, không đụng gì khác khi lần ghi vừa; nhóm “a draft the browser refused,
held in this tab”: nháp của tab được đọc trước nháp tab khác đã lưu, đi khi trình duyệt giữ được
hay khi không còn gì để giữ (trình duyệt chặn lưu trữ không biến việc đó thành lỗi), clearDraft
xoá từng bản theo chữ của chính bản đó, trang hỏi trước khi đóng tới khi nháp cuối cùng đi; nhóm “a
draft the browser refuses again and again”: mười lần dừng gõ chỉ hỏi trình duyệt một lần và nháp
của canvas khác chỉ bị lấy ra rồi đặt lại một lần, thử lại khi nháp nhẹ hơn (chữ ngắn hơn, hay
bản gốc ngắn hơn mà chữ vẫn dài như cũ: “is tried again once the version it was edited from is
shorter, its own text no shorter”) hay khi một nháp đã lưu bị xoá, một nháp nhẹ hơn đã được giữ thì
cỡ từng bị từ chối cũng được thử lại (“is tried again at the size refused once a smaller one was
kept”), xoá nháp chỉ có ở tab thì không thử lại, không cản nháp của canvas khác, trình duyệt
chặn hẳn lưu trữ thì không bị nhớ, và console.error khi nháp lấy ra không đặt lại được);
web/src/lib/local-store.test.ts (liệt kê khoá theo tiền tố, lần ghi báo có được giữ không khi
trình duyệt từ chối hay hết quota)FakeCanvas với trình duyệt chặn lưu trữ: web/src/app-canvas-tab-draft.test.tsx
(chữ gõ còn nguyên khi mở lại canvas sau lần đóng mà cả hai lần lưu đều 500, sau lần đóng mà lần
lưu để lại gặp 409 của người khác, và sau khi đổi cuộc trò chuyện; lần lưu kế gửi đúng chữ đó;
trang hỏi trước khi đóng suốt lúc ấy, thôi hỏi khi một bản đã giữ chữ hay canvas bị xoá, vẫn hỏi
và vẫn giữ chữ khi người khác chỉ lưu thêm một bản của canvas (“is still asked about when someone
else only saves its canvas anew, and is there on the way back”), và không hỏi gì khi lần lưu để
lại hạ cánh)FakeCanvas: web/src/app-canvas-held-draft.test.tsx (trình duyệt chặn lưu
trữ: mở lại canvas sau lần lưu để lại hỏng thì chữ được gửi 1,5 giây sau, không sớm hơn một mili
giây và không cần phím nào, rồi dòng trong luồng chat, dòng “Không giữ được bản nháp trên máy này”
trong panel và câu hỏi trước khi đóng trang đều đi; rời canvas ngay sau khi mở lại cũng lưu và
dòng trong luồng chat đi; nháp đã trộn với bản mới hơn lúc mở lại, lưu tay xong thì trang thôi
hỏi; canvas vẫn mở thì dòng trong panel chỉ còn tới khi lần lưu hạ cánh và hiện lại khi chữ kế
cũng bị từ chối; trình duyệt giữ được nháp thì mở lại chỉ hiện nháp, 60 giây không gửi gì, dòng
trong luồng chat ở lại và đi khi người tự lưu)FakeCanvas với đồng hồ giả: web/src/lib/canvas-runner-held-draft.test.ts
(“saves the draft by itself, as long after it opened as after a keystroke”; cờ nháp hỏng bật khi
nháp được ghi lại sau 0,3 giây và tắt khi lần lưu hạ cánh; nháp trộn được thì lưu đúng chữ đã trộn
trên bản vừa trộn; lưu tay trước khi nháp được ghi lại thì tab không còn giữ gì; nháp xung đột với
bản mới nhất thì không gửi gì, vẫn giữ và trang vẫn hỏi; lần đọc đầu hỏng thì lần đọc sau mang
nháp về và tự lưu; runner bị thả ngay lúc lần đọc đầu về thì không đặt lần lưu nào; nháp trình
duyệt đã giữ thì chỉ hiện, 60 giây không gửi gì cho tới khi người lưu hay gõ);
web/src/lib/canvas-runner-tab-draft.test.ts (nháp tab giữ của canvas đang mở: đi khi lần lưu đã
trộn với bản của người khác hạ cánh dù nháp còn mang chữ trước khi trộn, ở lại khi chữ gõ sau lúc
lần lưu đi còn chưa lưu, đi khi một lần đọc cho thấy lần lưu mất reply đã hạ cánh);
web/src/lib/canvas-machine-sync.test.ts (“drops the draft of a lost save a read shows landed, by
the text that landed”); web/src/lib/canvas-draft-held.test.ts (heldInTab chỉ đúng với canvas
mà trình duyệt từ chối nháp và thôi đúng khi nháp sau được giữ; dropFromTab bỏ nháp của tab dù
chữ gì, trang thôi hỏi khi đó là nháp cuối và vẫn hỏi khi tab còn giữ nháp của canvas khác, không
đụng nháp tab khác đã lưu, không làm gì với canvas tab không giữ nháp)web/src/lib/canvas-handoff-saved.test.ts (tới khi
lần lưu để lại hạ cánh, không tới khi nó hỏng, không tới khi một nháp của chữ gõ sau đó còn sống
lâu hơn lần lưu ấy (“does not come when the save that lands is outlived by a draft of words typed
since”) và tới khi lần lưu giữ đúng chữ của nháp; runner đã thả chỉ báo một lần, từ lần lưu để
lại; canvas đang mở báo khi lần lưu hạ cánh mà không gõ gì thêm, không báo khi còn chữ gõ sau lúc
lần lưu đi hay khi đang xung đột; mỗi lượt đăng ký nghe riêng và thôi nghe riêng);
web/src/hooks/use-canvas-chat-notices.test.ts (dòng về lần lưu hỏng đi khi canvas ấy được lưu,
dù nháp còn trên máy hay chỉ trong tab, còn dòng của canvas khác ở lại; đi khi lần lưu để lại sau
đó hạ cánh; danh sách vẫn là chính nó khi canvas được lưu chưa từng bị nhắc tới; hiện lại khi lần
lưu sau lại hỏng; nút ẩn vẫn cất như cũ)FakeCanvas với đồng hồ giả: web/src/hooks/use-canvas.test.ts (“saves once,
1.5 s after the last of thirty keystrokes and not a moment sooner”, Cmd/Ctrl+S lưu ngay và không lưu
lại khi hết lúc dừng gõ, sửa mà chữ không đổi không lùi lần lưu, “says so when this device cannot
keep the draft, until it can again”, canvas 100 KB ẩn tab hay tháo panel đi bằng request thường và
nháp giữ phím gõ ngay trước, canvas 10 KB tháo panel thì một PUT keepalive đúng base, “forgets the
draft of a canvas deleted while open once its panel goes”, trang hỏi trước khi đóng chỉ khi chữ
chưa lưu mà máy cũng không giữ được nháp, thôi hỏi khi chữ đã lưu hay đã về bản gốc, và vẫn hỏi
sau khi panel đi cho tới khi lần lưu để lại hạ cánh);
web/src/hooks/use-canvas-switch.test.ts (“shows the next canvas at once and finishes the last
one’s save behind it”, thử lại đang chờ được thử thêm một lần khi rời rồi dừng, ẩn tab lúc canvas
sau đang tải không lưu gì cho nó, “lets a canvas left behind finish its save without reading it
again or showing it”, cờ nháp hỏng không theo sang canvas sau);
web/src/hooks/use-canvas-sync.test.ts (đọc lại khi luồng nối lại sau lần rớt mà không đọc lúc mới
nối, khi tab hiện, khi event báo bản mới; “ends on the newest version when the newer one is
loaded while a read of one newer still is out”: với server giả, xung đột đang đứng ở v2, v3 được
báo và lần đọc nó bị giữ lại, bấm “Nạp bản mới” rồi thả lần đọc thì hook đọc thêm đúng một lần
(ba GET cả thảy) và dừng ở chữ của v3, trạng thái đã lưu; lần lưu không reply tới hạn (30 giây, thêm một giây cho mỗi
50 KiB thân) là chậm chứ không phải mất và được gửi lại;
nháp qua lần mount thử của StrictMode; “opens text typed after a save that landed unheard as the
person’s draft, not as a clash”; năm test flush); web/src/lib/canvas-handoff.test.ts (lần lưu
cuối của canvas đã rời: 409 của chính mình là đã lưu, lỗi giữ nháp và báo người nghe còn đăng ký,
gửi chữ gõ sau lần lưu đang bay, ngân sách keepalive tính bằng byte, mỗi lúc một lần lưu keepalive,
lỗi trả lại ngân sách; canvas rời đi mà máy không giữ được nháp thì trang hỏi trước khi đóng tới khi
lần lưu xong, hỏng hay ném lỗi cũng thôi hỏi và thông báo không hứa nháp, có nháp thì không hỏi gì,
hai canvas thì hỏi tới khi cả hai xong; runner thật mà trình duyệt từ chối nháp thì tab giữ chữ và
trang vẫn hỏi sau khi lần lưu hỏng; nháp đi khi bản lưu giữ chữ ở dạng đã trộn với bản của người
khác, và ở lại khi người đã gõ thêm từ lúc rời; “says nothing and keeps no draft when the canvas
was deleted before its last save got there”: canvas bị xoá trong lúc lần lưu cuối còn bay thì
PUT gặp 404, không người nghe nào được gọi và nháp của nó bị xoá, vì không có lần lưu nào hỏng
cả; “lets go of words only this tab held for a canvas deleted before its last save got there”:
nháp mà chỉ tab giữ cũng đi theo, nên trang thôi hỏi trước khi đóng)web/src/lib/canvas-requests.ts. Hạn của lần lưu có test
riêng ở web/src/lib/canvas-requests.test.ts: 30 giây cộng một giây cho mỗi 50 KiB thân, làm tròn
lên; đo trên byte UTF-8 của thân nên 100000 chữ “ệ” chờ lâu hơn 100000 chữ “x”; không reply tới hạn
thì báo saveTimedOut chứ không báo mất, trước hạn một mili giây thì chưa, 3 MB html không bị cắt
ở giây 31, reply mất không mang status, 413 mang trần của loại canvas. Nhóm “how long a save may go
unanswered after saves in a row had no reply in time” giữ việc nới hạn cho đường truyền chậm: sau
mỗi lần lưu hết hạn liên tiếp thì phần thời gian cho thân gấp đôi còn 30 giây chờ reply giữ nguyên
(4 MB: 111920, 193840, 357680, 685360 ms), dừng ở 8 lần dù hết hạn bao nhiêu lần, thân 34 byte vẫn
là nửa phút chứ không thành một phút, và request thật sống tới đúng hạn đã nới. requestSave trả
về cả hai hạn, { ms, firstMs }: hạn của chính lần lưu và hạn đầu của cùng thân ấy; “keeps the
first deadline what it was however many there were: only the save’s own grows” giữ firstMs đứng
yên ở 91441 ms khi ms lên 275763 rồi 521525. Lần đọc có nhóm “a read
coming back” ở cùng tệp: gửi đi cái server giữ và không để timer nào lại, “says the canvas could
not be read when what came back cannot be taken in, instead of leaving it loading” (thân trả về
mà máy trạng thái không nhận được thì báo đọc hỏng chứ không treo ở “đang tải”), “says in the
console what it could not take in” (lỗi ấy được ghi ra console đúng một lần kèm chính exception,
vì canvas chỉ nói là không đọc được), lần đọc bị từ chối báo hỏng kèm status đúng một lần và không
ghi gì ra console.
web/src/lib/canvas-runner-wait.test.ts giữ waitMs của runner (0 khi chưa có lần lưu nào, cả
hạn lúc lần lưu đi rồi ít dần, dài hơn với lần lưu lớn, về 0 khi lần lưu xong hay hết hạn, và vẫn là
0 chứ không âm khi đồng hồ nhảy qua hạn trước lúc timer chạy) và nhóm “a save the link was too slow
for”: đường truyền mất 150 giây cho 4 MB (web/src/test/slow-link.ts, PUT bị bỏ trước lúc tới thì
server không nhận) vẫn lưu xong mà không ai chạm vào canvas, lần gửi lại có waitMs 193842 ms và
còn trong hạn khi hạn đầu đã qua, hết hạn hai lần liên tiếp thì gấp bốn, lưu xong thì về hạn đầu,
lần bị server từ chối ở giữa không xoá mức nới, lần mất hay bị từ chối không làm hạn dài ra, canvas
nhỏ vẫn nửa phút. waitMs(true) là phần còn lại của hạn đầu, tính từ lúc chính lần lưu ấy đi:
“has, for a message, only what is left of the deadline it was first given, however long its own
has grown” (sau một lần hết hạn waitMs() là 193842 còn waitMs(true) là 111921, về 0 khi hạn
đầu qua dù lần lưu còn bay trong hạn đã nới, và 0 khi không lần lưu nào đang bay). Rời canvas hỏi
waitMs() nên theo hạn đã nới; tin gửi đi hỏi waitMs(true) nên không theo.
web/src/lib/unload-guard.test.ts giữ câu hỏi trước khi đóng trang (không hỏi khi chưa ai giữ,
hỏi khi có người giữ và đặt cả returnValue cho engine cũ, thôi hỏi khi nhả và nhả hai lần không
hại, còn hỏi khi còn người giữ khác), web/src/hooks/use-saving-note.test.ts giữ lời báo đang lưu
(không nói gì khi việc xong trong chưa tới 300 ms, nói từ 300 ms và thôi khi việc xong hay hỏng,
trả lại kết quả và chuyển lỗi đi, không để timer nào lại). Các test khác chạy hai request ấy thật
trên FakeCanvas, nên chuyển chúng khỏi canvas-runner.ts không đổi test nàoweb/src/lib/canvas-author.test.ts (người là “bạn”, agent theo tên, agent có id user vẫn
là agent, agent không còn thì theo id); web/src/lib/format-bytes.test.ts (bước 1024, một chữ số lẻ
dưới mười viết bằng dấu phẩy, làm tròn tới 1024 thì lên đơn vị); web/src/lib/hidden-chars.test.ts
(“shows a right-to-left override as a visible mark”, mọi ký tự điều khiển bidi và zero-width)web/src/components/canvas/canvas-panel.test.tsx (canvas người viết mở ở Sửa, sang Xem rồi
về không mất chữ gõ, “opens a canvas an agent wrote to read, and keeps reading when a newer version
arrives”, canvas người viết vẫn ở Sửa khi agent ghi vào, nháp trên máy mở ở Sửa kể cả trên canvas
agent viết, markup trong canvas markdown và code hiện thành chữ; đổi tên bằng PATCH, “shows the
name the server answered with, when the stream says nothing of the rename”: tên mới lấy từ chính
reply chứ không chờ luồng báo, canvas vừa
tạo mở với tên chờ gõ đè, đổi tên mà server không thấy canvas là canvas đã xoá; “keeps its text to
read and copy, and offers nothing that needs the canvas”; về danh sách, đóng, tải bản mới nhất,
“copies the text as it is, unmarked”, dock lưu được chữ đang mở, hỏi được canvas còn không và buông
khi panel tháo); web/src/components/editable-title.test.tsx (“renaming a canvas beside the
conversation”: ô tên có nhãn và placeholder riêng dưới tiêu đề của cuộc trò chuyện, “opens ready to
type over the name of something just made”); web/src/components/canvas/canvas-editor.test.tsx
(Ctrl/Cmd+S lưu ngay thay cho hộp lưu của trình duyệt, kể cả khi giữ Shift, còn chữ s để cho ô gõ;
báo canvas lúc ô mất focus và lúc soạn IME; code, html, svg, mermaid và một loại web không biết
đều dùng phông code và không kiểm chính tả, còn văn xuôi được kiểm chính tả trong phông của trang;
“stays on
the characters it was on when a line arrives above”, tính từ chỗ phím gõ để con trỏ, giữ chỗ cuộn);
web/src/components/canvas/canvas-panel-modes.test.tsx (canvas vừa tạo ở đây mở ở Sửa kể cả khi
agent ghi vào trước lần đọc đầu, chữ gõ gộp vào bản mới hơn của agent mở ở Sửa và nói đã gộp tới
khi lưu, canvas code của người dùng phông code; tên canvas vừa tạo mở để gõ, tối đa 200 ký tự;
không hiện dòng phiên bản khi canvas đang được đọc dù bản mới đã báo tới; chọn Xem hay Sửa thì
đóng lịch sử); web/src/components/canvas/canvas-panel-lent.test.tsx (cái panel đang mở cho dock
mượn: thời gian lần lưu đang bay còn được chờ là 0 khi chưa có lần lưu nào, đủ hạn cộng một mili
giây lúc lần lưu đi, ít dần theo giờ và về 0 khi lần lưu về; và máy này có giữ được nháp của chữ đang
gõ hay không, rồi giữ được trở lại khi một lần ghi nháp vừa)web/src/components/canvas/canvas-status.test.tsx (chữ cho từng trạng thái và lý do không
bản nào giữ chữ đang gõ, “follows a save from the keystroke until it lands”, server không trả lời
khi máy có mạng, lần lưu chờ mạng khi máy mất mạng, lần lưu hết hạn mà chưa reply là “Mạng chậm,
đang gửi lại” chứ không phải máy chủ không phản hồi, chữ nêu đúng trần (4 MB, 2 MB) mà canvas quá
lớn bị giữ, “shows the largest canvases when the server is full, saves on its own no more, and
saves on Cmd+S”, nội dung bản lưu trong lúc sửa chỉ hiện ở thanh xung đột, canvas xin đóng mà chưa
bản nào giữ chữ nói lý do rồi đóng hẳn khi được bảo, và nút nói bản nháp chỉ còn trong tab này khi
máy không giữ được nháp); web/src/components/canvas/canvas-status-cap.test.tsx (trần mà server
giữ một lần lưu được nói ở mọi chỗ panel nói vì sao chưa lưu: cạnh dòng phiên bản, trong thông
báo của canvas xin đóng, và ở Lịch sử khi một lần khôi phục chờ chữ không lưu được);
web/src/lib/canvas-reasons.test.ts (“reads %i as its reason, never the server’s own words”,
request không tới server là mất kết nối); web/src/components/diff-view.test.tsx (“keeps three
unchanged lines around a change and counts the rest”, chỗ sửa chỉ gồm một ký tự ẩn vẫn hiện ra, hai
bản giống nhau và hai bản quá lớn để so theo dòng đều được nói);
web/src/components/canvas/canvas-conflict.test.tsx (thanh xung đột nêu ai lưu bản nào, giữ chữ
của tôi thì không nạp bản kia, nạp bản kia khi được bảo, “shows what keeping mine would change in
theirs, and hides it again”; chữ mà “Nạp bản mới” thay lấy lại được cho tới khi gõ);
web/src/components/canvas/canvas-notices.test.tsx (“says the canvas could not be opened, and
opens it on retry”, máy này không giữ được nháp thì nói, canvas không có gì chưa lưu thì không
cảnh báo nháp dù trình duyệt chặn lưu trữ, cảnh báo đi ngay khi chữ về đúng bản đã lưu)web/src/components/canvas/canvas-history.test.tsx (“lists each version, the newest first,
and compares one with the version listed before it”, so với bản cũ nhất còn giữ khi được hỏi và nói
bản cũ nhất không có bản trước, đóng mà không khôi phục gì; “saves the typing first, then makes the
version picked the newest without waiting for the event”, ghi chú khôi phục hiện trên bản nó tạo,
không khôi phục khi chữ gõ chưa lưu được và nói vì sao, server hết chỗ thì hiện các canvas lớn nhất;
bản được chọn hay bản cần khôi phục đã bị gộp mất thì đọc lại danh sách và vẫn mở, “takes a restore
of a canvas deleted meanwhile for the deletion”);
web/src/components/canvas/canvas-history-reads.test.tsx (bản mới báo tới lúc lịch sử đang mở thì
đọc lại danh sách, chọn lại bản đọc hỏng thì đọc lại và thôi báo lỗi, danh sách hay một bản bị từ
chối vì canvas đã xoá là canvas đã xoá; khôi phục đang đi thì không bấm lại được, server từ chối thì
bấm lại được, lần thử lại lưu được chữ gõ thì thôi nói chữ gõ chặn lần khôi phục)web/src/hooks/use-canvas-list.test.ts (không hỏi gì khi chưa mở cuộc nào, chỉ canvas của
cuộc này và mới nhất trước, “drops the last conversation’s list at once, and its reply when it comes
late”, “reads the list once for a burst of changes, half a second after the first”, thấy canvas tạo
ở chỗ khác dù lời báo chưa nêu cuộc nào, không đọc được thì nói và đọc lại khi thử lại, đọc lại hỏng
vẫn giữ danh sách đang có, đọc lại khi luồng nối lại và khi tab hiện, đóng cuộc rồi thì không nghe
thay đổi nữa, không hiện gì của danh sách cuộc trước trong lúc đọc danh sách cuộc sau, thay đổi
nghe được ngay trước khi đóng cuộc không làm đọc lại danh sách cuộc ấy, lần đọc trước hỏng sau khi
lần đọc sau đã về thì giữ cái lần sau thấy); web/src/components/canvas/canvas-picker.test.tsx
(đang tải, chưa có canvas, lỗi có nút thử lại, “shows each canvas with its kind, version and last
change, and opens the one picked”,
“makes a canvas, one at a time, and says when one could not be made”, “names the kind of a
drawing, a diagram and a picture, and an unknown kind as it is”, “offers the five kinds a person
can make, never an image, and makes the one chosen”, “keeps the kind chosen for the next canvas,
and changes it on the next choice”, “holds the kind still while a canvas is being made”);
web/src/lib/artifact-events.test.ts (“announces a canvas found gone as a deletion that names no
conversation”)web/src/hooks/use-canvas-dock.test.ts (“never shows the last conversation’s canvas in a
render of the next one”, lần lưu cuối hỏng sau khi đổi cuộc không để gì lại ở cuộc sau; tạo canvas
markdown chưa có tên trong cuộc này rồi mở với tên chờ gõ, bảng “makes a %s canvas holding what
one of its kind starts from” cho code, html, svg và mermaid, “sends nothing, and shows no request
as out, while no conversation is open to put the canvas in”, không tạo được thì nói và ở lại danh
sách; “gives up on the open canvas’s save after 5 seconds”, đóng hay về danh sách mà không lần lưu
nào xong thì ở lại tới khi đóng hẳn, lần lưu quá hạn cũng ở lại, còn lần lưu chưa hết hạn riêng của
nó thì panel đóng và để lần lưu ở phía sau chờ nốt, canvas bị xoá lúc lần lưu cuối đang bay hay đã
bị xoá thì đóng ngay; lần lưu hỏng sau khi panel đi được báo tới khi ẩn, canvas đã đóng hẳn thì
không báo, trừ khi máy không giữ được nháp của nó: khi ấy thông báo là chỗ duy nhất người biết;
“brings the canvas forward on each opening, and the button toggles the dock”);
web/src/hooks/use-canvas-dock-moves.test.ts (mở canvas hay danh sách khi đang xem hoạt động thì
đưa tab canvas lên, không tạo canvas khi chưa mở cuộc nào, không báo lỗi tạo của canvas xin trước
khi người mở canvas khác, canvas mở lại mà lần lưu cuối hỏng thì được báo lại, panel cũ buông muộn
thì dock vẫn hỏi panel mới); web/src/components/canvas/canvas-dock-view.test.tsx (focus đi từ nút
Canvas vào cột mở ra, sang nút về chat khi dock phủ cuộc trò chuyện, và đứng yên khi dock chuyển từ
danh sách sang canvas; cột hoạt động nằm trong phần tử của dock nên bàn phím đang ở đó thì ở
yên khi cột mở ra từ một cú bấm không lấy bàn phím, dù hoạt động lùi về sau tab của nó, và về
nút Canvas khi dock đóng; hoạt động hiện khi chưa mở gì, tab hoạt động theo cú bấm, lớp phủ hiện cả
dock; cột rộng đúng như cạnh nói; nút Canvas đếm canvas, không hiện số khi chưa biết canvas
nào, và nói dock mở hay đóng; lần lưu không về
của canvas chưa có tên gọi là canvas không tên, và thông báo nói bản nháp vẫn trên máy khi giữ được,
nói nháp chỉ còn trong tab và mở lại canvas để lưu khi máy không giữ được; nhóm “making a canvas of
a kind from the list”: “asks for the kind chosen with what it starts from, and opens it to edit as
text”);
web/src/hooks/use-canvas-width.test.ts (“takes half the room beside the sidebar until the person
chooses”, cả canvas lẫn cuộc trò chuyện rộng ít nhất 360 px, chỉ nhớ bề rộng khi được bảo giữ, bề
rộng đã giữ co theo cửa sổ hẹp và trở lại khi cửa sổ rộng ra, theo cửa sổ khi người chưa chọn, không
hẹp dưới 360 px kể cả cạnh cuộc trò chuyện hẹp, bỏ bề rộng đã giữ không phải số hay lớn quá để là
số, đo chỗ cạnh sidebar theo bề rộng mà stylesheet đặt);
web/src/components/canvas/canvas-handle.test.tsx (“widens the canvas as the pointer moves left
and keeps where the button came up”, kéo không bôi đen chữ, sau khi nhả thì thôi theo con trỏ; chỉ
nút chính mới kéo; panel đóng giữa lúc kéo thì buông con trỏ);
web/src/components/conversation-header.test.tsx (“starts with the pill given first, ahead of spend
and options, and ends with the extras”: nút Canvas đứng đầu hàng pill nên ở 390 px vẫn trong màn
hình)web/src/hooks/use-canvas-create.ts và không có tệp test riêng: dock
đưa cho nó số lần chuyển và hai cách hiện kết quả, nên các test ở trên (tạo từng loại kèm chữ mở
đầu của loại ấy, không gửi gì khi chưa mở cuộc nào, lỗi tạo, chuyển trước khi reply về) chạy nó
qua dock và chuyển nó ra khỏi dock không đổi test nào. Chữ mở đầu của từng loại có test riêng ở
web/src/lib/canvas-templates.test.ts (năm loại theo thứ tự danh sách hiện, markdown và code bắt
đầu trống, html là trang nhỏ nhất còn chừa thân để viết, svg có viewBox để vẽ, mermaid có hai ô
đã vẽ được với tên tiếng Việt, mẫu nào cũng kết thúc bằng xuống dòng và lần nào cũng cho cùng
chữ, và nằm xa dưới trần nhỏ nhất của mọi loại)FakeCanvas: web/src/app-canvas.test.tsx (“opens as a tab over the activity,
in a column its edge widens, and keeps both mounted”, “keeps the open canvas and its typing as the
window crosses 1101 px both ways”, “makes a canvas from the list and opens its name to type over”,
“covers the chat column, and Escape saves the typing, closes it and gives focus back”, “goes back to
the chat from the covering list”, “closes the drawer opened over the canvas before the canvas”,
“closes as another conversation opens, and the typing left behind is saved”, “tells in the chat of a
save that failed after the switch, until dismissed”, “drops the ones older than 30 days as the app
starts”)canvas.spec.ts ở 1440 px (“opens in a tab beside the chat, and a pause in typing saves
once from the version it began on”, “keeps every character typed while a save is held for two
seconds”, “an agent’s save on the line being edited raises the conflict bar, and keeping mine saves
over it”), ở 1000 px và 390×844 cảm ứng (“saves, keeps the approval out of reach, fits a finger and
goes back to the chat”: nút Canvas trọn trong màn hình, Tab không tới nút duyệt dưới lớp phủ, mọi
nút trong dock ≥40px, không cuộn ngang)web/src/lib/canvas-handoff-flush.test.ts (handoffsSettled trả ngay khi không lần lưu
nào đang bay, chờ mọi lần lưu đang bay kể cả lần hỏng hay ném lỗi và không chờ lần bắt đầu sau lúc
hỏi; within trả giá trị của việc khi nó xong kịp rồi xoá timer, trả giá trị muộn khi hết giờ chứ
không sớm hơn, chuyển lỗi của việc đi và xoá timer; flushAll trả phiên bản panel vừa lưu khi mọi
lần lưu đang bay đã xong, giữ phiên bản ấy khi một lần lưu chưa về tới trần, chờ panel và các lần
lưu đang bay dưới một trần chung rồi trả null khi panel không còn, chỉ chờ các lần lưu đang bay
khi không có panel, bỏ chờ ở trần, xoá timer khi mọi thứ đã về; trần tính sau hạn của lần lưu đang
bay: panel chờ hạn 60 giây thì trả null ở 65 giây, hạn của các lần lưu đã rời tính lâu nhất chứ
không cộng dồn, hạn của panel và của các lần lưu ấy lấy cái dài hơn, không bao giờ ngắn hơn trần,
giữ phiên bản của panel khi lần lưu chậm của canvas đã rời còn trong hạn, trả lời ngay khi mọi thứ
đã về; “waits for a save left behind only as long as it was first given, not for the deadline it
has grown”: lần lưu đã rời có hạn đầu 60 giây và hạn đã nới 600 giây thì trả null ở 65 giây; nhóm
handoffOut: đúng với canvas mà lần lưu để lại còn bay và sai với canvas khác, sai trở lại khi lần
lưu ấy xong hay hỏng);
web/src/hooks/use-canvas-dock-flush.test.ts (“answers the version the open panel’s last save
landed”, null khi không phiên bản nào giữ chữ trong trần, chờ lần lưu của canvas đã rời trước đó
và giữ phiên bản của panel khi lần lưu kia muộn, chờ lần lưu của canvas đã rời khi không có panel
và trả null, một hàm duy nhất suốt lúc dock còn gắn, rời canvas bằng tay chỉ hỏi panel đang mở
chứ không chờ các lần lưu khác; lần lưu của panel được hạn riêng của nó rồi mới tới trần và trả
null ở 65 giây khi hạn là 60, trả phiên bản ngay khi lần lưu hạ cánh trong hạn, hỏi hạn của panel
sau khi lần lưu đã bắt đầu vì chính lần lưu đặt ra hạn ấy, và chờ cả hạn của lần lưu canvas đã
rời); web/src/components/composer-send-lock.test.tsx (“holds the
text in a read-only box and sends it once, however it is asked again”, chữ được nhận thì ô và
bản nháp đã giữ được xoá, không được nhận hay bị từ chối thì ô trả lại cùng chữ và bản nháp để
gửi lại được, từ chối còn báo ra console, onSend không trả gì thì xoá ô ngay như cũ; khoá thuộc
về hội thoại nơi chữ được gõ: chữ được nhận thì bản nháp của hội thoại đã rời bị xoá còn bản của
hội thoại đang ở giữ nguyên, không được nhận thì bản nháp ấy được giữ; Stop trả chữ xếp hàng về ô
trong lúc chờ thì chỉ phần đã gửi bị xoá, gợi ý thay chữ trong lúc chờ thì giữ gợi ý, danh sách
lệnh / bị dẹp và không nhận lệnh khi ô đang giữ)FakeCanvas: web/src/app-canvas-send.test.tsx (“saves the typing in the
canvas first, then sends the message naming that canvas”: PUT canvas đi trước POST tin; “names no
canvas when none was opened in this tab”: thân POST chỉ có {text}; “holds the words in a
read-only box until the canvas is saved, and sends once however often Enter comes”; rời sang hội
thoại khác trước khi lần lưu về thì không gửi gì và chữ trở lại ô khi quay lại; ở hội thoại khác
tin mới vẫn chờ lần lưu của canvas người vừa bỏ lại; “does not blame a passage the message never
named, and leaves the words in the box”: tin từ ô soạn nêu canvas mà không mang đoạn chọn, nên
422 của nó (ở đây là danh sách string_too_long của server) được nói bằng câu tiếng Việt cho
yêu cầu không hợp lệ chứ không bảo đoạn chọn không còn khớp, mã lỗi của server không lộ ra, chữ
ở lại ô, ô gõ lại được và không có
bong bóng; dưới ô soạn tin chỉ hiện “Đang lưu canvas…” khi lần chờ quá 300 ms và thôi hiện khi tin
đi, lưu ngay thì không hiện gì, lần lưu hỏng thì tin vẫn đi)canvas-send.spec.ts (“a message sent over typing the canvas has not saved yet waits
for the save, then names the canvas”: lần lưu bị giữ thì chưa có POST nào và ô soạn tin chỉ đọc
còn nguyên chữ, thả ra thì thứ tự là lưu rồi gửi, thân tin nêu canvas, ô trống và mở lại)web/src/hooks/use-canvas-dock-message.test.ts (nhóm “the wait of a message on a
save given longer after saves that had no reply in time”: panel có hạn đầu 60 giây và hạn đã nới
600 giây thì flush trả null ở 65 giây chứ không sớm hơn một mili giây, lần lưu của canvas đã rời
cũng vậy; nhóm “whether a message about to go names a canvas with text no version holds”:
flushForMessage trả false khi chưa mở canvas nào, khi lần lưu của canvas đang mở đã xong, khi
canvas đã bị xoá, khi lần lưu còn bay là của canvas khác với canvas tin nêu, khi tin không nêu
canvas nào (kể cả khi một panel có lần lưu hỏng vẫn còn gắn vào dock), và khi người mở canvas
khác trong lúc tin còn chờ; trả true khi lần lưu hỏng, khi
hết lượt chờ mà lần lưu chưa về (và không trả gì trước đó), khi canvas tin nêu đã được cất đi trên
màn hẹp mà lần lưu cuối còn bay; nhóm “the note that a message went before the canvas it names
was saved”: sentUnsaved bật và tắt theo noteSentUnsaved, thuộc về hội thoại tin đã đi (sang
hội thoại khác không hiện, quay lại thì hiện), hàm lấy ở hội thoại nào thì nói về hội thoại ấy dù
người đã sang nơi khác, mỗi hội thoại giữ lời của riêng mình, không hội thoại nào mở thì không
có); web/src/hooks/use-canvas-dock.test.ts giữ việc rời canvas vẫn hỏi hạn đã nới: panel giả
trả 0 cho hạn đầu và 20 giây cho hạn đã nới thì vẫn đóng được, trả 20 giây cho hạn đầu mà hạn
đã nới đã hết thì ở lại; web/src/components/canvas/canvas-dock-view.test.tsx nhóm “the line
saying a message went before its canvas was saved” (không có cho tới khi dock nói, là status
chứ không phải alert, đứng cạnh thông báo lần lưu không về và mỗi dòng được ẩn bằng nút riêng)FakeCanvas: web/src/app-canvas-send-unsaved.test.tsx (“goes once the save
has had the time it was first given, with the version saved before, and says so”: 500 KiB
markdown trên đường truyền mất 100 giây, ba lần lưu hết hạn, tin chưa đi ở 45 giây thiếu một mili
giây và đi ở đúng 45 giây, ô soạn tin mở lại, server chưa có chữ mới; “leaves the save the
longer time it was given”: lần lưu ấy vẫn hạ cánh ở giây 100 và dòng báo còn đó; dòng báo mang
đúng câu chữ, lớp notice warn canvas-notice và nằm trong cột chat; tin sau gửi lúc canvas đã lưu
thì dòng báo đi; “goes with a question about a passage of the canvas, which went with the canvas
saved”: câu hỏi về một đoạn của canvas, đi kèm bản panel vừa lưu, cũng làm dòng báo đi; câu hỏi
ấy không tới được server thì dòng báo còn, vì tin gửi gần nhất vẫn là tin cũ; nút “Ẩn thông báo”
cất nó mà không gửi gì thêm; tin server không nhận thì không báo; canvas lưu kịp hay không mở
canvas nào thì không báo)web/src/hooks/use-canvas-focus.test.ts nhóm “coming into a conversation” (màn rộng
thì mở lặng lẽ canvas server đang mở ở hội thoại ấy và không ghi gì, server không mở gì thì không
mở gì và tin đi không kèm canvas, hỏi lại ở mỗi hội thoại đổi sang, màn hẹp không hỏi gì và không
hỏi lại khi màn thành rộng hay dock đổi, id server không bao giờ tạo thì không mở, mở đúng một
lần và không ghi gì dù StrictMode chạy effect lần hai, câu trả lời tới sau khi người sang hội
thoại khác hay sau khi người mở rồi đóng canvas thì không mở gì, đọc hỏng chỉ ghi console và dock
vẫn dùng được); web/src/hooks/use-canvas-focus-close.test.ts (“tells the server once, from a
canvas opened here, and opening it told nothing”: đóng trên màn rộng gửi PUT {artifact_id:
null} đúng một lần, cả khi chỉ mới hiện danh sách; về danh sách, màn hẹp, hay sang hội thoại
khác với canvas đang mở thì không báo gì; ghi hỏng chỉ ghi console và dock vẫn đóng; focusId
thành null mà người không đóng gì, lúc hook gắn hay cùng lúc đổi hội thoại, thì không báo, và
lần đóng thật sau đó vẫn báo); web/src/hooks/use-canvas-dock-restore.test.ts (“opens it
quietly, and names it, when nothing has moved since the ticket”; người đã sang hội thoại khác rồi
quay lại, hay giữ tham chiếu lấy ở hội thoại đã rời, thì không mở gì; id không đúng mười hai chữ
số hex thường thì không mở: a1, ../x, chữ hoa, thiếu, thừa, xuống dòng ở cuối, rỗng);
web/src/hooks/use-canvas-dock-focus.test.ts nhóm “a canvas opened quietly” (đánh dấu tới bước
kế, mở thường thì không đánh dấu; canvas khác mở, danh sách mở, dock đóng hay bị buộc đóng đều
hết đánh dấu); web/src/components/canvas/canvas-dock-view.test.tsx nhóm “focus when a canvas
opens without being asked for” (mở thường thì focus vào dock dù từ đâu tới; mở lặng lẽ thì focus
ở đâu vẫn ở đó: không đâu thì vẫn không đâu, ở ô soạn tin thì vẫn ở ô soạn tin)web/src/api/artifact-client.test.ts (“reads what a conversation has open,
null when nothing, and sets or closes it under an encoded id”, “rejects a focus request the
server refused, with its status”); web/src/test/fake-canvas-focus.test.ts (route canvas đang mở
của hội thoại đọc và ghi như server: chưa mở gì thì null, mở một canvas thì chia sẻ nó với hội
thoại, null đóng, canvas không có thì 404 và giữ nguyên cái đang mở, đoạn chọn dài nhất cho
phép đếm theo ký tự, canvas bị xoá thì không còn mở ở đâu, canvas tạo trong hội thoại thì mở ở
đó và không kèm đoạn chọn còn canvas tạo riêng thì không mở ở đâu, một lần đọc cũng giữ và thả
được như request canvas khác, từ chối hay mất reply theo yêu cầu, method không có route thì 405; tin mang canvas được áp trước khi đi tiếp: không mang hay
mang null thì để yên, id thật thì mở và chia sẻ, id đã mất thì đóng, đoạn chọn ghi đè đoạn
trước, đoạn ghi chú sẽ bỏ thì 422 và không áp gì; FakeBackend từ chối nguyên tin với 422 mà
không lưu gì). Dụng cụ dùng chung: web/src/test/canvas-app.tsx (cả App trên hai hội thoại
“Một”, “Hai” và canvas “Ghi chú”), canvas-dock-hook.ts (dock trên server giả), screen-width.ts
(bề rộng cửa sổ giả cho matchMedia)FakeCanvas: web/src/app-canvas-focus.test.tsx (“tells the server once on a
wide screen, and the next message names no canvas”, “tells nothing on a narrow screen, where the
canvas put away is still the one the message names”, “opens beside the chat without taking the
focus from the box, and writes nothing”: chữ gõ ngay sau đó vào ô soạn tin chứ không vào canvas,
“carries no canvas from a read that was answered after the person went to another conversation”)web/src/lib/artifact-tag.test.ts (đọc canvas và phiên bản từ thẻ ở đầu kết quả
ghi, kể cả lần ghi không đổi gì; không có thẻ, thẻ không nằm ở đầu, thẻ không trọn, id server không
bao giờ tạo hay phiên bản số không giữ chính xác được thì không đọc; id là đúng mười hai chữ số hex
thường, có xuống dòng ở cuối thì bị từ chối); web/src/lib/canvas-title.test.ts (tên được làm sạch
đúng như server: xuống dòng và chuỗi khoảng trắng thành một dấu cách, mọi ký tự isspace của Python
tính là dấu cách, BOM bị bỏ chứ không thành dấu cách, ký tự điều khiển và định dạng kể cả bidi và
khoảng trắng rộng bằng không bị bỏ, ký tự nối giữ emoji liền nhau được giữ, chữ gõ rời được ghép
lại, không còn gì nhìn thấy thì xin tên khác, tối đa 200 ký tự và nói đã thấy bao nhiêu, mỗi ký tự
đếm một lần dù chiếm mấy đơn vị UTF-16, đếm sau khi làm sạch chứ không trước)web/src/hooks/use-canvas-list-verify.test.ts nhóm “the title of a canvas a card
names” (tên là tên danh sách đang giữ và không hỏi server thêm; canvas chưa ai đọc thì chưa biết
tên; canvas danh sách không giữ thì đọc một lần rồi giữ; danh sách giữ canvas rồi thì lấy tên của
danh sách), nhóm “asking whether a canvas is still there” (chờ danh sách đọc xong nên canvas danh
sách giữ không bao giờ bị hỏi; hỏi khi danh sách đã về mà không giữ canvas; hỏi lại sau mỗi lần đọc
danh sách khi chưa có câu trả lời; mất mạng không phải câu trả lời; 404 là canvas đã xoá và báo mọi
bên nghe đúng một lần; từ chối khác không phải xoá; mỗi canvas một lần đọc tại một lúc; id server
không tạo thì không hỏi; sang hội thoại khác thì quên việc định hỏi; một hàm duy nhất) và nhóm “a
canvas deleted” (nghe thấy từ luồng, vẫn là đã xoá sau khi danh sách đọc lại mà thiếu nó, khác với
canvas đổi); web/src/hooks/use-canvas-list-memory.test.ts (danh sách nhớ gì về các canvas thẻ
nêu: tên của mọi canvas đã đọc chứ không chỉ canvas đọc sau cùng, mọi canvas đã xoá chứ không chỉ
canvas xoá sau cùng; chờ danh sách của cuộc trò chuyện vừa mở và không lấy danh sách của cuộc
trước làm của nó, nên canvas chưa biết không bị hỏi trước khi danh sách mới về);
web/src/hooks/use-canvas-dock-typing.test.ts (typing() là không khi chưa có panel,
là điều panel nói và hỏi lại mỗi lần, lại là không khi panel buông, một hàm duy nhất);
web/src/components/canvas/canvas-dock-keyboard.test.tsx (dock thật trong cột: typing() là
không khi bàn phím không ở đâu hay ở ô soạn tin dù canvas đang mở; là có khi bàn phím ở một nút
của canvas đang mở, ở một tab của dock, ở một dòng trong danh sách canvas, ở khung của trang
HTML mà app chỉ thấy là iframe, và ở phần hoạt động dock đang hiện khi chưa mở canvas nào;
lại là không khi bàn phím về ô soạn tin; chữ chưa lưu thì vẫn là có sau khi bàn phím đã rời dock;
nhóm “whether the open canvas holds the person’s typing…”: editing() hẹp hơn, là không khi bàn
phím ở một nút của canvas, ở một tab, trong khung trang hay khi chưa mở canvas nào dù typing()
là có, là có khi bàn phím ở ô chữ của canvas và khi còn chữ chưa lưu);
web/src/hooks/use-canvas-auto-open.test.ts (mở lặng lẽ khi lệnh tạo xong ở lượt tab này đang
xem, đúng một lần dù thread vẽ lại bao nhiêu, hai canvas trong một lần vẽ thì mở theo thứ tự tạo,
lấy canvas thẻ nêu chứ không lấy tham số, cần thẻ ở đầu kết quả, mở được cả khi chưa tải chi tiết
hội thoại; không bao giờ tự mở: sửa hay viết lại, lệnh tạo hỏng hay bị từ chối hay bị dừng, canvas
của hội thoại tab chỉ mới đọc dù lúc gắn hay lúc tải, canvas của lệnh đã xong trước khi màn hình
được vẽ, canvas trên màn quá hẹp kể cả khi màn rộng ra sau đó, canvas xong lúc người đang gõ kể
cả khi họ ngừng;
lệnh chưa xong thì bỏ qua và xét một lần khi xong, đang chờ duyệt thì bỏ qua rồi mở khi xong, xét
theo lúc kết thúc chứ không lúc bắt đầu; nhóm “a call still going when the screen was drawn, as
when the person comes back to the chat mid-turn”: lệnh đang chạy hay đang chờ duyệt lúc màn hình
được vẽ thì mở canvas khi xong, lệnh giao việc cũng vậy, đúng một lần dù vẽ lại; không mở khi
lịch sử đã đọc có lệnh ấy, khi lệnh hỏng, bị từ chối hay bị dừng, khi màn quá hẹp hay khi người
đang giữ bàn phím trong dock; lệnh đã có trong lịch sử đã lưu thì không phải của lượt
này, không chặn lệnh của lượt này mà lịch sử chưa có, phân biệt theo lệnh chứ không theo canvas nó
tạo)web/src/components/canvas/canvas-card.test.tsx (thẻ nói canvas đã tạo, tên
và phiên bản, và mở được; sửa, viết lại và “không đổi” nói đúng phiên bản; là thẻ riêng chứ không
phải thẻ công cụ thường; thẻ đang chạy nói đang viết kèm vòng quay — xong thì hết vòng quay —,
không có nút mở và không hỏi server; tên của
lệnh tạo đang chạy làm sạch như server, tên server sẽ từ chối thì vẫn gọi là Canvas; sửa hay viết
lại canvas thread chưa biết thì gọi là Canvas bất kể tham số, tham số hợp lệ chỉ để tra tên; id tham
số mà server không tạo thì bỏ qua; tham số không phải mapping, của lần chạy cũ, vẫn là thẻ; tên là
tên thread biết chứ không phải tên trong tham số, kể cả tên rỗng; ký tự ẩn hay đảo chiều hiện thành
dấu, cả trong nhãn nút; canvas mở là canvas thẻ nêu bất kể tham số, sửa hay viết lại không có thẻ
thì lấy id đã đưa, lệnh tạo không có thẻ thì không có nút; thẻ không ở đầu kết quả thì không có
canvas; canvas đã xoá thì nói vậy, giữ tên và không có nút, và đổi từ có nút sang không có khi
canvas mất; mỗi canvas hỏi server một lần dù vẽ lại, hỏi lại khi thẻ đang chạy xong; không bao giờ
hiện tham số hay chữ của kết quả); web/src/components/tool-call-card-canvas.test.tsx (ba lệnh ghi
thành thẻ canvas khi đang chạy và khi xong; lệnh hỏng, bị từ chối, bị dừng hay đang chờ duyệt giữ
thẻ thường và hiện lý do; lệnh đọc giữ thẻ thường; thread không có đường mở canvas thì giữ thẻ
thường; lệnh giao việc và công cụ khác như cũ; trong thread, thẻ nhận đúng đường mở của thread và
nằm đúng thứ tự giữa các thẻ của lượt); web/src/components/canvas/canvas-panel-typing.test.tsx
(canvas vừa mở thì chưa là đang gõ dù ô soạn đã có; chữ chưa lưu là đang gõ và hết khi đã lưu; ô
soạn đang giữ bàn phím là đang gõ dù đã lưu và hết khi buông; bàn phím ở chỗ khác trong panel thì
không; canvas đang đọc không có ô để gõ; chữ chưa lưu vẫn là đang gõ ở chế độ đọc; ô soạn biến mất
mà không còn gì chưa lưu thì hết; hỏi lại mỗi lần qua một tay cầm duy nhất)FakeCanvas: web/src/app-canvas-thread.test.tsx (“opens beside the thread at
once, leaves the keyboard in the box and writes nothing”, “goes with the next message the person
sends as the canvas they have open”, “does not take the panel from a canvas the person has the
keyboard in”, “does not take the panel from a canvas the person has the keyboard on a button of,
and still opens from its card” (bàn phím ở nút “Xem” chứ không ở ô soạn của canvas: canvas mới
không tự mở, bàn phím ở nguyên, nút Mở trên thẻ vẫn mở nó), “does not take the panel for a file
read into a new canvas either, with the keyboard on a tab of the dock, and still opens from its
card” (lệnh artifact_import tạo canvas mới lúc bàn phím ở tab “Hoạt động” của dock: cũng không
tự mở, bàn phím ở nguyên, nút Mở trên thẻ vẫn mở nó), “opens only the once, however the
person goes from the chat to the crew and back”, “opens when its call ends after the person left
the chat mid-turn and came back” (lệnh tạo đang chờ duyệt, người sang Quản lý rồi về chat và cho
phép: canvas mở cạnh thread khi lệnh xong, không lần ghi “canvas đang mở” nào);
màn hẹp chỉ hiện thẻ có nút mở và không tự mở gì; lượt trước tạo canvas thì thẻ nằm trong thread đã
lưu, chỉ mở khi được bấm và kéo bàn phím vào dock; server không còn canvas thì thẻ nói vậy và không
có nút)canvas-thread.spec.ts (ở 1440 px canvas agent tạo mở bên cạnh thread trong khi bàn
phím ở ô soạn tin, nút Mở của thẻ đưa canvas đã đóng trở lại; ở 390 px thẻ có nút Mở và một lần
chạm phủ canvas lên thread, Escape cất canvas và bàn phím về nút Mở ấy); touch-targets-phone.spec.ts (“the canvas card’s Open button and the
note chip in the thread are big enough for a finger”: ≥40px mỗi chiều ở 390 px)tests/test_api_canvas_focus.py
(“test_a_selection_cut_through_a_character_is_422_and_nothing_changes”,
“test_a_selection_with_a_whole_emoji_in_it_is_stored”: đoạn cắt giữa một ký tự — nửa emoji, hai đơn
vị UTF-16 bị tách rời — bị từ chối 422 và vùng chọn đang mở giữ nguyên, emoji nguyên vẹn được lưu;
thân yêu cầu gửi như trình duyệt gửi, đơn vị lẻ viết thành escape, vì httpx không mã hoá nổi nó);
tests/test_api_chat_canvas.py (cùng hai ca ở POST tin nhắn: bị từ chối trước khi tin vào hàng đợi,
cả khi cuộc trò chuyện đang bận, không để lại tin, hàng đợi hay lần chia sẻ nào; đoạn cắt ở cuối
dòng, như web cắt đoạn dài hơn một tin, vẫn được ghi chú đặt theo dòng với dòng cuối đã hạ cho khớp;
đoạn đã cắt mà còn giữ dòng cuối của cả vùng chọn thì ghi chú đặt theo chữ (PICK_TEXT) và không
nêu dòng nào, vì các dòng ấy chạy quá đoạn)web/src/lib/rehype-source-lines.test.tsx (mỗi đoạn văn, tiêu đề mọi kiểu kể cả
tiêu đề gạch dưới trải hai dòng, mục danh sách — mục có danh sách lồng mang cả các dòng của phần
lồng —, bảng và từng hàng của nó (không phải hàng ngăn cách hay ô), khối code và code trong khối
với các dòng của khối kể cả hàng rào, khối trích dẫn và đoạn trong nó đều mang các dòng đã viết; code
trong câu, nhấn mạnh, liên kết, danh sách nói chung, đường kẻ và ô để nguyên; phần tử không có vị
trí thì không có dòng nào để nêu mà vẫn giữ thuộc tính; markup trong chữ chỉ hiện thành chữ nên
không giả được dòng); web/src/components/markdown-body.test.tsx nhóm “the source lines a canvas
needs to place a selection” (khối được đánh dấu dòng khi được yêu cầu, code trong khối rào mang dòng
của khối vì đó là phần trang giữ lại của khối, code trong câu không có dòng, không yêu cầu thì vẽ
đúng HTML như trước, markup trong chữ không giả được dòng); web/src/lib/canvas-selection.test.ts
(nhóm “a passage clipped to what a message can take”: đoạn trong giới hạn và đúng bằng giới hạn giữ
nguyên, giới hạn là đúng 20000 ký tự server nhận — hơn một hay kém một đều sai —, đếm ký tự như
server — một cặp đơn vị mã là một —, nên các dòng trọn đúng bằng giới hạn theo ký tự vẫn giữ nguyên
dù một cặp làm chúng dài hơn theo đơn vị mã; đoạn chỉ toàn khoảng trắng không có gì để hỏi; đoạn
dài hơn bị cắt ở cuối dòng cuối cùng còn vừa và dòng cuối hạ xuống dòng liền trước, kể cả khi cuối
dòng ấy đúng là ký tự cuối của giới hạn, và khi một dòng vừa chạm giới hạn thì ký tự xuống dòng liền
sau nó là ký tự đầu tiên bị bỏ; ký tự xuống dòng ở chỗ cắt và các dòng chúng thêm không nằm trong
đoạn cắt; một dòng dài hơn giới hạn thì cắt theo ký tự, không bao giờ giữa một cặp; khi ký tự xuống
dòng duy nhất trong giới hạn lại là ký tự đầu thì cũng cắt theo ký tự và giữ dòng trống ở đầu; dòng
đầu một mình đã quá giới hạn thì đoạn cắt dừng ở dòng đầu; đoạn cắt chỉ còn khoảng trắng thì không
có gì. Nhóm “the passage selected in the editor”: đoạn giữa dòng, đoạn
qua nhiều dòng với mỗi đầu ở đúng dòng của nó, đoạn dừng ngay sau ký tự xuống dòng không tính dòng
kế, đoạn bắt đầu ở ký tự xuống dòng không tính dòng trước, bỏ mọi ký tự xuống dòng ở hai đầu như khi
ba lần nhấp chọn một dòng, giữ thụt lề và dòng trống ở giữa; con trỏ, khoảng trắng hay chỉ ký tự
xuống dòng thì không đọc ra gì; đoạn quá giới hạn bị cắt mà vẫn nói đã chọn bao nhiêu ký tự, đếm
theo ký tự chứ không theo đơn vị mã); web/src/lib/canvas-selection-view.test.tsx (vùng chọn trong
canvas đang đọc: trong một đoạn văn là mọi dòng của đoạn, qua hai mục danh sách là dòng của cả hai,
từ tiêu đề xuống đoạn văn gồm cả dòng trống ở giữa, trong khối code là cả khối kể cả hàng rào, trong
ô bảng là cả hàng, trong mục có danh sách lồng là dòng của mục và của các mục lồng; số ký tự báo là
chữ nhìn thấy chứ không phải dấu markdown; code thì mỗi dòng đứng riêng, kể cả dòng trống và dòng cuối
không có ký tự xuống dòng; nhãn nút sao chép của khối code nằm giữa hai đoạn văn không làm lệch;
vùng chọn kết thúc ngay đầu khối kế, như ba lần nhấp để lại — dù đầu ấy là chữ, là phần tử, hay là
chữ ngoài vùng đọc — không lấy khối kế; con trỏ, khoảng trắng, không có vùng chọn, một đầu nằm ngoài
vùng đang đọc hay chữ không nêu dòng nguồn nào đều không đọc ra gì, và chữ ở trước vùng đọc kèm
khoảng trắng ở sau nó vẫn là chữ ngoài vùng; vùng chọn bắt đầu ở khoảng trắng trước vùng đọc rồi
kết thúc trong nó thì đọc được; vùng chọn quá giới hạn bị cắt như mọi vùng khác. Ca vùng chọn kết
thúc ở vị trí 0 của một nút chữ — dạng Firefox và WebKit để lại — chỉ có test jsdom này, vì Chromium
không bao giờ để lại nó); web/src/lib/canvas-selection-markup.test.tsx (vùng chọn trong markup do
thứ khác vẽ, để thử riêng từng điều kiện đặt ra cho khối: khối nêu dòng nhưng nằm quanh vùng đọc
chứ không trong nó thì bị bỏ; vùng chọn đi từ dòng đầu sớm nhất tới dòng cuối muộn nhất của khối ở
hai đầu, khối lồng nằm ở đầu nào cũng vậy; khối nêu dòng mà nguồn không có thì không đọc ra gì, còn
nguồn đủ dòng thì đọc được; dòng đầu trước dòng thứ nhất, dòng âm, dòng không nguyên ở đầu hay ở
cuối, giá trị không phải số đều không đọc ra gì; dòng nguyên nằm trong nguồn thì đọc ra đúng các dòng
ấy)web/src/components/canvas/canvas-ask.test.tsx (thanh hỏi không hiện gì khi
chưa chọn gì và hộp còn đóng, hay khi đang ẩn; hiện đoạn chọn, các dòng nó nằm trên và nút hỏi; đoạn
trên một dòng gọi tên dòng ấy một mình; đếm ký tự như server; đoạn trích hiện tối đa số ký tự cho
phép, cắt giữa các ký tự nguyên, đoạn vừa thì hiện nguyên không dấu đã cắt, khoảng trắng quanh đoạn không tính vào đoạn trích; ký tự không nhìn thấy
được viết ra thành dấu để người thấy điều agent sẽ nhận, dấu BOM cũng vậy dù mẫu khoảng trắng coi nó
là khoảng trắng; nhóm “what is said when more is sent than was selected”: nói rằng cả các dòng sẽ
đi và chỉ sang Sửa khi các dòng chứa nhiều hơn hẳn phần đã chọn, đến ngưỡng thì không nói, và thanh đếm số ký tự sẽ đi chứ không đếm số đã chọn; nhóm “the
question box”: hộp mở bằng nút, nhận con trỏ và vẫn để đoạn chọn hiện, bấm nút không làm mất vùng
chọn của trang, không gửi được khi rỗng hay chỉ có khoảng trắng bằng nút hay Enter, trang nhận Enter
và Escape thay cho ô — không để lại ký tự xuống dòng hay hành vi mặc định của phím — còn Shift+Enter
và phím của chữ đang gõ bằng bộ gõ thì để cho ô, Huỷ và Escape
đóng hộp, bỏ chữ đã gõ và lần sau mở ra rỗng, trả con trỏ về nút khi đóng bằng Escape hay Huỷ nhưng
không giật con trỏ đang ở chỗ khác, giữ đoạn đã chọn khi người rời trang để vào hộp, theo đoạn chọn
mới khi hộp đang mở và giữ đoạn ấy khi vùng chọn mất, biến khi ẩn và trở lại như cũ; nhóm “why asking
is off”: mỗi lý do khoá nút và nói đúng lý do ấy, hỏi được thì không nói gì, hộp đang mở thì khoá Gửi
và Enter nhưng vẫn cho viết tiếp câu hỏi, hết lý do thì mở lại Gửi);
web/src/components/canvas/canvas-ask-send.test.tsx
(lưu canvas trước rồi mới gửi đoạn ở đúng phiên bản lần lưu cho, kèm câu hỏi đã bỏ khoảng trắng hai
đầu; Enter gửi, Shift+Enter và Enter kết thúc một chữ đang gõ bằng bộ gõ thì không; server nhận hay
xếp hàng đều đóng hộp và báo panel; server từ chối thì hộp và câu hỏi còn nguyên, báo điều đã sai và
lần thử kế bắt đầu sạch; không lưu được canvas thì không hỏi gì, báo và giữ câu hỏi; canvas đổi giữa
lúc lưu thì không hỏi, bảo chọn lại đoạn và giữ câu hỏi, chọn lại thì hỏi về đoạn mới với câu hỏi
như cũ và bỏ lời báo cũ; bấm bao nhiêu lần cũng hỏi một lần, hộp bị khoá — chỉ đọc, Gửi và Huỷ tắt,
Escape không đóng — tới khi lưu xong; hỏi được câu thứ hai về đoạn khác khi câu đầu đã đi qua);
web/src/components/canvas/canvas-ask-failure.test.tsx (lời báo về câu hỏi server đã từ chối: còn
nguyên khi đúng đoạn ấy được báo lại; mất khi đoạn chọn khác đi — chữ khác, dòng đầu khác hay dòng
cuối khác — mà câu hỏi gõ dở vẫn còn; mất khi chữ của canvas đã đổi dù chữ chọn vẫn thế; không còn
khi hộp mở lại sau khi đóng; panel cứ đưa đoạn chọn trước lúc chữ đổi thì câu hỏi không đi, thanh
báo chữ đã đổi và bảo chọn lại);
web/src/components/canvas/canvas-panel-ask.test.tsx
(chạy cho cả canvas đang sửa lẫn đang đọc: đề nghị hỏi về đoạn đang chọn với các dòng của nó, chưa
chọn gì thì không đề nghị gì, panel không có đường hỏi thì không đề nghị dù chọn gì; đặt con trỏ vào
chữ thay cho đoạn thì thôi đề nghị, con trỏ đi nơi khác thì vẫn đề nghị; chữ đổi thì thôi đề nghị
đoạn cũ và đề nghị đoạn chọn kế; người chuyển sang cách hiển thị kia thì thôi; canvas bị xoá thì hết,
kể cả đoạn chọn sau đó; nói lý do tắt như chat nói; ẩn khi xem lịch sử, giữ câu hỏi đã bắt đầu qua
lần xem và không đưa lại đoạn chỉ chọn trước lần xem; hai bản xung đột thì không đề nghị gì; gửi câu
hỏi từ panel: lưu chữ đã gõ trước rồi hỏi về đoạn ở bản đã giữ nó, lấy phiên bản từ lần lưu của dock
— lần lưu có giới hạn thời gian —, chữ đổi trong lúc lưu thì không hỏi và bảo chọn lại);
web/src/components/canvas/canvas-editor.test.tsx nhóm “the selection the editor reports” (đoạn đang
chọn kèm các dòng, chỉ con trỏ thì báo không có, chữ đúng như đã gõ, vẫn ghi con trỏ cho các lần
thay của máy khi chẳng ai nghe) và nhóm “a selection that no key or mouse made” (vùng chọn mà trình
duyệt chỉ báo bằng selectionchange — tay cầm chọn trên màn cảm ứng, setSelectionRange, chọn tất
cả trong menu — được báo khi ô đang giữ bàn phím và bị bỏ qua khi bàn phím ở chỗ khác; con trỏ cho
các lần thay của máy dời theo như khi phím tạo vùng chọn; tài liệu chỉ được nghe khi trình sửa còn
trên trang, một trình nghe thêm vào và chính nó gỡ ra. Test bắn selectionchange vào chính ô, như
Chromium, vì React bỏ qua bản ấy và chỉ nghe bản bắn vào tài liệu — bắn vào tài liệu thì test vẫn
xanh dù trình sửa hỏng trên trình duyệt thật); web/src/components/canvas/canvas-view-selection.test.tsx
(canvas đang đọc báo gì về vùng chọn vượt ra ngoài nó: có cả hai đầu trong nó thì báo đoạn chọn;
bắt đầu trong nó rồi chạy tiếp ra trang, hay từ trang đi vào nó, thì báo không có đoạn nào; không
đầu nào nằm trong nó — kể cả vùng chọn chạy ngang qua nó — thì không nghe gì; chọn chữ khi không
ai nghe, và bỏ hẳn vùng chọn, đều không ném lỗi — test bắt sự kiện error mà jsdom bắn khi một
listener ném, vì không bắt thì lỗi ấy chỉ hiện thành “lỗi chưa xử lý” của cả lần chạy, không gắn
với test nào);
web/src/components/canvas/canvas-view-memo.test.tsx (canvas đang đọc không vẽ lại chữ khi cái nó
nhận không đổi, như khi panel vẽ lại vì người đang gõ câu hỏi, và vẽ lại khi chữ đổi; test đếm số
lần thân markdown được vẽ)FakeCanvas: web/src/app-canvas-ask.test.tsx (một tin duy nhất mang đoạn chọn
và câu hỏi, không lưu hay ghi gì thêm, cho cả canvas đang sửa lẫn đang đọc; chữ đã gõ được lưu trước
và đoạn được hỏi theo bản đã lưu; tin của người có chip ghi chú server giữ cùng câu hỏi; hộp đóng mà
canvas vẫn ở cạnh chat khi lượt vừa bắt đầu còn chạy, và hộp cũng đóng khi server xếp câu hỏi sau
một lượt tab này không thấy; câu hỏi bị từ chối bằng 409, 422 hay 429 thì hộp giữ câu hỏi, nói lý do
bằng lời của web chứ không bằng chữ của server, và lần thử kế đi qua; mất mạng thì hộp giữ câu hỏi và
nói không tới được server; sang cuộc trò chuyện khác trước khi canvas lưu xong thì không gửi gì;
canvas phủ lên chat được cất đi khi câu hỏi đã đi qua để câu trả lời hiện ra, còn nguyên cả canvas
lẫn câu hỏi khi server từ chối, Escape trong hộp chỉ đóng hộp, và không đụng tới canvas người mở ở
cuộc khác trong lúc câu hỏi còn trên đường; hỏi tắt khi lượt của tab đang chạy và bật lại khi xong,
khi kênh khác đang chạy lượt, khi cuộc trò chuyện chờ quyết định về một công cụ, khi đã hết ngân
sách, và chỉ nói một lý do một lúc: quyết định trước ngân sách, lượt đang chạy trước cả hai; nhóm
“sending the errors a page reported from the canvas beside the chat”: lỗi trang báo đi thành một
tin duy nhất nêu canvas mà không nêu đoạn nào, chỉ khi người bấm gửi, không lưu canvas và không
ghi canvas đang mở). Hàm
dùng chung: web/src/test/canvas-pick.ts (chọn chữ trên DOM như một cú kéo để lại, trong trang đọc
hay trong ô sửa; đặt dòng chữ ngoài canvas lên trang và dọn đi sau mỗi test), web/src/test/canvas-ask.tsx (thanh hỏi đứng riêng với hàm lưu và hàm gửi giả), và
FakeBackend.refuseMessage (POST tin nhắn từ chối với đúng trạng thái và detail đã đặt)canvas-ask.spec.ts (ở 1440 px: đoạn chọn trong ô sửa bằng setSelectionRange đi cùng
câu hỏi với đúng line_start và line_end, câu hỏi và câu trả lời hiện trong thread, chip ghi chú nằm
ngay dưới tin của người và mở ra đúng ghi chú, canvas vẫn ở cạnh chat; đoạn chọn bằng phím —
Shift+ArrowRight từng chữ, vì End trên Mac nhảy tới cuối văn bản chứ không phải cuối dòng — được đề
nghị khi còn chọn và hết cùng con trỏ; đoạn chọn trong canvas đang đọc bằng một Range dựng qua
page.evaluate; ba lần nhấp chọn đúng đoạn văn dưới nó, đoạn đầu, đoạn giữa và đoạn cuối, kể cả khi
Chromium để vùng chọn kết thúc ở một phần tử nằm ngoài vùng đọc như thanh hỏi; ở 390 px canvas phủ lên
chat được cất đi sau khi gửi để câu hỏi và câu trả lời hiện ra); touch-targets-phone.spec.ts (“the
buttons of the bar that asks about a passage of the canvas are big enough for a finger”: nút hỏi, ô
câu hỏi, Gửi và Huỷ ≥40px mỗi chiều ở 390 px)web/src/components/canvas/canvas-frame.test.tsx (khung, cửa sổ của nó và các tin nó
gửi dựng bằng web/src/test/canvas-frame.tsx, dùng chung với canvas-frame-keyboard.test.tsx)
nhóm “the page of a canvas, in its
frame” (“runs the page in a frame that has a script and nothing of the app”: sandbox đúng bằng
allow-scripts — thêm allow-same-origin là test đỏ —, allow là fullscreen, referrerpolicy
là no-referrer, tiêu đề của canvas; địa chỉ là route render không kèm phiên bản; nói đang tải
tới khi trang tải xong lần đầu; báo cho panel bản vừa đưa lên), nhóm “a newer version of the page”
(khung được thay bằng khung mới một giây sau lần đổi bản cuối, chờ bản đứng yên rồi đưa bản cuối
lên; người đang giữ focus trong trang thì giữ khung và mời bản mới bằng một nút, nút đưa lên bản
đang có lúc bấm, không có bản mới thì không có nút; tab ẩn thì chờ tab hiện lại rồi đưa lên ngay,
bản vẫn thế thì không đưa lại), nhóm “the live stream” (luồng nối lại sau khi rớt thì đưa trang
lên lại với bản mới nhất; lần nối đầu và luồng chưa từng nối thì không), nhóm “a page that moves
to another address” (tải lần thứ hai là bị gỡ và người được báo, tải một lần dù lâu thì không;
“takes a late load of the frame it replaced for no load of its own”: load của khung cũ tới sau
khi khung mới được hẹn mà chưa được vẽ thì khung mới vẫn nói đang tải, lần tải thật đầu của nó
không làm nó bị gỡ và lần thứ hai vẫn gỡ nó; đã
gỡ thì đứng yên tới khi người bảo chạy lại, khi ấy bản mới nhất được đưa lên và chạy như mọi
trang; “is told to the panel under the version that was up, though a newer one was waiting its
second”; trang đã gỡ thì bỏ nút mời bản mới);
web/src/components/canvas/canvas-panel-kinds.test.tsx nhóm “a canvas shown as a page” (html của
agent mở thành trang trong khung, html vừa tạo ở đây mở ở Sửa trong phông code, sơ đồ mermaid cũng
là một trang; “turns to the page at once when nothing is unsaved, and sends nothing”: đổi ngay
trong cú bấm; “saves what was typed before turning to the page, and says so on the View button
meanwhile”; “keeps the last click: Edit pressed while the save is out stays in Edit when it
lands”; lịch sử mở trong lúc lưu thì vẫn mở; lưu bị từ chối thì hiện bản đã lưu gần nhất và nói
chữ đang gõ chưa có trong đó; “opens the saved page in a new tab, and only once nothing is
unsaved”: window.open với noopener,noreferrer; canvas mới biết tên mà chưa đọc xong thì chưa
có trang để mở; trang có ký tự ẩn thì nói; canvas đã xoá thì không còn trang để mở) và nhóm “a
kind the web does not know” (hiện thành chữ trong phông code, không khung, không hình);
web/src/hooks/use-save-then-show.test.ts (lần chuyển sang cái server đang giữ: đổi ngay khi
không có gì phải chờ và không xin lưu, chờ lần lưu và nói đang chờ, lần lưu ném lỗi vẫn đổi và
thôi nói chờ, lần chuyển sau thắng lần đang chờ, huỷ thì bỏ lần đang chờ)canvas-render.spec.ts, trang html do web/e2e/mock-render.ts trả kèm đúng chính
sách và reporter của server (hai tệp mà tests/test_render_fixtures.py giữ bằng server từng byte;
mermaid không được phục vụ vì trang ấy tải thư viện từ CDN); web/e2e/render-page.ts dựng trang
và mở nó trong canvas, dùng chung với canvas-keyboard.spec.ts. Mở thẳng địa chỉ render: “has an
origin of its own and no storage, by the policy it comes with” (window.origin là null, đọc
localStorage ném SecurityError; bỏ sandbox khỏi render-policy.txt là test đỏ). Trong khung
ở 1440×900: “runs under an origin that is not the app’s, and what it throws is listed”; “fills
the panel, down to the lower edge of the room the canvas has” (mép dưới của khung trùng mép dưới
của .canvas-saved và khung cao hơn nửa cửa sổ; hộp .canvas-frame-box không co giãn hoặc
không còn là flex cột thì khung chỉ cao 150px và test đỏ); “cannot call the api: the policy
stops the request before it leaves, and says so” (không request nào đi từ khung, lời báo là
connect-src blocked); “cannot keep anything in the browser’s storage”;
“cannot put a dialog over the app”; “cannot move the app to another address” (top.location ném
SecurityError, app vẫn ở địa chỉ cũ); “is taken out when it moves itself to another address,
and the person is told” (trang đổi location sau khi tải xong; trang tự đi ngay lúc còn đang
parse thì không bị bắt, đó là phần rủi ro đã ghi nhận). Ở 390×844 cảm ứng: “gets most of the
screen, and its errors fit a finger without a sideways scroll” (khung cao ít nhất 60% màn hình,
mọi nút trong dock ≥40px, một từ 300 ký tự trong danh sách lỗi không làm cuộn ngang).
web/e2e/canvas-overflow.ts là hàm đo cuộn ngang dùng chung với canvas.spec.ts, đo cả
.canvas-errors-listtests/test_render_pages.py, bốn test của reporter về lời chào và cú bấm (xem mục trang
render ở trên): cú bấm chỉ được nói khi trình duyệt bảo một người đã bấm, lúc con trỏ xuống và
lần nữa lúc nó lên (pointerup, mouseup); click không được nóiweb/src/hooks/use-frame-focus-guard.test.tsx nhóm “the keyboard a page took unasked”
(một lần giành là khung có focus rồi cửa sổ app nhận blur; app chờ ATTEST_GRACE_MS (50 ms)
xem reporter có nói tới cú bấm không, tới mili giây cuối khung vẫn giữ focus, rồi bàn phím về
đúng phần tử vừa mất focus bằng focus({ preventScroll: true }); “is waited on for no longer
than a tenth of a second, in which the keys go to the page”: khoảng chờ không bao giờ quá 100 ms;
không phần tử nào giữ focus, phần tử ấy đã bị gỡ hoặc đã bị khoá thì khung bị blur; phần tử mất
focus từ task trước không được trả focus; không bao giờ trả focus cho chính khung; người tự đưa
focus đi chỗ khác trong lúc chờ thì focus ở yên đó và lần giành vẫn tính; trang tự buông trước khi
hết giờ vẫn bị tính; trang bị gỡ ở lần thứ năm thì bàn phím vẫn về chỗ cũ; cửa sổ mất focus vì
người sang cửa sổ khác thì không tính và không đụng tới focus), nhóm “the keyboard the person
offers the page” (reporter nói tới cú bấm trước khi trang lấy focus, hoặc trong lúc chờ, thì trang
giữ bàn phím và không lần nào bị tính, nói bao nhiêu lần cũng vậy; “comes too late once the
keyboard is back”: cú bấm nói sau khoảng chờ không xoá lần giành đã tính; Tab là phím cuối thì
trang giữ bàn phím, Shift+Tab cũng vậy; Tab kèm ctrlKey, metaKey hay altKey không phải lời
mời và còn thu lại lời mời đang có như mọi phím khác; “is over a task after Tab that did not
bring the focus to the page”: app giữ phím Tab cho mình, như danh sách lệnh trên ô soạn tin, thì
một task sau lời mời hết, trang lấy focus lúc ấy bị trả bàn phím và tính một lần giành; “is over
once a press was told and the frame did not take the focus in the time a page is given”: cú bấm
được nói mà sau ATTEST_GRACE_MS khung vẫn chưa có focus thì lời mời hết; focus tới 30 ms sau cú
bấm thì lời mời còn, và cú bấm được nói 30 ms sau khi trang lấy focus vẫn là lời mời (hai test
neo vào tình huống trình duyệt bận, không neo vào hằng số: rút khoảng chờ xuống vài mili giây là
đỏ); “is made anew by the second telling of one tap, long after the first was over”: ngón tay
trên điện thoại được nói hai lần, lần đầu hết hạn rồi lần hai vẫn đưa được bàn phím; “is made
anew when the press is told to be over, for the page that takes the keyboard at the click of a
button a person held”: cú bấm được nói, 150 ms sau (người giữ nút chuột chừng ấy) ô đang gõ vẫn
giữ focus, lúc nhả được nói thêm một lần rồi khung mới lấy focus thì trang giữ bàn phím và không
lần nào bị tính; “is not made by a button that is still held”: cùng tình huống mà không có lời
nào lúc nhả thì bàn phím về ô đang gõ và tính một lần giành, tức luật hết hạn không bị nới;
“stands for as long as the page has the keyboard, however long ago the press was told and
however often the page is looked at again”: cú bấm được nói, khung lấy focus, rồi sáu lần tab bị
ẩn một giây và hiện lại, mỗi lần hiện khung được xét lại: trang vẫn giữ bàn phím và không lần nào
bị tính, tức lời mời đứng vững chừng nào khung còn giữ focus chứ không hết theo đồng hồ; cho lời
mời luôn hết hạn là test đỏ; hạn
tính từ cú bấm được nói sau cùng; “is made to the frame on show and to no other”: lời mời bằng cú bấm
hay bằng Tab không theo sang khung thay thế; pointermove, pointerover, pointerenter,
wheel, scroll, mousemove
trên khung không phải lời mời, và pointerdown trên hộp của khung trong app, cái reporter không
nói tới, cũng không; lời mời đứng vững khi con trỏ di chuyển, rời đi hay cuộn ở chỗ khác và khi
bấm lên hộp của khung; pointerdown ở chỗ khác trong app, một phím khác Tab bấm trong app, Tab
đưa focus tới một nút của app, hay focus tới một phần tử của app thì lời mời hết và lần giành kế
tiếp bị trả lại; focus tới chính khung thì không), nhóm “what the app’s own handlers keep to
themselves” (bốn loại sự kiện vẫn được thấy khi handler của app gọi stopPropagation: nghe ở pha
capture), nhóm “a page that goes on taking the keyboard” (bốn lần thì chưa báo, lần thứ năm báo
đúng một lần và chỉ khi hết khoảng chờ; “is reported again each time it does after that, where
the app left the page up”; app nhận blur nhiều lần trước khi bàn phím về thì vẫn là một lần
giành; bàn phím do người đưa thì bao nhiêu lần cũng không tính; khung mới thay khung cũ bắt đầu
từ không, và không phải trả lời cho khung cũ đang được chờ; báo cho callback mới nhất), nhóm “a
page that held the app up while it had the keyboard” (khoảng chờ tới hạn trễ hơn LATE_MS
(100 ms): lần đầu của một khung chỉ là một lần giành như mọi lần khác, bàn phím về chỗ cũ, tính
một lần và trang không bị báo, vì có thể chính app đang bận; từ lần thứ hai trang bị báo, và mỗi
lần sau đó; trễ đúng LATE_MS thì chưa là trễ, quá một mili giây thì là trễ; “has the keyboard
for one task more when the wait comes due late, and no longer”: tới hạn trễ thì guard nhường
đúng một task cho cú bấm đang nằm trong hàng đợi của cổng rồi mới kết luận; “is not held to have
taken it when the press is told right after the wait came due late”: cú bấm được nói trong task
ấy thì trang giữ bàn phím, không bị báo, không bị tính, và lần trễ ấy không được nhớ cho lần
sau; khung thay thế bắt đầu lại từ chưa trễ lần nào; khoảng chờ kết thúc bằng một cú bấm thì dù
cú bấm tới trễ cũng không báo), nhóm “the keyboard a page took while the
window was behind” (cửa sổ app mất focus về thứ không phải khung thì phần tử đang giữ bàn phím
được nhớ; trang lấy focus trong lúc ấy, cửa sổ không được báo gì: khi cửa sổ nhận blur về khung
mà không phần tử nào vừa mất focus thì bàn phím về phần tử đã nhớ chứ không rơi xuống body;
focus rồi blur liền nhau chỉ là một lần giành, và cửa sổ khi ấy đã ở phía trước vì thứ nó mất
focus về là chính trang của nó: “does not go to what had it before the window said it is in
front and that it lost the focus to the page, once the person let that go” (người buông phần tử
đã nhớ rồi thì lần giành sau không đưa bàn phím về nó nữa; huỷ task 0 ms của focus ở mọi
blur, kể cả blur về khung, là test đỏ); cửa sổ nhận focus thì một task sau khung
được xét lại, chờ đủ khoảng chờ rồi bàn phím về phần tử đã nhớ và tính một lần giành; “is looked
for a task after the window says it is in front, when the browser has put the focus back”: xét
ngay trong focus là test đỏ; người đã quay lại và tự buông focus thì phần tử đã nhớ bị quên;
tab hiện lại thì khung được xét lại, tab đang ẩn thì không; cửa sổ chưa nói nó ở phía trước mà
con trỏ đã di chuyển hay bấm trong app thì khung cũng được xét lại; cửa sổ chưa từng ở phía sau,
hoặc người đã quay lại, thì con trỏ trong app không mở khoảng chờ nào; người quay lại bằng một
cú bấm trong trang thì trang giữ bàn phím, kể cả khi reporter nói tới cú bấm ấy sau focus của
cửa sổ, và không lần nào bị tính; “goes back within a beat of the person coming back by the
keyboard alone, which the app’s window is told nothing of”: không focus của cửa sổ, không
visibilitychange, không sự kiện con trỏ, chỉ document.hasFocus() đổi thành true: lúc còn ở
phía sau thì ba nhịp trôi qua khung vẫn giữ focus, quay lại thì tới đúng nhịp kế tiếp
(BEHIND_LOOK_MS, 200 ms) khung mới được xét, chờ đủ khoảng chờ rồi bàn phím về phần tử đã nhớ
và tính một lần giành; “is looked for on a beat no slower than a fifth of a second”: đo bằng
đồng hồ chứ không so hằng số với một con số, người quay lại bằng bàn phím thì 200 ms cộng khoảng
chờ sau bàn phím đã về phần tử đã nhớ, nên bỏ nhịp hay cho nhịp thưa hơn 200 ms đều là test đỏ;
người quay lại bằng một cú bấm trong
trang thì nhịp thấy khung mà không tính gì; nhịp thấy một phần tử của app đang giữ focus thì
không đổi gì, con trỏ trong app sau đó vẫn được xét; nhịp đã thấy khung thì con trỏ trong app
không mở khoảng chờ nào nữa và phần tử đã nhớ bị quên; “is looked for on the beat only while the
window is behind, and on one beat however often it says so”: một timer duy nhất dù cửa sổ nói hai
lần, không timer nào trước khi cửa sổ ra sau và sau khi nó nói đã ở phía trước; nhịp thấy khung
rồi thì dừng; cửa sổ nói blur, focus, blur trong cùng một task, tức người lướt qua cửa sổ
app trên đường sang cửa sổ khác, thì task 0 ms của lần focus ấy bị huỷ chứ không chạy sau
blur thứ hai mà coi như người đã về: “goes back within a beat of the person coming back by the
keyboard alone to a window that went behind again before it was settled as back” (phần tử đã
nhớ còn nguyên, trang lấy focus sau đó giữ nó qua ba nhịp, người quay lại bằng bàn phím thì sau
một nhịp cộng khoảng chờ bàn phím về phần tử ấy và tính một lần giành), “goes back at a pointer
in the app, where the window went behind again before it was settled as back” (con trỏ di chuyển
trong app lúc ấy vẫn mở khoảng chờ), “is still looked for on the beat when the window went
behind again before it was settled as back, until it says it is in front” (một timer qua ba
nhịp, hết khi cửa sổ nhận focus); bỏ lệnh huỷ ấy là cả ba đỏ: nhịp dừng và phần tử đã nhớ bị
quên trong lúc cửa sổ vẫn ở phía sau; khung được dựng lên lúc cửa sổ đã ở phía sau, tức
document.hasFocus() là false
lúc guard được dựng và không blur nào báo: “goes back within a beat of the person coming back
by the keyboard alone to a window the page was put up behind”: phần tử đang giữ focus lúc dựng
được nhớ, ba nhịp trôi qua khung vẫn giữ focus, hasFocus() thành true thì tới đúng nhịp kế
tiếp khung mới được xét, chờ đủ khoảng chờ rồi bàn phím về phần tử ấy và tính một lần giành;
“goes back at a pointer in the app, where the window the page was put up behind has not said it
is in front”: con trỏ di chuyển trong app lúc ấy cũng mở khoảng chờ; “is looked for on the beat
from the time the page is put up behind the window, until the window says it is in front”: một
timer ngay từ lúc dựng, hết khi cửa sổ nhận focus), nhóm “a guard that is put up” (hai
listener pointermove và
pointerdown gắn với passive: true và không có listener wheel nào, nên guard không bao giờ
giữ một cú cuộn lại), nhóm “a guard that is taken down” (không để lại timer nào trong ba timer
của nó, lời mời đang chờ hết hạn và nhịp xét lúc cửa sổ ở phía sau cũng dừng, không còn nghe cú
bấm, gỡ đủ từng listener đã
gắn với đúng cờ capture: sáu trên document, hai trên window);
web/src/components/canvas/canvas-frame-keyboard.test.tsx nhóm “a page that takes the keyboard”
(hộp div.canvas-frame-box chỉ mang class và tabindex="-1", không còn thuộc tính nào tắt con
trỏ của khung; một lần giành thì trang vẫn còn và ô đang gõ có lại focus; “does not keep what it
took while the window was behind: the person is back in front, and so is the keyboard”: cửa sổ
mất focus, trang lấy focus, cửa sổ có lại focus thì ô đang gõ có lại bàn phím; “does not keep what
it took behind the window when the person comes back by the keyboard alone, and nothing tells the
app’s window so”: cùng tình huống nhưng cửa sổ app không nhận focus, chỉ document.hasFocus()
đổi: sau một nhịp cộng khoảng chờ ô đang gõ có lại bàn phím, khung còn nguyên; lần thứ năm khung bị
gỡ, lời báo là “Trang liên tục giành bàn phím nên đã bị dừng.” kèm nút Nạp lại, panel được báo
như khi trang tự đổi địa chỉ; đã gỡ thì đứng yên qua bản mới, nối lại và tab hiện lại; Nạp lại
đưa khung mới lên và khung ấy lại có đủ năm lần; bàn phím người đưa bằng cú bấm trong trang thì
sáu vòng cũng không bị gỡ, không có lời báo dừng nào, cả lời báo giành bàn phím lẫn lời báo trang
tự chuyển địa chỉ, và bản mới chờ sau nút “Có bản mới”; trang tự giành thì không được coi
là đang dùng, bản mới thay khung không cần nút; nút mời bản mới mất khi trang bị gỡ), nhóm “the
press a page’s reporter tells of” (cổng chỉ được nhận từ tin đầu tiên khung nói, và tin ấy phải
là lời chào từ chính cửa sổ của khung dưới origin null: sau một lời báo, một tin bất kỳ hay một
lời chào khác thì cổng tới sau bị bỏ, cổng đầu vẫn được nghe khi tin sau mang cổng khác, lời chào
từ cửa sổ khác hay từ không đâu không được nhận còn lời chào của chính khung sau đó vẫn được,
lời chào dưới origin khác null không được nhận mà vẫn là tin đầu của khung; “is nothing the
page can say to the window: only the port tells of it”: {type: "press"} gửi lên cửa sổ không
bao giờ là lời mời; qua cổng chỉ đúng {type: "press"} mới là cú bấm; một nghìn cú bấm qua cổng
không tính vào năm mươi tin của khung, sau đó trang vẫn được nghe đủ bốn mươi chín lời báo; cổng
của khung bị thay được đóng và handler cũ của nó không nói thay cho khung mới; cổng đóng khi
trang bị gỡ vì đổi địa chỉ hay vì giành bàn phím; cổng đóng khi khung đi và không đóng khi khung
chỉ được vẽ lại), nhóm “the page that has the keyboard” (khung giữ focus thì hộp mang
data-keyboard và dòng “Bàn phím đang ở trang” với role="status" nằm trong hộp, khung vẫn là
đúng phần tử cũ, focus về app thì cả hai mất; được xét lại ở blur và focus của cửa sổ, ở
focusin và focusout của tài liệu; nhóm con “behind the app’s window, which is told nothing of
the focus a page takes there”: cửa sổ đã nhận blur thì dấu được xét theo cùng nhịp 200 ms mà
không cần sự kiện nào, hiện trong một nhịp kể từ lúc khung có focus và mất trong một nhịp kể từ
lúc khung buông; người quay lại bằng bàn phím thì dấu hiện trong một nhịp; khung mới thay khung
cũ lúc cửa sổ còn ở phía sau vẫn được xét theo nhịp ấy; nhịp của dấu dừng ở nhịp đầu tiên thấy
document.hasFocus() là true, và cả hai nhịp dừng khi khung đi; khung được dựng lên lúc cửa sổ
đã ở phía sau, không blur nào báo, thì dấu cũng hiện trong một nhịp kể từ lúc khung có focus,
hai nhịp của dấu và của guard chạy ngay từ lúc dựng, nhịp của dấu dừng ở nhịp đầu tiên thấy cửa
sổ ở phía trước và nhịp còn lại dừng khi khung đi; khung thay khung đang giữ bàn phím thì không
mang dấu ấy;
khung đi thì gỡ đủ listener đã gắn lên window và document), nhóm “a stopped page the person
asks for again” (lời báo đã dừng là một nút DOM khác với hộp của khung cũ và của khung mới; sau
Nạp lại, nút vừa bấm mất nên focus được đặt lên hộp của khung mới bằng focus({ preventScroll:
true }) đúng một lần; trình duyệt không cho nút nhận focus khi bấm thì focus ở yên chỗ người để
nó; các khung sau không kéo focus về nữa, kể cả khi người đã buông focus và không phần tử nào
giữ nó; khung không ai xin thì không dời focus, kể cả khi focus đang ở body)canvas-keyboard.spec.ts, Chromium thật với reporter thật của server. Ở 1440×900:
“goes to the page with a click in it and stays there, however often the person goes back and
forth” (sáu vòng bấm ô soạn tin rồi bấm vào ô của trang và gõ ngay mười phím cách nhau 30 ms, lâu
hơn khoảng chờ: trang nghe đủ mười phím mỗi vòng, bàn phím rời trang đúng số lần người lấy nó đi,
ô soạn tin không nhận phím nào và trang không bị gỡ; đây là test giữ thứ tự giữa cú bấm reporter
nói và blur của app: xét khung ngay trong blur, không chờ, thì cú bấm tới sau blur bị coi
là trang tự giành. Trong Chromium của Playwright cú bấm đã nằm trong hàng đợi trước khi cửa sổ
app nhận blur, nên ở đây một timer 0 ms cũng đủ: độ dài 50 ms của khoảng chờ chỉ được giữ bằng
các test vitest ở trên); “is, for the app, with the page’s frame inside the dock once the person
clicks in the page” (sau một cú bấm thật vào ô của trang, document.activeElement của app chính
là iframe của canvas và nằm trong phần tử của dock: đó là thứ duy nhất dock dựa vào để biết
người đang giữ bàn phím trong nó, nên canvas tự mở không thay trang ấy); “is
shown to be with the page by a line around it and a word over it, which move nothing and let the
pointer through” (khung không đổi vị trí hay kích thước, viền là solid 2px, dòng chữ nằm góc
trên bên phải trong khung, bấm lên dòng chữ là bấm vào trang bên dưới; về ô soạn tin thì viền và
dòng chữ mất); “stays with a message being written when the page focuses itself” (trang gọi
window.focus() rồi focus ô của nó; khi trang đã thấy blur của chính nó thì ba mươi phím gõ
vào đủ ô soạn tin và trang không nghe phím nào); “goes to a page that takes it at the click,
however long the person held the button and however often” (nút của trang gọi preventDefault()
ở mousedown nên cú bấm không dời focus, rồi trang focus ô của nó trong click; sáu vòng bấm ô
soạn tin, mouse.down() lên nút, giữ 150 ms trong lúc ô soạn tin vẫn giữ focus, mouse.up()
rồi gõ ngay mười phím: trang nghe đủ mười phím mỗi vòng, bàn phím rời trang đúng số lần người
lấy nó đi, ô soạn tin không nhận phím nào và trang không bị gỡ; reporter chỉ nói lúc con trỏ
xuống là test đỏ); “stays with the message when the page takes it while the button is still
held, long after the press” (đối chứng: cùng nút ấy nhưng trang tự focus bằng timer 150 ms sau
mousedown, nút vẫn đang giữ: bốn lần bàn phím về ô soạn tin, phím gõ lúc còn giữ nút vào ô
soạn tin, lần thứ năm trang bị gỡ, tức lần nào cũng bị tính); “stays with the message when the
keys meant for it press a button of a page that goes on taking it” (trang cứ 100 ms lại focus
một nút của nó, người gõ Enter liên tục: Enter rơi vào nút ấy trong khoảng chờ là một click
trình duyệt bảo là của người, nhưng reporter không nghe click, trang bị gỡ sau năm lần và ba
mươi phím gõ sau đó vào đủ ô soạn tin; để reporter nói mọi click isTrusted là test đỏ:
trang giữ được bàn phím và không bao giờ bị gỡ); hai test “stays with the message when that page
has the click those keys make say a pointer made it” và “… say it is a pointer coming up” (cùng
trang ấy, nhưng trước khi giành nó viết lại getter detail trên UIEvent.prototype cho trả 1,
hoặc getter type trên Event.prototype cho trả pointerup: trang vẫn bị gỡ sau năm lần và
ba mươi phím gõ vào đủ ô soạn tin; reporter nghe click và lọc bằng detail > 0 hay bằng
type là hai test đỏ ở Chromium, trang giữ bàn phím mà không ai bấm chuột vào nó); “stays with
the message when the page makes up a press of its own before it takes it” (script của trang tự
dispatchEvent bốn sự kiện reporter nghe, pointerdown, mousedown, pointerup, mouseup,
rồi cả click, đều với detail: 1, lên ô của nó, trang nghe đủ cả bốn, rồi lấy focus:
reporter không nói tới cú bấm trình duyệt không bảo là của người, bàn phím về ô soạn tin, ba
mươi phím gõ vào đủ ô soạn tin, trang không nghe phím nào và dòng “Bàn phím đang ở trang” không
hiện; bỏ điều kiện isTrusted trong reporter là test đỏ); “is not kept by a page that takes it while
nothing in the app holds it” (khung bị blur, focus của app ở body; bỏ taker.blur() trong
guard là test đỏ); “stays with the message when the page takes it as the pointer passes over”
(trang gọi window.focus() trong pointermove của nó: con trỏ đi qua trang không phải lời mời,
chữ gõ trước và sau đều vào ô soạn tin); “stays with the message while the wheel turned over the
page scrolls the page” (ngay sau khi gõ, lăn chuột trên trang thì trang cuộn và chữ gõ tiếp vẫn
vào ô soạn tin: khung luôn nhận con trỏ); “goes to the page with Tab” (cũng cho thấy hạn một
task của lời mời bằng Tab là đủ trong trình duyệt thật: cửa sổ app nghe blur trước khi task ấy
tới; bỏ lời mời của Tab là test đỏ, còn lời mời hết hạn thế nào thì trình duyệt không phân biệt
được, chỉ các test vitest ở trên giữ);
“stays with the message when the page takes it after Tab pressed with Control, which goes
through none of the app’s controls” (Control+Tab không dời focus; trang lấy focus sau đó thì bàn
phím về ô soạn tin và ba mươi phím gõ vào đủ ô soạn tin; coi Control+Tab là lời mời và để lời
mời ấy sống quá một task là test đỏ, chỉ bỏ điều kiện phím bổ trợ thì hạn một task đã che và
phần ấy do vitest giữ); “stays where the person
had it while a page that goes on taking it is stopped” (trang setInterval(window.focus, 100) bị
gỡ kèm lời báo, ba mươi phím gõ sau đó vào đủ ô soạn tin mà không cần bấm lại, Nạp lại đưa trang
mới lên và focus nằm trên canvas-frame-box chứ không ở body); “is not given to a page that
wrote itself anew, whose presses nothing vouches for” (trang gọi document.open(), xoá mọi
listener trên cửa sổ của nó kể cả của reporter: bốn cú bấm vào trang đều bị trả bàn phím về ô
soạn tin, cú thứ năm gỡ trang, năm phím gõ xen giữa vào đủ ô soạn tin). Ở 390×844 cảm ứng: “goes
to the page with the first tap, which the page hears as a pointer pressed” (cú chạm đầu tới trang
như một pointerdown, mười phím gõ sau đó trang nghe đủ, dòng chữ nằm trong bề ngang của khung,
khung không dời và không có cuộn ngang). Trang giành lúc cửa sổ ở phía sau chỉ có test vitest:
Chromium của Playwright luôn coi trang là đang có focus nên cửa sổ không bao giờ nhận blur vì
người sang cửa sổ khác. Trong vitest, web/src/test-setup.ts cho document.hasFocus() trả
true ở mọi test: jsdom chỉ nói cửa sổ có focus sau khi một phần tử được focus, còn test là một
người đang ngồi trước app; test nào cần cửa sổ ở phía sau thì spy hasFocus thành false, trước
khi dựng khung nếu khung phải được dựng ở đó. Phần còn lại đã ghi nhận: phím bấm trong khoảng
chờ, tối đa 50 ms giữa
lúc trang giành và lúc bàn phím được trả lại, thì tới trang; trang giành lúc cửa sổ ở phía sau
mà người quay lại bằng bàn phím, không có focus của cửa sổ, tab không đổi trạng thái và con
trỏ không động, thì phím tới trang tối đa một nhịp cộng khoảng chờ (250 ms) rồi bàn phím về; khung
được dựng lên khi cửa sổ đã ở phía sau từ trước thì theo cùng nhịp ấy ngay từ lúc dựng, và phần
tử được nhớ là cái đang giữ focus lúc dựng; trang chặn hành vi mặc định của cú bấm
(khung không nhận focus) rồi tự focus hơn 50 ms sau lúc con trỏ xuống mà nút vẫn đang giữ, hoặc
hơn 50 ms sau lúc nhả (chẳng hạn sau một hoạt cảnh), thì bị coi là tự giành: lời mời của cú bấm
ấy đã hết; hai sự kiện lúc nhả, pointerup và mouseup, thừa nhau với chuột trong Chromium (bỏ
riêng một cái thì test trình duyệt vẫn xanh), từng cái chỉ được giữ bằng test chữ của reporter;
Enter hay phím cách rơi vào khoảng chờ thì tới trang như mọi phím khác, nhưng không còn là lời
mời dù trang cho sự kiện của nó tự nói gì; tha lần trễ đầu của một khung có cái giá của nó: ở
nơi khung chạy trên cùng luồng với app, một trang giành bàn phím rồi chặn luồng thì nghe mọi
phím gõ trong lúc luồng bị chặn, và mỗi phần tử khung có hai khoảng nghe như thế trước khi trang
bị dừng, không phải mộtweb/src/components/canvas/canvas-image.test.tsx nhóm “a picture of a canvas” (hình là
raw?version=n của đúng bản được giao, dưới tên canvas; svg mang lớp vector; mỗi bản mới được
báo lên một lần và vẽ từ địa chỉ của chính nó) và nhóm “a picture that does not load” (địa chỉ
vẫn trả lời thì cái nó giữ không phải hình: nói hình hỏng; “gives the agent a drawing the server
holds that does not draw, once”: chỉ svg mới được báo cho agent, và một lần; bản đã mất thì đọc
lại canvas chứ không đổ cho hình; server đang lỗi thì không đổ cho hình hay bản nào; địa chỉ
không trả lời thì chờ mạng và không nói gì với agent; luồng nối lại sau khi rớt thì vẽ lại, hình
đang hiện thì để yên, hình hỏng thì không thử lại; hai lần kiểm cùng bay thì lần mới nhất quyết
định; hình đi thì bỏ lần kiểm còn bay); web/src/hooks/use-canvas-image.test.ts (canvas ảnh mở
ra là đã đọc và đã lưu với chữ rỗng không bao giờ được gửi, ảnh mới tới qua luồng thì đọc một lần
và chỉ chuyển sang bản ấy, ảnh được khôi phục là bản mới nhất, vẫn không có chữ và không có gì
để lưu); web/src/components/canvas/canvas-panel-kinds.test.tsx nhóm “a canvas shown as a
picture” (svg của agent vẽ từ bản đã lưu, không có trang để mở và vẫn sửa được; “shows an image
only to look at, even when it was just made, with nothing to copy”: chỉ có Xem, không ô sửa,
không nút chép, không danh sách lỗi) và nhóm “asking about a passage of a canvas shown as what
the server holds” (thanh hỏi và hộp đang mở biến mất khi chuyển từ chữ của html, svg hay mermaid
sang cái nó hiện; ảnh không có chữ để chọn nên không có gì để hỏi);
web/src/components/canvas/canvas-saved-view.test.tsx nhóm “what a drawing reports, in the
panel” (“stays one picture however often the panel is drawn again”; svg mà trình duyệt không vẽ
được dù server giữ nó thì vào danh sách lỗi để gửi agent; bản kế là một hình riêng và không lỗi
nào tính cho nó; “reads the canvas again when the version it draws is gone, and draws the one
that took its place”; “draws a picture that did not arrive again when the activity stream comes
back”); web/src/components/canvas/canvas-history-kinds.test.tsx (lịch sử của ảnh hiện mỗi bản
thành một hình riêng, không có dòng để so, không có ô chọn; khôi phục một ảnh cũ chỉ bằng lệnh
khôi phục và hiện nó là bản mới nhất; với canvas chữ, một dòng dài quá 2000 ký tự trong diff bị
cắt kèm số ký tự đã bỏ)web/src/lib/frame-messages.test.ts nhóm “what a page in the canvas frame may tell the
panel” (nhận lời báo từ chính cửa sổ của khung, origin trình duyệt ghi là null; bỏ lời từ cửa
sổ khác, từ không đâu — kể cả khi khung đã đi —, từ origin khác null, dữ liệu không phải object
hay không phải canvas-error, message không phải chữ; giữ message rỗng; cắt message ở 2000
ký tự và tên tệp ở 300, không bao giờ cắt giữa hai nửa một ký tự ngoài mặt phẳng cơ bản; vị trí
không phải số đếm thành 0; “turns a position too large to be a count into 0, and keeps the
largest that is one”: 1e308, 2 ** 53 thành 0 nên danh sách không hiện 1e+308, còn
Number.MAX_SAFE_INTEGER được giữ; không giữ gì của trang ngoài bốn trường), nhóm “the hello of
a page’s reporter” (lời chào trao cổng nó mang và không phải lời báo; nhiều cổng thì lấy cổng
đầu, không cổng thì không có gì; không nhận từ cửa sổ khác, từ không đâu hay dưới origin khác
null; lời báo mang cổng thì cổng bị bỏ; “reads what a message carries once, whatever the
message is” và “reads nothing of a message that is not the frame’s own” đếm số lần data bị đọc
bằng một getter), nhóm “what a page’s reporter says over the port” (chỉ object {type: "press"}
là cú bấm; chữ press gửi lên cửa sổ không là gì cả), nhóm “where a page says its error is” và
nhóm “a file the page asked for that did not arrive”;
web/src/lib/clip-text.test.ts (chữ vừa chỗ thì để nguyên, giữ các đơn vị đầu, bỏ cả cặp chứ
không để lại nửa đầu của nó); web/src/components/canvas/canvas-frame.test.tsx nhóm “what the
page reports” (chuyển đi lời báo từ cửa sổ của trang; không nhận gì từ cửa sổ khác, từ trang đã
gỡ hay từ khung đã bị thay; thôi nghe khi khung đi; “tells the panel’s newest listener alone,
once the panel has given it another”; tệp không tới lúc máy mất mạng thì bỏ qua còn lời báo khác
vẫn giữ, có mạng lại thì liệt kê; mỗi khung chỉ được nghe năm mươi tin: “hears fifty messages of
a page that sends ten thousand, and reads nothing of the rest” đếm số lần data bị đọc bằng một
getter, tin không phải lời báo cũng tính vào năm mươi tin ấy, khung mới có năm mươi tin của riêng
nó, tin từ cửa sổ khác không tính cho trang; “tells the panel when it has heard the last of the
fifty, reports or not, and not before”: onSilenced được gọi đúng một lần ở tin thứ năm mươi,
dù đó là lời báo, một tin bất kỳ hay chữ press, và không lần nào ở tin thứ bốn mươi chín; lời
báo thứ năm mươi vừa được chuyển đi vừa được nói là tin cuối; mỗi khung được nói một lần của
riêng nó; tin từ cửa sổ khác không làm panel được nói; chỉ listener mới nhất của panel được
nói); web/src/components/canvas/canvas-errors.test.tsx
nhóm “the errors a page reported” (không hiện gì khi trang chưa báo gì, nói số lỗi và giữ danh
sách đóng tới khi được hỏi, dấu “+” theo việc khung đã thôi nghe trang chứ không theo số lời
báo: bốn mươi chín hay năm mươi lời báo mà trang còn được nghe thì không có “+”, đã thôi nghe thì
“50+”, “49+” hay “1+”; “says the page is no longer heard when it was heard out without one
report, with nothing to list or send”: chưa có lời báo nào mà khung đã thôi nghe thì nhóm chỉ có
một dòng role="status" đúng câu “Trang gửi quá nhiều tin nên lỗi nó báo từ đây không còn được
nghe” và không nút nào; “leaves saying so to the count once the page has reported something”: đã
có lời báo thì dòng ấy không hiện, dấu “+” nói thay;
liệt kê cũ trước mới sau, “draws what the page wrote as text and never as markup, with hidden
characters as marks”: cả lời báo lẫn tên tệp ở dòng vị trí, app[U+202E]gnp.js:3) và nhóm
“sending the errors to the agent” (chỉ có nút
gửi khi có chat để gửi; “sends nothing until the button is pressed, however many errors arrive”;
lưu canvas rồi gửi một tin nêu trang và bản của nó; gửi năm lỗi mới nhất; nút bị giữ trong lúc
lưu và gửi nên bấm bao nhiêu lần cũng gửi một lần; canvas chưa lưu được thì nói, không gửi và cho
thử lại; server từ chối thì hiện lời của web và lần bấm sau gửi lại; lần gửi hay lần lưu ném lỗi
thì nói một câu chung; tin xếp hàng tính là đã gửi và nút tắt tới khi trang báo lỗi mới; gửi
đúng các lỗi có lúc bấm; nút tắt và nói lý do khi chat bận, chờ quyết định hay hết ngân sách);
web/src/lib/error-report.test.ts (tin là các lỗi trong một rào giữa một dòng nói đó là gì và
một dòng xin sửa; “says the fenced text was written by the page, and is data and not a
request”; tên canvas nằm trong inline code trên một dòng kèm bản được đưa lên, tên có dấu huyền
thì bọc bằng dãy dấu huyền dài hơn và đệm khoảng trắng khi nó mở đầu hay kết thúc bằng dấu ấy;
ký tự ẩn hiện thành dấu thấy được; năm lỗi mới nhất của hai mươi lăm, đánh số từ một; lỗi 5000 ký
tự cắt còn 2000, cắt theo chữ trang viết chứ không theo dấu thay ký tự ẩn; cả tin luôn nằm trong
giới hạn của route chat; rào là ba dấu huyền và dài hơn mọi dãy dấu huyền trong lỗi, “lets the
page end no fence early, whatever it writes”; vị trí lỗi chỉ nêu tới mức trang cho);
web/src/components/canvas/canvas-saved-view.test.tsx nhóm “what the page of a canvas reports,
in the panel” (“lists what the page itself says, and nothing another window says”; “sends the
errors as those of the version the page was put up for, while a newer one waits its second”;
bản mới được đưa lên thì quên lỗi của trang cũ và đếm lại từ không, và gửi lại được dù lỗi trước
đã gửi; trang bị gỡ vì tự đi nơi khác thì quên lỗi của nó; “counts every report the page makes
and keeps the newest five to list”; “stops counting at fifty, says the page reported more, and
lists the newest five of the fifty”: mười nghìn lời báo thì thanh ghi “50+” và danh sách là lỗi
46 tới 50; bốn mươi chín lời báo thì chưa có “+”; lời chào của reporter tới trước thì mười nghìn
lời báo sau nó ra “49+” và danh sách là lỗi 45 tới 49, vì lời chào là một trong năm mươi tin;
“says the page is no longer heard once it has said fifty things, though none was a report”: bốn
mươi chín tin không phải lời báo thì panel chưa hiện gì, tin thứ năm mươi thì hiện câu ấy, lời
báo tới sau không đổi được nó, và trang kế tiếp thì không còn câu ấy;
trang kế tiếp chưa nói gì thì không mang dấu “+” của trang trước; luồng nối lại thì trang được
đưa lên lại và không lỗi nào
tính cho nó; chat không nhận tin được thì nút gửi tắt và nói lý do; “does not send the errors
when what was typed could not be saved, and says so”)FakeCanvas: web/src/app-canvas-ask.test.tsx nhóm “sending the errors a
page reported from the canvas beside the chat” (xem mục hỏi về một đoạn ở trên: một tin duy nhất
với selection: null, không lần lưu và không lần ghi canvas đang mở nào)canvas-render.spec.ts ở 1440×900 (“is the only window whose reports are listed:
one the app posts to itself is not”; “is heard for twenty of the twenty-five errors it throws,
and the list keeps the newest five”: reporter dừng ở hai mươi, test chờ bằng một lời báo trang tự
gửi sau đợt lỗi nên số đếm là 21; “is heard out on fifty messages of the ten thousand reports it
posts past the reporter”: trang gọi thẳng parent.postMessage mười nghìn lần, tin đầu trong năm
mươi tin là lời chào của reporter nên thanh ghi “49+” và đứng yên, danh sách là tin 45 tới 49;
“is said to be no longer heard when what it posts by the thousand is no report at all”: mười
nghìn tin là chữ thường thì panel chỉ có câu trang không còn được nghe, không nút nào;
“reports a picture from elsewhere that the policy keeps it from
loading”: cả dòng img-src blocked lẫn dòng failed to load, và request kết thúc với lỗi csp
trước khi ra mạng; “reports a promise nothing caught as one error”; “shows at most two thousand
characters of an error of five thousand”: cái reporter gửi đi cũng đã chỉ dài 2000, đo ngay trên
cửa sổ của app trước khi app cắt; “sends what it reported to the agent only when asked, as one
message that fences it as data”: chưa bấm thì không có POST nào, bấm thì đúng một tin với
selection: null, từng dòng của tin khớp mẫu và tin hiện trong thread)web/src/lib/canvas-source.test.ts (nguồn workspace:<agent>/<đường dẫn> cắt
ở dấu / đầu tiên nên mã agent có gạch ngang và đường dẫn có / hay : vẫn đúng; thiếu agent
hay thiếu đường dẫn thì không phải tệp; địa chỉ http và https là trang web; “is no link when the
address names who logs in there, by a name, a password or both”: địa chỉ có tên đăng nhập hay
mật khẩu trước @ thì không là nguồn nào, phần đăng nhập rỗng thì trình duyệt đã bỏ @ nên vẫn
là link, @ trong đường dẫn, truy vấn hay mảnh thì giữ; “is no link when the host holds a
character that would read as part of the words around it”: mười lăm host có ngoặc, phẩy, nháy
hay ký tự lạ mà bộ đọc URL của trình duyệt vẫn nhận đều không thành link, còn host gồm chữ, số,
chấm, gạch và gạch dưới, có hay không có cổng, host chữ có dấu (thành xn--…) và địa chỉ số
trong ngoặc vuông thì thành; “is no link to the app’s own address, where a page an agent wrote
would open as the app’s”: cùng origin với app thì không có link, khác cổng hay khác scheme thì
có; “is a workspace file only when the text
opens with the word, not when a link or a sentence holds it”: link có đoạn workspace: trong
đường dẫn vẫn là link, câu có chữ đó ở giữa thì không là gì; “gives the link whole, as the host
it names is read, so a page cannot resolve it to somewhere else”: https:example.com/thuc don
viết thiếu // trả về https://example.com/thuc%20don, vì để nguyên thì một trang cùng scheme
sẽ phân giải nó về chính địa chỉ của mình trong khi nhãn ghi host khác; javascript:, data:, file:, chuỗi
rỗng và chữ thường đều không là nguồn nào); web/src/lib/hidden-chars.test.ts nhóm “what a path
holds that nobody would see” (hai mươi ba ký tự server có lưu trong đường dẫn mà mắt không thấy,
gồm khoảng trắng không phải dấu cách thường, ký tự chèn, bộ chọn biến thể, ô chữ nổi trống và
vùng dùng riêng, mỗi ký tự hiện thành mã [U+XXXX]; dấu cách ở đầu hay cuối một đoạn của đường
dẫn hiện thành [U+0020] từng dấu một, dấu cách giữa tên thì giữ; ký tự đảo chiều vẫn hiện
thành dấu; tên tiếng Việt dạng tổ hợp lẫn dạng tách, tiếng Hàn dạng tách, tiếng Nhật, emoji và
tệp chấm đầu giữ nguyên); web/src/lib/canvas-import-call.test.ts (tham số
đọc từ mapping model gửi, lệnh cũ không có mapping thì không có gì; lần nhập “có id” là khi
id là chữ không rỗng, như server đọc một giá trị trống; “reads a blank as Python’s strip does,
which is not what trim strips”: id chỉ gồm U+001C tới U+001F hay U+0085 là trống như strip()
của server, còn U+FEFF thì không; tên tệp là phần cuối của path, không
có khi path thiếu, toàn dấu /, không phải chữ hay trống theo cách server đọc; đoạn cuối chỉ
gồm dấu cách vẫn là tên tệp, vì server đọc đúng tệp tên đó; cả hai chỉ hỏi về lệnh nhập);
web/src/api/artifact-client.test.ts (“asks for a canvas’s file to be read again, on the version
the text stands on”: POST …/reimport dưới id đã mã hoá, body chỉ có base_version; “passes a
read’s signal, a re-import’s, and a save’s signal and keepalive, through to fetch”: signal của
lần nhập lại tới đúng fetch; “reads the newest version alone from a 409, which a picture’s
carries with no text”: conflictHeadOf lấy head_version của 409 kể cả khi content là null,
số viết thành chữ, 409 không phải của canvas, mã khác hay lỗi mạng thì không có gì)web/src/lib/canvas-machine-sync.test.ts (“reads once for a
version it hears of twice while the read is out”, “reads again for a version it hears of during
a read that was asked for before it”, “reads once for a version heard of twice across a
conflict”); web/src/hooks/use-canvas-imported.test.ts (imported(summary) đọc bản mới như khi
luồng báo; lần đọc đó hỏng thì trạng thái là “Có thay đổi mới” chứ không phải “Đã lưu”; luồng
báo trước rồi reply tới, hay reply tới trước rồi luồng báo, đều chỉ một lần đọc và đúng chữ;
được báo về bản đang hiện thì không đọc gì; người đang gõ thì giữ chữ của họ và nói có thay đổi
đang chờ); web/src/hooks/use-canvas-reimport.test.ts (“sends nothing and says the text is
not saved, whether the flush names none or one below the first”: flush trả null, 0 hay -1 thì
không có request nào và lời báo là “Chưa lưu được bản đang sửa nên chưa nhập lại.”; “asks on the
first version, which is one”: flush trả 1 thì POST mang base_version: 1)web/src/components/canvas/canvas-source.test.tsx nhóm “where a canvas came
from” (dòng nguồn nêu tên agent và tệp, thư mục và tên tệp tách riêng, cả đường dẫn ở title,
nút nhập lại là nút phụ ghost;
“shows what the server stores of a path and nobody would see as marks, on hover too”: U+00A0,
U+3164, U+FE0F và dấu cách cuối đoạn, những thứ server có lưu, hiện thành dấu ở thư mục, ở tên
tệp và ở title; ký tự đảo chiều cũng vẫn hiện thành dấu; “shows a character that hides or
reorders text in the agent’s name as a mark as well”: tên agent có U+202E thì ký tự đó hiện
thành dấu ở thư mục và ở title; nguồn là trang web thì có
link chữ “Mở nguồn (example.com:8787)”, tên truy cập “Mở nguồn (example.com:8787) (mở trong tab
mới)”, title là cả địa chỉ, target="_blank" và rel="noopener noreferrer", không có nút nhập
lại; “shows no line for a link that carries a login, names a host no label can hold, or leads
to the app”: ba loại link đó không có dòng nguồn, không có thẻ a nào và mật khẩu không nằm
trong trang; nguồn rỗng, chữ thường, javascript: hay data: thì không có dòng và không có link nào)
và nhóm “reading a canvas’s file again” (“saves the typing first, then asks on the version that
holds it, and shows the file’s text after one read”: PUT rồi POST với base_version của
PUT đó, đúng hai lần GET, lời báo đúng chữ “Đã nhập lại thành v3. Bản trước ở Lịch sử.”,
vẻ info, nằm ngay dưới dòng nguồn, dòng lịch sử ghi “Nhập từ tệp”; “shows the version the file
became on the reply alone, when the stream says nothing of it”: luồng im thì canvas vẫn đọc bản
mới nhờ reply; chữ chưa lưu được thì không có
POST nào và nói vậy; tệp không đổi thì nói vậy và không đọc lại; “reads a version saved
unheard that holds what the file does, so what it calls the same is what shows”: tệp giống một
bản đã lưu mà canvas chưa nghe tới thì vẫn nói không đổi, và canvas đọc bản đó chứ không đứng ở
chữ cũ; “says the typing is not saved when the save met a version written meanwhile, beside the
choice that version asks for”: lần lưu gặp 409 thì không có POST nào, thanh xung đột hiện và
lời “Chưa lưu được bản đang sửa nên chưa nhập lại.” vẫn đứng cạnh nó; “is held off while the
file is read, takes no second press, and forgets what it said last”: nút mang
aria-disabled="true" chứ không bao giờ có thuộc tính disabled, ghi “Đang nhập…”, bấm lần
nữa không thành request thứ hai, lời báo trước biến mất; “is held off until the canvas has been
read, and a press before then asks for nothing”: luồng báo tên canvas trước khi lần đọc đầu về
thì dòng nguồn đã hiện nhưng nút bị giữ, bấm không gửi gì và không nói gì, đọc xong thì bấm
được; “stays held off on a canvas whose first read failed, which stands on no version to ask
on”: lần đọc đầu hỏng thì nút vẫn bị giữ và lần bấm không thêm câu nào vào lời panel đang nói;
“gives a read that never answers thirty seconds, then says so and offers the button again”:
AbortSignal.timeout được gọi đúng với 30 000, ở 29 999 ms nút còn “Đang nhập…”, thêm 1 ms thì
nút trở lại và lời báo đúng chữ “Không nhập lại được: máy chủ không phản hồi”, không đọc lại
canvas; “cuts no read that answers in time, however long after the deadline was set”; “words a
read the browser cut short itself as a lost connection, for no deadline ran out”: request bị
huỷ bằng AbortError chứ không phải TimeoutError thì lời báo là “Không nhập lại được: mất kết
nối”, không phải câu của hạn chờ, và nút trở lại; “says its
news in a place that was there before it had any, and an error apart from it”: phần tử
role="status" nằm ngay sau dòng nguồn từ trước khi có lời nào, rỗng và không mang lớp
notice, lời báo tin hiện trong chính phần tử đó, còn lời báo lỗi là một role="alert" khác;
403, 410, 413, 422 mỗi cái một câu của web, so nguyên văn nên hai lời từ chối không lẫn chữ của
nhau, vẻ error, không bao giờ hiện chữ của server, và không lần nào đọc lại canvas; câu của
422 là “Tệp nguồn không đọc được, không phải tệp thường, hoặc không hợp với loại canvas này.”,
đúng cho mọi lý do server trả 422; 413 đúng một câu dù server trả câu chữ hay object cỡ; tệp
không còn là 410; 409 thì đọc lại canvas, nói đúng chữ “Không nhập lại được: máy chủ có bản mới
hơn” và không hiện gì của body từ chối; canvas đã xoá thì chỉ còn lời báo đã xoá, nút bị giữ và
bấm lần nữa không gửi gì; 500, 507 và mất mạng nói bằng lý do của web, đúng chữ “Không nhập lại
được: máy chủ không phản hồi”, “…: hết chỗ lưu canvas trên máy chủ” và “…: mất kết nối”, và
cũng không đọc lại canvas) và nhóm “what a re-import said, once the canvas has a newer version”
(lời báo tin lẫn lời từ chối đều biến mất khi agent ghi một bản mới hơn hay khi lần lưu của
chính người dùng tạo ra một bản, và chỗ nói tin trở lại rỗng; “stands when a refusal follows a
save of the typing, for that version is the one it was said on”: bản do lần lưu trước khi nhập
lại tạo ra không xoá lời từ chối vừa nói; “stands through the read of the version a conflict
told of, when it was said before that read answered”: lời báo 409 hiện khi canvas còn đứng ở v1
và lần đọc lại còn đang treo, lần đọc về v2 thì lời báo vẫn đứng; “is taken back by a version
newer than the one a
conflict told of, not by that one”: lời báo 409 đứng qua lần đọc bản mà 409 nêu và chỉ biến mất
ở bản sau đó; “is said again by the next press, on the version the canvas has by then”);
web/src/components/canvas/canvas-card-import.test.tsx nhóm “a file read into a canvas, in the
thread” (là thẻ canvas khi đang chạy và khi xong, thẻ thường khi hỏng, bị từ chối, bị dừng hay
chờ duyệt; “Đang nhập…” kèm vòng quay; “Đã nhập · v1” khi lệnh không nêu canvas, “Đã nhập lại ·
v4” khi có nêu, id rỗng, trống theo cách server đọc hay không phải chữ tính là không nêu; “Không đổi · vcanvas-import.spec.ts ở 1440×900 (“saves the typing first, then reads the file
again on the version that save made”: request PUT đi trước POST …/reimport và POST mang
base_version của PUT đó, chữ người gõ còn lại là phiên bản ngay dưới bản nhập, và nút “Nhập
lại” nằm sát cuối dòng dù đường dẫn ngắn, lời báo đúng chữ “Đã nhập lại thành v3. Bản trước ở
Lịch sử.”; “says the file holds what the canvas does, and writes nothing”: đúng chữ “Tệp nguồn
không đổi.”; “says its news in a place that was in the panel before, which takes no room while
it has none”: phần tử role="status" đã gắn trong trang và rỗng trước lần bấm, cao 0 px, chữ
của canvas bắt đầu đúng một khoảng row-gap dưới dòng nguồn như khi không có phần tử đó, và
lời báo hiện ra trong chính phần tử ấy (so cùng một node); “keeps the keyboard on the button
while the file is read, and takes no second press”: trong Chromium thật, nhấn Enter khi nút
đang giữ focus và request bị giữ lại thì nút ghi “Đang nhập…”, aria-disabled="true", thuộc
tính disabled là false, focus vẫn ở nút, opacity 0.45 và con trỏ not-allowed; Enter, phím
cách và một cú bấm chuột nữa không thành request thứ hai; thả request thì nút trở lại “Nhập
lại”, vẫn giữ focus; “gives nothing under the pointer or under a press while the file is
read”: trong lúc nút bị giữ, đưa chuột lên rồi nhấn giữ chuột thì màu nền, màu chữ và
transform của nút (đọc sau khi mọi transition của nút chạy xong) vẫn y như lúc chưa có
chuột, nền trong suốt và không co lại; nhả chuột không thành request thứ hai; thả request
thì nút sáng lên dưới con trỏ như mọi nút ghost; “gives up a read that never answers, says the server did not, and offers
the button again”: AbortSignal.timeout(30 000) của trang được rút ngắn bằng addInitScript,
request không bao giờ được trả lời thì trình duyệt huỷ đúng request POST …/reimport, lời báo
là “Không nhập lại được: máy chủ không phản hồi”, nút trở lại và chữ của canvas còn nguyên;
“links the page a canvas was taken from,
named by its host, in a tab that cannot reach back”: tìm link bằng đúng tên truy cập “Mở nguồn
(tin-tuc.example.com) (mở trong tab mới)”, chữ trên dòng không có đoạn sau, title là cả địa
chỉ) và ở 390×844 cảm ứng (“keeps the name of
the file in view and gives the folder up, with a button a finger can hit”: đường dẫn dài thì
thư mục bị cắt còn tên tệp nguyên tới chữ cuối và nút “Nhập lại” nằm trọn trong màn, nút cao ít nhất 40 px, không
phần nào của canvas cuộn ngang, kể cả sau khi lời báo “Đã nhập lại thành v2. Bản trước ở Lịch
sử.” hiện ra, nhãn, tên tệp và nút
nằm trên một hàng có chung đường giữa và có khoảng hở giữa các phần; “shows the link to a
page as a control a finger can hit, inside the width”: link “Mở nguồn” cao ít nhất 40 px và chữ
của nó nằm giữa chiều cao đó; “cuts a name too long for the width with an ellipsis, and nothing
runs past the row”: tên tệp rộng hơn màn thì thư mục nhường hết chỗ (rộng 0), tên tệp chiếm cả
phần đường dẫn, bị cắt bằng text-overflow: ellipsis chứ không chạy quá mép phần đó, cả tên
vẫn ở title, nút “Nhập lại” nằm trọn trong màn và scrollWidth của trang không quá
innerWidth; “wraps the name of a host too long for the width, and nothing
scrolls sideways”: host dài không có gạch ngang thì xuống dòng trong bề ngang, link vẫn nằm trọn
trong màn); bản
giả của route nằm ở web/src/test/fake-canvas-import.ts, dùng chung cho vitest và Playwrightweb/src/hooks/use-route.test.ts (“reads the canvas library, and the
canvas a page is opened on, out of a manage link”: #/manage/canvas và #/manage/canvas/<id> đi
về qua parseRoute/routeHash, mục viết khác đi thì về Hoạt động);
web/src/screens/manage-screen.test.tsx (“lists the canvases under the crew, after the memory, and
opens their library”: mục Canvas đứng cuối nhóm Đội, sau Trí nhớ; “names the agent that made a
canvas as the rest of the screen names it”: thư viện gọi agent bằng tên mà màn Quản lý đang dùng;
“reads the canvases again when the stream comes back”: màn Quản lý báo cho thư viện biết stream
vừa nối lại)web/src/api/artifact-client.test.ts (“deletes a canvas under an encoded id, and
reads nothing from the server’s empty answer”: DELETE /api/artifacts/<id đã mã hoá>, 204 không
có thân)web/src/hooks/use-canvas-library.test.ts (đọc mọi canvas bất kể hội thoại, với
limit=200 và không conversation_id; 201 canvas thì hiện 200 và số đếm vẫn là 201; danh sách và
dung lượng là hai lần đọc gửi cùng lúc, cái nào về trước hiện trước; không đọc được dung lượng thì
vẫn có danh sách; lần đọc đầu hỏng thì báo và đọc lại khi thử lại, lần đọc sau hỏng thì giữ danh
sách cũ cùng chữ đã tìm; tìm theo tên chờ 250 ms sau phím cuối (249 ms thì chưa hỏi, con số viết
thẳng trong test) và gửi tên thành tham số q riêng;
ô chỉ khác dấu cách thì không hỏi lại; câu trả lời hay lỗi về sau lần hỏi mới hơn thì bỏ; một loạt
thay đổi gom thành một lần đọc sau nửa giây; stream nối lại và tab hiện lại thì đọc lại với chữ
đang tìm; rời thư viện thì thôi đọc); web/src/hooks/use-canvas-library-delete.test.ts (canvas
rời danh sách và tổng khi server trả lời, không sớm hơn; 404 cũng là đã xoá; 500 hay mất mạng thì
giữ canvas và ghi nhận nó bị từ chối cho tới lần thử sau; hai canvas cùng bị từ chối thì cả hai
được ghi nhận, thử lại một canvas chỉ gỡ ghi nhận của chính nó; canvas chưa đọc được cỡ thì tổng
giữ nguyên; nơi khác đang hiện canvas ấy được báo; lời đáp nói canvas đã hết hay chưa: không khi
server giữ nó, có khi server xoá hay đã không còn nó)web/src/components/canvas/canvas-library.test.tsx (mỗi dòng nói loại, phiên
bản, ai tạo, lúc nào và cỡ mọi phiên bản; sáu loại, canvas mã kèm ngôn ngữ, loại lạ in nguyên;
ngôn ngữ chỉ nói ở canvas mã dù server giữ ngôn ngữ cho loại nào cũng được; thời điểm là lần ghi
cuối, không phải lúc tạo; người là “bạn”, agent là tên của nó kể cả agent có mã user, ký tự ẩn
trong tên agent được viết ra; tiêu đề là chữ thuần, ký tự ẩn được
viết ra, thẻ HTML không thành phần tử; nguồn tệp là <agent>/<đường dẫn>, nguồn web là “agent ghi
nguồn: web/e2e/canvas-library.spec.ts (ở 1440 px và 390 px: ba canvas với người tạo, nguồn,
cỡ và dòng tổng, không link nào, tiêu đề và đường dẫn dài không làm trang, mục, thư viện hay dòng
nào tràn ngang; tìm không dấu “ke hoach” ra đúng một canvas, tên lạ ra câu không khớp, xoá ô thì
đủ lại; bấm xoá rồi từ chối thì canvas còn, đồng ý thì dòng mất, server giả hết canvas ấy và tổng
giảm; trong Chromium thật, dòng nói “Đã xoá canvas…” không chiếm chỗ nào khi còn trống, và sau khi
xoá dòng cuối thì bàn phím nằm ở tên của dòng trước, dòng ấy hiện đúng tên canvas vừa xoá, cùng
cỡ chữ và không lề riêng như dòng tổng; “the canvas library is reached from the crew’s part of
the manage screen”);
web/e2e/touch-targets-phone.spec.ts (trang “the canvas library”: nút xoá của một dòng đủ 40 px);
bản giả của GET /api/artifacts/usage nằm ở web/src/test/fake-canvas-usage.ts
(web/src/test/fake-canvas.test.ts, “counts what every version of each canvas holds, on a path
no canvas’s id answers”)#/manage/canvas/<id>, mở từ tên của nó trong thư
viện hoặc nút “Mở riêng” của panel; rời trang không mất chữ; trang nói canvas dùng trong những hội
thoại nào
web/src/components/canvas/canvas-section.test.tsx (“shows the library for %j,
which is no canvas’s id, and asks the server nothing about it”: a1, chữ in hoa, thừa hay thiếu
một ký tự, ..%2F..%2Fsettings, id kèm ?x=1 và chuỗi rỗng đều ra thư viện, và chỉ hai lần đọc
của thư viện được gửi; “is shown on its page instead of the
library, with the way back above it”; “is read anew when the address names another, and nothing
typed in the first is lost”; “a save that fails after its page was left”: câu “lưu hỏng, nháp còn
giữ” hiện phía trên thư viện vừa quay về, hoặc phía trên canvas vừa chuyển sang, và ở lại qua lần
chuyển kế; “is not said of a canvas deleted from the library before the save got there”: xoá
canvas ngay sau khi rời nó thì không có câu báo nào và nháp đi theo; “opens the canvas whose name
is clicked in the library”; “shows what was written to it while the stream was down, once the
stream is back”: bản ghi lúc mất stream không ai báo, stream nối lại thì trang đọc lại và hiện
chữ mới; “names a conversation it came to be used in while the stream was down, once the stream
is back”: hội thoại nhận canvas lúc mất stream không ai báo, stream nối lại thì dòng “Dùng
trong…” có tên nó); web/src/components/canvas/canvas-section-crash.test.tsx (trang vỡ
thì nút “← Canvas” vẫn đứng phía trên chỗ vỡ vì nó nằm ngoài ErrorBoundary; địa chỉ đổi sang
canvas khác hoặc về thư viện thì chỗ vỡ bị bỏ lại)web/src/components/canvas/canvas-page.test.tsx (trang dùng đúng panel mà hội
thoại dùng; không có thanh hỏi agent, không có “Mở riêng”, không ghi “canvas đang mở” cho hội
thoại nào; “opens a canvas an agent wrote to be read, under its name and not a box to name it in”:
trang mở canvas như một canvas có sẵn, không như canvas vừa tạo; “opens the next canvas the way
that one opens, not the way the one before it was left”: đổi id thì chế độ xem hay sửa tính lại
từ đầu; “goes back to the library at once with words unsaved, and their save goes on behind”;
nút đóng và nút về danh sách của panel đều là đường về; canvas không có hay bị xoá lúc đang mở thì
panel nói “đã bị xoá” và đường về còn; “Nhập lại” gửi đi không kèm hội thoại nào; dòng “Dùng trong
web/src/components/canvas/canvas-panel.test.tsx (“the way from the panel
to the canvas’s own page”: window.open(<địa chỉ>, "_blank", "noopener,noreferrer") và không lưu
gì trên đường; còn chữ chưa lưu thì nút bị giữ lại bằng aria-disabled, bấm không mở gì, và nhận
lại khi một phiên bản giữ chúng; “stays within the keyboard’s reach while words are unsaved, and
says why it cannot be taken yet”: nút không disabled nên vẫn nhận bàn phím, lý do “Lưu trước khi
mở riêng” nằm ở title và ở dòng mà aria-describedby trỏ tới, dòng ấy mang lớp sr-only nên
chỉ được đọc lên chứ không vẽ ra, lưu xong thì lý do không còn và bàn phím vẫn ở nút; đứng cạnh
nút “Mở trang” của canvas trang, mỗi nút một tên; không có khi
panel không được cho địa chỉ, khi canvas mới biết tên chưa đọc xong, và khi canvas bị xoá);
web/src/components/canvas/canvas-dock-view.test.tsx (“the open canvas’s own page”: ở cột lẫn lớp
phủ, địa chỉ là #/manage/canvas/<id> của canvas đang mở);
web/src/components/canvas/canvas-library.test.tsx (“opens the canvas whose name is clicked, one
with no name too, and deletes nothing by it”)web/src/app-canvas-page.test.tsx (link tới trang mở đúng trang, “← Canvas” đưa
địa chỉ về #/manage/canvas, tên trong thư viện đưa địa chỉ về lại #/manage/canvas/<id>; bấm
tên hội thoại thì sang #/chat/<id>; chọn mục khác rồi quay lại mục Canvas thì là thư viện, id đã
rời địa chỉ; link có id không hợp lệ thì chỉ /artifacts/usage được hỏi dưới /artifacts/;
“shows the library, not a blank screen, for a link whose canvas is written %j”: link cụt giữa một
ký tự mã hoá (%, %E0%A4%A, %zz) ra thư viện thay vì màn trắng, và cũng chỉ /artifacts/usage
được hỏi; “Mở riêng” từ dock của hội thoại gọi window.open với đúng địa chỉ trang)web/src/hooks/use-route.test.ts (“opens the section alone when what it is
opened on is written %j, an escape that cannot be read”: %, %E0%A4%A, %zz, ghi%2 ở chỗ
của id thì chỉ còn mục, phần cần hiện và ?job= đi theo nó; “keeps what the section is opened on
and drops the part to show, when only that part cannot be read”; “reads a broken escape anywhere
else in the address as the letters it is written in”: ở tên mục, ở id hội thoại và trong ?job=
không có gì phải giải mã nên không gì ném lỗi; “starts on the section, and follows the address
bar to one, when the address holds an escape that cannot be read”: useRoute lúc khởi động và
lúc hashchange đều ra mục chứ không ném lỗi)web/e2e/canvas-page.spec.ts (ở 1440×900 và 390×844: bấm tên trong thư viện thì địa
chỉ có id, trang chạm đúng chân mục mà mục không cuộn dọc, panel là cả trang trừ dòng “Dùng
trong…” và cao hơn nửa màn, ô soạn chiếm phần panel mà đầu panel để lại, không chỗ nào tràn
ngang, tải lại vẫn ở trang đó; rời trang ngay khi chữ chưa lưu thì lần lưu vẫn tới server và không
có câu báo; lần lưu bị từ chối thì câu báo hiện phía trên thư viện, mở lại canvas là thấy nháp;
dòng hội thoại đúng thứ tự và mở đúng cuộc; tiêu đề dài không ngắt được vẫn nằm trong màn; canvas
không còn thì nói đã xoá và “← Canvas” vẫn về thư viện; địa chỉ ..%2F..%2Fsettings ra thư viện
và không địa chỉ nào dưới /api/artifacts/ ngoài usage được hỏi; “an address cut short in the
middle of an escape shows the library instead of a blank screen”: #/manage/canvas/%E0%A4%A ra
thư viện và trang không ném lỗi nào; “opens on its own page in a
new tab cut off from this one, once what was typed is saved”: nút bị giữ lại cho tới khi lần lưu
xong mà vẫn nhận bàn phím: aria-disabled, không disabled, lý do ở title và ở mô tả, mờ như
nút tắt, Enter, Space và cú bấm chuột đều không gọi window.open lần nào (trang ghi lại từng
lần gọi, vì tab mới chỉ hiện trong danh sách trang của trình duyệt sau một lúc); lưu xong thì bàn
phím vẫn ở nút và lý do không còn, cú bấm lúc này là lần gọi duy nhất và đúng địa chỉ; tab mới có
window.opener là null và document.referrer rỗng, tab cũ vẫn ở cuộc trò chuyện; “gives
nothing under the pointer or under a press while words are unsaved”: lúc bị giữ lại, nút không
đổi nền, màu chữ hay transform khi rê chuột vào và khi nhấn giữ, cú nhấn không mở gì; lưu xong
mà chuột còn trên nút thì nút sáng lên như mọi nút cùng loại);
web/e2e/touch-targets-phone.spec.ts (“every control on a canvas’s own page is big enough for a
finger, a conversation named in one letter too”; “the name that opens a canvas from the library is
big enough for a finger, one letter long too”); nút “Mở riêng” trong dock ở 390 px và 1000 px nằm
trong phép kiểm sẵn có của web/e2e/canvas.spec.ts (smallTargets(page, ".canvas-dock"), không
tràn ngang)web/src/components/canvas/delegate-canvases.test.tsx (đúng thứ tự của kết
quả, mỗi dòng có tên, v<n> và nút “Mở” gọi đúng id; là một danh sách có tên; tên lấy từ thread
trước, rồi từ dòng kết quả, rồi “Canvas”; ký tự ẩn được viết ra, thẻ HTML không thành phần tử; mỗi
canvas được hỏi verify một lần, vẽ lại không hỏi lại; canvas đã xoá chỉ còn tên và “Canvas đã bị
xoá”, không phiên bản, không nút; kết quả nêu một canvas hai lần thì vẽ cả hai dòng, và khi kết
quả được đọc lại với một dòng chen lên đầu thì các dòng vẫn đúng theo kết quả, không dòng nào
thừa; không canvas nào thì không vẽ gì và không hỏi gì)web/src/components/delegate-cards.test.tsx (“the canvases a handed-off task wrote,
on its card”: danh sách đứng dưới dòng trạng thái và chi phí, trên nút xem kết quả; thread không
đưa đường mở canvas thì không có dòng nào; khối canvas rỗng là ca đối chứng: không dòng nào và
không hỏi server; câu trả lời của agent con mở đầu bằng một dòng giống thẻ canvas thì không thành
dòng nào; việc hết giờ vẫn nêu canvas đã viết kèm lý do; việc đang chạy thì chưa có; kết quả mở ra
chỉ còn lời của agent con)web/src/hooks/use-canvas-auto-open.test.ts (“opening the canvas a handed-off task
wrote”: mở lặng lẽ khi lệnh giao việc xong, chỉ canvas đầu tiên, một lần dù vẽ lại, bất kể
outcome là gì miễn lệnh gọi xong; hai việc giao song song thì việc xong sau là canvas còn mở; “a
handed-off task that opens nothing by itself”: không viết canvas nào; hết giờ, hỏng, bị từ chối
hay bị dừng; lệnh đã có trong lịch sử hay đã xong lúc màn hình được vẽ; màn hẹp, kể cả khi sau đó
rộng ra;
người đang gõ trong canvas, kể cả khi sau đó thôi gõ; dòng thẻ chỉ nằm dưới dòng trống, khối lẫn
dòng lạ, khối không có dòng trống, kết quả không có dòng outcome, output rỗng hay rác; tool
canvas có output trông như kết quả giao việc)web/src/app-canvas-delegate.test.tsx (ở 1440 px: hai dòng trên thẻ, canvas đầu
mở cạnh thread, bàn phím ở lại ô soạn, không lần ghi “canvas đang mở” nào; tên trên dòng là tên
server đang giữ, mỗi canvas được hỏi đúng một lần; tin kế tiếp mang canvas vừa tự mở; nút “Mở” của
dòng khác thay canvas đang mở và tin kế tiếp mang canvas ấy; thay canvas người đang mở khi bàn
phím đã về ô soạn tin, không thay canvas người đang đặt con trỏ, không thay trang HTML mà khung
của nó đang giữ bàn phím — dòng của canvas mới vẫn mở nó khi bấm —, không thay canvas mà bàn
phím đang ở một nút của nó; canvas bị xoá lúc đang hiện thì dòng thôi mời mở;
khối rỗng và câu trả lời giống thẻ thì không dòng, không mở, không hỏi server; việc hết giờ có
dòng mà không tự mở; ở 1000 px có dòng, không tự mở, bấm thì thành lớp phủ; lượt đã lưu từ trước
có dòng, chỉ mở khi bấm và khi đó bàn phím vào dock; canvas server không còn thì “Canvas đã bị
xoá”)web/e2e/canvas-delegate.spec.ts (ở 1440×900 và 390×844: hai dòng đúng tên và phiên
bản, nằm dưới dòng chi phí, xếp chồng, nút ở cuối dòng, không chỗ nào tràn ngang; khối rỗng và câu
trả lời giống thẻ thì không dòng nào và không canvas nào mở; canvas server không có thì “Canvas đã
bị xoá” và không nút; tiêu đề dài không ngắt được thì chính tiêu đề xuống dòng, còn phiên bản, nút
và chữ “đã bị xoá” giữ nguyên một dòng; ở màn rộng canvas đầu mở cạnh thread, ô soạn vẫn giữ focus
và tin kế tiếp mang canvas ấy, nút “Mở” của dòng thứ hai thay canvas đang hiện; ở điện thoại không
canvas nào tự mở và chạm “Mở” thì canvas phủ lên thread); web/e2e/touch-targets-phone.spec.ts
(“the Open button of each canvas a handed-off task wrote is big enough for a finger, beside a name
one letter long too”)FILE: artifact:<id> hoặc MEDIA: artifact:<id> của một câu trả lời hiện
thành chip tên canvas kèm nút “Mở”, không thành link tải tệp; dòng ghi sai mã thì hiện lại đúng
như đã viết, không chip, không câu “Canvas đã bị xoá” và không hỏi server
web/src/lib/artifact-ref.test.ts (cùng bảng với artifact_ref của
Python: mã 12 ký tự hex thường là canvas, khoảng trắng quanh mã được bỏ; mở bằng artifact: mà
phần sau không phải đúng một mã thì là ""; còn lại là null, kể cả Artifact: viết hoa,
artifact: đứng giữa đường dẫn hay sau một dấu cách)tests/test_channels_telegram.py::test_the_web_tells_a_line_that_sends_a_canvas_by_the_prefix_the_channel_reads
(tiền tố ARTIFACT_REF trong web/src/lib/artifact-ref.ts bằng đúng hằng của Python);
tests/test_channels_telegram.py::test_the_web_takes_for_a_canvas_id_what_the_channel_takes_for_one
(mẫu mã ID_RE trong web/src/lib/artifact-tag.ts, neo ở cả hai đầu và không cờ, bằng đúng
mẫu _ARTIFACT_ID mà my_agent_crew/reply_attachments.py so bằng fullmatch)web/src/lib/reply-blocks.test.ts (splitMedia, nay ở
web/src/lib/reply-blocks.ts: dòng MEDIA: và FILE: tách khỏi lời, giữ đúng thứ tự, câu chỉ
nhắc tới tiền tố và tiền tố trần vẫn là lời; dòng có MEDIA: đứng sau chữ khác, hay tiền tố
không viết hoa hết (media:, file:, File:), cũng vẫn là lời như phía kênh đọc; một đoạn
lời giữ nguyên chỗ xuống dòng, dòng trống và khoảng trắng đầu cuối từng dòng, là những thứ
Markdown đọc; dòng gửi canvas dưới cả hai tiền tố thành khối canvas mang mã và nguyên dòng
đã viết; dòng tệp workspace không đổi; mã sai thành khối canvas có mã rỗng, kể cả mã đúng
có đuôi .md hay dấu chấm theo sau, và khối vẫn giữ nguyên dòng đã viết)web/src/components/message-thread.test.tsx (“a line of a reply that
sends a canvas”: thread có đường mở canvas thì câu trả lời có chip, không link tệp, không chữ
FILE:, lời quanh dòng còn nguyên và nút gọi đúng mã; dòng ghi mã kèm đuôi .md là một đoạn
chữ đúng như đã viết, đứng cạnh chip của dòng ghi đúng, không câu “đã bị xoá” và chỉ mã đúng
được hỏi; thread không có đường mở canvas thì dòng là một đoạn chữ)web/src/components/canvas/canvas-ref-chip.test.tsx (tên thread đang giữ, biểu
tượng, đúng một nút và nút gọi đúng mã; không in lại dòng hay mã; chưa biết tên thì là “Canvas”
và vẫn mở được; ký tự ẩn được viết ra; verify được gọi một lần, vẽ lại không gọi lại, đổi mã
thì gọi lại, và được đưa một verify khác thì hỏi lại qua verify ấy; canvas đã xoá còn tên
và “Canvas đã bị xoá”, không nút; mã rỗng thì là đúng dòng đã viết trong một đoạn chữ, không
chip, không nút, không hỏi verify, titleOf hay isGone; không có đường mở canvas thì cũng
là đúng dòng đã viết, trong một đoạn chữ)web/src/app-canvas-ref-chip.test.tsx (câu trả lời đã lưu: chip mang tên canvas
và bấm thì canvas mở cạnh thread; bấm lúc khung “đang viết” của một canvas khác đang hiện thì
khung ấy rời đi, panel hiện canvas của chip và thẻ “đang viết” vẫn còn; sau lần bấm ấy khung
không tự nổi lại trong cả lượt, kể cả khi model viết lại câu trả lời và bản nháp mới đã đủ dài
để tự hiện, còn nút trên thẻ “đang viết” vẫn gọi được khung ra với bản nháp mới; canvas không
gắn với hội thoại lấy tên server đang giữ sau đúng một lần hỏi; canvas server không còn thì
“Canvas đã bị xoá” cạnh một chip còn mở được; ba dòng ghi sai mã, FILE: artifact:xyz, mã của
một canvas đang có kèm đuôi .md và mã ấy kèm dấu chấm cuối, mỗi dòng là một đoạn chữ đúng như
đã viết, không chip, không câu “đã bị xoá”, không nút, không link tệp, không ảnh và không gây
lời hỏi /artifacts/ nào)web/e2e/canvas-thread.spec.ts (ở 1440 px chip nằm trong câu trả lời, tên đứng
trước nút, chữ của nút trên một dòng, không link tệp, bấm thì canvas mở cạnh thread; cũng ở
1440 px chip của một canvas tên ngắn là một khung nhỏ, hẹp hơn nửa bề ngang câu trả lời, nên bỏ
luật .bubble .canvas-ref thì chip thành một thanh ngang suốt câu trả lời và test đỏ; ở 390 px
smallTargets(page, ".canvas-ref") rỗng, nút cao ít nhất 40 px và chạm thì canvas phủ lên
thread, Escape cất canvas và bàn phím về nút của chip; tiêu đề một từ rất dài thì chính tiêu đề xuống dòng trong câu trả lời, nút vẫn nguyên
một dòng và không chỗ nào tràn ngang)tests/test_api_agents_edit.py::test_an_id_that_is_not_a_safe_folder_name_is_refused
(../escape và coder có xuống dòng ở cuối đều bị từ chối, không thư mục nào được tạo);
tests/test_artifact_guards.py
(“test_an_author_that_is_neither_the_person_nor_an_agent_is_refused”,
“test_a_creator_that_does_not_match_its_author_is_refused”: agent:Coach và mã có xuống
dòng ở cuối không ghi được canvas); mẫu ở my_agent_crew/agent_ids.pyhooks/use-auto-scroll.test.ts (“stays pinned when its own scroll is reported after the
content grew again, and lets go once the reader moves up”: trình duyệt báo cú cuộn của chính hook
chậm một khung hình, lúc đó nội dung đã dài thêm nên khoảng cách tới đáy đọc ra xa dù không ai
cuộn, thread vẫn bám và chỉ nhả khi vị trí đi lên; “stays pinned when the report of its own jump
finds the element exactly where it left it”; “counts the jump its size observer makes as where
the element was left”: bộ quan sát kích thước cũng ghi lại chỗ nó vừa nhảy tới; “does not take
hold again on a scroll down that stops short of the bottom”; “lets go when the reader scrolls up
straight after a jump to the newest”). Lỗi thật thấy trên trình duyệt (390 px và 1000 px, model
echo): sau vài tin thread ngừng bám giữa chừng, nút “Tin mới nhất” hiện lên và câu trả lời nằm
ngoài màn hìnhcomponents/message-thread.test.tsx (“marks the thread as following while it is at its
newest line, so the browser’s anchoring stays out of it”, “drops the mark once the reader scrolls
up, and offers the way back to the newest line”, “brings the mark back when the reader jumps to
the newest line”: thread mang data-following khi đang ở dòng mới nhất, mất khi người đọc cuộn
lên và có nút “Tin mới nhất” để quay về)thread-follow.spec.ts (390 px, hội thoại tám lượt với câu trả lời nhiều dòng, câu
trả lời của lượt mới được giữ lại tới khi test thả ra; “the browser’s own anchoring is off while
the thread follows its newest line and on once it is read”: overflow-anchor tính ra là none
khi thread bám, auto khi người đọc cuộn lên và none lại sau “Tin mới nhất”, đây là test ghim
nguyên nhân; “a reader who scrolled back keeps their place when the buttons above leave”: đầu câu
trả lời đang đọc không xê dịch khi nút rẽ nhánh phía trên biến mất, chặn việc tắt neo cuộn cho
mọi thread; “a turn that starts with the thread on its newest line leaves it there”: lượt bắt đầu
rồi câu trả lời về xong, thread vẫn ở dòng mới nhất và không có “Tin mới nhất”)overflow-anchor đỏ trước khi sửaweb_url): đọc từ config.yaml hoặc MY_AGENT_WEB_URL; giá trị không phải một
địa chỉ http trơn thì dừng lúc nạp cấu hình
tests/test_config.py::test_the_web_address_comes_from_yaml_or_env_and_defaults_to_none
(env thắng tệp, / cuối được bỏ, chưa đặt là rỗng, khoá có trong YAML_KEYS);
tests/test_config.py::test_no_web_address_reads_as_empty;
tests/test_config.py::test_a_web_address_keeps_its_scheme_host_port_and_path_without_the_last_slash
(127.0.0.1, localhost, tên miền có cổng, tên có gạch nối và gạch dưới, IPv6 trong ngoặc
vuông kể cả dạng bọc IPv4; khoảng trắng ở hai đầu và dấu ? hay # không có gì theo sau
được bỏ đi cùng dấu / cuối);
tests/test_config.py::test_a_web_address_that_is_not_a_plain_one_is_refused (scheme khác
http/https, thiếu scheme, thiếu host, có tên đăng nhập, query, fragment, khoảng trắng và ký
tự ẩn ở path lẫn ở host, ký tự điều khiển đứng đầu mà phép tách địa chỉ sẽ lặng lẽ bỏ, nửa
cặp surrogate, cổng không đọc được, path có ký tự phải mã hoá, giá trị không phải chuỗi; host
có \, *, chữ có dấu, dấu : không kèm cổng, ngoặc vuông chứa thứ không phải IPv6 trơn;
câu lỗi của hàng nào cũng không lặp lại giá trị);
tests/test_config.py::test_a_refused_web_address_stops_the_load_and_is_not_repeated (câu lỗi
nêu khoá web_url và không lặp lại giá trị, vì giá trị có thể mang mật khẩu)tests/test_reply_file_line.py):
::test_a_notice_that_cannot_be_sent_does_not_stop_the_next_attachment
(sendMessage trả 500 trong lúc một tệp và một ảnh không có: ok.pdf vẫn tới, send không ném,
mỗi dòng báo hỏng một dòng log mức warning);
::test_an_upload_telegram_refuses_is_said_and_does_not_stop_the_next (tệp có thật mà Telegram
từ chối lúc tải lên: chat nhận một dòng báo, ok.pdf vẫn tới); bản giả Telegram
(tests/telegram_fake.py) cho một method trả lỗi bằng fail, giữ một lời gọi bằng hold, và
đọc tên tệp, caption, bytes của mỗi lời tải lên vào uploadsFILE: artifact:<id> hoặc MEDIA: artifact:<id> của một câu trả lời gửi canvas đó tới
chat Telegram dưới dạng tệp, dựng trong bộ nhớ, sau các tệp workspace
tests/test_telegram_canvas_file.py):
::test_a_path_names_a_canvas_only_as_the_prefix_and_a_whole_id
(mã 12 ký tự hex thường là canvas; mở bằng artifact: mà phần sau không phải đúng một mã thì là
"", không bao giờ được tìm như một tệp; còn lại là đường dẫn tệp);
::test_lines_that_misname_a_canvas_get_one_notice_that_repeats_none_of_them (một dòng báo cho
mọi dòng ghi sai mã, không lặp lại chữ của model);
::test_a_canvas_named_twice_in_one_reply_is_sent_once;
::test_workspace_files_go_first_and_as_before_then_the_canvases::test_a_markdown_canvas_arrives_as_a_document_with_its_secrets_covered
(giá trị biến môi trường mang tên khoá và chuỗi dạng sk-… được che trong nội dung);
::test_a_text_canvas_is_named_so_a_phone_opens_it_as_text (markdown giữ .md, các loại chữ
khác kết thúc bằng .txt); ::test_a_picture_is_named_by_what_its_bytes_are;
::test_a_picture_arrives_as_a_photo_with_its_bytes_as_they_are (loại canvas quyết định cách
gửi, không phải tiền tố của dòng); ::test_a_text_canvas_on_a_media_line_is_still_a_document;
::test_the_newest_version_is_sent_and_the_caption_says_which;
::test_the_caption_ends_with_a_link_when_the_web_has_an_address;
::test_a_link_that_would_make_the_caption_too_long_is_left_out (đúng 1024 ký tự thì còn link,
1025 thì bỏ cả link);
::test_a_secret_in_the_title_reaches_neither_the_caption_nor_the_file_name;
::test_the_caption_is_plain_text_like_every_message;
::test_bytes_are_uploaded_under_a_name_with_a_bounded_wait (send_bytes: không có phần
caption khi rỗng, không parse_mode, mỗi lần tải lên chờ tối đa 60 giây)::test_a_picture_refused_as_a_photo_arrives_as_a_document (sendPhoto trả
400 thì gửi lại một lần bằng sendDocument);
::test_a_picture_refused_both_ways_is_tried_once_more_and_no_further;
::test_a_photo_that_fails_for_another_reason_is_not_sent_again;
::test_a_document_that_is_refused_is_not_sent_again;
::test_a_canvas_that_cannot_be_sent_does_not_stop_the_next (lý do chỉ vào log);
::test_a_canvas_over_what_a_chat_takes_is_not_uploaded (đo bằng byte, bằng đúng mức trần thì
vẫn gửi); ::test_a_canvas_that_is_not_there_is_said_so;
::test_a_canvas_deleted_while_it_is_read_reads_as_one_that_is_not_there (bị xoá giữa lúc tìm
thấy và lúc đọc nội dung: cùng câu với canvas không có, không có lời tải lên nào);
::test_a_notice_about_a_canvas_that_cannot_be_sent_stops_nothing;
::test_the_log_names_the_canvas_by_id_version_and_size_alone (log không mang tiêu đề hay nội
dung)tests/test_telegram_canvas_scope.py):
::test_a_canvas_out_of_reach_reads_as_one_that_is_not_there (cùng một câu với canvas không tồn
tại, để câu trả lời không dò được mã nào có thật);
::test_an_agent_sends_what_it_made_whatever_became_of_the_conversation;
::test_a_linked_canvas_is_sent_for_its_conversation_alone;
::test_what_the_root_of_a_delegation_shares_reaches_its_child;
::test_the_master_sends_a_canvas_of_any_agent_and_any_conversation;
::test_a_canvas_imported_from_a_file_no_reply_could_send_is_not_sent (canvas nhập từ tệp
workspace có đuôi không thuộc danh sách FILE: cũng không phải một loại canvas, ví dụ .env,
.pem, tệp không đuôi);
::test_a_canvas_imported_from_a_file_of_a_known_kind_or_a_link_is_sent;
::test_a_picture_is_sent_whatever_its_file_was_called;
::test_reach_is_checked_before_where_a_canvas_came_from;
::test_a_turn_in_the_chat_sends_what_its_conversation_holds_however_it_began (lượt mở bằng
một tin nhắn, bằng /approve cho lượt đang dừng, hay bằng tin đã chờ trong hàng: câu trả lời
nào cũng được gửi theo hội thoại của lượt, nên canvas gắn với hội thoại đó tới được chat)FILE: artifact:<id> trong câu trả lời của agent con đi tiếp tới agent cha theo tầm với
của agent con, không qua đường chép tệp workspace
tests/test_delegate_canvas_lines.py):
::test_a_canvas_line_that_is_carried_rides_on_as_it_is;
::test_a_canvas_line_that_is_not_carried_becomes_the_sentence_of_a_lost_file (dòng ghi sai mã
được hỏi bằng ""); ::test_a_line_that_opens_as_a_canvas_is_never_looked_up_as_a_file (kể cả
khi workspace của agent con có tệp đúng tên đó);
::test_workspace_files_beside_a_canvas_line_are_copied_as_before;
::test_canvas_lines_are_asked_about_when_parent_and_child_share_a_workspace (chung workspace
chỉ miễn việc chép tệp, không nói gì về canvas agent con với tới);
::test_an_answer_with_nothing_to_carry_comes_back_as_it_was (không có gì phải đổi thì trả lại
đúng từng ký tự, kể cả dấu ngắt dòng)canvas_carrier):
::test_a_canvas_within_the_childs_reach_is_carried_and_linked_to_the_parent (canvas của chính
nó, canvas gắn với hội thoại của nó, canvas gốc chia sẻ; liên kết tạo ra không chia sẻ cho các
agent con khác);
::test_a_canvas_outside_the_childs_reach_is_not_carried_and_links_nothing (kể cả canvas của
chính agent cha, canvas agent cha giữ riêng, mã không có canvas, mã rỗng);
::test_the_master_as_a_child_carries_every_canvas_that_is_there (master với tới mọi mã, nhưng
mã không có canvas vẫn không được mang);
::test_with_no_conversation_to_link_a_reached_canvas_is_still_carrieddelegate):
::test_a_delegated_canvas_reaches_the_parent_with_the_reach_to_send_it (trước khi giao việc
agent cha không với tới canvas, sau đó thì có);
::test_a_delegated_line_for_a_canvas_the_child_cannot_reach_is_said_in_words::test_a_canvas_line_the_parents_reply_left_out_is_put_back
(dòng đã thành câu báo thì không đặt lại)reply_lines), nên một dòng đính kèm đứng sau dấu ngắt dòng Unicode không lọt qua bên nào
tests/test_delegate_canvas_lines.py):
::test_a_reply_is_cut_into_lines_at_every_line_break_a_string_knows;
::test_a_line_after_any_line_break_is_an_attachment_to_the_channel_and_the_relay (U+2028
trước dòng MEDIA: và dòng FILE: artifact:: tệp được chép, canvas được hỏi);
::test_the_childs_last_words_are_read_line_by_line_the_same_way;
::test_dropped_lines_are_looked_for_line_by_line_the_same_wayweb_url. Danh sách đọc từ kết quả tool của
chính lượt đó, không đọc từ lời của model, và chỉ nêu canvas agent còn với tới
tests/test_telegram_canvas_notice.py, đọc thẻ):
::test_a_tag_reads_back_as_what_it_was_built_from;
::test_a_line_that_does_not_open_with_a_tag_reads_as_none;
::test_the_web_reads_a_tag_by_the_pattern_the_server_writes_it_to (mẫu TAG_RE của server
trùng mẫu ở web/src/lib/artifact-tag.ts, đọc từ tệp TS)tags_of, merged, WrittenCanvases):
::test_each_tool_that_writes_a_canvas_names_the_version_it_left (bốn tool ghi, chạy thật);
::test_a_tool_that_writes_no_canvas_names_none_whatever_its_result_says;
::test_a_write_that_was_refused_names_nothing;
::test_a_write_that_changed_nothing_names_nothing;
::test_a_delegated_task_names_what_its_child_wrote_even_when_the_wait_ran_out;
::test_a_script_names_what_its_own_calls_wrote_and_nothing_it_printed (canvas do lời gọi
của một script ghi được nêu; lời gọi đọc, lời gọi không đổi gì, lời gọi hỏng và thẻ script
tự in ra thì không);
::test_a_turn_names_what_a_script_wrote_the_same_live_and_read_back (xem lúc lượt đang
chạy hay đọc lại từ phần lưu kèm tin tool đều nêu cùng canvas, cạnh canvas model tự ghi);
::test_a_stored_result_that_lists_no_calls_names_what_its_own_text_says_and_no_more (phần
lưu kèm rỗng, không phải JSON hay sai hình không làm hỏng việc đọc lại);
::test_a_live_turn_keeps_what_a_child_wrote_before_its_wait_ran_out (kết quả giao việc hết
giờ là một kết quả hỏng: xem lúc lượt đang chạy hay đọc lại từ run đều nêu cùng canvas);
::test_a_canvas_written_twice_is_named_once_at_its_newest_version_where_it_first_stood;
::test_a_live_turn_is_watched_without_changing_what_passes;
::test_a_turn_that_breaks_is_still_broken_and_keeps_what_it_wrote_by_thenwritten_by, đọc từ tin tool run đã lưu):
::test_a_run_with_no_lapsed_approval_names_everything_it_wrote (thẻ trong lời của model
không tính);
::test_a_run_whose_approval_lapsed_names_only_what_it_wrote_after_the_last_one (phần trước
mốc đã được nêu lúc run dừng lại; yêu cầu bị từ chối không phải mốc);
::test_a_person_who_quotes_the_lapsed_sentence_marks_nothing;
::test_what_another_run_of_the_conversation_wrote_is_not_this_runs;
::test_no_run_and_a_run_that_does_not_know_where_it_began_name_nothingnotice_text, lời của danh sách):
::test_the_notice_names_each_canvas_with_the_version_written_and_its_page;
::test_without_an_address_the_notice_says_where_to_look;
::test_the_notice_names_only_what_the_agent_still_reaches (canvas đã xoá và canvas ngoài
tầm bị bỏ; không còn canvas nào thì không có danh sách);
::test_a_long_list_is_cut_at_ten_and_the_rest_counted;
::test_a_canvas_renamed_since_is_named_by_its_new_title;
::test_a_secret_in_a_title_is_coveredtests/test_delegate_outcome.py, khối canvas trong kết quả giao việc):
::test_the_canvases_a_result_names_are_read_back_as_they_were_written;
::test_a_result_that_names_no_canvas_reads_as_none;
::test_a_tag_in_the_childs_own_words_is_not_a_canvas_it_wrote;
::test_anything_but_the_block_as_the_tool_writes_it_names_no_canvas (cùng luật với thẻ
giao việc trên web: dòng outcome, dòng trống bên dưới, mọi dòng ở giữa mở bằng thẻ; chữ
outcome= agent con tự viết bên dưới dòng trống không phải dòng outcome)tests/test_telegram_canvas_notice_sent.py, run gửi sau qua deliver):
::test_a_brief_is_followed_by_its_files_then_its_canvases_then_why_it_stopped;
::test_a_run_that_only_wrote_canvases_is_answered_by_their_list (không còn canvas nào để
nêu thì vẫn là câu “không có nội dung” như trước);
::test_a_run_that_stopped_without_a_word_names_what_it_wrote_before_saying_so;
::test_a_run_that_wrote_no_canvas_gets_no_list;
::test_what_no_run_is_known_to_have_written_is_not_named;
::test_a_crew_members_list_goes_out_under_its_name;
::test_a_brief_sends_and_names_what_its_own_conversation_holds;
::test_a_wordless_brief_names_what_its_own_conversation_holds;
::test_a_run_delivered_twice_names_each_canvas_once (chờ duyệt rồi hết hạn: hai lần gửi,
mỗi canvas nêu một lần);
::test_the_list_is_of_what_the_run_had_written_when_delivery_began (thứ run ghi thêm, hay
run sau ghi, trong lúc đang gửi thuộc về lần gửi sau)reply_to):
::test_a_turn_names_its_canvases_after_its_reply_and_the_files_it_attached;
::test_a_turn_that_only_wrote_canvases_is_answered_by_their_list;
::test_a_wordless_turn_that_wrote_nothing_is_answered_as_it_always_was;
::test_a_turn_that_breaks_still_names_what_it_wrote;
::test_a_turn_sends_and_names_what_its_own_conversation_holds;
::test_each_leg_of_a_turn_that_waited_for_approval_names_its_own (vòng lặp thật, /approve)::test_a_job_names_what_it_wrote_unless_it_has_nothing_to_report (job trả lời OK vẫn im
lặng dù có ghi canvas);
::test_a_list_that_cannot_be_sent_costs_the_turn_one_line (một dòng báo, lượt không bị coi
là hỏng);
::test_a_wordless_run_whose_list_cannot_be_sent_is_not_called_empty;
::test_with_nothing_to_name_nothing_is_sent_and_with_nothing_written_nothing_is_read;
::test_a_title_that_reads_like_an_attachment_line_attaches_nothing (tiêu đề là FILE: …
không làm Telegram nhận một lời tải lên nào)ask_user, và được nhận ra bằng chính yêu cầu đã hết hạn của lời gọi (tool_call_id), không bằng
chữ của kết quả tool. Run dừng để hỏi, được gửi lúc chờ rồi tự đi tiếp, nêu mỗi canvas đúng một
lần
tests/test_telegram_canvas_notice.py):
::test_a_question_nobody_answered_marks_the_run_like_an_approval_that_lapsed (câu hỏi có và
không có mặc định);
::test_only_a_request_nobody_answered_is_a_mark_whatever_a_result_says (kết quả tool chứa
hay trùng hẳn câu báo hết hạn, câu hỏi được trả lời, tool bị từ chối, mã lời gọi lặp lại dưới
tool khác: không cái nào là mốc)tests/test_telegram_canvas_notice_sent.py):
::test_a_run_whose_question_lapsed_between_two_deliveries_names_each_canvas_once (lần gửi
thứ hai là lời của run rồi danh sách chỉ có canvas thứ hai; không có dòng báo hết hạn duyệt);
::test_a_job_that_asked_and_went_on_unanswered_names_each_canvas_once (job thật và lượt quét
hết hạn thật)tests/lapse_helpers.py: lời gọi của model, yêu cầu nó mở, và tin
tool đóng yêu cầu bằng chính lời của vòng lặp (close_interrupted).tests/test_telegram_canvas_notice.py):
::test_a_request_is_closed_once_whatever_comes_under_its_call_id_afterwards (tool chạy được,
bị từ chối lần nữa, bị báo trùng mã, câu hỏi được đáp lại như lần trước; có và không có lời gọi
của model đứng trước kết quả; id của tin nhắn khác thứ tự của nó trong hội thoại);
::test_a_result_under_another_tools_name_closes_no_request (kết quả đứng đầu dưới mã ấy mà
mang tên tool khác thì không đóng yêu cầu);
::test_a_request_is_closed_by_its_own_call_wherever_that_stands_in_the_message (một tin của
model gọi ba tool, lời gọi phải chờ đứng đầu, giữa hay cuối: kết quả của chính nó là mốc,
canvas ghi trước nó đã được nêu lúc dừng, canvas ghi sau nó còn phải nêu)tests/test_telegram_canvas_notice_sent.py):
::test_a_job_whose_model_asks_again_under_the_same_id_names_what_it_wrote_between (job thật,
lượt quét hết hạn thật, vòng lặp thật đáp cùng một mã hai lần);
::test_a_later_run_that_sends_a_lapsed_calls_id_again_names_all_it_wrote (hết hạn ở run
trước, run sau dùng lại mã: qua ba lần gửi mỗi canvas được nêu đúng một lần)tests/test_telegram_canvas_notice_sent.py):
::test_a_closing_line_that_cannot_be_sent_is_a_failure_the_chat_hears_of (lượt không lời có
ghi canvas, canvas bị xoá trước khi lượt xong, Telegram từ chối câu còn lại: chat nhận
TELEGRAM_TURN_FAILED, log ghi lỗi kèm nguyên nhân)FILE:/MEDIA:: dòng trông như vậy nằm lại trong câu và không tệp, ảnh hay canvas nào
được gửi theo
tests/test_telegram_runtime_sentences.py):
::test_why_a_failed_run_stopped_is_said_whole_and_attaches_nothing (run hỏng, lý do là lỗi
của provider có ba dòng đính kèm trỏ tới thứ có thật; có lời và không lời);
::test_the_question_a_job_waits_on_is_said_whole_and_attaches_nothing (job thật được giao
lúc đang chờ câu hỏi của chính nó);
::test_no_sentence_of_a_delivery_is_read_for_attachment_lines_whatever_it_says (từng câu
trong bốn câu của deliver, đổi hẳn lời thành một dòng FILE:);
::test_a_reason_longer_than_one_message_arrives_whole (lý do dài hơn một tin vẫn tới đủ,
cắt ở chỗ xuống dòng);
::test_a_live_turn_that_broke_says_the_error_whole_and_attaches_nothing (vòng lặp thật,
provider lỗi);
::test_a_turn_that_spoke_before_it_broke_attaches_what_it_named_and_no_more (lời của agent
vẫn được đọc, câu lỗi bên dưới thì không, cả hai vẫn chung một tin);
::test_a_reply_keeps_its_error_apart_and_reads_whole_as_it_always_did (collect_reply giữ
câu lỗi riêng ở error, said là phần còn lại, to_dict cho relay API không đổi);
::test_only_the_error_a_reply_ends_with_is_kept_out_of_what_was_said (agent tự trích đúng
câu lỗi đó trong lời của mình thì said vẫn giữ đủ lời: chỉ câu lỗi ở cuối bị tách ra)/status, /tools, /new và lệnh lạ trên Telegram cũng là câu của runtime:
gửi nguyên văn, không đọc để tìm dòng FILE:/MEDIA:, nên tiêu đề hội thoại trông như dòng
đính kèm vẫn nằm trong /status và không tệp nào được gửi theo. /approve và /deny như cũ:
tự nó không nói gì, còn câu trả lời của lượt chạy tiếp là lời của agent nên vẫn được đọc
tests/test_telegram_runtime_sentences.py):
::test_a_status_is_said_whole_whatever_the_conversation_is_titled (tiêu đề lần lượt là dòng
trỏ tới một canvas, một ảnh, một tài liệu đều có thật);
::test_no_answer_to_a_command_is_read_for_attachment_lines_whatever_it_says (/tools,
/new lúc rảnh và lúc còn tin xếp hàng, lệnh lạ, /approve và /deny khi không có gì chờ
duyệt: đổi hẳn lời thành một dòng FILE:);
::test_a_command_is_answered_under_no_name_in_a_chat_a_crew_shares (chat cả đội dùng chung:
câu trả lời không mang tên thành viên nào ở đầu);
::test_a_decision_says_nothing_itself_and_the_reply_it_resumes_is_the_agents (/approve và
/deny khi một tool đang chờ: chat chỉ nhận lời của lượt, tệp lời ấy nêu vẫn được gửi)delegate trên một dòng và cắt như mọi
trường trích khác, nên dòng FILE:/MEDIA: trong lỗi của provider hay trong câu hỏi của con
không thành dòng đính kèm của cha
tests/test_delegate_unfinished.py):
::test_the_reason_a_run_stopped_for_is_quoted_on_one_line_and_cut_like_the_calls_under_it
(lý do nhiều dòng về một dòng; lý do dài bị cắt ở FIELD_CHARS; không có lý do thì dùng
trạng thái);
::test_a_child_whose_error_names_files_hands_the_parent_no_attachment_to_send (con thật có
provider lỗi, dropped_attachments của cha trả về rỗng)Ứng dụng cao đúng một cửa sổ, mỗi cột tự cuộn. Lỗi thật thấy trên live ngày 2026-10-05 (v0.11.0, canvas
dài mở cạnh chat): cuộn hết một cột thì cả ứng dụng bị kéo lên, bên dưới là một dải trắng. Nguyên nhân: nhãn
.sr-only trong tin nhắn lấy khung ngoài thread làm mốc, nên nằm lại ở chỗ của thread chưa cuộn, cách cửa
sổ hàng nghìn pixel, và làm chính trang cuộn được. Sửa: .thread có position: relative.
web/e2e/page-scroll.spec.ts (ở 1440×900 và 390×844, hội thoại 30 tin, 40 hội thoại ở
thanh bên, một canvas 60 mục: “has nothing to scroll under a long thread and a long list of
conversations”: trang không có gì để cuộn; “stays put when a wheel runs off the end of a long
canvas”: lăn chuột quá cuối canvas rồi lăn ngược, trang vẫn ở 0; “has nothing to scroll on the
manage screen”). Bỏ dòng position: relative thì bốn trong sáu test đỏ, trang cuộn được hơn 8.000 px.Rà các cột cuộn còn lại cùng ngày tìm ra hai chỗ nữa mắc đúng lỗi này, đều đã sửa bằng position: relative
trên chính cột cuộn: .conversation-list (chấm chưa đọc mang một nhãn .sr-only) và .manage-body (huy
hiệu, dòng trạng thái và .canvas-library-said:empty). Các cột khác đã có mốc sẵn: mỗi .step của dòng thời
gian và mỗi .md-code tự là mốc cho nhãn của mình.
web/e2e/page-scroll.spec.ts (“has nothing to scroll under a long list of unread
conversations”: 60 hội thoại, 59 chấm chưa đọc; “has nothing to scroll under an agent’s editor”: trang
sửa agent với lời nhắc đã mở). Bỏ từng dòng position: relative thì test tương ứng đỏ: danh sách dư
2.496 px ở 1440 px, trang sửa agent dư 2.106 px ở 1440 px và 2.686 px ở 390 px.Lỗi thật thấy trên Telegram ngày 2026-10-05: được nhờ “viết vào canvas rồi gửi tệp”, agent gọi
artifact_export rồi viết FILE: <tệp workspace> ở cả bốn lượt, không lần nào dùng FILE: artifact:<id>.
Kết quả tool nó vừa đọc nói “người thấy nó ngay cạnh khung chat”. Sửa: lượt Telegram và job nhận câu khác và
một dòng nêu đúng FILE: artifact:<id>.
tests/test_canvas_away_result.py (lượt Telegram và job: kết quả tạo có dòng gửi với đúng mã,
không còn chữ “cạnh khung”; lượt web chat giữ nguyên câu cũ; artifact_import thêm cùng dòng ở lượt xa
và không thêm ở web chat). Đảo điều kiện trong send_lines thì cả sáu test đỏ.FILE: trỏ vào ảnh trên TelegramThấy trên Telegram thật ngày 2026-10-05: agent viết FILE: hinhtron.png và chat chỉ nhận “(không gửi được
tệp: hinhtron.png)”. Từ nay dòng FILE: trỏ vào png, jpg, jpeg hay webp trong workspace đi bằng sendPhoto.
tests/test_reply_file_line_image.py (bốn đuôi ảnh, kể cả chữ hoa, đi bằng sendPhoto và không
thành tài liệu; ảnh và tài liệu trong cùng câu trả lời đi hai đường; ảnh thiếu và ảnh ngoài workspace
nhận câu báo của ảnh; .svg vẫn bị từ chối; danh sách tài liệu không thêm đuôi ảnh). Tắt nhánh ảnh
trong TelegramFiles.document thì bảy test đỏ.Trước đây lượt web sống nhờ chính kết nối SSE của tab gửi: đóng tab hay đổi cuộc trò chuyện là lượt dừng giữa
chừng, và tab mở sau không thấy gì cho tới khi lượt xong. Từ nay server tự đọc lượt tới hết
(turn_host.py); luồng SSE chỉ là một góc nhìn. Mọi lượt đi qua tracked() đều phát sự kiện vào
hub.turns (activity/turn_watch.py), nên lượt của Telegram và job cũng xem được qua
GET /api/conversations/{id}/turn. Chữ đang viết chỉ nằm trong bộ nhớ: restart là mất, và nhật ký cuộc trò
chuyện lại là toàn bộ sự thật.
tests/test_turn_watch.py (người xem có mặt từ đầu đọc đủ mọi sự kiện rồi dừng; cuộc không có
lượt thì không có gì để xem; người vào trễ bắt đầu bằng một khung Resync mang chữ đang viết gộp làm
một, cờ đang nghĩ và bản canvas đang nháp gộp theo vị trí; lượt thử bị bỏ thì bản nháp của nó cũng bỏ;
câu trả lời đã lưu không còn là “đang viết”; Resync thay cho những sự kiện cuối nói rõ lượt đã xong;
người xem quá WATCH_BACKLOG sự kiện nhận một Resync thay vì bị cắt, không lặp và không sót; trong
ngưỡng thì không mất gì; người xem bỏ đi thì không còn bị giữ sự kiện; chỉ lượt đang chạy mới bị chính
lượt đó kết thúc; lượt xong hay dừng chờ người là buông người xem ngay dù bên đọc còn nán lại; lượt vỡ
vì lỗi server báo TURN_BROKE cho người xem; bên đọc bỏ giữa chừng thì người xem được buông). Bỏ kiểm
tra danh tính trong TurnWatch.end, bỏ nhánh kết thúc ở tracked, hay chỉ coi AssistantMessageEvent
là mốc “hết đang viết” thì mỗi lần một test đỏ.tests/test_hosted_turns_api.py (GET …/turn trả 204 khi không có lượt và 404 cho cuộc không
có; người gửi đọc lượt của mình như cũ, không có watching; tab mở trễ nhận watching kèm chi tiết
cuộc trò chuyện đúng lúc đó (tool call chưa có kết quả), rồi chữ đang viết thành một text_delta, rồi
theo tới done; cái được trao cộng cái theo sau là đúng một lần mỗi tin; người gửi bỏ đi thì lượt vẫn
chạy tới done; stop kết thúc lượt cho mọi người đang xem; tin xếp hàng không giành lượt của người
đang xem; quyết định và câu trả lời cũng do server đọc tới hết; lượt do bên khác đọc xem được nhưng
stop của web không huỷ; lượt vỡ trong server báo TURN_BROKE lên tab và ghi nguyên nhân vào log;
server tắt thì dừng các lượt nó đang đọc). Cho post_message đọc thẳng inbound.stream, cho tin xếp
hàng cũng đi qua host, hay bỏ dòng host.cancel trong stop thì mỗi lần một test đỏ.tests/test_busy_queue_api.py::test_stop_ends_a_turn_the_web_started (thay cho test cũ “stop
để nguyên lượt web đang đọc”: đây là đổi hành vi có chủ ý, vì lượt web giờ là của server).tests/test_hosted_turns_api.py (xoá cuộc đang có
lượt do server đọc: luồng của người đang xem đóng lại, tool đang chạy bị cắt, model không được hỏi
thêm, run đóng là bị ngắt, cuộc thôi bận và không dòng lỗi nào vào log; lời gọi model đang dở bị cắt
chứ không chạy tới hết; lần xoá hỏng thì lượt vẫn chạy tới done) và
tests/test_busy_queue_api.py::test_deleting_a_conversation_ends_the_turn_the_queue_runs (lượt nền
của hàng đợi cũng dừng). Ba sửa đổi ở server (để nguyên lượt của web, để nguyên lượt của hàng, dừng
lượt trước khi biết lần xoá có thành không), mỗi cái đều làm ít nhất một test đỏ.api/client.test.ts (watchTurn trả false khi server đáp 204 và đọc luồng tới hết khi có
lượt); state/thread-reducer.test.ts (khung watching dựng lại luồng từ chi tiết server trao, đặt
busy theo running, và giữ thông báo cùng lựa chọn “đừng tự mở canvas” khi tab đang bận sẵn);
hooks/use-thread.test.ts nhóm “a turn this tab reads along with” (vào giữa lượt và theo tới hết;
204 thì không hề hiện bận; không hỏi được server thì im lặng; luồng xem hết mà không có sự kiện cuối
thì các tool call thôi quay; rớt kết nối giữa lượt không báo lỗi; đang đọc lượt của mình thì không
hỏi; lượt tab này gửi giành chỗ của lần xem chưa kịp vào; đổi cuộc là buông chứ không dừng; lần tải cũ
hơn lúc trao bị bỏ và tải lại sau lượt; Dừng: cancelled: true thì cắt và báo đã dừng, false thì
vẫn xem dưới dòng “đang chạy ở nơi khác”, server không đáp thì cắt và trả run về cho việc xem) và nhóm
“a run this tab’s stream no longer reads”; hooks/use-turn-watch.test.ts (hỏi một lần cho mỗi run,
quên chữ “không có gì” khi run hết, đổi cuộc hay luồng hoạt động nối lại);
hooks/use-external-run-refresh.test.ts (run đang xem vẫn là run “ở nơi khác” để nút Dừng và chỉ báo
đang nghĩ dựa vào); app-turn-watch.test.tsx (cả App: lượt Telegram hiện đúng lúc đang viết, không có
nút Dừng, không vẽ gì hai lần và chỉ hỏi …/turn một lần; sang cuộc khác thì lượt vẫn chạy, quay lại
thấy phần viết thêm; Dừng từ tab chỉ xem dừng được lượt server giữ; server không còn gì để đọc thì
vẫn báo đang chạy và không hỏi lại; lượt của chính tab không bị xem thêm lần nữa; mất luồng của mình
giữa lượt thì chuyển sang xem, không để lại thông báo lỗi).turn-watch.spec.ts với served-turn.ts (lượt và luồng hoạt động dựng ngay trong trang,
vì Playwright trả cả thân route một lần): lượt bắt đầu ở nơi khác hiện đúng lúc đang viết và theo tới
hết ở 1440/1000/390 mà không tràn ngang; tải lại giữa lượt thì vào lại đúng chỗ, không gửi stop; rời
cuộc thì số người xem về 0, quay lại thấy phần viết thêm; Dừng từ tab chỉ xem. Bỏ useTurnWatch khỏi
app.tsx thì cả sáu test đỏ.state/thread-reducer.test.ts
(“does not carry the word that a turn was stopped into a turn found going”, “keeps the word that the turn was
stopped through the loads that follow it, until one brings a request”, “drops that word with the rest when
another conversation is opened, or a message is sent”); app-turn-watch.test.tsx (“is still said to have been
stopped once its run has ended and the conversation has been read again”). Playwright: turn-watch.spec.ts
(“Stop ends a turn the server reads itself, from a tab that only watches it”) kiểm dòng “Đã dừng lượt này.”
thêm một lần sau khi run hết và cuộc trò chuyện được đọc lại, là lúc trước đây dòng ấy bị xoá.hooks/use-thread.test.ts
(“counts the Stops the server answered by ending the turn, and no other”); hooks/use-turn-watch.test.ts
(“does not ask again about a run the server said it ended on this tab’s Stop”, “asks about a run it ended once
the activity stream comes back: the next server may carry it on”); app-turn-watch.test.tsx (“is not joined
again while the server lets go of a turn it ended on this tab’s Stop”).state/thread-reducer-turn-notice.test.ts (dòng báo ở lại khi luồng server lưu kết
thúc đúng chỗ luồng trên màn hình kết thúc, kể cả khi lần tải ấy là lần đầu về tới và mang theo lịch sử, và
qua mọi lần tải như thế; mất khi lần tải mang về bất cứ gì đứng sau; tin của chính người dùng được nhận ra
bằng lời, mọi thứ khác bằng id, nên ghi chú tiến độ lưu như một tool call vẫn khớp; lỗi của một yêu cầu do
chính tab gửi không mang ofTurn và bị lần tải kế tiếp xoá; tab đang rảnh mà thấy một lượt khác đang chạy thì
bỏ mọi dòng báo trừ dòng “đã quyết ở nơi khác”; hai dòng báo của cú bấm giữ quy tắc riêng) và
app-turn-notice-after-load.test.tsx (cả App: tin gửi sớm vẫn giữ dòng báo lỗi và dòng leo thang sau lần tải
nợ, và buông khi lần tải ấy mang về tin đến sau; tab chỉ xem giữ cả hai sau khi đọc lại lúc run hết, và buông
khi thấy một lượt khác đang chạy). Kiểm chứng đột biến: hai mươi sửa đổi ở thread-reducer.ts (không đánh dấu
lỗi của lượt; đánh dấu cả lỗi của yêu cầu; giữ bất kể lần tải mang gì; bỏ từng loại dòng báo; so bằng id cho
mọi thứ hay bằng lời cho mọi thứ; so phần tử đầu thay vì phần tử cuối; tab thấy lượt khác chỉ bỏ dòng “đã
dừng”, hoặc bỏ luôn dòng “đã quyết”), mỗi cái đều làm ít nhất một test đỏ.Tab gửi tin, server đã nhận và chạy lượt, nhưng câu trả lời rớt trên đường về: chữ quay lại ô nhập như mọi
lần gửi không nghe thấy gì, và người dùng bấm gửi lần nữa. Trước đây đó là hai tin và hai lượt. Từ nay mỗi
lần gửi từ web mang một request_id (32 ký tự hex, web/src/lib/request-id.ts); server lưu tên đó trong
cùng một commit với tin mà nó đặt tên (bảng message_requests, hoặc cột queued_messages.request_id khi
tin còn xếp hàng), nên không thể nhớ tên mà mất tin hay giữ tin mà quên tên. Gửi lại cùng tên thì không lưu
gì và không chạy gì: tin còn xếp hàng được đáp lại đúng sự kiện queued cũ (cùng item_id, vị trí hiện
tại); tin đã vào nhật ký được đáp bằng khung watching rồi lượt đang chạy, hoặc running: false nếu lượt
đã xong. Tên theo từng cuộc trò chuyện và còn nguyên sau khi restart. Lần gửi bị từ chối (409, 422, 429) hay
tin bị Dừng rút khỏi hàng thì tên chưa dùng, gửi lại là tin mới. POST /api/inbound không nhận
request_id.
tests/test_message_request_id.py (gửi lại khi lượt đang chạy thì vào xem đúng lượt đó; gửi lại
sau khi lượt xong thì không chạy gì; gửi lại khi còn xếp hàng thì giữ đúng một chỗ; tin bị Dừng rút
gửi lại được dưới tên cũ; lượt bị dừng trước bước đầu tiên để tên chưa dùng và không để lại lượt treo;
bị từ chối vì đang chờ quyết định hay vì hàng đầy thì tên chưa dùng; lần gửi không tên luôn là tin mới;
tên thuộc riêng từng cuộc; tên không phải token trơn hay dài quá 64 ký tự bị 422; hai lần gửi sát nhau
trước khi lượt kịp bước là một lượt; server khởi động lại vẫn biết tên; gửi lại khi tool call của chính
nó đang chờ quyết định thì thấy lời chờ chứ không bị 409; gửi lại không kiểm và không đổi canvas đang
mở; tên được lưu cùng tin hoặc không lưu gì; xoá cuộc trò chuyện là quên các tên của nó).tests/test_queue_store.py (tìm tin đang chờ theo tên kèm vị trí; lúc giao, mọi tên của các
tin được gộp đều được nhớ; giao hỏng thì không nhớ tên nào; tin bị rút để tên chưa dùng; tin có tên
vẫn chờ sau restart; database cũ giữ nguyên tin đang chờ và nhận được tin có tên).lib/request-id.test.ts (32 ký tự hex, khớp mẫu server nhận, mỗi lần một tên, không cần
randomUUID); api/client.test.ts (thân POST mang request_id khi có, bên cạnh canvas, và không mang
gì khi không có); state/thread-reducer.test.ts (queued cho tin đã có chip không thêm chip thứ hai,
vẫn bỏ bong bóng tạm); hooks/use-thread-send.test.ts nhóm “the name a message is sent under” (mỗi lần
gửi một tên; cùng chữ gửi lại sau lần không nghe thấy gì thì giữ tên; sửa chữ hay sang cuộc khác là tên
mới; lỗi sau khi server đã đáp thì không giữ tên, ở cả đường gửi thường và đường xếp hàng) và nhóm “a
send made again behind this tab’s own turn” (server đáp bằng lượt đang chạy thì coi là đã gửi, buông
request đó, không thêm chip hay thông báo); app-send-again.test.tsx (cả App: mất câu trả lời rồi gửi
lại thì một bong bóng, một tin trong nhật ký, hai POST cùng request_id; lượt còn chạy thì vào xem
tới hết; tin đang xếp hàng giữ một chip; còn tin server chưa từng nhận thì gửi lại như tin mới).send-again.spec.ts (mock lưu tin và chạy lượt rồi cắt kết nối: chữ quay lại ô nhập, gửi
lại thì một câu hỏi, một câu trả lời, hai POST cùng tên; kèm đối chứng không mất gì). Cho lần gửi lại
lấy tên mới thì test này đỏ.test/canvas-app.tsx::bodies, app.test.tsx,
hooks/use-thread.test.ts, keyboard-smoke.spec.ts, canvas-writing.spec.ts, và các spec canvas
đọc thân qua e2e/sent-message.ts) nay đòi thêm một request_id đúng dạng; không test nào bị nới.Tên của lần gửi không nghe hồi âm được giữ cho tới khi trang hiện tin ấy là đã nói
(web/src/lib/send-names.ts). Người dùng chỉ biết điều trang cho thấy: chừng nào trang còn nói gửi lỗi và trả
chữ về ô nhập, cùng những chữ ấy gửi lại là cùng một tin và đi dưới cùng một tên. Khi cuộc trò chuyện đã hiện
tin đó, ngay trong lần vẽ ấy trang rút những chữ nó đã trả về ra khỏi ô nhập (trừ khi người dùng đã sửa chúng)
và buông tên; cùng những chữ ấy gõ lại sau đó là tin mới với lượt riêng, như khi đọc xong câu trả lời rồi gõ
“tiếp tục” lần nữa.
Bản đầu của quy tắc này suy từ các run: buông tên khi tab thấy lượt mà tin có thể đã nhận kết thúc. Nó sai ở đúng trường hợp chính. Tab nghe tin run của mình xong và tin lần gửi hỏng gần như cùng lúc, buông tên trong khi trang vẫn nói gửi lỗi và vẫn giữ chữ trong ô, nên bấm gửi lại thì tin đi dưới tên mới và server chạy lượt thứ hai. Self-test trên trình duyệt thật bắt được lỗi này (15 trong 358 phép kiểm, ở cả ba bề rộng); các test đơn vị lúc đó thì không, vì hai tin ấy chỉ cách nhau vài mili giây bên trong trang. Quy tắc mới không nhìn run nào nữa.
Server không nói tên nào nhận lượt nào, nên “đã hiện” là một phép đếm: số tin của người dùng có đúng những chữ
ấy trong thread trên màn hình, kể cả bong bóng tạm của chính tab và các chip đang xếp hàng, so với con số lúc
tên đi lần đầu. Đếm được nhiều hơn tức là tin đã có mặt. Lúc tên đi mà thread đã lưu của cuộc trò chuyện chưa
lên màn hình (lần tải mở đầu chưa về) thì không có con số gốc, và tên ấy không bao giờ bị coi là đã hiện. Mỗi
cuộc trò chuyện giữ một tên, của lần gửi không hồi âm gần nhất ở đó; gửi chữ khác vào cuộc ấy thì buông, gửi
sang cuộc khác thì không. Lần gửi đang trên đường giữ ô nhập của nó (holdDraft trong
web/src/hooks/use-draft.ts, gọi từ use-send-lock.ts): trong lúc đó trang không rút chữ và không buông tên,
để lần gửi ấy lấy đúng tên cũ. Không có hạn theo thời gian. Giữ tên quá lâu tốn một lần gửi bị nuốt, buông quá
sớm thì agent làm việc hai lần, nên chỗ không chắc đều nghiêng về giữ.
lib/send-names.test.ts (phép đếm: tin của người dùng đúng từng chữ, bong bóng tạm, chip xếp hàng
dù thuộc loại nào; không đếm câu trả lời, ghi chú, hay tin chỉ chứa những chữ ấy. Tên: mỗi lần gửi một
tên, giữ cho cùng chữ ở cùng cuộc, lần gửi lấy tên thì tiêu tên, chữ khác thì buông, mỗi cuộc một tên
riêng. “Đã hiện”: chỉ khi đếm được nhiều hơn lúc tên đi, không bao giờ khi lúc ấy không đếm được, hỏi
không làm buông, đang gửi lại thì không tính, forget chỉ buông đúng cuộc ấy);
hooks/use-thread-send-name.test.ts (qua hook thật: tải lại mà chưa thấy tin thì giữ tên; thấy tin, hoặc
thấy tin đang xếp hàng, thì tên mới; chữ đã có từ trước thì phải hiện thêm một lần; lần gửi trước khi cuộc
trò chuyện được đọc thì giữ mãi; cuộc khác hiện cùng chữ không làm buông, kể cả khi thread của cuộc vừa
rời đổi trong đúng lần vẽ mở lại cuộc này; đọc theo một lượt cũng theo đúng phép đếm ấy; chữ trả về rời
bản nháp khi tin hiện, ở lại khi chưa hiện, để nguyên khi người dùng đã sửa và từ đó là tin mới, ở lại
cùng với tên khi một lần gửi đang giữ ô; con số đi kèm tên là số lần trang hiện những chữ ấy, và 0 khác
với không biết); hooks/use-draft.test.ts nhóm withdrawDraft (xoá bản nháp và ô đang mở, bỏ qua khoảng
trắng bao quanh, để yên ô đã sửa mà vẫn đáp là đã rút, xoá bản nháp của cuộc không ở trên màn hình, chạy
được khi trình duyệt từ chối lưu, ô vừa lên màn hình trong chính lần vẽ chữ bị rút cũng trống, không có
bản nháp nào vẫn đáp là đã rút) và nhóm holdDraft (ô đang bị giữ thì trang không rút; giữ tới khi mọi
lần gửi buông; buông hai lần chỉ tính một; mỗi cuộc giữ riêng); components/composer-send-lock.test.tsx
nhóm “the box of a send on its way, when the page would take its words back” (giữ tới khi lần gửi xong,
buông khi lần gửi hỏng hay được nhận, lần gửi không hứa hẹn gì thì không giữ, và ô bị giữ là ô nơi chữ
được gõ dù người dùng đã sang cuộc khác); state/thread-reducer.test.ts nhóm “the conversation whose
stored thread is on screen” (rỗng lúc chưa đọc gì, có sau một lần tải và sau khi vào xem một lượt, rỗng
lại khi mở cuộc khác, giữ nguyên qua mọi việc tab làm và nghe trong cuộc ấy);
app-send-again-shown.test.tsx (cả App, 11 trường hợp: trang tự đọc theo lượt mà tin đã bắt đầu thì chữ
rời ô ngay trong khung hình tin hiện và không có lần gửi thứ hai; tải lại sau run thì chữ rời ô và gõ lại
là tin mới; tab nghe run xong và gửi lỗi cùng lúc thì vẫn chỉ nói một lần, là trường hợp bản đầu sai; thấy
run xong trước khi biết gửi lỗi cũng vậy; ô đã sửa thì để yên và lần gửi sau là tin mới; tin xếp hàng thì
chữ rời ô khi trang đọc theo lượt nhận nó, hoặc khi chip của nó hiện; tên còn nguyên sau một tin gửi ở
cuộc khác; tin cũ cùng chữ đã hiện sẵn không làm buông; gửi trước khi cuộc trò chuyện được đọc thì giữ
tên, và gửi lại không đổi gì trên màn hình đang đọc lượt ấy; rời cuộc rồi quay lại thì chữ đã không còn
trong ô).send-again.spec.ts “the words handed back leave the box once the page shows the message, and
typed again they are a new message”.send-names.ts, use-draft.ts, use-send-lock.ts, use-thread.ts,
use-thread-send.ts và thread-reducer.ts (đếm sai, không tiêu tên, giữ chung một tên cho mọi cuộc, coi
“bằng” là “đã hiện”, rút chữ dù ô đang bị giữ, buông tên mà không rút chữ, xét sau khi vẽ, v.v.). Lần chạy
đầu 60 cái làm test đỏ và một cái sống sót: xét “đã hiện” theo cuộc được yêu cầu chứ không theo cuộc có
thread trên màn hình. Test “is kept, with its words, as its conversation is opened while the one left
still shows them” được thêm cho nó. Cái xét sau khi vẽ lúc đầu chỉ bị bắt nhờ thời điểm (6 trong 9 lần
chạy), nên hai trường hợp đọc theo lượt nay đọc ô nhập bằng MutationObserver ngay trong khung hình tin
hiện, và bắt nó 6 trên 6 lần. Ngoài bộ này, đổi chỗ đăng ký nghe của useDraft sang useEffect (cần hai
chỗ sửa nên làm tay) làm đỏ đúng test ô vừa lên màn hình.app-send-again.test.tsx khẳng định chữ còn trong ô sau khi tab tự vào xem lượt, điều quy tắc mới cố ý
đổi. Kịch bản ấy nay là trường hợp đầu của app-send-again-shown.test.tsx (chữ rời ô, không có lần gửi
thứ hai), và các khẳng định cũ của nó (gửi lại không đổi gì trên màn hình đang đọc lượt) được giữ ở trường
hợp “is kept for a message sent before the conversation had been read, and sent again changes nothing on a
screen that reads its turn”. app-send-again-turn-over.test.tsx và nhóm “a turn that is over for good”
của lib/turn-end.test.ts thuộc về quy tắc cũ nên được gỡ cùng với nó./steer), không bao giờ bị coi là đã hiện;
tải lại trang thì mất tên (chỉ ở trong bộ nhớ) trong khi bản nháp còn; lần gửi không bao giờ kết thúc thì
giữ ô của nó mãi; người dùng sửa chữ đúng lúc tin hiện thì ô được để yên và chữ đã sửa là tin mới. Muốn
quyết định chính xác thì server phải nói tên nào nhận lượt nào.Server tắt giữa một lượt (kickstart, crash, mất điện) từng để lại run interrupted và một câu hỏi không ai
trả lời cho tới khi người dùng nhắn tiếp. Từ nay server khởi động xong thì tự nối lại lượt đó, đúng một
lần: run cũ được mở lại (cùng id, cùng timeline, cùng số tiền đã tiêu), được đánh dấu resumed và lưu
ngay trước khi lượt bước bước đầu tiên, nên một lượt làm sập server sẽ không được nối lần thứ hai. Lượt nối
lại vẫn nằm dưới trần chi phí của cuộc trò chuyện: đã hết ngân sách thì không nối, đang nối mà chạm trần thì
dừng như mọi lượt. Tool call chưa có kết quả lúc server tắt được xử theo chính sách của tool: tool khai
replay_safe (chỉ đọc) được gọi lại; tool không khai gì coi như có đổi trạng thái, call được đóng bằng một
kết quả nói nó bị cắt và không rõ đã chạy hay chưa, để model tự kiểm rồi quyết. Call chưa kịp chạy (còn phải
hỏi người, tool không tồn tại, đối số hỏng) được lượt xử như thường lệ. Chỉ server có lịch (schedule=True)
mới tự nối; Dừng do người bấm, lượt lỗi hay bot Telegram khởi động lại khi server vẫn chạy thì đóng run như
cũ.
tests/test_run_restart_settlement.py (run đang chạy được đóng trong store và trao lại đúng một
lần; run đã nối một lần bị đóng hẳn; mỗi cuộc trò chuyện chỉ trao lại run mới nhất; run không có cuộc trò
chuyện không có gì để nối; cuộc đang chờ quyết định chỉ giữ run đang dừng; cờ resumed được lưu và có
trong to_dict; database cũ nhận thêm cột; run mở lại được ghi running + resumed trước bước đầu tiên
và giữ cuộc trò chuyện ở trạng thái bận; lần gọi model chết cùng server không để lại step; step đã xong
được giữ; lượt bắt đầu kế tiếp nối đúng run đó và chỉ lượt đó; buông run khi server còn chạy thì đóng,
khi server đang tắt thì để mở cho lần khởi động sau; người đọc bỏ đi khi server còn chạy thì run đóng,
khi server đang tắt thì run ở lại running; chờ một run đã xong ở tiến trình trước được trả lời từ
store; chờ một run vừa mở lại thì kéo tới khi nó xong; chờ một cuộc chưa từng chạy vẫn hết giờ).tests/test_tool_replay.py (tool không khai gì thì không được gọi lại; đúng mười tám tool chỉ
đọc khai replay_safe, không tool nào cần duyệt nằm trong đó, và các tool ghi/shell thì không; một lần
ghi đã được người cho phép và đang chạy lúc server tắt không chạy lại, call đóng bằng
RESTART_CUT_TOOL; call bị cắt trước khi kịp hỏi thì vẫn được hỏi; tool lạ hay đối số hỏng thì lỗi như
chính nó; replayable theo từng trường hợp quyết định của người: chưa hỏi, đang chờ, từ chối, đã cho
phép, quyết định của call khác cùng id, và ask_user; script bị cắt chỉ chạy lại khi agent của nó
không bật script_calls: gated, xem Gọi tool từ script).tests/test_turn_resume.py (lượt bị cắt giữa tool đi tiếp bằng chính run đó và call không chạy
lại; call chỉ đọc được gọi lại và giữ chỗ trên timeline; lần gọi model chết cùng server được hỏi lại;
lượt chỉ được nối một lần, bị cắt lần nữa thì đóng; cuộc đã tiêu hết ngân sách không được nối; lượt nối
lại dừng ở trần như mọi lượt; Dừng kết thúc lượt nối lại; tab đọc theo được lượt nối lại; tin gửi trong
lúc đó chờ sau nó).tests/test_turn_resume_readers.py (lượt Telegram bị server cắt được bot khởi động kế tiếp trả
lời, và lời báo lúc tắt hứa sẽ làm tiếp; bot khởi động lại khi server vẫn chạy thì không hứa gì và run
đóng; không có bot thì lượt Telegram để đóng; job bị cắt được nối và câu trả lời tới chat đúng một lần,
last_run vẫn là run đó; job đã bị gỡ thì nối mà không báo ai; lượt giao việc và lượt con cùng được
nối, cha trước con sau, gặp lại nhau qua cùng một cuộc con, tiền cộng một lần, con không được trao
delegate; con đã xong trước khi cắt thì cha đọc kết quả từ store; con không được nối nếu cha không đi
tiếp).tests/test_turn_resume_readers.py (lượt
Telegram được nối trả lời bằng cả phần nó viết trước lúc cắt; nêu canvas nó đã viết trước lúc cắt;
không nhắc lại phần đã gửi khi nó dừng để hỏi người; bị cắt đúng lúc lời gọi vừa được hỏi đang chạy
thì không nhắc lại gì) và tests/test_turn_unsent.py (phần chat chưa nhận được đọc lại từ nhật ký
thành đúng những sự kiện người đọc cũ đã cầm: tin của model với lời gọi, tuyến, giá và số token, rồi
kết quả tool; chỉ lượt bị cắt, không phải lượt trước hay lượt sau nó; run từ trước khi run biết nó
bắt đầu ở đâu thì không nợ gì; tin người viết giữa lượt không phải lời của lượt; phần viết trước một
lần dừng để hỏi người đã được gửi lúc đó, dù yêu cầu được duyệt, bị từ chối, hết hạn hay được trả
lời; hai lời gọi trong cùng một tin, cái phải hỏi đứng sau hay đứng trước; mã lời gọi model dùng
lại không phải lần dừng cũ; phần nợ được đọc trước phần lượt viết tiếp, và lượt vỡ sau phần nợ vẫn
vỡ). Hai mươi tám sửa đổi ở server, mỗi cái đều làm ít nhất một test đỏ.tests/test_turn_resume_readers.py::test_a_child_done_before_the_cut_costs_its_parent_what_it_spent_once
(con đã xong trước lúc cắt mà kết quả giao việc chưa kịp ghi: lượt cha được nối gọi lại delegate,
gặp đúng cuộc con đó, và tiền của con vào cuộc cha đúng một lần) và
tests/test_canvas_payload_trim.py::test_a_write_closed_by_a_restart_leaves_the_same_note_as_one_cut_off
(lần ghi canvas bị một lần khởi động lại đóng để lại cùng ghi chú như lần ghi bị người dừng: xem
lại trước khi ghi nữa, không phải “chưa lưu gì”). Bảy sửa đổi ở server (cộng tiền ngay lúc con
xong, không cộng lúc ghi kết quả, cộng hai lần, kết quả không mang số tiền; chỉ coi một trong hai
kiểu ghi bị cắt là bị cắt), mỗi cái đều làm ít nhất một test đỏ.tests/test_home_lock.py (một home mỗi lúc chỉ một server: home tiến trình khác đang giữ thì
không giữ được, và rảnh lại khi tiến trình ấy mất dù bị giết; tiến trình con mà bên giữ khởi chạy
không giữ home theo; hai home không chặn nhau; home tới bằng một đường dẫn khác vẫn là home đó; home
chưa có thì được tạo rồi giữ; hệ thống không khoá được vì lý do khác thì nói đúng lý do đó, không gọi
là home đang dùng; server giữ home trước khi dựng runtime và suốt lúc phục vụ; gặp home đang bị giữ
thì không đọc gì, không phục vụ gì, thoát mã 1 và nói home nào cùng MY_AGENT_HOME, dù được cho cổng
khác hay --no-schedule; một home khác đang bị giữ không cản; các lệnh con agent vẫn chạy trên
home server đang giữ; và chính lệnh trong README chạy thật, bị từ chối khi một server đang lên rồi
lên được ngay sau khi server kia bị giết). Mười chín sửa đổi ở server, mỗi cái đều làm ít nhất một
test đỏ.tests/test_turn_resume_startup.py (lượt đã hỏi người thì chờ người, không khởi chạy lại; run cũ
dưới một lượt đã xong sau nó thì để đóng; cuộc trò chuyện đã xoá thì để đóng; danh sách run bị cắt chỉ
được lấy một lần mỗi lần khởi động; app thật với lifespan để lại run running khi tắt và tự nối ở lần
khởi động sau; app không có lịch thì không nối gì).resumed lúc mở lại, trao lại cả run đã
nối, bỏ kiểm ngân sách, gọi lại mọi tool, coi mọi call là đã chạy, đóng run khi server đang tắt, bỏ
deps_for_child cho lượt con, settled_run trả về rỗng, v.v.), mỗi cái đều làm ít nhất một test đỏ.components/run-progress-header.test.tsx (run có resumed hiện dòng “Tiếp tục sau khi server
khởi động lại” cả lúc đang chạy lẫn khi đã xong; run chưa từng bị cắt không nói gì về khởi động lại).conversation-activity-smoke.spec.ts (run được nối hiện dòng đó trên dải ở 1024, trong cột
ở 1440, và ở 390 thì vừa dải, không làm trang rộng ra; run khác trong cùng danh sách không có dòng đó).tests/test_store.py được sửa theo kiểu trả về mới của bước dọn lúc khởi động (Settled(paused, cut)
thay cho danh sách run đang dừng); các khẳng định giữ nguyên.lib/turn-end.test.ts (nhóm “the last
word of a turn on a stream that reads it”: done, halted, error và approval_required là lời cuối của
lượt; câu server nói không có gì đang chạy thì không phải, vì lượt bị cắt cũng được nói y như vậy);
hooks/use-thread.test.ts (“is given up when its stream ends on the server’s word that nothing is going: a
turn cut off is spoken of the same way”, “stays as it is when the server only put the message in line: that
stream has said all it had to”, “is given up when the stream of a decision ends before the turn’s last word”,
“is given up once when a decision was refused as already taken elsewhere”); app-turn-watch.test.tsx (“is
asked about once when its stream ends before the turn’s last word, and ends there when the server has no turn
under way”, “is not asked about when its stream ended on the turn’s last word, though its run is said to be
going a while longer”). Playwright: turn-watch.spec.ts (“a turn this tab started is read on when its stream
closes before the turn’s last word”).tests/test_hosted_turns_api.py::test_a_server_told_to_stop_begins_no_stream_of_a_turn_it_still_reads
(server đã nhận lệnh dừng thì GET …/turn đáp 204 như với cuộc trò chuyện không có lượt nào để xem, không
ghép ai vào lượt, và lượt vẫn chạy tới hết. Tab hỏi câu này ngay khi luồng của nó bị đóng, tức là đúng lúc
server vừa nhận lệnh dừng; sse-starlette cắt một luồng mở lúc ấy trước byte đầu tiên, nên trước khi sửa tab
nhận 500 và log có traceback ở mỗi lần khởi động lại dưới một lượt đang mở). Bốn sửa đổi ở watch_turn (vẫn
mở luồng; ghép vào lượt rồi mới đáp; đảo điều kiện; không bao giờ mở luồng), mỗi cái đều làm ít nhất một test
đỏ.Tóm tắt cuộc trước và ghi chú hôm qua, hôm nay từng khép system prompt, nên một dòng memory_save đổi
văn bản đứng trước cả cuộc trò chuyện và provider bỏ cache của mọi thứ đã nói (đo trên bộ cài thật: 15,1%
token prompt được tính giá cache ở lần gọi ngay sau memory_save, so với 85,5% ở các lần khác). Nay chúng
được lưu cùng tin mở lượt, trong cột messages.turn_notes, và model đọc chúng trong một khối có khung
ngay trước tin đó. Mỗi tin chỉ mang phần đã đổi so với lần gần nhất cuộc trò chuyện được kể: trọn mục lần
đầu, các dòng mới của ghi chú chỉ dài thêm, bản mới của mục bị viết lại, một dòng cho mục đã trống. System
prompt chỉ còn dòng ngày là đổi, và lịch sử là một prefix chỉ dài thêm.
tests/test_turn_notes.py (một ghi chú lưu giữa hai lượt để nguyên system prompt và mọi tin đã
gửi, lượt sau chỉ nghe dòng mới; tin đầu mang đúng khối có khung gồm tóm tắt cuộc trước, hôm qua, hôm
nay; không có gì mới thì tin được đọc đúng như người viết và lưu {"sections": []}; agent không có ghi
chú và tóm tắt chỉ có khoảng trắng thì không khối nào; ghi chú bị viết lại được kể trọn với ba trường
hợp cùng độ dài, ngắn hơn, dài hơn nhưng đầu khác; ghi chú biến mất thì nói vậy, sau đó im, có lại thì
kể trọn; sang ngày mới chỉ kể ghi chú của ngày mới và dòng ngày trong system prompt đổi theo; một lượt
tự memory_save không làm đổi prefix của chính nó và tin mở lượt kế tiếp mang dòng đó; ghi chú dài
hơn trần được kể tới trần đúng một lần).tests/test_turn_notes.py còn giữ khung của khối (một ghi chú không đóng được khối đang chứa
nó, cũng không mở được khối khác; tin nhắn chép lại các dòng khung được đọc với các dòng ấy trong
ngoặc tròn; một dòng khung chỉ là khung ở đúng chỗ khối được đặt). Bốn sửa đổi ở server (không đổi
dòng khung trong phần được kể, trong tin nhắn; chỉ đổi dòng mở; chỉ đổi dòng đóng), mỗi cái đều làm
ít nhất một test đỏ.tests/test_turn_notes_carriers.py (tin chen ngang mang phần đổi từ lúc lượt bắt đầu; loạt tin
chờ được giao kèm phần đổi; lượt đi tiếp sau duyệt không kể gì, chỉ tin của người mang khối, và
GET /api/conversations/{id} không có khoá turn_notes; khối đứng trước ghi chú canvas rồi mới tới
tin; fork giữ khối của các tin chép sang, chỉ nghe phần đổi, tóm tắt xuất hiện một lần và không bao giờ
bị gọi là đã trống; cuộc trò chuyện bắt đầu trước khi có cột đọc trí nhớ hiện tại trước tin đầu và thôi
ngay khi tin kế tiếp được lưu; database cũ nhận thêm cột; dựng khối lỗi thì tin vẫn lưu và có log; khối
lưu hỏng được đọc như không có gì).tests/test_memory_session_summary.py (tóm tắt cuộc trước được kể trước tin mở lượt và không
nằm trong system prompt; lượt được giao việc và fork không có mục này, kèm đối chứng: bỏ forked_from
thì mục xuất hiện), tests/test_agent_context.py (hai mục ghi chú đúng tên và thứ tự, ngày không có
ghi chú vẫn được nêu tên với thân rỗng), tests/test_canvas_prompt_tail.py (ghi chú ngày nằm cạnh tin,
không nằm trong system prompt có mục canvas).tests/test_api_agent_prompt.py (opening rỗng khi agent chưa có ghi chú; có ghi chú thì bằng
đúng khối mà tin đầu của một cuộc thật được đọc sau, opening_chars là độ dài của nó, và ghi chú không
nằm trong prompt), tests/test_trajectory.py (trajectory mang khối đã che secret, tin không có gì mới
thì không có khoá, bản Markdown xếp khối, ghi chú canvas rồi tin).turn_notes ở từng chỗ trong ba chỗ lưu tin mở
lượt và ở từng tầng store; không gắn khối khi đọc lịch sử; đổi chỗ khối với ghi chú canvas; fork không
chép cột; coi mọi ghi chú dài hơn là “chỉ thêm”; coi cùng độ dài là không đổi; không nói mục đã trống;
ghi độ dài của phần đã kể thay cho cả mục; cho fork hay con mục “Cuộc trước”; lưu chuỗi rỗng khi không
có gì mới; bỏ hay giữ mãi đường lùi cho cuộc cũ; bỏ khung; trajectory bỏ khối hay để JSON thô; preview
bỏ opening; chỉ đọc ghi chú hôm nay), mỗi cái đều làm ít nhất một test đỏ.components/agent-editor/prompt-section.test.tsx (khối hiện dưới lời nhắc với tiêu đề và độ
dài riêng, không lẫn vào lời nhắc, nút chép vẫn chép đúng lời nhắc, Ẩn thì khối cũng ẩn; agent chưa có
ghi chú thì không hiện gì thêm, và mỗi lần mở đọc lại). Năm sửa đổi ở component (luôn hiện, không bao
giờ hiện, hiện lời nhắc vào chỗ khối, sai độ dài, chép cả khối) đều làm test đỏ.manage-smoke.spec.ts (màn sửa agent hiện khối ghi chú dưới lời nhắc, đúng nội dung và độ
dài, lời nhắc không chứa ghi chú, trang không rộng ra, Ẩn thì khối mất; agent chưa có ghi chú thì không
có khối).Một agent có thể nêu escalation_route trong agent.yaml: một provider:model mà lượt của nó chỉ chuyển
sang khi bị kẹt — bộ chặn lặp sắp dừng lượt, hoặc một lần gọi model hỏng trên mọi tuyến chính trước khi
hiện chữ nào. Tắt theo mặc định, không thừa hưởng, mỗi lượt chuyển tối đa một lần và lượt sau quay về
tuyến chính. Việc chuyển phát event escalated, web hiện dòng báo và run có step escalation.
tests/test_escalation.py (lượt sắp dừng vì lặp thì chuyển: lệnh lặp bị từ chối bằng câu nói
lượt được chuyển, lần gọi kế tiếp đi trên tuyến leo thang và lượt xong; lặp tiếp trên tuyến đó thì dừng
loop; lần gọi model mà mọi tuyến đều hỏng được hỏi lại trên tuyến leo thang với đúng yêu cầu cũ; phần
còn lại của lượt ở lại tuyến đó và lượt sau bắt đầu trên tuyến chính; chỉ chuyển một lần, lỗi ở đó kết
thúc lượt; chuyển vì lỗi rồi lặp thì dừng; agent không nêu tuyến thì kết thúc bằng lỗi như cũ; lần gọi
đã hiện chữ không được hỏi lại ở nơi khác, còn chữ của một lần gọi trước trong cùng lượt thì không bị
tính; không còn lần gọi model nào thì dừng hay báo lỗi chứ không chuyển, còn đúng một lần thì lần đó đi
trên tuyến leo thang; lượt vừa nói vừa lặp vẫn được chuyển; lượt đã chuyển vẫn dừng ở giới hạn bước và
ở trần chi phí, và lượt đã quá trần thì không chuyển).tests/test_escalation_setup.py (step escalation đứng vào chỗ step model đang chờ khi lý do
là lỗi, mang lý do, tuyến và lỗi rút gọn, và lần gọi kế tiếp mở step riêng; chuyển vì lặp là một step
riêng 0 ms không có lỗi, step model trước nó giữ nguyên; event ra dây đúng năm khoá; một lượt thật qua
API cho route_fallback rồi escalated rồi done, và run giữ đủ ba step; khoá trong agent.yaml
được đọc kèm reasoning của agent; agent không nêu thì không có, dù cài đặt chung có; giá trị không
phải một provider:model báo lỗi nêu tên agent và khoá; tuyến dùng được thành chuỗi một tuyến; tuyến
của provider chưa dựng và tuyến trùng tuyến chính bị bỏ kèm cảnh báo, còn cùng model trên provider khác
thì dùng được).tests/test_api_agents_escalation.py (màn sửa đặt và bỏ tuyến, tệp ghi đúng một dòng
provider:model và agent đang chạy dùng ngay; agent mới tạo kèm tuyến; tuyến không phải lối ra bị từ
chối 422 trước khi ghi tệp; đổi routes trùng tuyến leo thang cũng bị từ chối; agent mới nêu tuyến
như thế không được tạo; hình dạng khác bị từ chối; tệp viết tay có tuyến như thế không chặn một sửa
đổi khác).state/escalated-event.test.ts (dòng báo mang lý do và tuyến, thay dòng báo đổi tuyến và còn
đó sau done; trên run, chuyển vì lặp là step sau lần gọi đã trả lời, chuyển vì lỗi thay step model
đang mở, lỗi rút còn 160 ký tự), app-escalated-notice.test.tsx (cả ứng dụng: dòng báo đúng câu và
đúng lớp cho từng lý do), components/escalation-step.test.tsx (dòng trên timeline có tuyến, chữ “leo
thang”, lý do, lỗi; trạng thái xong; không gộp với dòng khác; có thời gian khi mất thời gian, không có
khi 0 ms hay chưa biết).components/agent-editor/escalation-route-editor.test.tsx (chưa đặt thì nói rõ lượt kẹt sẽ
dừng như trước; thêm trên một provider thật, gõ model, lưu gửi đúng escalation_route; chỉ hỏi tên
model khi bấm lưu; trùng một tuyến ở trên thì giữ lưu và nói vì sao; tuyến tệp nêu vẫn hiện kể cả
provider chưa dựng; bỏ thì gửi null; đổi một tuyến ở trên trùng nó cũng bị giữ; lời từ chối của
server hiện ra; agent chỉ đọc thì khoá; server cũ không trả khoá thì coi như chưa đặt),
hooks/agent-draft-escalation.test.ts (các kiểm tra của bản nháp và phần gửi đi: cắt khoảng trắng,
gửi null, không gửi khi không đụng tới), hooks/use-agent-draft.test.ts (bản nháp mở đầu mang khoá).escalation-route.spec.ts (đặt tuyến trong màn sửa agent: hỏi tên model khi lưu, từ chối
tuyến trùng, lưu gửi đúng thân PATCH với tên model đã cắt khoảng trắng, không đòi khởi động lại, tải
lại vẫn còn, bỏ thì gửi null; trên điện thoại 390px hàng không tràn, ô đủ 16px và đủ cao để chạm;
lượt đã chuyển hiện dòng báo đúng câu cho từng lý do; timeline của run nêu bước chuyển, lý do, lỗi và
model trả lời sau nó).state/thread-reducer-turn-notice.test.ts, app-turn-notice-after-load.test.tsx).reasoning; bỏ kiểm tra khi sửa routes; step không thay step model
đang chờ; bỏ lý do, tuyến hay lỗi) và bốn mươi bảy ở web (bỏ lý do khỏi dòng báo hay dùng kiểu đổi
tuyến; step không thay step đang mở, hoặc thay cả step đã trả lời; sai trạng thái; hiện thời gian
0 ms; bỏ chữ “leo thang”, lý do hay lỗi trên timeline; bỏ từng kiểm tra của bản nháp; không cắt khoảng
trắng; nút bỏ không gửi null; bỏ khoá khỏi bản nháp), mỗi cái đều làm ít nhất một test đỏ. Một sửa
đổi ở web từng sống sót vì một mặc định ?? null thừa trong bản nháp; mặc định đó đã được bỏ.Agent dùng được tool của các máy chủ MCP ở xa. Máy chủ khai trong mcp_servers của config.yaml, agent
nêu tên máy chủ trong mcp:, và mỗi tool vào sổ đăng ký dưới tên mcp__<máy chủ>__<tool>. Mọi tool MCP
hỏi trước khi chạy và không được gọi lại sau restart, trừ những tool chủ ghi vào read_only. Tệp cấu
hình không giữ khoá; đăng nhập OAuth chỉ bắt đầu từ chính máy chạy crew. Chi tiết ở
tools.md. Mọi test nói chuyện với một máy chủ MCP giả trong tiến trình
(tests/mcp_fakes.py), không test nào ra mạng.
config.yaml, không khoá nào nằm trong tệp:
tests/test_mcp_config.py (chỉ cần địa chỉ, mặc định deferred và hỏi trước; đọc đủ mọi
khoá; mức mở của một tool là tên đúng, rồi pattern đầu tiên khớp, rồi mức của máy chủ; * là một
đoạn bất kỳ và không ký tự nào khác là pattern; header được điền từ môi trường ở mỗi lần hỏi; biến
thiếu hay rỗng được nêu tên chứ không gửi rỗng; viết sai thì dừng khởi động và nói sai ở đâu; http
chỉ cho máy chủ trên máy này; tên phải đặt được cho tool và cho biến; hai tên dùng chung biến bị từ
chối; một khoá viết thẳng vào tệp làm dừng khởi động; agent không nêu máy chủ nào thì không có tool
nào; giá trị header được gửi không kèm khoảng trắng hay dấu xuống dòng quanh nó; địa chỉ được giữ
đúng như đã viết, còn địa chỉ nêu một biến thì bị từ chối mà không được nhắc lại; tên máy chủ là
các đoạn chữ và số ngăn nhau bằng một dấu mỗi chỗ, tên quá dài bị từ chối dù ghép từ gì, và không
hai máy chủ nào tệp nhận lại ra cùng một tên tool).tests/test_mcp_session.py (mở phiên, đọc tool và gọi tool dù máy chủ trả JSON hay SSE;
header đọc lại ở mỗi request; đọc tool theo trang tới khi hết, bỏ thứ không phải tool, dừng một máy
chủ phân trang mãi; phiên máy chủ đã quên được mở lại và lời gọi gửi đúng một lần, nhiều lời gọi cùng
biết phiên mất chỉ mở một phiên, lời gọi đến giữa lúc mở lại thì chờ, máy chủ quên mọi phiên thì lời
gọi hỏng chứ không lặp; ping của máy chủ được trả lời, yêu cầu khác bị từ chối, thông báo không
được trả lời; luồng sự kiện kết thúc thiếu dòng trống vẫn cho câu trả lời; câu trả lời sai là lỗi
model đọc được; chuyển hướng bị từ chối; câu trả lời quá lớn bị cắt khi đang đến; hết giờ và không
với tới được đều nói rõ; phiên bản lạ thì không dùng; lời từ chối 401 mang nơi đăng nhập, được gia
hạn đúng một lần, và gia hạn hỏng thì lời từ chối là câu trả lời; phiên không mở lại được thì lời
gọi kế tiếp mở, lời gọi chờ sau một lần mở hỏng tự mở lấy, phiên chưa được báo sẵn sàng không được
dùng, lời gọi bị dừng giữa lúc mở không để lại phiên dở, máy chủ không giữ phiên không bị mở lại ở
mỗi lời gọi, lời gọi trên phiên chưa từng mở thì mở trước; luồng sự kiện xuống dòng theo cả ba
cách, dấu xuống dòng bị cắt đôi trên đường vẫn là một, ký tự chỉ trông giống dấu xuống dòng không
cắt sự kiện, một dòng đến thành nhiều mảnh được ghép lại, luồng không bao giờ xuống dòng bị cắt ở
ngưỡng; máy chủ được liệt kê một nghìn tool và không hơn, quá số đó thì không bị hỏi phần còn lại).tests/test_mcp_tools.py (tên chỉ gồm ký tự provider nào cũng nhận, tên dài bị cắt kèm băm;
mô tả nói tool ở máy chủ nào; mọi tool hỏi trước cho tới khi chủ ghi là chỉ đọc; tool mang mức mở
của nó; hai tool ra cùng tên thì giữ cái đầu và nêu tên cái kia; kết quả đọc thành chữ, thứ không
phải chữ được gọi tên; lời gọi qua sổ đăng ký tới máy chủ và trả chữ; tool hỏng là kết quả hỏng model
đọc được; tool không nói gì vẫn báo đã chạy; khoá bị lấy đi sau khi kết nối làm lời gọi hỏng và nêu
tên biến; agent chỉ giữ tool của máy chủ nó nêu, giao lại thì thu về thứ profile không còn nêu; máy
chủ không agent nào biết chỉ là cảnh báo; chỉ tool direct được khai cho model ở mọi lần gọi; máy
chủ không kết nối được là một dòng nói vì sao và không cản máy chủ khác; khoá thiếu được nêu tên,
khoá bị từ chối không phải là “cần đăng nhập”; khoá trong tệp thắng phiên đăng nhập đang giữ; lần
thử bị cắt để máy chủ ở trạng thái còn phải thử; thứ chủ được xem không mang khoá nào; tool có tên
quá dài hay tham số quá lớn bị bỏ ra và được nêu tên, tên bị bỏ vì quá dài không chiếm chỗ của tên
nó rút gọn thành, tool vừa quá lớn vừa trùng tên được ghi là quá lớn, tham số lồng quá sâu tính là
quá lớn, mỗi tool bị bỏ là một dòng log dù tên nó chứa gì; giao lại thu mọi tool về trong một lần
dựng sổ; máy chủ liệt kê quá nhiều tool thì hỏng và nói vì sao; cả lần kết nối chỉ có thời gian
của một request; header không viết được bị từ chối mà không lộ giá trị),
tests/test_tools_registry.py (thu hẹp sổ theo nhiều tên một lần giữ nguyên thứ tự phần còn lại).tests/test_mcp_schema_fold.py (đoạn đứng hai chỗ được viết một lần trong $defs và trỏ
tới, phần thêm vào đứng cuối; bản gốc không bị đụng; đoạn được dời khỏi mọi chỗ một schema đứng,
với từng từ khoá ở từng dạng; thứ nằm dưới enum, const, default, examples, required hay
một từ khoá lạ không bao giờ bị dời; tham số tên như một từ khoá vẫn là tham số; bảng tên không bị
dời như một schema; từ khoá giữ thứ không phải schema thì để nguyên; tên đặt theo thứ tự đoạn đứng
lần đầu; đoạn đã dời trỏ tới đoạn dời ra từ trong nó; cùng một schema luôn gấp ra cùng một chuỗi
ký tự, dù các đoạn là một object hay được viết riêng; đoạn chỉ được dời khi cả schema ngắn lại,
đếm bằng số ký tự thật của cả hai cách viết: khi nó đứng một mình, khi đứng cạnh một đoạn đã dời,
khi phần lớn của nó là một đoạn đã dời, khi nó nhận cái tên dài hơn là shared10, và ký tự có
dấu được đếm là một ký tự; đoạn đứng một lần trong một đoạn đã dời thì được viết nguyên trong đó;
không gì lặp thì không gấp; tên mới tránh tên đã có và tên đang được trỏ tới, kể cả con trỏ không
dẫn tới đâu; $ref tới định nghĩa của chính schema được để nguyên; schema trỏ đi nơi khác, tự
đặt tên, có $defs không phải bảng, dài quá mức được đọc hay lồng quá sâu thì không gấp; bản gấp
mở lại không ra đúng bản gốc thì không được dùng; một phần tham số viết đủ ở mọi chỗ gấp còn một
phần nhỏ và mở lại đúng từng ký tự),
tests/test_mcp_folded_tools.py (tool vừa mức sau khi gấp được nhận, giữ bản máy chủ viết, nói
hai con số và ghi một dòng log; phần tham số vừa mức được trao nguyên, kể cả khi vừa đúng mức, khi
lặp nhiều hay đã có $defs riêng; thêm một ký tự là bị gấp; gấp rồi vẫn quá thì bị bỏ và nêu tên
như trước; mức được hỏi ở bản đã gấp, đúng tới từng ký tự; phần tham số không gấp được thì tool bị
bỏ như trước; tool bị bỏ vì trùng tên không được nói là đã gấp; tool_search đọc bản máy chủ
viết, không sâu hơn mức nó vẫn đọc; script được tả tool theo bản máy chủ viết; model được nói bản
đã gấp ở mọi lần gọi, cả trong request mà provider gửi đi, và cả khi tool được nạp bằng
tool_search), tests/test_api_mcp.py (dòng của tool ở GET /api/mcp mang folded với hai con
số, các tool khác mang null). Schema trong các test này được dựng riêng
(tests/mcp_schema_fakes.py); không schema của máy chủ thật nào được chép vào repo.components/mcp-servers-card.test.tsx (dòng của tool có tham số đã gấp mang nhãn “đã
gộp tham số” và hai con số, số trước rồi số sau; tool có folded là null không mang nhãn;
server cũ không gửi khoá ấy cũng không).mcp-servers.spec.ts (“a tool whose parameters were folded says so with both
sizes, within the screen”).folded là null, đổi chỗ hai con
số) đều bị test bắt.tests/test_tools_registry.py (nửa ký tự trong câu trả lời, dạng chữ hay dạng kết
quả, được thay bằng dấu U+FFFD và kết quả ghi được vào store; trong lời lỗi của chính tool
cũng vậy; chỉ nửa ký tự bị thay, mỗi nửa một dấu, nửa đầu hay nửa sau),
tests/test_registry_spill.py (đầu ra dài mang nửa ký tự vẫn được giữ lại để đọc, với nửa ấy
đã đánh dấu).tests/test_mcp_turns.py (một lượt thật: tool chủ ghi chỉ đọc chạy không hỏi và chữ của nó
là kết quả; tool khác chờ người và chỉ tới máy chủ khi được cho phép; lời gọi bị từ chối không bao
giờ tới máy chủ; máy chủ tự nhận chỉ đọc không làm tool chạy không hỏi; model chỉ được khai tool
direct; dựng lại đội để mỗi tool đúng một lần ở đúng chỗ cũ; lời gọi bị restart cắt không được gọi
lại trừ khi tool chỉ đọc, còn tool chỉ đọc thì được gọi lại).tests/test_mcp_oauth.py (đăng nhập trọn vòng; lần khởi động sau đọc lại phiên đăng nhập;
máy chủ không nói nơi đăng nhập thì tìm ở chỗ nó công bố; phiên đăng nhập không tin được thì không
bắt đầu: thiếu PKCE S256, sai tên, không https; nơi đăng ký được kiểm khi biết; đăng ký bị từ chối
không thành client id; địa chỉ trỏ vào mạng nội bộ không bao giờ được gọi; tài liệu dò đường chuyển
hướng không được đi theo; máy chủ trên máy này và máy chủ có khoá trong tệp không đăng nhập OAuth;
nơi không cho tự đăng ký thì dùng client id của chủ; state dùng một lần và chỉ cho lần bấm mới
nhất, về quá muộn thì được bảo bắt đầu lại; lời từ chối kèm mã vẫn là từ chối; token gửi kèm lỗi
không được giữ; grant không giữ trọn được thì không giữ nửa; token hết hạn được gia hạn và lời gọi
gửi một lần, nhiều lời gọi bị từ chối cùng lúc chỉ gia hạn một lần; gia hạn bị từ chối thì máy chủ
thành “cần đăng nhập” và không lời gọi nào được gửi lại; máy chủ đăng nhập sập hay không với tới được
làm lời gọi hỏng mà vẫn giữ phiên; đăng xuất quên token và giữ client id; không có home thì phiên
đăng nhập sống theo tiến trình; khoá trong tệp bị từ chối giữa lời gọi là khoá phải sửa chứ không
phải đăng nhập; lời gọi bị từ chối sau khi lời gọi khác đã gia hạn được gửi lại bằng token đó; chỉ
lời từ chối chính grant hay chính client mới kết thúc phiên đăng nhập, lỗi khác giữ phiên; token
mới không lưu được thì giữ phiên cũ và nói vì sao; refresh token chỉ được nói với nơi đã cấp, kể
cả giữa lời gọi, và không nói với ai khi không biết nơi cấp; đăng nhập bắt đầu rồi bỏ dở không đổi
gì của phiên đang dùng; đăng nhập ở nơi khác thay trọn phiên đang giữ; phiên mới không ghi được
hay không giữ được thì phiên đang dùng còn nguyên; client id chủ tự viết được để nguyên; phiên
không gia hạn được lúc khởi động được giữ và thử lại; máy chủ từ chối token vừa gia hạn thì cần
đăng nhập; mã quay về từ nơi khác không được đổi, từ đúng nơi thì được, nơi nói sẽ xưng tên thì
phải xưng, và nơi đăng nhập được nhận theo tên nó tự xưng; tên nơi đăng nhập và tên nơi gửi người
về được cắt ngắn trước khi hiện; lần gia hạn đang dở không ghi đè phiên chủ vừa mang về và không
đăng nhập lại sau khi chủ đăng xuất; máy chủ từng hỏng nay cần đăng nhập không còn nói là hỏng; nơi
cấp token, nơi đăng ký hay tài liệu dò đường trả lời nhỏ giọt thì bị bỏ, và việc dò nơi đăng nhập
có hạn chót dù bao nhiêu nơi trả lời muộn; đăng xuất không ghi được thì phiên còn như tệp đang
giữ), tests/test_env_file.py (nhiều tên được ghi và bỏ trong một lần thay tệp; bỏ những tên không
có thì không ghi gì; lần thay hỏng để tệp nguyên như cũ và không để lại gì bên cạnh).tests/test_mcp_lifecycle.py (khởi động kết nối các máy chủ và giao tool; crew không có máy
chủ không hỏi ai; không chờ máy chủ chậm; máy chủ hỏng lúc đầu được thử lại, thưa dần khi vẫn hỏng,
đặt lại nhịp khi lên lại hay khi chủ tự kết nối; khoá đổi thì thử ngay; được đánh thức khi mọi máy
chủ đang lên thì không hỏi gì; máy chủ cần đăng nhập không được thử lại; app kết nối máy chủ trước
khi tiếp tục một lượt bị cắt, chỉ thử lại trong lúc đang phục vụ, và không dựng gì khi không có máy
chủ).tests/test_mcp_lifecycle.py cũng giữ cho mỗi máy chủ chỉ một lần thử mỗi lúc (lần thử bị
cắt lúc khởi động không cản vòng thử lại kế tiếp; máy chủ đang được ai đó kết nối thì vòng thử lại
không thử thêm, và máy chủ chỉ nghe một lần mở phiên; máy chủ đã có người nhận ngay từ lúc lần kết
nối được yêu cầu, trước khi bất cứ gì được gửi đi; trong lúc đó vòng thử lại vẫn thử các máy chủ
khác; lần kết nối được yêu cầu giữa chừng thì chờ lần đang chạy rồi thử lại từ đầu, và dòng của máy
chủ là kết quả của lần thử sau, dù lần ấy bị từ chối hay được nhận).tests/test_api_mcp.py (danh sách trước và sau khi thử; tên lạ là 404; khoá bị máy chủ từ
chối nói rõ và khoá đúng lưu từ màn Kết nối làm máy chủ lên; agent được giao máy chủ từ trình sửa
và giữ tool ngay; tên không có trong tệp bị từ chối 422, còn tệp đã nêu sẵn tên như thế không chặn
sửa đổi khác; kết nối lại giao tool cho agent đang nêu máy chủ; đăng nhập từ web kết thúc với máy
chủ đã kết nối và không token nào hiện ra; đăng xuất bỏ token và thu tool về; đăng nhập hỏng vẫn về
đúng màn nói vì sao; callback không ai chờ không đổi gì; chỉ bắt đầu đăng nhập được từ chính máy
chạy crew, với tên nào của máy đó cũng được; đăng nhập không bắt đầu được thì nói vì sao; đường
quay về chuyển tiếp lời xưng tên của nơi cấp mã; dòng của tool mà model được nói ở bản đã gấp mang
folded),
tests/test_local_guard.py (chỉ đường quay về từ đăng nhập nhận một lượt điều hướng từ trang khác).hooks/use-mcp-servers.test.ts (đọc một lần khi mở và không đọc lại khi mọi máy chủ đã có
câu trả lời; máy chủ đang thử thì đọc lại mỗi 2 giây tới khi có; thôi hỏi máy chủ không bao giờ trả
lời và bắt đầu lại khi làm mới; máy chủ hỏng chỉ được theo dõi sau khi một khoá đổi và trong chốc
lát, còn máy chủ cần đăng nhập thì không; hai câu trả lời chéo nhau thì giữ câu của request sau cùng;
kết nối lại và đăng xuất lấy danh sách request trả về, không đọc thêm, và theo dõi lại máy chủ khác
còn đang thử; tên lạ thì nêu đúng lời server; đăng nhập đưa người tới địa chỉ server trả, bị từ chối
thì không đi đâu; các hàm giữ nguyên qua mỗi lần vẽ), components/mcp-servers-card.test.tsx (chưa
khai máy chủ nào thì nói cách khai; trạng thái đúng chữ và đúng màu; địa chỉ, mô tả, lý do hỏng, và
lý do của lần thử trước còn đó khi đang thử lại; agent nào dùng; tool với mức mở, hỏi trước hay
không, lời tự nhận chỉ đọc; tool bị bỏ, kèm đủ ba lý do có thể (trùng tên, tên quá dài, tham số quá
lớn); nút nào hiện ở trạng thái nào; mỗi thao tác
giữ mọi nút của đúng dòng đó tới khi xong và nói đang làm gì; lời từ chối hiện dưới dòng; đăng xuất
chỉ sau khi người đồng ý, và không nói “đã đăng xuất” khi hỏng), components/connections-panel.test.tsx
(thẻ nằm trong màn Kết nối, biến của header nằm dưới thẻ kèm tên máy chủ đọc chúng),
test/fake-mcp.test.ts (máy chủ giả của test giữ đúng luật của server).components/agent-editor/tools-section.test.tsx (danh sách cho phép không liệt kê tool MCP;
ô chọn nêu từng máy chủ đã khai; tích và bỏ tích sửa mcp; tên tệp không còn khai có một dòng riêng
nói phải bỏ; chưa khai máy chủ nào thì nói thế; agent chỉ đọc thì khoá),
hooks/use-agent-draft.test.ts (bản nháp mở đầu mang mcp, chỉ gửi khi đổi, gửi [] khi bỏ hết),
components/tools-matrix.test.tsx (tool MCP mang nhãn máy chủ và mức mở; ô của agent chưa bật máy
chủ có dấu và chú giải riêng, không bị giải thích bằng danh sách cho phép, khoá hay chế độ).mcp-servers.spec.ts (đăng nhập từ Kết nối: lần đầu bị từ chối thì hiện đúng lời và
không rời trang, lần sau đi tới máy chủ đăng nhập rồi về với máy chủ đã kết nối và các tool; bảng
Công cụ nêu tool cùng máy chủ; đăng xuất sau khi đồng ý; máy chủ hỏng nói vì sao và lên lại từ nút
trên dòng; máy chủ đang thử tự có câu trả lời mà không bấm gì; lưu biến của header làm máy chủ lên
không cần tải lại và giá trị không hiện trên trang; giao máy chủ cho agent trong trình sửa, tên tệp
không còn khai phải bỏ trước, lưu gửi đúng mcp, không đòi khởi động lại, tải lại vẫn còn; trên
điện thoại 390px thẻ và ô chọn không tràn, địa chỉ và tên dài xuống dòng, nhãn không bị ép thành
nhiều dòng, mọi nút đủ lớn để chạm).http ngoài máy này; nhận header viết thẳng
giá trị; cho tool chạy không hỏi vì máy chủ tự nhận chỉ đọc; gọi lại tool ghi sau restart; khai tool
deferred cho model; giao tool hidden; bỏ kiểm PKCE, tên máy chủ đăng nhập hay state; cho đăng
nhập từ máy khác; gọi địa chỉ trong mạng nội bộ; giữ token gửi kèm lỗi; trả token trong danh sách; thử
lại máy chủ cần đăng nhập; chờ máy chủ chậm lúc khởi động) và một trăm hai mươi ở web (bỏ việc đọc lại
khi đang thử; theo dõi mãi; theo dõi máy chủ cần đăng nhập; nhận câu trả lời chéo; không rời trang khi
đăng nhập; đăng xuất không hỏi; nói “đã đăng xuất” khi hỏng; sai màu trạng thái; bỏ lý do hỏng, nhãn
hỏi trước, lời tự nhận chỉ đọc; liệt kê tool MCP trong danh sách cho phép; không gửi mcp; bỏ dấu của
ô chưa bật máy chủ; bỏ luật xuống dòng của địa chỉ; ép nhãn trên một hàng), mỗi cái đều làm ít nhất một
test đỏ. Sáu sửa đổi ở web từng sống sót ở vòng đầu: máy chủ cần đăng nhập bị theo dõi sau khi đổi
khoá, lý do của lần thử trước, chữ “đang đăng xuất”, và hai luật CSS mà bề rộng trang không lộ ra vì
khung ngoài cắt phần tràn; mỗi cái giờ có test riêng, và việc đặt lại vòng theo dõi sau kết nối lại
và sau đăng xuất được gộp về một chỗ.enum, const, default, examples, required như chỗ của schema; dời cả
một bảng tên; bỏ sót not, items dạng danh sách hay dependentSchemas; nhận schema tự đặt tên hay
con trỏ đi nơi khác; nói cho model bản máy chủ viết; giữ bản đã gấp làm bản của tool; không ghi log,
ghi ở mức thấp hơn, hay ghi cho tool bị bỏ; để folded trống trên dòng của tool. Năm mươi tám cái làm
ít nhất một test đỏ. Mười cái trong số đó từng sống sót: bảy cái ở phép cân từng đoạn, mà các test lúc
ấy chỉ thử với một đoạn đứng một mình, nơi phép so cuối cùng với độ dài bản gốc che mất phép cân; ba
cái còn lại là bỏ bước mở lại để so, đếm theo số lần đứng trong bản gốc, và ghi log cho tool bị bỏ vì
trùng tên. Giờ phép cân được thử cạnh một đoạn đã dời, đúng tới từng ký tự, và mỗi cái có test riêng.
Hai sửa đổi còn lại không làm test nào đỏ vì hai dòng chúng sửa không mang hành vi nào (một dòng cho
khoá không phải chuỗi, một dòng cho cùng một object gặp lại); hai dòng ấy đã được bỏ.tool_search)Tool của máy chủ MCP mặc định không được khai cho model ở mỗi lời gọi. Agent giữ tool như thế được
giao tool_search để tìm bằng vài từ khoá; tool tìm thấy được khai từ lời gọi model kế tiếp, và cái
đã nạp được đọc lại từ chính hội thoại nên còn nguyên sau restart
(tools.md).
tests/test_mcp_tool_search.py (từ được so không phân biệt hoa thường, dấu, đ và s
số nhiều, createPage là hai từ; một từ trong tên tool nặng hơn hai lần trong mô tả; từ hiếm
nặng hơn từ tool nào cũng có; mô tả dài hay ngắn là so với các tool khác; tìm được theo tên máy
chủ, theo tên và mô tả tham số dù lồng sâu, kể cả tham số tên là description hay properties;
schema sai dạng không làm hỏng lần tìm và từ khoá của schema không phải là từ của tool; schema
chỉ được đọc tới một độ sâu; tool được gọi đúng tên đứng đầu dù không từ nào khớp; khớp ngang
nhau thì xếp theo tên; truy vấn tiếng Việt tìm được mô tả tiếng Việt).tests/test_mcp_tool_search.py (mỗi tool nạp một dòng; mặc định 5, nhiều nhất 10, ít
nhất 1, limit sai kiểu thì về mặc định; nói còn bao nhiêu tool khác khớp và không nói khi đã
nạp hết; thiếu query là lỗi model đọc được; không tìm thấy thì không nạp gì và nói mỗi máy chủ
có bao nhiêu tool; không gì máy chủ viết trong mô tả giả được một dòng nạp, mô tả cắt còn một
dòng 200 ký tự; lần tìm giữ danh sách tool lúc nó được dựng; lời mô tả của tool_search nêu
từng máy chủ bằng lời của chủ; nó không hỏi trước và được gọi lại sau restart).tool_search:
tests/test_mcp_tool_search.py (agent có tool chưa khai được giao nó, đứng sau tool có
sẵn và trước các tool MCP, giao lại không lặp, bỏ máy chủ thì thu về; mọi tool đều direct hoặc
hidden thì không giao; tool mức codemode cũng chờ được tìm; chỉ tìm trong tool chưa khai của
máy chủ agent được giao; lời mô tả chỉ nêu máy chủ còn tool để tìm; hai máy chủ ra cùng một tên
thì tìm theo cái agent đang giữ; không bao giờ chờ người duyệt), tests/test_mcp_tools.py (tool
deferred không được khai mà tool_search thì có; màn Kết nối hiện một dòng mô tả của mỗi tool,
còn model vẫn được nói đủ), tests/test_api_mcp.py (GET /api/tools nêu tool_search kèm
with_mcp và agent nào giữ; bỏ máy chủ khỏi agent thì dòng đó mất), tests/test_mcp_turns.py
(hồ sơ agent sau khi giao máy chủ có tool_search trước các tool của máy chủ).tests/test_mcp_tool_search.py (cái đã nạp đọc từ các câu trả lời tool_search trong
hội thoại, theo thứ tự nạp, mỗi tool một lần, không lẫn sang hội thoại khác; chỉ câu trả lời của
chính tool_search mới nạp, dòng gần giống không tính; tool agent không còn giữ thì thôi khai;
tool sau đó được mở direct chỉ khai một lần), tests/test_mcp_tool_search_turns.py (tool tìm
được khai từ lời gọi model kế tiếp và nối vào cuối, phần đã khai và system prompt giữ nguyên;
lần tìm không hỏi ai và không gọi máy chủ; tool đã nạp vẫn chờ người duyệt, và lượt chạy tiếp sau
khi duyệt vẫn khai nó; cái đã nạp theo hội thoại sang lượt sau, không sang hội thoại khác; sau
restart lượt được tiếp tục khai đúng các tool đó theo đúng thứ tự; tool máy chủ thôi liệt kê thì
thôi khai).tool_search ở bảng Công cụ và trình sửa agent:
components/tools-matrix.test.tsx (agent có tool để tìm thì giữ nó, agent khác có dấu
riêng; ô đó không bị giải thích bằng danh sách cho phép, khoá hay chế độ; nhãn “đi kèm MCP” nói
nó đến từ đâu; chú giải chỉ có dấu ấy khi crew có tool_search),
components/agent-editor/tools-section.test.tsx (danh sách cho phép không liệt kê tool_search,
và “chọn tất cả” không gửi nó), test/fake-mcp.test.ts (máy chủ giả của test giao tool_search
đúng luật của server: chỉ cho agent có tool chưa khai, đứng trước tool của máy chủ).mcp-servers.spec.ts (bảng Công cụ có dòng tool_search với nhãn và lời giải thích
của nhãn, agent giữ nó và agent không có gì để tìm, chú giải có dấu riêng; đăng xuất máy chủ thì
dòng đó và dấu trong chú giải cùng mất; trình sửa agent không liệt kê nó trong danh sách cho
phép; trên điện thoại 390px nhãn của dòng tool_search và của tool MCP không bị ép, bảng và
chú giải không làm trang tràn).screens/manage-screen.test.tsx (mở màn ở mục Công cụ thì đọc một lần; mỗi lần mở mục
đó đọc lại và hiện đúng điều vừa đổi ở trang khác, rời mục thì không đọc; đang mở mà tool của
máy chủ đổi thì đọc lại ngay; đang ở trang khác thì không đọc, tới khi mở),
hooks/use-mcp-servers.test.ts (giá trị “máy chủ đang giao gì” không đổi theo trạng thái, lý do
hỏng hay agent dùng, không đổi vì máy chủ không có tool, và đổi khi một tool mất, hết tool, đổi
mức mở, thôi hỏi trước, đổi mô tả, thêm tool của máy chủ khác, hay cả máy chủ mất).mcp-servers.spec.ts (giao máy chủ cho agent rồi mở Công cụ thấy agent đó giữ tool
của máy chủ và tool_search mà không tải lại trang; đăng xuất rồi mở Công cụ thì các dòng của
máy chủ đã mất; đang mở Công cụ mà máy chủ vừa nối xong thì các dòng của nó và tool_search
hiện ra, không bấm gì).hooks/use-mcp-servers.test.ts nhóm “where the servers stand, as one value” (giá trị
“các máy chủ đang đứng ở đâu” đổi khi một lần thử kết thúc theo cả bốn kiểu: hỏng, cần đăng nhập,
nối được mà không có tool, nối được kèm tool; đổi khi máy chủ không có tool rớt, khi máy chủ
hỏng chuyển sang cần đăng nhập hay bắt đầu được thử, khi một lần thử kết thúc đúng lúc lần khác
bắt đầu, khi một tool đến mà không có lần thử nào ở giữa, khi một tool đổi mức mở; không đổi cho
một danh sách đọc lại, cũng không đổi theo lý do hỏng, agent dùng, đã đăng nhập hay mô tả; và mọi
máy chủ còn đang thử thì đọc như chưa có danh sách) và nhóm “when the crew changes” (đọc lại danh
sách, thứ nói ai đang dùng máy chủ nào; vẫn theo dõi tiếp máy chủ đang hỏng sau khi một khoá đổi;
đọc hỏng thì nói vì sao và giữ danh sách đang có); screens/manage-screen.test.tsx (“is read
again when the list it opens with finds the try already over”, và nhóm “who uses a server, on the
manage screen”: xoá agent thì dòng thôi nêu agent đó, tạo agent thì ai dùng máy chủ nào được đọc
lại); test/fake-mcp.test.ts (máy chủ giả của test vẫn nêu tool của một máy chủ trong lúc nó
được thử lại, tới khi lần thử kết thúc, và nêu tool mà lần thử thứ hai tìm thấy chứ không phải
tool các agent giữ suốt lúc đó).mcp-servers.spec.ts (“the tools grid lets go of a server whose try failed while the
grid was open”: đang mở Công cụ mà lần thử lại hỏng thì các dòng của máy chủ mất đi, không bấm
gì; “a server’s card follows who uses it as an agent is saved and removed, with the page never
loaded again”).tool_search khi không có gì để tìm, sau tool
của máy chủ, hay giao lại mà không thu về trước; giao tool hidden; coi tool codemode là đã
khai; đọc lời nạp từ tin không phải của tool_search; giữ tool máy chủ đã thôi liệt kê; đổi thứ
tự khai; không cho gọi lại tool_search sau restart; bỏ việc rút mô tả về một dòng) và
ba mươi tư sửa đổi ở web (bỏ, đổi chỗ hay đổi chữ của dấu “không có gì để tìm”; chú giải hiện dấu
ấy khi không có tool_search; nhãn “đi kèm MCP” mất, sai chữ hay hiện ở mọi dòng; danh sách cho
phép liệt kê tool_search; bảng Công cụ không đọc lại, đọc lại ở lần mở đầu, đọc lại khi đang ở
trang khác, hay không theo tool của máy chủ). Vòng đầu ở server sót mười một, ở web sót một (chỉ
thiếu ở Playwright: bảng đang mở mà máy chủ vừa nối xong); mỗi cái được thêm test, một số hạng
thừa (tên máy chủ, vốn đã nằm ở đầu mô tả) bị bỏ, và phép đọc schema được viết lại cho đúng hình
(trước đó nó đọc bảng properties như một schema, nên tham số tên properties làm lộ từ khoá
của schema thành tên tham số). Một sửa đổi chỉ đổi con số độ sâu bị loại vì test đọc chính hằng
số ấy. Sau đó tất cả đều bị test bắt.tool_script)Agent giữ ít nhất một tool script gọi được (một tool có sẵn chỉ đọc, hoặc một tool MCP nằm trong
read_only và không hidden) được giao tool_script, có máy chủ MCP hay không. Model viết một
đoạn script ngắn; script chạy trong một tiến trình con, tự gọi các tool chỉ đọc, và chỉ
những gì nó in ra mới quay về cho model (tools.md). Agent bật
script_calls: gated thì script của nó gọi được cả tool có ghi, mỗi lời gọi qua cổng duyệt trước
khi chạy; phần đó có mục riêng ở dưới.
tests/test_script_language.py (mỗi đoạn trong bảng in ra đúng điều Python in với cùng
những dòng ấy; mỗi chỗ cố ý khác Python là một dòng của bảng thứ hai; tool được gọi theo tên và
lời gọi có giá trị là chữ tool trả về; tham số gửi đi là thứ JSON chở được; câu trả lời là JSON
thì được dùng như dữ liệu; script rỗng không làm gì và không nói gì).tests/test_script_refusals.py (thứ không có trong ngôn ngữ bị từ chối trước khi dòng
nào chạy, kèm số dòng, và script bị từ chối không gọi tool nào; thuộc tính không bao giờ được
đọc hay gán; script Python không đọc nổi được nói bằng lời của Python, kể cả byte rỗng, biểu
thức lồng quá sâu, script nhiều tới mức Python bỏ cuộc và chữ không mã hoá được; cảnh báo của
Python không lọt ra ngoài và không làm script hỏng; tên của Python vươn ra ngoài dữ liệu thường
thì không có; lỗi dừng script ở đúng dòng với điều sai, giữ những gì đã in, và bắt được bằng
except theo tên Python đặt cho nó; Python hết bộ nhớ hay hết độ sâu thì script dừng ở dòng
đang chạy và except không bắt được; tool hỏng là lỗi script bắt được nhưng không mang tên lỗi
nào của Python; raise, raise trần và finally đi như Python; lời gọi script không được
phép làm dừng script dù nó viết gì, kể cả từ trong một hàm; gọi tool sai cách là lỗi và tool
không được hỏi).tests/test_script_limits.py (vòng lặp vô tận bị dừng và những gì đã in được giữ; số
bước tính trên cả script, ghi một giá trị ra tốn một bước cho mỗi thứ trong nó; range dài
bằng số bước; không giá trị nào dài quá mức dù được tạo bằng cách nào, viết ra từng phần tử
cũng vậy, và phép lặp quá dài bị từ chối trước khi Python được nhờ làm; dict được trải ra dài
quá mức thì script dừng trước khi cái đứng sau chỗ trải được tính; dữ liệu được ghi ra sâu năm
mươi tầng và dài hai trăm nghìn thứ, dữ liệu chứa chính nó thì không bao giờ; in đúng sáu mươi
nghìn ký tự thì còn nguyên, thêm một ký tự là đã quá; in quá mức thì giữ phần đầu và phần cuối
kèm một dòng nói đã bỏ bao nhiêu, cả đoạn không dài hơn mức, script không bị dừng vì thế và
chạy tiếp; in từng dòng thì còn những dòng đầu và những dòng cuối; in bao nhiêu thì cái được
giữ cũng chỉ nhỉnh hơn mức, nên in nhiều hơn hẳn không làm script dừng vì bộ nhớ; vòng in mãi
thì dừng vì số bước mà hai đầu vẫn còn; giá trị của dòng cuối cũng tính là in ra; số nguyên
256 bit; hàm gọi lồng bốn mươi
tầng, độ sâu được trả lại khi lời gọi kết thúc theo cách nào đi nữa; hai mươi lăm lời gọi
tool, lời gọi hỏng vẫn tính còn lời gọi chưa gửi thì không; tham số dài tới đúng mức thì gửi,
thêm một ký tự thì không; JSON sâu quá sức Python là lỗi script sửa được; lỗi được kể trong hai
nghìn ký tự; không giá trị nào được ghi ra dài hơn mức đã tính cho nó; script bị dừng vì cái nó
đang giữ chứ không vì cái nó đã tạo, bộ nhớ được xem mỗi khi có thêm năm mươi nghìn ký tự hay
phần tử, mức là 256 MB không hơn một byte, cái có từ trước khi script chạy không tính, và cách
tạo giá trị nào cũng được đếm). Các mức của một lần script tự hỏi model nằm ở mục “Script tự
hỏi model” bên dưới.tests/test_script_runner.py (script chạy ở tiến trình khác còn lời gọi của nó được
trả lời ở tiến trình này; chữ đi và về giữ nguyên, câu trả lời dài hơn hẳn sức chứa của pipe
vẫn tới đủ, nửa cặp ký tự thành dấu hỏi ở cả hai chiều; tiến trình được báo script và số giây
CPU; vòng lặp vô tận dừng vì số bước, thứ số bước không thấy thì dừng vì giây CPU, giữ quá
nhiều thì dừng vì bộ nhớ; script gọi lồng hết mức vẫn đủ độ sâu của Python; tiến trình vốn bị
giữ ở mức CPU thấp hơn vẫn chạy; im lặng quá lâu thì bị giết, thời gian tool chạy không tính là
im lặng, và có một mức cho cả script; lượt bị huỷ không để lại tiến trình nào; tiến trình kết
thúc không trả lời, nói điều vô nghĩa, nói JSON không đúng thoả thuận, bị hệ thống dừng vì CPU
ngay sau khi im, bỏ đi khi câu trả lời dài còn trên đường, hay bỏ đi trước khi tool kịp trả
lời đều được kể đúng là thế, có ghi log kể cả khi lời cuối bị cắt giữa một chữ; tiến trình
mất cha thì dừng chứ không chạy tiếp;
dòng dài hơn mọi thứ script viết được thì không được đọc; stderr nhiều không làm nghẽn; tiến
trình không nhận gì của server: môi trường rỗng, và không module nào đứng cạnh gói chen vào
chỗ module của Python được; trên macOS sandbox chặn mạng, mọi tệp ghi và tiến trình thứ hai, và
không cho tiến trình biến thành một helper ngoài sandbox).tests/test_shell_network_sandbox.py (lệnh không mạng cũng không tra được dịch vụ của
một helper theo tên; luật ấy dùng chung cho sandbox của shell và của script).tool_script: script gọi được gì, ghi lại gì, nói gì với model:
tests/test_script_canvas.py (với bộ tool canvas thật, script gọi được
artifact_list nhưng không gọi được artifact_read: script lật từng trang một canvas thì dừng
ở lời gọi đầu với dòng bảo gọi trực tiếp và không lời gọi nào được ghi; con trỏ đã thấy đứng
yên ở bản agent tạo nên artifact_rewrite sau đó vẫn bị từ chối và bản người dùng lưu còn
nguyên; agent tự đọc đến hết bằng lời gọi thường thì ghi đè được).tests/test_script_tool.py (tool chỉ đọc và không hỏi ai thì script gọi được; năm tool
nói thay agent hoặc để lại dấu trên cuộc trò chuyện thì không, dù cũng chỉ đọc; tool của máy chủ khi chủ ghi read_only mà không
ẩn, ở mức mở nào cũng được, còn tool hỏi trước hay bị ẩn thì không, dù mở codemode; tool chủ
ghi chỉ đọc chạy được từ script dù lời mô tả không nêu tên nó; tool bị ẩn mà vẫn nằm trong sổ
thì bị từ chối chứ không chạy;
chỉ những gì script in ra mới quay về; tool của máy chủ được gọi bằng tên agent
giữ nó; mỗi lời gọi được giữ lại cho thẻ run, lời gọi nhận một bảng tham số rộng thì tool vẫn
nhận đủ còn phần giữ lại chỉ có mười hai tham số đầu và số tham số còn lại, lời gọi trả tiền
cho model mang giá của nó; hai
script cùng lúc mỗi cái giữ lời gọi của mình; lời gọi không được phép làm dừng script và nói
phải gọi thế nào thay, và được trả cho tiến trình con như một lời gọi hỏng chứ không như lời
tool nói; tên không có là một lỗi bắt được kèm tên gần nhất trong số tool gọi được (tool
script không được gọi thì không bao giờ được gợi ý) và chỗ tìm tiếp, rồi script gọi lại được;
hook của agent được hỏi về từng lời gọi; script gọi tool agent đang giữ lúc nó chạy; script nhận câu trả lời dài hơn hẳn một
lượt, dài quá thì cũng bị cắt; cái script in ra được cắt theo trần của agent mà vẫn giữ các
lời gọi, và số ký tự in ra vẫn đếm cái script đã in chứ không đếm phần lọt qua trần; thiếu script thì không khởi động gì; script dài đúng mức thì chạy, dài hơn một chữ
thì không; script không chạy xong, dù bị dừng, gặp lỗi, không đọc được, bị từ chối, hết giờ,
hết CPU hay tiến trình con chết, đều nói “Script không chạy xong.” trên một dòng riêng trước
mọi thứ khác, còn script chạy xong thì không, kể cả khi chính nó in câu đó;
tool_script không hỏi ai, được gọi lại sau restart và mang with_reads chứ không mang
with_mcp; lời mô tả nêu tool có sẵn của agent trước, mỗi tool một dòng với tham số theo tên
và kiểu mà không kèm mô tả, rồi tới các tool mở cho script (codemode), mỗi tool của máy chủ
một dòng có thêm một câu mô tả; hai nhóm chung một trần ba mươi dòng, tool có sẵn tính trước,
mười hai tham số đầu, tham số có tên không phải một từ thường thì bỏ, schema hình gì cũng đọc
được; agent có hơn ba mươi tool có sẵn thì được nói còn bao nhiêu; tool ở mức mở khác, và
tool ngoài ba mươi dòng của cả hai nhóm, được đếm chung một lần trong một dòng “và N công cụ
nữa” mà vẫn gọi được; script báo số ký tự các lời gọi đã trả cho nó
và số ký tự nó in ra; script không gọi gì thì nhận không; lời gọi hỏng cũng tính vào cái
script nhận; tên không có thì không có câu trả lời nào để đếm; script hỏng chỉ tính cái nó
in trước khi hỏng).tools.names, tools.search, tools.describe):
tests/test_script_discovery.py (một lời tra tới tiến trình giữ tool kèm tham số theo
tên, dù script viết theo vị trí hay theo tên; câu trả lời là dữ liệu script dùng tiếp; mỗi
cách viết sai (thừa, thiếu hay sai kiểu tham số, True thay cho số, chữ dài quá 1.000 ký
tự) là một lỗi bắt được và không hỏi ai, còn chữ dài đúng 1.000 thì được hỏi nguyên; lời tra
và lời gọi đếm riêng, đủ 40 lời tra cùng 25 lời gọi vẫn qua, lời tra thứ 41 bắt được còn lời
gọi thứ 26 vẫn dừng script; tiến trình cha từ chối một lời tra thì script bắt được; tool đặt
tên giống một lời tra không bao giờ được gọi như tool; câu trả lời quá lớn tính như mọi giá
trị script cầm; names là những tool gọi được theo thứ tự agent giữ chúng, kể cả tool mức
deferred, không có tool phải hỏi, ghi dữ liệu, nói thay agent hay bị ẩn; search xếp tool
khớp nhất lên đầu và chỉ trong số tool gọi được, mặc định năm, nhiều nhất mười, rỗng khi không
khớp, mô tả gọn một dòng và cắt ở 200 ký tự; tool chưa được nêu tìm được bằng một script rồi
gọi được bằng tên ở script sau; describe ghi đúng từng dòng cho tham số lồng nhau, enum,
mảng và tool không tham số, cắt ở dòng nguyên khi quá 2.000 ký tự, gợi ý tên gần nhất khi tên
không có, và nói phải làm gì thay với tool đang giữ mà script không được gọi; lời gọi hỏng vì
thiếu hay thừa tham số được thêm dòng cách gọi, lời gọi hỏng vì lý do khác hay lời gọi thành
công thì không; không tool có sẵn nào của sản phẩm, ở chế độ trợ lý, làm việc, master hay
không, mang tên của một lời tra; xếp hạng chạy được trên tool có sẵn và tên nêu nguyên văn
đứng đầu; một câu hỏi tới tiến trình cha mà query, limit hay name sai kiểu hay thiếu,
hoặc là câu hỏi không có, vẫn bị từ chối dù script không bao giờ hỏi thế),
tests/test_script_signature.py (describe chỉ cắt khi một dòng vượt quá mức nó được viết,
dòng vừa khít thì giữ; enum chỉ liệt kê giá trị khi có không quá tám giá trị, mỗi giá trị
ngắn và là chữ hay số nguyên, thiếu một trong ba điều ấy thì chỉ ghi kiểu; tham số có tên
không phải một từ dài tới bốn mươi ký tự, kể cả tên có dấu cách, gạch nối hay xuống dòng
giả một dòng khác, thì không hiện, ở trong object cũng vậy; schema chỉ đọc sâu bằng mức
tool_search đọc; lời của tham số gộp một dòng và cắt ở 120 ký tự, lời mô tả tool bỏ khoảng
trắng hai đầu và cắt ở 600), tests/test_script_runner.py (lời tra được trả lời ở tiến
trình này và không tính là lời gọi; lời tra bị từ chối là việc của script để bắt; script chạy
không có nơi tra thì được nói là không tra được; cái lời tra trả về là một giá trị, cái nó từ
chối là một lỗi; câu trả lời dài hơn một ống dẫn vẫn tới script nguyên vẹn),
tests/test_script_turns.py (một lượt
tra tool xong thì máy chủ không bị hỏi và run không giữ lời gọi nào; tool model chưa được
khai được một script tìm ra và script sau gọi).tools.batch):
tests/test_script_batch.py (một lô được trả lời từng lời gọi, đúng thứ tự đã viết,
viết theo vị trí hay theo tên calls đều được; một lời gọi lỗi là câu trả lời riêng của nó và
các lời gọi kia được giữ; thứ gửi đi là thứ JSON mang được, như với lời gọi lẻ; lô dài đúng
mười thì chạy, dài hơn một lời gọi thì không; mỗi cách hỏi sai (không có gì, hai thứ, thêm
tham số lạ, tên tham số khác, không phải list kể cả tuple hay chữ, rỗng, dài quá, phần tử
không phải dict, thiếu hay sai kiểu tool, args sai kiểu, khoá lạ trong phần tử, tham số quá
100.000 ký tự hay không phải JSON) là một lỗi bắt được, không lời gọi nào được gửi, và câu lỗi
nêu đúng số thứ tự của phần tử đầu tiên sai, đếm từ 0; lô bị từ chối không tốn lời gọi nào;
mỗi lời gọi của lô là một trong 25, kể cả lời gọi lỗi; lô nhiều hơn số lời gọi còn lại là lỗi
bắt được và không tốn gì, còn hết sạch thì script dừng như lời gọi lẻ thứ 26; lời gọi script
không được gọi làm dừng script dù nó đã in gì; câu trả lời của cả lô tính chung vào bộ nhớ như
mọi giá trị script cầm, dù từng câu đều nhỏ; ví dụ trong lời mô tả chạy đúng như được viết),
tests/test_script_batch_run.py (các lời gọi của lô chạy cùng lúc, nhiều nhất bốn và đủ bốn;
câu trả lời và các lời gọi giữ lại cho thẻ run theo thứ tự viết chứ không theo thứ tự xong;
mỗi lời gọi được tính giờ từ lúc nó bắt đầu chứ không từ lúc cả lô được hỏi; lời gọi lỗi là
câu trả lời riêng và các lời gọi kia vẫn chạy; tên không có là một lời gọi lỗi mà không nằm
trong các lời gọi giữ lại; hook của agent được hỏi về từng lời gọi; mỗi câu trả lời giao nguyên
tới mức script nhận; lời gọi trả tiền cho model mang giá của nó; lô có một lời gọi script không
được gọi thì không lời gọi nào chạy, script dừng, câu nói là của lời gọi đầu tiên theo thứ tự
viết trong số bị từ chối, và mỗi lời gọi của lô đều có đúng một câu trả lời; lô bị cắt giữa
chừng giữ các lời gọi đã xong theo thứ tự đã viết, còn lời gọi đang dở thì không; script hết
giờ giữa lô vẫn giữ các lời gọi nó đã làm và số ký tự nó đã nhận),
tests/test_script_runner.py (tiến trình con gửi một lô và từng lời gọi được trả lời ở tiến
trình này; lô bị từ chối là lỗi để script bắt; câu trả lời đầu tiên mang cờ dừng thì dừng
script dù nó đã in gì; chạy không có nơi gọi theo lô thì được nói là không gọi được; lô dài
đúng mức mà mỗi lời gọi mang tham số dài nhất, nặng nhất khi viết ra, vẫn tới đủ; dòng nói về
lô mà sai thoả thuận làm tiến trình bị coi là đã chết), tests/test_script_turns.py (mỗi lời
gọi của lô được tính tiền và giữ lại như khi gọi lẻ), tests/test_script_tool.py (lời mô tả
dạy tools.batch, nêu mức tối đa và có ví dụ), tests/test_script_discovery.py (tên batch
thuộc về tools như ba tên tra cứu: tool đặt tên vậy không gọi được từ script, và không tool
có sẵn nào của sản phẩm mang một trong bốn tên đó).tests/test_script_kept_answer.py (câu trả lời quá dài tới script đã cắt, kèm dòng
nói bản đầy đủ nằm ở đâu và đọc tiếp từ đâu; script đọc tiếp đúng từ chỗ bị cắt; dài đúng mức
thì tới nguyên và không giữ gì; mỗi câu trả lời dài của một script được giữ dưới một số
riêng, các câu trả lời dài của một lô cũng vậy, và script khác của cùng lượt giữ riêng phần
mình; agent không đọc lại được thì chỉ bị cắt, không giữ gì; script không bị chỉ sang một
tool nó không gọi được; lỗi dài bị cắt chứ không giữ; script đọc hết một câu trả lời một
triệu ký tự bằng năm lần đọc tiếp ở agent có trần 8.000 và các đoạn ghép lại đúng chỗ; đoạn
tool_output_read trả cho script vừa phần script cầm, còn dòng chỉ đường ở cuối, không nêu
độ dài thì dài hết mức, và không được giữ lần nữa; trong một lượt, lời đọc tiếp của chính
model sau script vẫn theo trần của agent; không ghi được thì cắt và không chỉ đi đâu; ngoài một lượt thì không giữ
gì; trong một lượt thì giữ dưới id lời gọi của chính model; câu trả lời dài mang nửa ký tự
được giữ và đọc với nửa ấy đã đánh dấu, script chạy tiếp và giữ đủ các lời gọi; lời gọi hỏng
ở ngoài tool chỉ hỏng riêng nó, bằng lời script bắt được, các lời gọi cạnh nó vẫn được trả
lời và đều được giữ).tests/test_script_live_calls.py (lượt phát từng lời gọi của script giữa lời gọi và
kết quả của script; lời gọi được liệt kê, tính vào run và vào cuộc trò chuyện khi script còn
chạy; lượt bị dừng lúc script đang gọi đã tính cho cuộc trò chuyện đúng số run đã tính; lời
gọi kết quả kể mà chưa từng được báo thì tính từ kết quả, một lần; các lời gọi chạy cạnh nhau
không ai nghe nên tính theo kết quả của chúng; chỉ lời gọi trả tiền cho model mới bị tính,
lời gọi không có giá được đếm riêng; phần còn phải tính là những lời gọi của kết quả chưa
được báo, hai lời gọi giống hệt nhau là hai; lời gọi được báo thì tính cho cuộc trò chuyện
ngay lúc được chuyển tiếp; các lời gọi của
một lô được báo theo thứ tự xong và giữ theo thứ tự đã viết; lời gọi nghe hai lần được giữ
một lần và tính tiền một lần; kết quả chỉ tính tiền những lời gọi chưa sự kiện nào báo và giữ
chúng như đã viết; hai lời gọi giống hệt nhau mỗi cái được tính tiền; script chạy lại sau
restart liệt kê lại từ đầu và trả tiền cho cả hai lần; lời gọi không thuộc step nào của run
thì bỏ qua; sự kiện đi dưới tên riêng và mang đủ lời gọi; một lời gọi đang chạy chuyển tiếp
cái được báo theo thứ tự rồi mới tới kết quả; cái một lời gọi báo không tới lời gọi sau và
không tới chỗ không có lời gọi nào; lời gọi bị bỏ giữa chừng thì bị huỷ và được chờ cho xong;
lượt bị dừng lúc script đang gọi thì lời gọi dừng theo; lỗi của một lời gọi được ném ra ở chỗ
đọc kết quả của nó).tests/test_script_turn_budget.py (bộ đếm cho đủ số được xin hoặc không cho gì, và đã
hết thì vẫn hết; mỗi lượt được mười lời gọi cho mỗi lần gọi model, đếm từ không, và bộ đếm của
test này không lọt sang test sau; script vượt
trần bị dừng ngay đó và lượt dừng theo, model không được hỏi lại; script được dùng hết cả
trần; các script của một lượt dùng chung một bộ đếm; cái model yêu cầu sau script đã tiêu hết
trần được đóng lại không chạy, nên lời gọi nào cũng có kết quả; lô không còn đủ chỗ thì không
chạy phần nào; script không bắt được việc hết trần; tên không có thì không tính; lượt sau đếm
lại từ đầu; lý do dừng tới người đọc bằng lời chứ không bằng mã; chỗ đã lấy cho những lời gọi
rốt cuộc không được làm thì trả lại được cho bộ đếm, trả lại không làm một lượt đã chạm trần
thành chưa chạm, và bộ đếm không xuống dưới không), tests/test_loop_guard.py
(cùng một script gọi đi gọi lại thì được nhắc rồi bị dừng như mọi lời gọi lặp; script mỗi lần
một khác thì không phải lặp), tests/test_tool_reply_openings.py (lời mở của câu từ chối
NESTED_HALTED_TOOL mà web đọc là lỗi đúng là chữ server viết).tests/test_script_gate.py::test_the_calls_a_batch_did_not_make_are_left_for_the_next_script_of_the_turn
(lô sáu lời gọi, một trong đó gọi một tên không có, bị dừng ở lời gọi thứ ba khi quyền tự chủ
đã bị thu lại: lượt chỉ bị tính hai lời gọi đã chạy, và script kế tiếp của lượt dùng được hết
phần còn lại của trần),
tests/test_script_batch_run.py::test_a_batch_cut_short_takes_from_the_turns_count_the_calls_that_began
(lô bị cắt khi các lời gọi còn treo: trong lúc lô chạy thì chỗ được giữ cho cả lô, cắt xong
thì lượt chỉ bị tính những lời gọi đã bắt đầu, bốn với lô chỉ đọc chạy cùng lúc và một với lô
có ghi chạy từng lời gọi).lib/run-summary.test.ts (run dừng vì nested_calls đọc thành câu, còn run xong mang
chữ đó thì để nguyên), state/thread-stored-status.test.tsx (lời gọi bị đóng vì lượt hết trần
lời gọi lồng vẫn là thẻ failed sau khi tải lại; câu chỉ giống mà thiếu con số thì không).tests/test_script_stored_calls.py (kết quả của script được lưu cùng cái nó đã gọi và
hai số đo, chữ có dấu giữ nguyên, còn nội dung tin nhắn chỉ là cái script in ra; model chỉ
được gửi cái script in ra, không gì trong phần lưu kèm; lượt sau không thấy ghi chú canvas
nào đứng trước kết quả của script; lượt nào đọc lại thì tin nhắn cũng là đúng tin đã lưu;
script không gọi gì vẫn giữ hai số đo; kết quả của tool khác không giữ gì; phần lưu kèm rỗng
trừ khi một script đặt nó; nhánh rẽ chép theo; bản ghi quỹ đạo của run chỉ nói cái model đã
đọc, còn lời gọi nằm ở step của run).tests/test_activity.py (tham số vẫn là một bảng tên và giá trị; giá trị dài và
danh sách lớn bị cắt; lời gọi nhiều tham số chỉ giữ mười hai tham số đầu theo đúng thứ tự,
rồi một mục ghi còn bao nhiêu; đúng mười hai thì giữ cả, mười ba thì ghi còn một; tên dài bị
cắt như giá trị; các tên trùng nhau sau khi cắt được đếm vào phần không nêu tên; số viết ra
dài hơn 160 chữ số bị cắt như chữ, đúng 160 thì giữ là số), tests/test_script_tool.py (lời
gọi của script cũng vậy), tests/test_trajectory_redact.py (bí mật nằm ở tên tham số vẫn
được che).tests/test_failure_previews.py (bước hỏng giữ nguyên điều nó nói khi vừa 600 ký tự;
dài hơn thì giữ 200 ký tự đầu, dấu … và 400 ký tự cuối, quá một ký tự cũng cắt, nên phần
dặn ở cuối một câu từ chối không bị cắt mất; độ dài được đo trên một dòng như nó được giữ,
sau khi mỗi quãng trắng thành một dấu cách; bước đạt vẫn giữ 160 ký tự như trước; lời gọi
hỏng của một script giữ được dòng nói tool ấy gọi thế nào, lời gọi đạt của script vẫn là 160;
lần hỏi model hỏng của script giữ lý do dài hơn 160; tuyến bị bỏ qua và điều model nói vẫn
giữ 160; hai câu từ chối của cổng duyệt đều kết bằng phần chỉ cách làm, tức là phần mà phép
cắt này giữ lại), tests/test_script_job_gate.py (bước đóng của một job script hỏng giữ câu
từ chối theo cùng phép cắt ấy),
tests/test_trajectory_redact.py::test_a_secret_across_either_cut_of_a_failure_is_covered_in_both
(bí mật nằm vắt qua một trong hai chỗ cắt của lời một bước hỏng, còn lại 8 ký tự, 19 ký tự hay
thiếu đúng một ký tự, được che trong bản xuất ở cả hai dạng, trên output của bước lẫn trên
lời gọi hỏng của một script: phần đầu trước dấu …, phần đuôi sau dấu …, và chữ quanh nó
giữ nguyên). Kiểm chứng đột biến: hai sửa đổi (chỉ tìm phần đuôi ở đầu cả đoạn chữ như trước;
không che phần đầu) đều bị test bắt.tool_script:
tests/test_script_handout.py (agent không nêu máy chủ nào được dựng với
tool_script đứng cuối, và script của nó gọi các tool có sẵn chỉ đọc; tool không theo danh
sách tools: và không theo mode: agent chỉ có workspace_read vẫn có nó ở cả hai chế độ;
agent không có tool nào script gọi được (chỉ ghi, chỉ hỏi, hay chỉ progress_note) thì được
dựng không có nó; script của agent không máy chủ đọc được tệp của agent; lời mô tả nêu từng
tool có sẵn theo thứ tự agent giữ, kèm tham số; đội giữ tool_script ở cuối, sau delegate,
cả khi nối lại, và lời mô tả không nêu delegate; script gọi cái agent đang giữ sau khi đội
nối lại chứ không phải lúc được dựng; máy chủ tới rồi đi thì lời mô tả đổi còn chỗ đứng thì
không, và máy chủ không với tới được trả lời mô tả về đúng như lúc chưa có nó; agent chỉ ghi
giữ tool_script chừng nào một tool của máy chủ còn chỉ đọc, và mất nó khi chủ bỏ
read_only; master được kể agent nào viết script như mọi tool khác, và agent mất hết tool
đọc thì được liệt kê không có nó), tests/test_script_tool.py (agent có tool chủ ghi chỉ
đọc, ở mức mở nào cũng được, giữ nó sau tool_search và trước các tool của máy chủ; máy chủ
không có tool nào script gọi được, hay agent không nêu máy chủ nào, thì agent vẫn giữ nó cho
tool có sẵn của mình; giao lại không lặp; chủ thôi ghi một tool là chỉ đọc thì lời mô tả thôi
nêu nó; script của agent đọc được tệp của agent và máy chủ của agent; lượt được uỷ thác giữ
bản sao danh sách tool lúc nó bắt đầu, còn script của nó chỉ gọi được cái agent đang giữ),
tests/test_api_mcp.py (agent không nêu máy chủ nào giữ tool_script trên API; lời mô tả
của agent có máy chủ nêu tool máy chủ đó mở cho script), tests/test_mcp_tools.py (một lần
nối dựng lại hộp đồ của mỗi agent giữ tool_script đúng một lần, còn agent không có gì để
thu về thì giữ nguyên hộp đồ), tests/test_mcp_tool_search.py,
tests/test_mcp_tool_search_turns.py, tests/test_mcp_turns.py (thứ tự khai cho model: tool
có sẵn, tool_search, tool_script, rồi tool của máy chủ; máy chủ đi thì tool_script ở
lại), tests/test_crew_roster.py (roster kể tool_script trong dòng Công cụ),
tests/test_agent_templates.py (agent dựng từ mẫu có danh sách tools: riêng vẫn có
tool_script ở cuối, ngoài những tool danh sách ấy nêu), tests/test_tool_replay.py (tool_script nằm trong số tool được gọi lại sau
restart, trừ ở agent bật script_calls: gated).tests/test_script_turns.py (script chạy không hỏi ai và model chỉ đọc cái nó in; run
giữ từng lời gọi của script; step của tool mang calls và tính tiền những lời gọi có trả cho
model như khi gọi trực tiếp; model được khai tool_script chứ không được khai các tool nó gọi;
lời gọi không được phép không tới máy chủ và không hỏi ai; script hỏng báo cho model cái đã in
và lý do; script bị restart cắt ngang được chạy lại từ đầu; run giữ số ký tự script nhận và số
ký tự nó in; tool không phải script thì step không có hai số đó; step giữ hai số khi chúng
bằng không và không có khi chưa ai đặt), tests/test_trajectory_redact.py
(bí mật nằm trong lời gọi của script được che cùng phần còn lại của bản ghi).script_calls: gated):
tests/test_script_calls_setting.py (ba giá trị là đúng ba chữ tệp ghi, none,
read_only, gated; mặc định là chỉ đọc; chỉ đọc từ agent.yaml của chính agent, agent mặc
định không truyền nó cho ai và cấu hình chung có ghi gì cũng không được thừa hưởng; giá trị
lạ, viết hoa, chuỗi true, off hay None, rỗng, bỏ trống, một con số hay một danh sách làm
tệp không nạp được, câu lỗi nêu cả ba chữ; boolean thật cũng bị từ chối, mở đầu bằng đúng câu
ấy; cả đội không được đọc lên với một tệp như vậy; sửa qua API thì bật, tắt được và agent đang
chạy theo ngay; sửa một khoá khác thì giữ nguyên nó; tạo agent mới kèm khoá này được; giá trị
lạ bị từ chối 422 trước khi ghi tệp, khi sửa cũng như khi tạo, boolean thì được dặn thêm chữ
phải viết). none lấy gì của agent thì ở mục “Công tắc script theo agent” bên dưới.tests/test_script_gated_scope.py (script như thế gọi được tool có ghi và tool phải
hỏi trước; đúng hai tool luôn chờ người, ask_user và schedule_create, đứng ngoài dù chúng
khai kiểu nào; năm tool không script nào gọi và bốn tên của tools vẫn đứng ngoài; tool ghi
của máy chủ MCP là của script trừ khi chủ để hidden, và tool không gọi được thì được chỉ
đường tới nó; danh sách giữ thứ tự agent đang giữ; tools.names, tools.search,
tools.describe và gợi ý tên gần đúng thấy cả tool ghi, chỉ ở agent đã bật; describe nói
vì sao một tool vẫn không gọi được; lời mô tả tool_script của agent đã bật nêu cả tool ghi
kèm đoạn nói về cổng duyệt, còn của agent khác giữ nguyên từng chữ; agent dựng thật ở hai chế
độ có thêm đúng các tool ghi và không tool nào khác; agent chỉ giữ tool ghi vẫn được giao
tool_script khi đã bật và không được giao khi chưa; tool ghi của máy chủ được trao cho
script, còn tool của máy chủ mà người phải được hỏi thì không chạy; script của agent dựng
thật ghi được tệp và chạy được lệnh, không đổi gì khi người phải được hỏi, và ở agent chưa
bật thì không ghi gì).tests/test_script_gate.py (bảng của cổng duyệt, theo đúng các luật của một lời gọi
trực tiếp: lời gọi mà cổng cho chạy không hỏi (cuộc tự chủ, lệnh khớp danh sách cho phép,
tool người đã bảo luôn cho phép) thì chạy và được ghi là gated; lời gọi mà người phải được
hỏi (lệnh khớp danh sách hỏi dù cuộc tự chủ hay lệnh đã được cho phép, cuộc cần duyệt) hay
lệnh bị cấm thì làm script dừng và không chạy, script không bắt được; lời từ chối nói phải
làm gì và chỉ nêu mẫu của chủ, không lặp lại lệnh; lý do hỏi của riêng một tool không được
chuyển cho script; tool phải hỏi trước mà không phải shell thì theo cuộc trò chuyện, không
theo danh sách nào của shell; tool không cần duyệt thì chạy như khi gọi trực tiếp; điều
người cho phép được đọc lại ở từng lời gọi; hai tool luôn chờ người vẫn đứng ngoài; agent
chưa bật bị từ chối như trước; lời gọi chỉ đọc không bao giờ tới cổng; không có lượt hay
cuộc trò chuyện không còn thì lời gọi có ghi bị từ chối; lời gọi đã chạy mà hỏng thì script
bắt được; hook của agent được hỏi về lời gọi cổng đã cho chạy; lời gọi bị từ chối không lấy
gì từ trần lời gọi lồng của lượt; lô có một lời gọi bị từ chối thì không lời gọi nào chạy,
từng lời gọi vẫn được trả lời, lời bị từ chối kèm lý do; lô có lời gọi ghi chạy từng lời gọi
một theo đúng thứ tự viết, còn lô chỉ đọc vẫn chạy song song; các lệnh của một lô chạy theo
thứ tự viết, mỗi lệnh có câu trả lời riêng; mỗi lời gọi của lô có ghi được xét lại khi tới
lượt nó: quyền tự chủ bị thu lại ở lời gọi thứ hai thì lời gọi thứ ba không chạy, script dừng
và không bắt được, cái đã chạy vẫn được kể; lời gọi bị từ chối lúc tới lượt được trả lời kèm
lý do, các lời gọi sau nó được trả lời là không chạy dù tự chúng vốn chạy được; cái một lời
gọi của lô để lại có đó khi lời gọi sau được xét, nên lệnh xoá qua một liên kết vừa được nối
ra ngoài thư mục tạm không chạy; tool mà agent không còn giữ lúc lời gọi tới thì không chạy
theo lời script, script dừng với câu nói vì sao).tests/test_script_gated_turns.py (lượt thật qua server: lời gọi đã qua cổng mang
gated ở mọi nơi kể lời gọi của script, tức calls của kết quả, sự kiện nested_call,
step của run và phần lưu kèm tin nhắn, và chỉ được ghi khi đúng; trong một lô chỉ lời gọi
qua cổng mang dấu; agent chưa bật thì script dừng và không lời gọi nào mang dấu; script bị
cổng dừng ở cuộc cần duyệt để lại lời gọi cho model gọi trực tiếp, người được hỏi, và tool
chạy đúng một lần sau khi được cho phép).tests/test_tool_replay.py (cùng một script bị restart cắt ở cùng một lời gọi: ở
agent chưa bật thì chạy lại từ đầu, ở agent đã bật thì lời gọi đóng bằng RESTART_CUT_TOOL
và không gì chạy lại, dù nó bị cắt ở một lời gọi đọc hay một lời gọi ghi; tool_script chỉ
được coi là gọi lại được khi script của agent không ghi được).hidden lọt vào, mọi agent
đều như đã bật, script của agent đã bật vẫn chạy lại sau restart, lời mô tả không đổi hay
chỉ nêu tool đọc, agent chỉ có tool ghi không được giao script, khoá được thừa hưởng, giá
trị nào cũng nạp, thiếu khoá coi là bật, trình sửa không biết khoá, dấu luôn hay không bao
giờ được ghi, roster đánh dấu sai agent hay sai câu) đều bị test bắt.script_calls: none, script_llm: false):
tests/test_script_switches.py (tệp ghi none được đọc thành đúng chữ ấy; tệp ghi
off, no, false, on, yes, true, viết hoa hay thường, mà YAML đọc thành boolean, làm
tệp không nạp được, câu lỗi nêu cả ba chữ và dặn viết none; agent ghi none được dựng không
có tool_script và giữ mọi tool khác theo đúng thứ tự; tool của máy chủ MCP mà script gọi được
cũng không đem tool_script về, kể cả khi máy chủ nối lại; script của một lịch vẫn chạy ở agent
như thế, và chỉ đọc: lời gọi lệnh làm script dừng với dòng bảo gọi trực tiếp, lệnh không chạy;
master được kể agent ấy không có tool_script, và đổi từ gated sang none thì roster thôi
ghi nó viết script có ghi; script_llm mặc định bật, chỉ đọc từ tệp của chính agent, cấu hình
chung có tắt cũng không được thừa hưởng; chỉ boolean thật mới đặt được nó: chuỗi false,
true, no, off, chuỗi rỗng, 0, 1, bỏ trống hay một danh sách làm tệp không nạp được; agent
tắt script_llm đọc lời mô tả tool_script không có chữ llm nào, ở cả hai mức
script_calls, phần còn lại giữ nguyên từng chữ; script vẫn hỏi thì nhận một lỗi bắt được và
chạy tiếp, không bắt thì dừng như mọi lỗi, không model nào được hỏi và không lời gọi nào được
ghi; lần hỏi bị từ chối như thế vẫn được script đếm, nên script bắt lỗi rồi hỏi tiếp bị dừng ở
lần hỏi quá mức, không lời gọi nào trên thẻ run và không model nào nghe; script của một lịch ở
agent ấy cũng không hỏi được model: run error, không tiền, không cuộc trò chuyện; sửa qua API
thì agent trong đội mất
tool_script mà không cần khởi động lại và được trao lại khi đổi về read_only, tệp ghi đúng
chữ none chứ không phải null của YAML; tắt script_llm qua API thì lời mô tả của agent
trong đội đổi theo, gỡ khoá thì về mặc định và tệp không còn dòng ấy; lần lưu có hiệu lực từ
lượt kế tiếp: lượt đang chạy lúc lưu giữ tool_script và model nó đã có tới hết, script đang
chạy của nó xong êm và script sau đó của nó vẫn hỏi được model, còn lượt kế tiếp của cùng cuộc
trò chuyện nhận agent như đã lưu, không tool_script; lần chạy kế tiếp của một lịch cũng nhận
agent như đã lưu: trước lần lưu script của lịch hỏi được model, sau lần lưu run là error với
câu không hỏi được model; sửa một khoá khác giữ nguyên cả hai; tạo agent mới kèm cả hai được;
giá trị không phải boolean cho script_llm, và boolean cho script_calls, bị từ chối 422 trước
khi ghi tệp, câu lỗi của script_calls dặn chữ phải gửi).store / load):
tests/test_script_store.py (script đọc lại được điều chính nó vừa ghi, và không gì
vào bảng trước khi nó chạy xong; cái đã giữ còn đó cho script sau của cùng cuộc trò chuyện;
store(khoá, None) bỏ khoá, khoá chưa từng có cũng không sao, còn None nằm trong một list
hay dict thì được giữ như mọi giá trị; script hỏng vì một lỗi không
ai bắt hay bị dừng ở lời gọi nó không được gọi thì giá trị mới và khoá nó vừa bỏ đều không
được ghi; script hết giờ hay bị dừng giữa chừng không giữ gì; lần chạy xong sau mà làm chỗ
giữ quá mức thì không cái nào của nó được giữ, kể cả cái đứng riêng thì vừa, và kết quả là
script lỗi nói rõ điều đó; hai cuộc trò chuyện của một agent giữ riêng; hai agent có job
cùng tên không đọc được của nhau; lượt của cuộc trò chuyện do job mở giữ dưới lịch của job;
lượt người tiếp lời trong cuộc ấy giữ dưới chính cuộc trò chuyện; script chạy ngoài mọi cuộc
trò chuyện được báo là không có chỗ giữ, bắt được và chạy tiếp; model được kể hai tên, rằng
khoá chưa có thì đọc ra None, rằng store(khoá, None) là quên khoá đó và rằng chỉ giữ khi
script xong không lỗi, ở cả hai chế độ script_calls).tests/test_script_limits.py (khoá là chuỗi không rỗng, dài đúng tới mức và không
hơn, kể cả khi bỏ khoá; giá trị dài đúng tới mức và không hơn, giá trị quá dài không được
giữ và script đọc lại giá trị có trước đó; giá trị được đo theo JSON nó được lưu thành, tính
theo ký tự: một chữ có dấu là một, một dấu xuống dòng là hai; set, NaN, vô cực và dict có
khoá không phải chuỗi là lỗi script bắt được, không ghi gì; cái JSON viết được thì đọc lại y
như đã ghi, riêng tuple thành list; một chỗ giữ chứa khoá và giá trị đúng tới mức, đo theo
cái nó sẽ chứa sau lần ghi, nên ghi đè hay bỏ khoá thì trả lại chỗ; chỗ đã đầy vẫn bỏ được
khoá; chỗ khác và agent khác không lấy gì của chỗ này; cái không giữ được bị từ chối ngay
trong tiến trình chạy script, trước khi hỏi server; server đo lại mọi thứ tiến trình gửi lên
và tự từ chối cái làm đầy chỗ; store nhận đúng một khoá và một giá trị, load đúng một
khoá; store không trả gì và load khoá chưa có trả None; giữ và đọc lại nhiều hơn cả số
lời gọi lẫn số lần tra cứu vẫn không lấy gì của hai trần đó).tests/test_api_script_store.py (agent chưa giữ gì thì danh sách rỗng; mỗi chỗ giữ
kèm tổng số ký tự, mỗi giá trị kèm số ký tự của nó và giờ ghi theo UTC, xếp theo chỗ rồi
theo khoá; giá trị dài chỉ hiện phần đầu kèm độ dài thật; agent chỉ thấy cái script của
chính nó giữ; agent không có thì 404; limit_chars nằm một lần ở gốc, là đúng mức mà
store của script bị giữ, 262.144, cả khi agent chưa giữ gì; chỗ giữ của một lịch mang
kind: job, ref là id của lịch và title là tên lịch; lịch cùng id của agent khác không
đặt tên cho chỗ của agent này, chỗ ấy là orphan; lịch tạo từ một cuộc trò chuyện đặt tên
cho chỗ của nó cho tới khi bị gỡ, gỡ rồi thì chỗ ấy còn đó và là orphan; chỗ giữ của một
cuộc trò chuyện mang kind: conv và tiêu đề của cuộc ấy, cuộc không còn thì title là
null và là orphan; tiêu đề chỉ có khoảng trắng thì title là null mà không phải
orphan; chỗ không thuộc hai loại, kể cả tên chỉ gần giống tiền tố, là other, ref là cả
tên, không bao giờ orphan; POST, PUT và PATCH không ghi được gì qua địa chỉ này; cái script
của một lượt giữ thì hiện ra, và mất theo khi xoá cuộc trò chuyện).tests/test_api_script_store_edit.py (một giá trị được đọc nguyên dù dài tới đâu,
kèm số ký tự và giờ ghi như trong danh sách; khoá có &, =, /, #, ?, %, dấu cách,
dấu +, chữ có dấu, hay dài hết mức, được gọi đúng như script đã viết, khi đọc cũng như khi
xoá; giá trị không có ở đó là 404, kể cả tên chỗ hay khoá chỉ giống phần đầu, chứa ký tự đại
diện của LIKE, khoá rỗng, hay giá trị agent khác giữ ở chỗ cùng tên; agent không có thì 404
dù bảng có dòng mang id ấy; thiếu namespace hay key khi đọc là 422; namespace hay key
nêu hai lần, khi đọc cũng như khi xoá, là 422 nêu tên tham số ấy, không gì được đọc và không
gì bị xoá, dù hai lần nêu cùng một giá trị hay lần xoá không nêu khoá; xoá một khoá thì chỉ
khoá ấy mất, ở danh sách, ở lần đọc và với script đọc kế tiếp; không nêu khoá thì xoá cả chỗ,
và chỗ chỉ giống phần đầu tên là chỗ khác; xoá cái không có trả deleted: 0 và không phải
lỗi, khoá rỗng không bao giờ là cả chỗ; agent chỉ xoá được cái script của chính nó giữ; agent
không có thì 404 và không gì bị xoá; xoá mà không nêu namespace là 422, không địa chỉ nào
xoá hết mọi thứ của một agent trong một lần; cái đã xoá vẫn mất khi script kế tiếp bị từ chối
vì ghi quá mức; script đang chạy mà đã đọc chỗ giữ thì đi tiếp theo cái nó đã thấy và khi xong
chỉ ghi lại khoá chính nó ghi, cái nó chỉ đọc vẫn mất; script chưa đọc thì thấy chỗ giữ như
sau lần xoá; script bỏ một khoá đã bị xoá vẫn xong êm; xoá agent thì mọi chỗ giữ của nó mất,
của agent khác còn nguyên, và agent mới mang cùng id bắt đầu trống; agent không xoá được, vì
còn agent khác giao việc cho nó, vì là master, hay vì thư mục không dời đi được, thì giữ
nguyên cái nó đã giữ).tests/test_script_store_agent_removed.py (script chạy xong êm thì giữ được cái nó ghi,
trong một lượt cũng như khi một lịch chạy nó; script đang chạy lúc agent bị gỡ thì không dòng
nào còn dưới id ấy, và nó được trả lời như script hỏng, kèm dòng nói agent đã bị gỡ; agent mới
được tạo cùng id trước khi script cũ xong thì bắt đầu trống, không nhận gì của script ấy, và tự
giữ được như mọi agent; script mà một lượt sống lâu hơn agent chạy sau lúc gỡ cũng không giữ
được gì, kể cả khi id đã có chủ mới; script không ghi gì thì xong êm như thường; gỡ một agent
không lấy gì của script agent khác đang chạy; lưu agent không phải là gỡ, script đang chạy lúc
lưu vẫn giữ được; ở bảng: lần ghi mang đời của một agent đã bị gỡ không ghi gì, bỏ khoá cũng là
ghi; mỗi lần gỡ một id cho một đời khác mọi đời trước, id khác không đổi; script được báo agent
đã bị gỡ chứ không phải kho đầy; script của agent đã bị gỡ load lần đầu sau lúc gỡ thì nhận
rỗng và được trả lời như script hỏng với cùng dòng ấy, khi id chưa có chủ cũng như khi agent
mới đã giữ giá trị ở đúng chỗ đó; script đang chạy lúc gỡ mà chưa đọc cũng vậy; cái script đã
đọc trước lúc gỡ thì nó đọc tiếp và xong êm; ở bảng: lần đọc mang đời đã qua không trả gì, đời
hiện tại đọc được của mình, và không lần gỡ nào lọt vào giữa lúc so đời và lúc đọc; script
chỉ đọc cho agent đã bị gỡ vẫn được báo agent đã bị gỡ).
Script ở đây chờ ở một công cụ chỉ trả lời khi test cho phép,
nên lần gỡ rơi đúng chỗ test đặt: sau khi script đã ghi, trước khi nó xong. Kiểm chứng đột biến:
năm sửa đổi (lần ghi không so đời, gỡ agent không đổi đời, lấy đời lúc script bắt đầu thay vì
lúc agent được dựng, agent được dựng không nhận đời hiện tại, luôn báo kho đầy) đều bị test bắt.
Phía đọc, thêm năm sửa đổi (lần đọc không so đời, so đời ở một lần giữ khoá riêng trước lần
đọc, script đọc không nêu đời, không ghi nhớ rằng agent đã đi, không báo cho script chỉ đọc)
đều bị test bắt.tests/test_store.py (xoá một cuộc trò chuyện xoá cả chỗ giữ của nó, của agent nào
cũng vậy, và không đụng chỗ của cuộc khác hay của job; lần xoá hỏng giữa chừng thì cái đã
giữ còn nguyên, vì xoá trong cùng một giao dịch).load là một lần
tra cứu, lượt của cuộc trò chuyện do job mở giữ dưới cuộc trò chuyện, lời mô tả không nói
khi nào thì giữ, lời mô tả không nói khoá chưa có thì đọc ra gì) đều bị test bắt.tests/test_script_limits.py, hai ca mới trong
danh sách không phải JSON), store(khoá, None) xoá luôn khoá cùng tên ở chỗ giữ khác của
cùng agent hay ở agent khác (tests/test_script_store.py, hai test về hai cuộc trò chuyện
và hai agent nay xoá ở một bên rồi đếm lại bên kia), và job script chỉ báo các lời gọi của
nó khi script đã xong, hoặc báo lời gọi nào cũng là lời gọi đầu
(tests/test_script_job.py::test_the_calls_of_a_jobs_script_are_listed_and_charged_as_each_ends).tests/test_scheduler_agent_removed.py (trên một home có master, hai agent yaml mà
id của agent ở lại bắt đầu bằng id của agent bị xoá và hai job trùng tên, cùng một agent
của kit: xoá agent thì job khai trong tệp của nó rời đồng hồ và GET /api/jobs ngay, lịch
tạo từ chat và mọi công tắc bật/tắt của job của nó mất khỏi database, của agent kia còn
nguyên, chạy tay job ấy là 404; id được so như chữ, nên a_b không lấy gì của axb,
A_B, a_b-two hay a_; job đến giờ mà agent không còn trong đội thì bị bỏ qua, job đứng
sau nó trong nhịp vẫn chạy, và qua hai nhịp chỉ có một dòng log nêu tên job; agent bị xoá
giữa lúc một job trước đó của nhịp đang chạy cũng không chặn job nào, với agent yaml bị xoá
qua API cũng như với agent của kit rời đội khi đang giữ một lịch từ chat; agent của kit
không xoá được qua API (409) và giữ nguyên lịch và công tắc của nó; nạp lại sau khi thư mục
của một agent bị dời đi không để lại trên đồng hồ job nào của agent không còn trong đội; kết
quả của một run mà agent đã rời đội không được gửi cho ai và không ném lỗi; agent tạo lại
dưới id vừa xoá không có job nào trên đồng hồ, không dòng nào trong database, và nhịp kế chỉ
chạy job của các agent khác). Kiểm chứng đột biến: mười lăm sửa đổi (xoá agent không báo
đồng hồ, không bỏ job khai trong tệp, không xoá lịch từ chat, không xoá công tắc, chọn job
theo chữ đầu của id thay vì theo agent, xoá lịch từ chat không kèm điều kiện, so id bằng
LIKE ở cả hai bảng, bỏ dấu / sau id, bỏ phép kiểm ở nhịp, giữ job trên đồng hồ sau khi
đã báo, hạ dòng log xuống mức gỡ lỗi, và hai sửa đổi để lần gửi kết quả tra thẳng agent)
đều bị test bắt.components/agent-editor/delete-agent.test.tsx (hộp xoá agent nói, trước ô gõ id,
thứ thư mục trong .trash không mang về: những gì script của agent giữ lại, lịch nó được tạo
từ chat và trạng thái bật/tắt của từng lịch; hội thoại và lịch sử run được nêu là giữ nguyên).tests/test_profile_schedules_live.py (qua API, trên một home có một agent yaml, đồng
hồ đứng ở chỗ test đặt và lệnh của job được thay bằng một bản ghi lại: đổi cron thì job chạy ở
giờ mới, giờ cũ không còn chạy và giờ mới đã qua trong ngày không được chạy bù; đổi every thì
khoảng chờ tính từ lúc lưu; lần lưu không đụng tới giờ của một dòng, dù đổi lệnh và tên của
chính dòng ấy hay tên của agent, không dời lần chạy kế tiếp, và lần chạy ấy dùng lệnh vừa lưu;
dòng tệp vừa bật lại không chạy bù giờ đã qua trong lúc nó tắt; dòng mới chạy ở giờ đầu tiên
sau lúc lưu, không sớm hơn; dòng bị bỏ không chạy nữa, rời GET /api/jobs và chạy tay là 404,
dòng ở lại vẫn chạy; dòng bị bỏ mất công tắc tạm dừng, và dòng thêm lại dưới cùng id là job
mới, đang bật, không chạy bù giờ đã qua khi không tệp nào nêu nó; công tắc tạm dừng sống qua
lần lưu đổi lệnh và lần lưu đổi giờ của dòng; job đang chạy lúc dòng của nó được lưu thì xong
với tên và lệnh nó bắt đầu, không chạy lại, và dòng mới chạy ở giờ của nó; job đang chạy lúc
dòng của nó bị bỏ thì xong và lần hỏng ấy không được đếm cho dòng thêm lại; dòng được đổi giờ
khi một job trước nó của nhịp đang chạy thì chờ giờ mới; dòng bị bỏ lúc đó không chặn job
đứng sau; dòng được đổi lệnh lúc đó chạy đúng một lần, bằng lệnh mới; danh sách job đang đọc dở
khi một dòng bị bỏ vẫn được trả lời đủ; dòng bị bỏ không để lại số lần hỏng nào cho dòng tạo
lại dưới id của nó, còn lần lưu giữ dòng thì đếm tiếp từ số cũ; agent bị xoá mang theo số lần
hỏng của job của nó; agent tạo kèm lịch, agent cài từ một template có lịch và thư mục chép tay
rồi reload đều có job trên đồng hồ ngay, câu trả lời của lần cài là needs_restart: false;
lần cài force đè lên agent đang chạy vẫn là needs_restart: true, live rỗng, và đồng hồ
chưa có job nào của tệp mới; tệp sửa tay của agent đang chạy lên đồng hồ khi một khoá ở Kết
nối được lưu; giờ của memory_consolidate theo cùng quy tắc, và gỡ khoá ấy thì job và công
tắc của nó đi theo; lần lưu mà tệp không ghi được là 422 mang lý do của hệ điều hành, còn
agent đang chạy, các dòng job, công tắc tạm dừng và giờ chạy vẫn như trước; agent tạo mới mà
tệp không ghi được không vào đội; lần sửa mà bước dựng agent từ chối không ghi gì vào tệp;
mỗi thay đổi về việc đội có những job nào (lưu, tạo, xoá agent, cài template, reload, lưu
một khoá ở Kết nối) làm luồng activity phát đúng một payload jobs, còn lần lưu bị từ chối
và lần reload không đem ai vào thì không phát gì);
tests/test_api_agents_edit.py::test_no_save_asks_for_a_restart_and_a_save_of_schedules_no_more_than_another
(restart_required là danh sách rỗng ở mọi lần lưu). Kiểm chứng đột biến: hai mươi ba sửa
đổi (dòng đổi giờ giữ mốc cũ, xoá mốc của dòng bị bỏ, giữ công tắc của dòng bị bỏ, xoá công tắc
hay số lần hỏng của mọi dòng được lưu, không đọc lại bảng, giữ số lần hỏng của dòng bị bỏ,
agent bị xoá để lại số lần hỏng, lần lưu nào cũng đặt lại mốc, bỏ qua lần đổi every, bỏ qua
lần đổi cron, dòng vừa bật lại giữ mốc cũ, dòng mới nhận mốc id của nó để lại, bỏ phép xét lại
giữa nhịp, xét lại cả job mà agent đã rời đội, chỉ xét lại dòng bị bỏ, đánh dấu dòng bị bỏ qua
như đã xét, lần lưu, lần nhận agent hay lần lưu ở Kết nối không báo đồng hồ, lần cài vẫn đòi
khởi động lại vì có lịch, lần cài không bao giờ đòi, lần lưu vẫn nêu một lý do khởi động lại)
đều bị test bắt. Sáu sửa đổi nữa ở thứ tự của lần lưu và ở tín hiệu cho trang (đồng hồ đọc
lại lịch mà không báo luồng, agent rời đội mà không báo luồng, ghi tệp trước khi dựng agent,
ghi tệp sau khi nối, ghi tệp sau khi nối nhưng trước khi đồng hồ đọc lại, không ghi tệp) cũng
đều bị test bắt. Một sửa đổi không ép được bằng test: sửa bảng tại chỗ thay vì đặt bảng mới
chỉ hỏng khi hai luồng chạm nhau.hooks/use-agent-draft.test.ts (lần lưu đổi lịch được nói là có hiệu lực ngay khi
server không nêu lý do khởi động lại nào; cron của memory_consolidate cũng là một lịch; lần
lưu không đổi lịch nào không nói gì và rút lại câu của lần trước; server nêu một lý do thì
không gọi lịch là đã có hiệu lực; lần lưu bị từ chối rút lại câu ấy; câu ấy rút đi ngay khi
một ô được sửa tiếp, và khi form được đặt lại về điều tệp đang giữ; mở agent khác thì câu về
lịch của agent trước không còn), components/agent-editor/schedules-section.test.tsx (sau
khi lưu một dòng lịch, và sau khi bỏ một dòng, trình sửa nói đúng câu “Đã lưu. Thay đổi lịch
chạy có hiệu lực ngay, không cần khởi động lại.” trong một vùng role="status", không chữ
nào đòi khởi động lại), components/crew-panel.test.tsx (cài một template: câu về khởi
động lại chỉ hiện khi agent đã ghi mà không vào được đội đang chạy).app-jobs-follow-crew.test.tsx (màn Lịch chạy có dòng lịch vừa thêm mà không
tải lại trang; không còn dòng vừa bỏ; lần lưu không đổi lịch giữ nguyên các dòng; danh sách
job được đọc lại khi một agent được tạo, khi một template được cài và khi một agent bị xoá).manage-smoke.spec.ts (“a schedule added from the jobs list through the agent’s
editor is on the list at once”: đúng câu ấy, không còn băng khởi động lại, hai dòng trên màn
Lịch chạy mà không tải lại trang); schedule-script-editor.spec.ts và
script-jobs-phone.spec.ts (câu ấy hiện mà trang không rộng hơn màn hình).role="alert", câu vẫn đòi khởi động lại) đều bị test bắt; sửa đổi “lần lưu
không đọc lại job” còn bị manage-smoke.spec.ts bắt trong trình duyệt thật. Câu ấy rút đi ở
lần sửa kế tiếp nên lần lưu bị từ chối không còn nhánh riêng: muốn lưu lại thì phải sửa một ô
trước, và lần sửa ấy đã rút câu.tests/test_job_failure_streaks.py (job lệnh hỏng lần đầu thì kênh nhận ngay một tin
bốn dòng: tên lịch, mã thoát kèm dòng cuối lệnh in ra, chỗ xem run, và câu nói tin nhắc kế tiếp
đến sau 6 giờ; job hỏng mỗi năm phút suốt một ngày, 288 lần chạy, nhắn một nhúm tin chứ không
phải mỗi lần một tin và cũng không im sau tin đầu, hai tin liền nhau không gần hơn 6 giờ và
không xa hơn 6 giờ cộng một lần chạy, tin cuối nói đó là lần hỏng thứ 217 liên tiếp; lần hỏng
trước khi hết 6 giờ chỉ được đếm; đúng 6 giờ thì tin kế nói số lần hỏng liên tiếp và từ lúc
nào, và khoảng chờ sau đó tính từ tin ấy; tin nhắc mang lý do của lần chạy gần nhất; mỗi job
được đếm riêng; tin mà kênh không nhận, vì không có chỗ nhận, vì bị từ chối hay vì không trả
lời trong hạn, được thử lại ở lần hỏng kế tiếp với số lần đã đếm, và khoảng 6 giờ chỉ bắt đầu
khi một tin tới nơi; đồng hồ không có ai để báo vẫn đếm; job chạy lại được thì có đúng một dòng
nêu số lần hỏng, và chỉ khi chủ đã từng được báo; lần hỏng sau khi job chạy lại là một chuỗi
mới, được báo ngay; dòng báo chạy lại mà kênh không nhận thì không gửi lại; job lệnh chạy tốt
không nhắn gì và không đưa ra lý do hỏng nào; run bị dừng, lượt hỏng trong một cuộc trò chuyện
và lượt đang chờ duyệt đóng chuỗi mà không có dòng nào, còn lượt chạy xong thì đóng chuỗi bằng
dòng chạy lại; khoảng chờ tính từ lúc kênh nhận tin chứ không từ lúc run kết thúc; lần hỏng
được đếm trong lúc một tin đang trên đường vẫn được giữ; job rời đồng hồ trong lúc tin đang
trên đường không để lại chuỗi nào; chuỗi mất theo job và theo agent, id được so trọn vẹn nên
agent có id chỉ giống phần đầu giữ chuỗi của nó; job rời đồng hồ trong lúc đang chạy không được
báo; với lệnh thật: mã thoát và dòng cuối, dòng in ra stderr, dòng trống ở cuối được bỏ qua,
lệnh không in gì thì chỉ có mã thoát; lệnh bị dừng vì quá hạn được kể là chạy quá hạn, với đúng
hạn 900 giây của job lệnh; dòng cuối dài bị cắt còn 200 ký tự đầu và 400 ký tự cuối, và dòng in
trước nó không có trong tin; job script hỏng liên tiếp giữ dòng đầu của riêng nó và dòng cuối
chung; job prompt hỏng liên tiếp không bao giờ được đếm; lần cô đọng bộ nhớ ném lỗi được báo
bằng tóm tắt của run, và job tới hạn sau nó trong cùng nhịp vẫn chạy; lần cô đọng mà model
không trả về gì được báo bằng đúng dòng nói vậy; lần cô đọng không có gì mới là done và không
được báo; lần cô đọng ném lỗi trước khi có run của riêng nó, hoặc khi run của nó còn mở, thì
lỗi được ném ra như cũ và không ai được báo bằng run của một đêm trước; GET /api/jobs cho
thấy failing qua từng chặng: null, đã đếm mà chưa báo được, đã báo kèm giờ báo và giờ nhắc
kế, đếm thêm trong khoảng chờ, rồi null lại sau khi job chạy được, các mốc mang múi giờ của
chủ). Kiểm chứng đột biến: hai mươi bảy sửa đổi (coi tin gửi hỏng là đã báo, bỏ phép kiểm khoảng
chờ, báo chạy lại cho chuỗi chưa từng báo, báo chạy lại ở mọi kết cục, đòi khoảng chờ dài hơn
hẳn 6 giờ, tính khoảng chờ từ lúc chuỗi bắt đầu, lấy giờ báo là lúc run kết thúc, ghi đè chuỗi
bằng bản đọc trước khi gửi, giữ chuỗi qua một run bị dừng, bỏ dấu / sau id agent, không quên
chuỗi của job, tính giờ nhắc kế từ lúc chuỗi bắt đầu, tin thứ hai kết như tin đầu, lần thử lại
của chuỗi chưa báo kết như tin đầu, vẫn đếm job đã rời đồng hồ khi đang chạy, dòng job không
mang chuỗi, lấy dòng đầu thay dòng cuối, lấy dòng cuối kể cả khi trống, kể lệnh quá hạn bằng mã
thoát, đưa lý do hỏng cho lệnh chạy tốt, không bắt lỗi của lần cô đọng, nuốt lỗi của lần cô
đọng khi run còn mở, báo bằng run của một lần cô đọng trước, không báo lần cô đọng mà model
không trả về gì, chỉ báo job script, bỏ hạn chờ của tin, coi mọi câu trả lời khác None của
kênh là đã nhận) đều bị test bắt.tests/test_script_job_failure_notice.py (lý do của job script và đường gửi tin, ở mục
“Lịch chạy script” bên dưới).tests/test_job_streak_reaches_the_page.py (dòng job nói telling từ lúc tin lên
đường tới lúc kênh trả lời, rồi mang giờ báo; tin mà kênh không nhận, trả lời False hay ném
lỗi, không còn được nói là đang trên đường; lần gửi bị cắt giữa chừng cũng vậy; tin nhắc lại
đang trên đường giữ nguyên giờ báo của tin trước; lần hỏng được đếm trong lúc một tin đang chờ
vẫn còn sau khi tin ấy không tới nơi; lần hỏng trong khoảng chờ không gửi gì và không nói
telling; dòng mà một trang đọc đúng lúc run kết thúc được đọc lại khi chủ đã được báo: luồng
activity phát đúng một lần payload {"type": "jobs"}, không mang gì khác; lần hỏng không báo
được cho ai, lần hỏng chỉ được đếm trong khoảng chờ và job vừa chạy lại được cũng phát tín hiệu
ấy; job chưa từng hỏng thì không). Kiểm chứng đột biến: mười bốn sửa đổi (không bao giờ đặt
telling, không bao giờ xoá nó, ghi giờ báo dù tin không tới, không bao giờ ghi giờ báo, dòng
job không mang telling, dòng luôn nói telling, chỉ xoá telling khi lần gửi trả về, ghi
đè bằng chuỗi đọc trước lúc chờ, không bao giờ báo trang, báo trang sau mọi run, chỉ báo khi
không có gì đổi, báo trước khi đếm, phát một tên sự kiện khác, payload mang thêm khoá) đều bị
test bắt.components/job-last-run.test.tsx (dòng nói số lần hỏng liên tiếp, giờ chủ được báo
và giờ nhắc kế; tin chưa tới nơi thì nói lần hỏng kế tiếp sẽ thử lại; tin đang trên đường mà
Telegram chưa trả lời thì nói “Đang báo qua Telegram.”; tin nhắc lại đang trên đường vẫn nói
lần báo trước; một lần hỏng không được gọi là chuỗi; server không nêu giờ nhắc kế thì câu dừng
ở giờ báo; job có lần chạy gần nhất hỏng mà không ở trong chuỗi nào thì không có câu; server
cũ không gửi khoá ấy cũng vậy; job chưa xong lần chạy nào vẫn có câu),
lib/relative-time.test.ts (dayClock: giờ trong hôm nay, Hôm qua HH:MM, còn lại là ngày
kèm giờ, tính theo lịch của người xem; chuỗi không phải thời điểm được để nguyên),
hooks/use-activity.test.ts (payload jobs gọi đúng một lần hàm đọc lại job, để nguyên
trạng thái các run và không đọc danh sách run; hàm được gọi là hàm của lần vẽ gần nhất, trên
luồng mở từ đầu; không có hàm nào thì trạng thái không đổi).app-jobs-follow-stream.test.tsx (dòng job được đọc lại khi luồng phát jobs
mà không run nào kết thúc, từ “Đang báo qua Telegram.” sang giờ đã báo; sang câu tin không
tới nơi; hết câu hỏng khi job đã chạy lại được; câu trả lời của lần đọc hỏi trước mà về sau
không đè lên lần đọc hỏi sau; lần đọc mà luồng đòi bị hỏng thì màn nói không đọc được danh
sách, không vẽ một danh sách rỗng; công tắc tạm dừng vừa gạt không bị một lần đọc hỏi trước
đó mà về sau gạt ngược lại; dòng lịch tạo từ chat vừa xoá không trở lại vì lần đọc ấy; công
tắc hiện theo chính câu trả lời của server trước khi danh sách được đọc lại; sau lần gạt,
danh sách được đọc lại nên điều mà lần đọc bị bỏ mang về không mất).jobs-list-smoke.spec.ts (“a job that keeps failing says since when and what
the owner was told”, ở ba bề rộng; “a notice on its way becomes one the owner was told of
when the stream says the list changed”: luồng thật của trình duyệt được giữ lại, rồi phát
jobs, và dòng đổi câu mà không tải lại trang), legibility-smoke.spec.ts (câu ấy đạt 4.5:1
ở nền sáng và nền tối).told_at là null, gọi một lần hỏng là chuỗi, không bao giờ nêu giờ nhắc kế, dayClock lệch
một ngày ở “Hôm qua”, chỉ hiện câu khi đã báo), bốn ở công tắc và nút xoá (lần gạt không đọc
lại danh sách, lần gạt chỉ bỏ lần đọc đang chờ mà không đọc lại, lần xoá không đọc lại danh
sách, công tắc chỉ hiện điều lần đọc lại mang về) và mười ba ở đường đọc lại (không nghe sự kiện
jobs, nghe mà không gọi hàm, để tín hiệu đi tiếp vào trạng thái run, tín hiệu đọc thêm danh
sách run, giữ hàm của lần vẽ đầu, App không trao hàm nào, vẽ câu trả lời nào về sau cùng, chỉ
vẽ lần đọc đầu tiên, coi lần đọc hỏng là danh sách rỗng, không bao giờ nói tin đang trên
đường, nói vậy khi không có tin nào, để “đang báo” thắng “đã báo”, đổi câu) đều bị test bắt;
sửa đổi “không nghe sự kiện jobs” còn làm spec Playwright nói trên hỏng trong trình duyệt
thật.tests/test_job_run_summary.py (chữ không quá 160 ký tự thì tóm tắt là cả chữ ấy,
không dấu gì; dài hơn thì là 160 ký tự cuối đứng sau dấu …, 161 ký tự; job command nói
nhiều, xong hay hỏng, được tóm như vậy; script chạy xong mà in nhiều cũng vậy; script hỏng
được tóm bằng dòng đã làm nó dừng, cắt từ đầu như mọi phần xem trước, gộp về một dòng, và
không mang chữ nào nó đã in; script bị cổng dừng được tóm từ đầu lời từ chối; chỉ khi không
ai nói script dừng vì đâu thì tóm tắt mới là phần cuối của cái nó nói; lần viết lại bộ nhớ
và lần dựng wiki hỏng được tóm bằng phần đầu câu lỗi, một dòng, có dấu … khi bị cắt).
Kiểm chứng đột biến: mười một sửa đổi (cắt mà không đánh dấu, coi chữ dài đúng 160 ký tự là
bị cắt, lấy phần đầu thay phần cuối, không bỏ khoảng trắng ở đầu, script hỏng tóm bằng cái
đã in, không còn gì để tóm khi không ai nói script dừng vì đâu, tóm cái đã in từ đầu, dòng
làm script dừng không được gộp và cắt, script chạy xong tóm từ đầu, lỗi của lần viết lại bộ
nhớ và của lần dựng wiki cắt thẳng 160 ký tự) đều bị test bắt.script, then_prompt):
tests/test_schedule_script_rows.py (dòng có script là job script, không mang
prompt hay command, và ô trống mà form gửi kèm không được giữ; then_prompt vắng hay
null đều là không có; dòng không chạy script vẫn mang hai khoá, để trống; một dòng làm
đúng một trong ba việc, không thiếu không thừa; then_prompt chỉ đứng cạnh script, là
chuỗi có chữ hoặc không có; script là chuỗi có nội dung, dài đúng tới mức của script một
lượt và không hơn; script không chạy được bị từ chối bằng chính lời của bước kiểm trước khi
chạy; dòng không có id được gọi theo vị trí; hai câu lỗi mà trình sửa trên web tự kiểm
trùng từng chữ với câu server nói; qua API, dòng mà tệp sẽ bị từ chối thì 422 trước khi
ghi, script không chạy được cũng vậy; dòng script gửi lại y như đã đọc thì được ghi y như
cũ, có hay không có then_prompt; script, then_prompt và prompt nhiều dòng lưu từ web
đứng trong tệp thành khối, đọc lại đúng từng ký tự; script người viết tay thành một khối
trong tệp vẫn là script đó, vẫn là khối, sau khi lưu một dòng khác; GET /api/jobs cho biết
job script bằng chính script của nó, và dòng nào cũng mang đủ script, then_prompt).tests/test_profile_write_blocks.py (chuỗi nhiều dòng được viết thành khối và cả
hai trình đọc đọc lại đúng nó: có hay không có dòng cuối, dòng đầu thụt vào, dòng kết bằng
dấu cách, dòng trống, dấu của YAML, tiếng Việt; chuỗi có \r, tab, dấu ngắt dòng của bảng
mã khác hay ký tự không in ra thì không thành khối và vẫn đọc lại nguyên; chuỗi có ký tự
U+0085 đọc lại nguyên chứ không thành dấu cách; chuỗi một dòng được viết y như trước; chỉ
chuỗi bị đổi, ở sâu mấy tầng cũng vậy, và giá trị người gửi không bị sửa tại chỗ; chuỗi dài
viết trong ngoặc kép, có tab, \r lẻ, U+0085, BOM, ESC hay một dấu \ đứng sau ký tự không
in ra, vẫn là chuỗi đã lưu qua cả hai trình đọc; dòng dài không bị ngắt thành một chuỗi khác,
kể cả dòng có hai dấu cách liền nhau; chuỗi một dòng mà lúc khởi động sẽ đọc thành thứ khác,
như No, on, yes, 1:30, được viết trong ngoặc kép và đọc lại là chính nó).tests/test_api_agents_edit_read_back.py (qua đúng cửa một người dùng: script viết tay
thụt bằng tab vẫn là script đó sau khi lưu một dòng lịch khác, thử ở bốn mươi độ dài tên vì chỗ
ngắt dòng đi theo độ dài mọi tên đứng trước, và lần khởi động sau vẫn lên; prompt là No hay
1:30 được lưu thành đúng chữ ấy; lần lưu mà lần khởi động sau sẽ đọc ra khác là 422 nêu khoá,
tệp không đổi một byte và agent đang chạy giữ nguyên, thử bằng cách cho trình ghi ngắt dòng trở
lại; thứ người viết tay mà hai thư viện đọc khác nhau, như max_steps: 010 hay enabled: no,
không chặn lần lưu nào; công tắc viết tay bằng chữ, script_llm là off, no, on, yes và
shell_network là no, off, yes, thì agent lên được, lần lưu khoá khác là 200, agent đang
chạy giữ đúng/sai đó và tệp vẫn ghi đúng chữ đó; lần lưu tự gửi một trong sáu chữ cho một trong
hai công tắc vẫn là 422, tệp không đổi một byte; lần lưu đặt chính công tắc đó thì ghi giá trị
vừa gửi và để yên chữ của công tắc kia; sau một lần lưu khoá khác, agent đang chạy vẫn là agent
mà lần khởi động sau đọc ra, max_steps 8 và dòng lịch tắt, không đòi khởi động lại. Và chính
phép so: tệp sắp ghi là tệp hiện có với các khoá của lần sửa được đặt, kể cả khoá bị gỡ và lần
sửa trống; tệp của agent mới chỉ là lần sửa; mọi khoá đọc lại ra khác đều được nêu tên, khoá bị
đổi, khoá bị mất và khoá không ai xin; tệp hiện có mà lúc khởi động không đọc nổi là việc của
người sửa tay; tệp sắp ghi mà lúc khởi động không đọc nổi thì không được ghi).
Một khoá viết tay hai lần (ở hồ sơ, ở một dòng lịch, ở telegram) chặn lần lưu bằng 422,
tệp không đổi một byte, và lời từ chối nêu đúng tên khoá ấy; đột biến bỏ lời của thư viện
khỏi lời từ chối bị test bắt.tests/test_script_job.py (job script là một run chỉ có một step tool_script,
không gọi model khi script không tự hỏi nó, không có cuộc trò chuyện, không gửi cho ai, và
lời gọi tính tiền được tính vào run; step ấy được lưu y như step script của một lượt, cùng khoá, các lời gọi ở cùng một
chỗ; chạy được cả ở agent không giữ tool_script; tool cần cuộc trò chuyện thì báo lỗi
script bắt được và không cuộc nào bị dựng ra; tóm tắt của run là phần cuối của cái script
in ra, còn step giữ phần đầu như mọi step; job tới giờ thì đồng hồ chạy nó, một lần; script
không in gì, hay chỉ in khoảng trắng, không mở lượt và không báo ai dù có then_prompt;
không có then_prompt thì in gì cũng không mở lượt; có cả hai thì model nhận đúng một lượt,
tin nhắn là then_prompt, một dòng trống, câu nói phần dưới là dữ liệu, rồi cái đã in nằm
giữa hai dòng đánh dấu mang cùng một số mười sáu chữ số hệ mười sáu, lượt mở như job prompt
mở, câu trả lời được gửi đi và run của lượt là run của job; hai lần chạy cho hai số khác
nhau, và câu ấy nói rõ đó là dữ liệu chứ không phải chỉ dẫn; cái in ra tự viết một dòng đóng
thì không đóng được gì, nó vẫn nằm trong hàng rào; then_prompt viết thành khối trong tệp
vẫn cách câu ấy đúng một dòng trống; lượt nhận skill của lịch và skill mà prompt nêu; lượt
chỉ trả lời OK thì không gửi; cái in ra dài hơn mức của một kết quả tool thì bị cắt trước
khi model đọc; script hỏng là run error, tóm tắt là đúng dòng đã làm nó dừng, không mở lượt;
tóm tắt ấy không kèm chữ nào script đã in; script không chạy nổi thì job hỏng, run chỉ nêu loại lỗi, và
job sau vẫn chạy; job bị dừng giữa lúc script chạy không để lại run đang chạy và lần sau
lại chạy được; script của job giữ dưới lịch từ sáng này sang sáng sau; script hỏng không
giữ gì; cái script giữ có sẵn cho lượt nó mở, và cái lượt ấy giữ có sẵn cho lần chạy sau;
script không in gì vẫn giữ cái nó ghi).tests/test_script_job_gate.py (lệnh mà không ai phải được hỏi thì chạy trong job,
có trong danh sách cho phép hay không; lệnh khớp danh sách hỏi, kể cả khi cũng khớp danh
sách cho phép, hay lệnh bị cấm thì script của job dừng ngay ở đó, không chạy, script không
bắt được, không gì chờ ai và lời từ chối không lặp lại lệnh; tóm tắt của run là phần đầu của
lời từ chối ấy, mở bằng Dừng ở dòng 3: ; lý do hỏi của riêng một tool
cũng làm script dừng và không được lặp lại; tool phải hỏi trước mà không có lý do riêng thì
chạy, như trong lượt do job mở; job của agent mà script chỉ được đọc bị từ chối đúng cái
lượt của nó bị từ chối; job script không đọc được gì của lượt mà job trước nó để lại: không
cuộc trò chuyện, id lời gọi của riêng nó, không tính vào lượt nào; lượt của job chạy sau
một job script là của riêng nó, trần lời gọi lồng còn nguyên; job script kết thúc thế nào
thì cũng trả lại nguyên những gì tác vụ đang chạy theo).tests/test_script_job_failure_notice.py (job script hỏng thì agent của lịch ấy được
báo đúng một tin: tên lịch trong ngoặc kép, lý do là đúng dòng lỗi đã làm script dừng, chỗ
xem run, và dưới đó là câu nói tin nhắc kế tiếp đến sau 6 giờ nếu lịch còn hỏng; chữ script in
trước khi hỏng, kể cả thứ một tool vừa đưa cho nó, không có trong tin,
trong khi step của run vẫn giữ đủ như trước và tóm tắt của run là đúng dòng lỗi ấy; tool hỏng
được kể bằng lời của chính nó,
tham số mà nó nhắc lại vì thế có trong tin; câu của lỗi do script tự raise có trong tin;
lỗi trình thông dịch ném ra trên thứ một tool trả về, int(...) của một chuỗi hay một khoá
mà mapping không có, được kể đúng lời của nó, trích cả giá trị ấy, còn chữ đã in thì không;
giá trị dài thì bị cắt như mọi lý do, 200 ký tự đầu và 400 ký tự cuối;
script chạy xong mà không giữ được gì bằng store thì lý do là đúng dòng nói vậy; script không
chạy nổi thì lý do là loại lỗi, như run ghi; câu từ chối của cổng duyệt tới nơi nguyên vẹn, dài
hơn 160 ký tự và kết bằng phần dặn gọi trực tiếp, không kèm chữ đã in; lỗi dài thì tin mang 200
ký tự đầu và 400 ký tự cuối của dòng lỗi, 601 ký tự, trong khi tóm tắt của run là 160 ký tự đầu
của dòng ấy kèm dấu … và step
cắt theo cách của nó; tin không mang chữ nào của script, tên tool hay tham số của một lời gọi
chạy tốt, dù cả hai có trên run; tên lịch nhiều dòng hay rất dài được kể thành một dòng, cắt
bằng …, và tin vẫn đúng bốn dòng; không ai nói script dừng vì đâu thì lý do là trạng thái của
run, không bao giờ là thứ run đã giữ; lý do nhiều dòng được gộp thành một dòng; script chạy tốt,
in hay không in, không có tin; lượt mà script mở thì do việc gửi câu trả lời lo, dù lượt xong
hay hỏng; job prompt hỏng không có tin này và dòng của nó không bao giờ mang failing; gửi hỏng, vì Telegram từ chối hay
vì một lỗi khác, thì ghi một dòng log mức lỗi kèm ngoại lệ, run vẫn là run hỏng vì lý do của
script; tin không gửi xong trong hạn thì bị bỏ, lời gửi bị huỷ chứ không treo lại, log ghi như
một tin gửi hỏng kèm TimeoutError, và job tới hạn sau nó vẫn chạy trong cùng nhịp đồng hồ;
đồng hồ không có ai để báo thì không nói và không ghi log gì; gửi được hay không được ghi ở log
mức thông tin, không kèm chữ nào của script; không có kênh thì job kết thúc như cũ; master được
báo bằng lời của bot, agent khác thì dưới dòng [Tên]; tin đi như mọi tin của bot, **đậm** và
*nghiêng* trong tên lịch hay trong lý do mất dấu sao còn 2*3 giữ nguyên; dòng trông như
FILE: hay MEDIA: không đính kèm tệp nào; agent mà bot không biết thì không được báo gì; chat
từ chối tin thì run và đồng hồ giữ nguyên, job không còn bị coi là đang chạy; job chạy từ POST
/api/jobs/{agent}/{id}/run được báo như job đồng hồ chạy, và không có thêm tin “lượt chưa xong”
nào).tests/test_approval_ttl.py::test_the_web_editor_and_the_server_agree_on_what_a_schedule_row_holds
(danh sách khoá của một dòng lịch mà trình sửa trên web giữ trùng với danh sách của server,
nay gồm cả script và then_prompt, dù trình sửa viết danh sách ấy trên mấy dòng),
tests/test_api_agents_edit.py (dòng lịch đọc qua API mang hai khoá đó, để trống).then_prompt; job bị dừng để lại run đang chạy; tóm tắt của run hỏng lấy từ
cái đã in; lượt của job script không được gửi đi; then_prompt đứng cạnh việc nào cũng
được, một dòng làm hai việc, script không được kiểm khi ghi, prompt được giữ cạnh
script) đều bị test bắt. Sửa đổi còn lại, mở lượt cả khi script chỉ in khoảng trắng,
không đổi được hành vi nào: cái script in ra đã được bỏ khoảng trắng cuối trước khi tới chỗ
đó, nên chuỗi chỉ có khoảng trắng tới nơi đã là chuỗi rỗng.llm.classify, llm.summarise):
tests/test_script_model_calls.py (script được trao một nhãn và một bản tóm tắt, mỗi
lần hỏi là một lời gọi của nó, mang tên llm.classify hay llm.summarise, có giá và luôn
metered; bản ghi của một lần hỏi có đúng các khoá của một lời gọi chỉ đọc, không gated,
không child_spent_usd, và model không báo giá thì giá để trống; tham số và câu trả lời được
giữ ở dạng rút gọn, max_chars không nêu được ghi là 500; lần hỏi và lời gọi tool nằm chung
một danh sách theo thứ tự script viết, và số ký tự script nhận đếm cả cái mỗi lần hỏi trao
cho nó; model không trả lời được là một lời gọi hỏng và một lỗi script bắt được, lý do chỉ
nằm trên thẻ run chứ không tới script; không bắt thì script dừng như mọi lỗi; cái hỏng quanh
một lần hỏi chỉ làm hỏng lần hỏi đó và chỉ được gọi bằng tên loại lỗi; câu trả lời không dùng
được vẫn tính tiền, script nghe một câu cố định còn thẻ run mới thấy model đã nói gì; model
không nói gì là một lời gọi hỏng; văn bản bảo model bỏ qua các nhãn mà trả lời điều khác chỉ
cho ra một nhãn script đã đưa hoặc một lỗi, và nó chỉ nằm trong tin của người dùng, giữa hai
dòng rào; bản tóm tắt bị văn bản kéo dài vẫn bị cắt đúng mức; script chạy không có model để
hỏi thì được nói vậy, không lời gọi nào được ghi và lời mô tả không nêu llm.; lời mô tả
của nơi hỏi được kết bằng đúng đoạn nói về hai lời hỏi, đứng sau nguyên lời mô tả cũ; agent
được giao tool_script thì được giao luôn model của chính nó, ở cả hai mức script_calls;
server tự đếm số lần hỏi và dừng script ở lần quá mức, try của script không giữ được; mười
lăm câu hỏi sai hình mà tiến trình con có thể gửi lên đều không tới model và không được đếm;
tiến trình con chỉ nhận lại một chuỗi, hoặc một câu lỗi cố định; câu hỏi phải chờ kết thúc
theo đúng ba cách của câu hỏi được trả lời ngay: một giá trị, một lỗi bắt được, một lệnh
dừng; model chậm hơn mức im lặng của script không làm script bị dừng; chỉ tuyến đầu của agent
được hỏi, nó hỏng thì tuyến dự phòng và tuyến leo thang không được hỏi; model không được
khai tool nào, tuyến có nêu mức suy nghĩ thì được hỏi ở mức thấp nhất, tuyến không nêu thì
không được bảo gì, và tuyến của agent giữ nguyên như tệp ghi; sổ cái ghi lần hỏi dưới mục
đích script; tuyến được đọc lúc script hỏi chứ không phải lúc tool được giao; lần hỏi bị
cắt khi model đang trả lời không nằm trên thẻ nào, còn sổ cái ghi một lời gọi không rõ giá;
trong một lượt thật, ở cả hai mức script_calls, lần hỏi được báo ngay khi xong, nằm trên
step của run và trong phần lưu kèm tin nhắn, không ai bị hỏi, tiền của nó vào run và cuộc
trò chuyện đúng một lần, sổ cái ghi nó dưới cuộc trò chuyện của lượt; lần hỏi hỏng trong một
lượt được tính là một lời gọi không rõ giá; mỗi lần hỏi lấy một từ trần lời gọi lồng của
lượt, lần hỏi quá trần không tới model và lượt dừng, lời gọi sau một lần hỏi thấy trần còn
ít đi một; script của một lịch hỏi model của agent ở cả hai mức, tiền vào run của job, sổ
cái ghi không kèm cuộc trò chuyện nào và không cuộc nào bị dựng ra).tests/test_script_model_calls.py, phần trần chi phí của cuộc trò chuyện (câu từ chối
dùng đúng lời mà lượt dừng vì tiền; lần hỏi đầu đưa cuộc trò chuyện chạm trần thì lần hỏi thứ
hai không được gửi, script dừng ở đó, lượt dừng vì budget, không gì được hỏi hay tính tiền
sau đó; còn tiền, hay cuộc trò chuyện không đặt trần, thì cả hai lần hỏi chạy; script bắt đầu
khi đã hết tiền vẫn chạy và đọc tool, chỉ lần hỏi model là không được gửi; lần hỏi bị từ chối
vì tiền không được đếm vào số lần hỏi, số lời gọi của script hay trần của lượt, và try của
script không bắt được; cuộc trò chuyện bị xoá trong lúc script chạy không phải là lý do từ
chối).tests/test_script_asks_after_handing_on.py (tiền các việc script đã giao được tính là
đã tiêu khi xét trần trước một lần hỏi model, dù cuộc trò chuyện chỉ được cộng lúc script xong:
qua delegate thật, việc giao đưa cuộc trò chuyện vượt trần thì lần hỏi sau đó bị từ chối bằng
đúng câu của trần chi phí, try của script không bắt được, model của agent không được hỏi thêm
lần nào, sổ cái không có lời gọi phụ nào và lượt dừng vì budget; tiền cuộc trò chuyện đã tiêu
và tiền việc giao được cộng lại, chạm đúng trần là hết; hai việc giao lần lượt hay cùng một
tools.batch đều được cộng với nhau; một việc được tính từ lúc nó trả kết quả, nên lần hỏi
trước nó vẫn chạy và được trả tiền, lần hỏi sau nó thì không; còn tiền thì script hỏi được và
mỗi khoản vào cuộc trò chuyện đúng một lần; cuộc trò chuyện không đặt trần thì hỏi được dù việc
giao tiêu bao nhiêu; tiền script trước của cùng lượt đã giao không bị script sau tính hai lần;
lần hỏi bị từ chối vì tiền việc giao không được đếm vào số lần hỏi, số lời gọi của script hay
trần của lượt, và không tốn gì). Việc giao ở phần lớn các ca là một tool delegate thế chỗ, trả
về số tiền test đặt. Kiểm chứng đột biến: ba sửa đổi (không tính tiền việc giao, chỉ tính tiền
việc giao mà bỏ tiền cuộc trò chuyện đã tiêu, lấy việc tốn nhất thay cho tổng) đều bị test bắt.tests/test_script_model_answers.py (câu trả lời nêu đúng một nhãn thì thành nhãn
đó: viết hoa khác, cách chữ khác, dấu gõ rời, bọc trong ngoặc kép, chữ đậm, dấu code hay kèm
dấu chấm, hoặc là nhãn duy nhất đứng trong một câu; câu trả lời là trọn một nhãn thì là nhãn
ấy dù bỏ dấu bọc đi nó trùng nhãn khác; nhãn trao lại là chính chuỗi script viết chứ không
phải chữ của model; câu trả lời nêu hai nhãn, nêu một nhãn nằm trong nhãn dài hơn giữa một
câu, chỉ chứa nhãn trong lòng một từ, từ chối hay nói điều văn bản bảo nó nói đều là một
lỗi, không bao giờ là một lần đoán; nhãn được tìm như những từ đứng riêng; nhãn có dấu riêng
được so đúng từng dấu; câu trả lời rỗng là lỗi ở cả hai lời hỏi; câu lỗi script nghe không
lặp lại chữ nào của model; bản tóm tắt được cắt đúng mức và không hơn một ký tự, đo sau khi
bỏ khoảng trắng hai đầu và không kết bằng dấu cách; văn bản nằm trong một tin riêng giữa
hai dòng rào và không ở đâu khác, tin của server nêu đúng hai dòng ấy và nói cái ở giữa là
dữ liệu chứ không phải chỉ dẫn; hai dòng rào mang một con số sinh riêng cho lần hỏi đó, nên
văn bản tự viết một dòng đóng thì không đóng được gì; các nhãn được kể trong tin của server,
mỗi nhãn một dòng; độ dài script nêu được nói với model).tests/test_script_limits.py (mười lần hỏi model mỗi script, lần thứ mười một làm
script dừng ở đúng dòng và try không bắt được; phân loại và tóm tắt đếm chung; lần hỏi
hỏng vẫn tính; mỗi lần hỏi là một trong hai mươi lăm lời gọi của script, lời quá mức là lời
hỏi hay lời gọi tool thì script cũng dừng ở đó, và lần hỏi lấy chỗ của một lô; text dài
đúng 20.000 ký tự thì được hỏi nguyên, thêm một ký tự là lỗi bắt được nói phải tự cắt, không
bao giờ bị cắt hộ; text phải là chuỗi có chữ; labels là hai tới hai mươi chuỗi, mỗi chuỗi
tối đa 100 ký tự, không rỗng, không hai chuỗi nào trùng nhau khi bỏ qua hoa thường, khoảng
trắng và cách gõ dấu, còn nhãn chỉ khác nhau ở dấu thì là nhãn khác nhau; max_chars là số
nguyên từ 1 tới 4.000, không nêu thì 500; lời hỏi viết sai, thừa, thiếu hay lẫn tham số của
lời hỏi kia, nói lại cách viết đúng; llm chỉ có hai lời hỏi và không phải một giá trị
script cầm được; lời hỏi chưa từng được gửi thì không tính vào mức nào; các con số ấy đúng
là các con số đã nói với model và trong tài liệu).metered, lần hỏi hỏng được ghi là thành
công; ghi dưới mục đích khác, script không còn là một mục đích, sổ cái không được báo, ghi
dưới một cuộc trò chuyện không phải của lượt, khai tool của agent cho model, giữ mức suy
nghĩ của tuyến, hỏi mọi tuyến của agent; số ký tự script nhận không đếm lần hỏi, hay đếm chữ
model nói thay cho cái script được trao) đều bị test bắt.script_calls: gated):
tests/test_script_canvas_writes.py (canvas một script tạo đúng là canvas một lời
gọi trực tiếp sẽ tạo, ở cuộc tự chủ cũng như cuộc cần duyệt: cùng câu trả lời với thẻ đứng
đầu, cùng tác giả là agent, gắn với cuộc trò chuyện của lượt và đã được thấy ở bản vừa ghi,
không ai bị hỏi, và lời gọi được ghi là gated, không tính tiền; script sửa, viết lại và
nhập tệp vào một canvas, mỗi bản mang tác giả và cuộc trò chuyện của lượt, mỗi lời gọi mở
đầu bằng thẻ của bản nó ghi; script không đọc được trang nào nên không ghi đè lên bản người
lưu mà nó chưa thấy: artifact_rewrite bị từ chối như với model, còn artifact_read làm
script dừng với dòng bảo gọi trực tiếp; canvas script tạo được đếm chung với canvas model
tạo trong cùng một lượt, theo cả hai thứ tự, và lượt sau đếm lại từ đầu; số bản script ghi
lên một canvas đếm chung với số bản model ghi; lượt tới từ API, hay lượt con của một lượt
như thế, không ghi canvas nào từ script, cả bốn tool đều trả câu từ chối của kênh; script
ghi cho người đọc ngoài web (Telegram, job, lượt con của Telegram) được nói dòng gửi canvas
đi; bản rút gọn giữ nguyên thẻ đứng đầu dù sau nó là gì; cái được giữ của một lời gọi ghi mở
đầu bằng thẻ nguyên vẹn dù tên canvas dài và hook của agent có thêm ghi chú, lời gọi không
đổi gì thì thẻ nói vậy, lời gọi hỏng thì không có thẻ; trong một lượt thật, canvas script
tạo được nêu ở mọi nơi kể lời gọi của script, tức calls của kết quả, sự kiện
nested_call, step của run và phần lưu kèm tin nhắn, và tin “Canvas vừa ghi:” của Telegram
đọc ra đúng canvas đó từ cả hai nơi nó đọc; script của agent chưa bật
script_calls: gated không ghi canvas nào, dừng ở lời gọi với dòng bảo gọi trực tiếp, với
cả bốn tool; script của một lịch không có cuộc trò chuyện để ghi canvas: mỗi lời gọi hỏng
bằng câu nói canvas cần một cuộc trò chuyện, script bắt được, không gì được ghi vào cuộc
trò chuyện mà lượt của job trước để lại; không bắt thì run của job hỏng và chỉ có thế).tools.delegate, script_calls: gated):
tests/test_script_delegate.py (hai việc của một script mở hai cuộc trò chuyện, mỗi
cuộc dưới id lời gọi của script kèm số thứ tự của lời gọi, và run của con mang nguồn
delegate:<cuộc trò chuyện cha>; cùng script chạy lại dưới cùng id tìm lại cả hai và không
mở cuộc nào; script không có id lời gọi thì không lấy cuộc trò chuyện của script nào khác,
hai lần chạy mở bốn cuộc; id của lời gọi được trả lại sau mỗi việc, dù việc xong hay hỏng;
câu trả lời dài của một việc vẫn được giữ dưới id của script; script không được giao việc
thì dừng với dòng bảo gọi delegate trực tiếp, ở agent mà script chỉ đọc, trong lượt của
một việc được giao và trong script của một lịch; tools.names, tools.search và
tools.describe chỉ kể delegate ở nơi script gọi được nó; agent không giữ delegate thì
script của nó không có;
việc cho một agent không giao được làm script dừng, câu trả lời nêu những agent giao được
và không nhắc lại tên đã viết; một việc như thế trong lô thì không lời gọi nào của lô chạy;
chỉ việc được giao mới bị xét theo agent nó nêu, tool khác có tham số agent thì không;
các việc của một lô chạy cùng lúc và được trả lời theo thứ tự đã viết; lô có lời gọi ghi
thì giao từng việc một; các việc giao cùng lúc được đếm từng cái vào mức tám việc của cuộc
trò chuyện, việc vượt mức là lỗi của riêng nó; tiền các con tiêu nằm trên kết quả của script
và trên từng lời gọi được giữ, không phải là giá của lời gọi; khoá child_spent_usd và
gated chỉ được ghi khi có, không bao giờ là null, ở to_dict() và ở phần lưu kèm tin
nhắn; script hỏng vẫn mang tiền các việc của nó đã tiêu; câu trả lời của con là của script,
không bao giờ thành câu trả lời của lượt dù relay đặt thế nào; lượt thật qua server: cuộc
trò chuyện cha được cộng tiền của con đúng một lần, run của cha không cộng, sự kiện
nested_call, step của run và phần lưu kèm kể cùng một lời gọi, và model trả lời bằng lời
của nó sau khi đọc cái script in; con dừng chờ người duyệt thì script chờ tới khi con được
trả lời; chỉ agent mà script giao việc được mới đọc thấy câu về tools.delegate trong lời
mô tả).tests/test_script_limits.py (không quá ba việc được giao cùng lúc và đủ ba; việc
đang chờ tới lượt không làm lời gọi đọc nào của lô phải chờ; script giao việc được thì sống
qua hạn của một script thường, còn cũng script đó trong lượt của một việc được giao thì bị
cắt ở hạn thường; script giao việc được vẫn bị dừng như mọi script khi nó im lặng quá hạn
và khi nó dùng hết giây CPU, với hạn cả lần chạy của runner đặt ngắn hơn cả hai; script hết
giờ nói rõ là hết giờ, việc nó đã giao vẫn chạy tiếp như run của nó và xong sau đó; hai mức
đúng là con số đã nói với chủ).tests/test_store.py (tìm con theo id của lời gọi chạy script: chỉ những id là id đó
kèm dấu hai chấm và một dãy chữ số, không phải id chỉ giống phần đầu, khác hoa thường, chứa
ký tự đại diện của LIKE hay có chữ sau số; id rỗng không tìm ra gì, kể cả cuộc trò chuyện
ghi một con số mà không có id nào trước dấu hai chấm; id kết thúc ngay ở dấu hai chấm, không
có số nào, là id trọn vẹn của một lời gọi khác: con ấy thuộc lời gọi mang đúng id đó, không
thuộc script, và con của một cuộc trò chuyện khác mang id như thế không bị đếm vào những gì
cuộc này đã giao; một cuộc trò chuyện đã giao những gì thì kể cả việc script của nó giao, khi
được hỏi kèm tên tool script).tests/test_tools_delegate.py (việc script đã giao được đếm vào mức tám việc: bảy
việc của script cộng một lời gọi trực tiếp là đủ, lời gọi thứ chín bị từ chối, và con của
cuộc trò chuyện khác không bị đếm; mức ấy là của cuộc trò chuyện chứ không của lượt nào:
người nói tiếp rồi thì việc kế vẫn bị từ chối, câu từ chối nêu cuộc trò chuyện và không có
chữ “lượt”, còn một cuộc trò chuyện mới thì giao được; mẫu CHILD_HEADER của web, đọc từ
web/src/lib/nested-calls.ts, tìm ra id cuộc trò chuyện con trong phần được giữ của lời
gọi), tests/test_server_agents_activity_jobs.py (run của con do script mở nằm trong hoạt
động của cuộc trò chuyện cha, còn con của cuộc khác có id lời gọi chỉ giống phần đầu thì
không), tests/test_trajectory.py (bản xuất của run mang con do script mở cả khi script
còn chờ nó lẫn khi script đã xong, tool_call_id là id lời gọi script kèm số thứ tự, đúng
thứ tự mở; con của cuộc khác và của một run trước đó không đi theo),
tests/test_telegram_canvas_notice.py (giới hạn đã biết: tin “Canvas vừa ghi:” không nêu
canvas mà con của một script đã viết, trong khi lời gọi delegate trực tiếp thì nêu).delegate là của mọi script hay không của script nào; lời mô tả không nói, hay nói
với mọi agent gated; ba lời tra không biết tới nó; không xét agent đích, từ chối việc
không nêu agent, coi agent nào cũng sai; lời từ chối nhắc lại tên đã viết hay không nêu
agent nào), một ở relay và một ở tin canvas. Vòng đầu sót sáu. Ba là chỗ test hở, nay có
test: script không có id lời gọi, id rỗng trong children_of, và hạn im lặng bị nâng theo
(sửa đổi nâng hạn CPU được thêm cùng lúc); sửa đổi cho mọi script hạn dài lúc đầu chỉ bị bắt
vì test treo tới hết giờ, nay test hỏng trong mười giây. Ba cái tương đương: xét delegate
theo quy tắc chung khi script không được giao việc (tên nó vẫn nằm trong NOT_FROM_SCRIPT,
nên câu trả lời như nhau); xét agent đích sau khi hỏi cổng (cổng không bao giờ được hỏi về
delegate, vì theo khai báo nó chỉ đọc); và cho tin canvas đọc cả phần được giữ của một
việc script giao (phần đó mở đầu bằng dòng conversation= hay một câu báo lỗi, không bao
giờ bằng thẻ canvas).components/script-calls.test.tsx (dòng tóm tắt nói có bao nhiêu lời gọi, và bao nhiêu
lần lỗi chỉ khi có; danh sách gấp lại cho tới khi bấm và gấp lại ở lần bấm sau; mỗi lời gọi một
dòng theo thứ tự script gọi, cùng một lời gọi hai lần là hai dòng; mỗi dòng có tên, tham số và
thời gian; chỉ lời gọi lỗi mang dấu lỗi; lời gọi trả tiền cho model có giá, nói rõ khi không có
giá, lời gọi miễn phí thì không nói gì; hiện cái trả về cho script và không để dòng trống khi
không có gì; giá đứng giữa tham số và cái trả về), components/activity-cards.test.tsx (thẻ run
liệt kê lời gọi của script dưới bước của script, trên cái script in ra), lib/run-rows.test.ts
(script đã gọi gì đó thì không bao giờ bị gộp dòng, dù không in gì; script đã được đếm cũng
vậy, dù không gọi và không in gì),
state/activity-reducer.test.ts (lời gọi của script được giữ trên step của script như server
lưu; hai số đo được giữ trên step và không có ở kết quả không mang chúng; nhóm “what a script
calls, heard as each call ends”: lời gọi vừa nghe được liệt kê trên step còn mở của script và
cộng tiền ngay, lời gọi không có giá được đếm riêng, lời gọi không tính tiền thì không cộng
gì; nghe hai lần thì giữ một và cộng một lần; kết quả chỉ cộng cái chưa sự kiện nào báo và xếp
lời gọi theo thứ tự script viết, không cộng gì khi mọi lời gọi đã được nghe; lời gọi chạy lại
dùng lại step còn mở, danh sách về không còn tổng tiền giữ nguyên; id có step đã trả lời thì
mở step mới; lời gọi không có step nào ở đây không đổi gì; run đưa vào không bị sửa),
components/script-calls.test.tsx nhóm “what a script was handed against what it handed on”
(một dòng dưới danh sách: nhận bao nhiêu ký tự từ bao nhiêu lời gọi, trả về bao nhiêu; script
không gọi gì được đếm đúng như thế; số chưa ai đếm là gạch ngang chứ không là số không; dòng
đó chờ script xong trong khi lời gọi vẫn được liệt kê dần; tool không phải script thì không
có gì), components/activity-cards.test.tsx (thẻ run có dòng hai số đo dưới lời gọi của
script khi script đã xong), lib/nested-calls.test.ts (sáu mươi tám ca cho luật dùng chung
của thẻ run và thẻ chat: lời gọi nào của kết quả chưa được liệt kê, kể cả khi một lời gọi
được thực hiện hai lần và khi chỉ một trường khác nhau; thêm một lời gọi vừa nghe vào danh
sách, nghe lại thì giữ chỗ cũ và nói rõ không mới; kết quả nói gì về việc của script, số không
vẫn là một số đếm còn null thì bỏ; đọc phần lưu kèm một cách thận trọng: ghi chú canvas,
JSON hỏng hay một lời gọi thiếu trường thì không liệt kê gì chứ không liệt kê nửa chừng, và
không làm hỏng cả luồng), state/thread-reducer-script-calls.test.tsx (thẻ tool trong khung
chat: lời gọi được liệt kê dần khi thẻ còn chạy, nghe hai lần giữ một; khi kết quả tới thì
theo thứ tự script viết cùng hai số đo; tool khác không có gì; lời gọi vào đúng thẻ của script
đã gọi nó; đọc lại từ tin đã lưu cho đúng thẻ như lúc kết quả vừa tới; script hỏng vẫn là thẻ
failed kèm danh sách; thẻ đọc từ trên xuống: được yêu cầu gì, đã gọi gì, tổng lại ra sao, in
ra gì; đang chạy, đang chờ, bị dừng hay bị từ chối thì liệt kê mà chưa đếm),
lib/conversation-markdown.test.ts nhóm “what a script called on its own” (lời gọi của script
nằm dưới tên agent, giữa câu hỏi và câu trả lời dựa vào chúng; nằm dưới lời agent nói lúc viết
script mà không thêm tiêu đề thứ hai; hai script của một lượt chung một tiêu đề, mỗi cái một
danh sách; không ghi tham số hay câu trả lời nên bản xuất không có JSON; là danh sách đánh số;
ghi chú canvas của người dùng không bị đọc thành danh sách dù cùng một trường).script-calls.spec.ts (lời gọi của script tới cùng kết quả và được liệt kê, gấp
lại, dưới bước của nó; run đọc lại từ server vẫn liệt kê, chữ của mỗi thời gian kết thúc ở mép
phải của dòng nên các thời gian thẳng hàng, danh sách đứng thẳng dưới tham số của bước, dòng mở
danh sách chỉ rộng bằng chữ của nó, và lời gọi đứng giữa tham số với cái script in ra; trên
điện thoại 390px dòng mở danh sách đủ lớn cho ngón tay, và danh sách vừa màn hình dù tên, tham
số hay câu trả lời dài tới đâu; mỗi lời gọi của script được liệt kê và tính tiền trên thẻ run
ngay khi nó xong, còn dòng hai số đo chỉ hiện khi script đã xong; dòng hai số đo đứng dưới
danh sách, thẳng cột với nó, và xuống dòng trong thẻ khi con số dài trên điện thoại; trên thẻ
tool trong khung chat các lời gọi được liệt kê dần, được đếm khi có kết quả và còn đó sau khi
tải lại trang; danh sách và dòng đếm vừa thẻ dù tên hay câu trả lời dài tới đâu).components/job-schedule.test.tsx (job chạy script được gọi tên theo loại của nó; dòng
của job hiện script đúng như được viết, cả chỗ xuống dòng; có then_prompt thì hiện prompt
nhận kết quả, không có thì không nói gì về prompt; script của job nào nằm ở dòng của job ấy;
job lệnh và job prompt vẫn được gọi tên như trước; dòng vẽ ra như nhau dù hai khoá script tới
là null hay không tới), components/agent-editor/schedule-script-row.test.tsx (dòng script
trong trình sửa: sửa dòng khác thì nó được gửi lại nguyên vẹn; đổi tên, giờ chạy và công tắc
của nó thì script vẫn còn; không bị coi là dòng chưa có việc; hiện loại đang chọn trong ba loại,
cả script và prompt nối tiếp; script gõ được tại dòng, gõ gì lưu nấy; dòng nào cũng chọn được
script, dòng form thêm bắt đầu là lời nhắc; script không có prompt nối tiếp thì không
gửi prompt nào; xoá được như mọi dòng), hooks/schedule-row-shape.test.ts (dòng script là
dòng tệp khai có script, không gì khác; thiếu prompt không làm nó thành dòng lệnh; gửi đi
script và prompt nối tiếp đúng như đã nhận, từng ký tự, không kèm prompt hay lệnh dù dòng lỡ
giữ một cái; bỏ prompt nối tiếp khi script không có; giữ giờ chạy, công tắc, kỹ năng và thời
hạn chờ duyệt; cái gửi đi là cái bộ đọc của server nhận, và đọc lại ra đúng dòng cũ; dòng
prompt và dòng lệnh không mang khoá script nào dù chúng tới là null hay không tới, và không
gửi prompt nối tiếp sót lại; lỗi của dòng script không phải là thiếu prompt hay lệnh, vẫn là
giờ chạy mà bộ lịch từ chối, vẫn là ô trống của dòng bên cạnh; cả danh sách vẫn gửi được, dòng
script đứng đúng chỗ), test/schedule-contract.test.ts (máy chủ giả của test theo đúng luật
của server: nhận prompt, lệnh hoặc script đứng một mình; từ chối dòng không có cái nào hoặc có
hai; prompt nối tiếp chỉ đi cùng script; then_prompt là null hay vắng đều là không có; vẫn
từ chối khoá lạ, kể cả kind; dòng script vẫn cần đúng một kiểu giờ chạy; mỗi dòng trả về có
đủ hai khoá script, null khi không có; loại của job gọi theo việc dòng của nó làm; một dòng
có hai việc thì cả bản sửa bị từ chối và agent giữ nguyên),
components/script-call-gated.test.tsx (lời gọi qua cổng duyệt nói điều đó bằng chữ trên dòng
của nó, sau dấu lỗi và trước thời gian; lời gọi không có khoá ấy, hoặc có mà là sai, đọc đúng
như trước; dấu có trên thẻ chat khi script còn chạy, khi đã xong và khi đọc lại từ tin đã lưu;
kết quả cũ không có khoá vẫn được liệt kê, không dấu; kết quả mà dấu không phải có hay không
thì không liệt kê; dấu có trên bước của script trong run và trên dòng của lời gọi trong bản
xuất Markdown; vẫn là một lời gọi dù lần nghe nào mang dấu), hooks/use-agent-draft.test.ts
(dòng lịch đọc về mang cả hai khoá script).script-jobs-phone.spec.ts (điện thoại 390px, màn cảm ứng: dòng của job script hiện
phần đầu script và không tràn trang; dòng script trong trình sửa được gõ ngay tại chỗ, và cái
được gõ là cái được lưu; lời gọi qua cổng duyệt nói điều đó trên dòng riêng của nó trong run,
cạnh thời gian).components/script-model-call.test.tsx (lời gọi model nằm chung danh sách với các lời
gọi khác của script, bằng chữ nói nó là gì, kèm giá; không bao giờ được đọc bằng cái tên nó
mang trên đường truyền, cũng không bị tra như một tool; chưa có chữ cho việc được hỏi thì vẫn
nói là lời gọi model; đọc như nhau trên thẻ chat khi script còn chạy, khi đã xong và khi đọc
lại từ tin đã lưu; đọc như thế trên bước của script trong run, không có bước riêng; trong bản
xuất Markdown cũng bằng những chữ ấy, kèm giá, không phải dạng mã),
components/script-delegate-call.test.tsx (việc script giao cho agent khác nói agent kia đã
tiêu bao nhiêu, như thẻ của việc giao trực tiếp, và không ghi giá cho lời gọi; mở được cuộc
trò chuyện việc ấy chạy trong đó, đọc từ đầu câu trả lời; nơi danh sách không được trao lối
vào thì không có nút; đọc như nhau trên thẻ chat ở cả ba lúc; bản xuất Markdown ghi agent kia
tiêu bao nhiêu bằng chữ; ở Hoạt động, bước của script nói mỗi agent kia tiêu bao nhiêu và
không đụng tới số tiêu của chính run; run của các việc ấy treo dưới lượt đã giao, ba cái cạnh
nhau, mỗi lời gọi mở đúng cuộc trò chuyện của việc mình; ở danh sách cuộc trò chuyện, cuộc do
script mở được đánh dấu là việc của agent khác, nằm dưới cuộc của người dùng, không bao giờ
bị nói là chưa đọc, và đọc đúng như cuộc do việc giao trực tiếp mở),
components/canvas/script-canvases.test.tsx (mỗi canvas script ghi có thẻ riêng dưới thẻ của
script, như lần ghi agent tự làm; mỗi thẻ mở đúng canvas mà nhãn của nó nêu; canvas đã bị xoá
thì thẻ nói không còn và không có gì để mở; lần ghi hỏng không có thẻ, chỉ còn là một dòng
của danh sách nói nó hỏng; cái script chỉ đọc, xuất ra tệp hay giao đi thì không có thẻ; nơi
khung chat không được trao cách mở canvas thì không vẽ thẻ; thẻ có ngay khi nghe tin mỗi lần
ghi xong, dù script còn chạy; cùng một lời gọi làm hai lần là hai thẻ; ở cuộc trò chuyện đọc
lại từ server thẻ vẫn đứng ngay sau thẻ của script và trước lời gọi kế tiếp của lượt, mở được
qua các đường dẫn khung chat được trao, và không vẽ khi không được trao),
hooks/use-canvas-auto-open-script.test.ts (canvas script vừa tạo được mở lặng lẽ một lần khi
script xong, không mở theo từng lời gọi và không mở lại dù khung chat vẽ lại bao nhiêu lần;
script tạo nhiều canvas thì cái cuối cùng mở; tệp script đọc vào thành một canvas mới cũng
mở; canvas của nhiều script và của lời gọi trực tiếp mở theo thứ tự lời gọi; script còn chạy
lúc màn hình được vẽ thì mở khi nó xong; xét theo người dùng đang ở đâu lúc script xong chứ
không phải lúc lời gọi tạo canvas; không tự mở: lần sửa, lần viết lại, tệp đọc vào một canvas
được nêu tên hay lần ghi để canvas nguyên như cũ, lời gọi hỏng dù câu trả lời mở đầu bằng
nhãn canvas, nhãn không đứng đầu câu trả lời, canvas mà agent được giao việc đã ghi, canvas
trên màn hình quá hẹp dù sau đó màn hình rộng ra, canvas tạo lúc bàn phím đang ở khung canvas
dù sau đó người dùng rời đi, canvas ở cuộc trò chuyện tab này chỉ đọc, và canvas của script
đã xong từ trước khi màn hình được vẽ), lib/nested-calls.test.ts (trong sáu mươi tám ca:
việc đã giao được nhận ra là cái đã liệt kê dù lần nghe có nói số tiêu hay không, giao hai lần
vẫn là hai, hai việc phân biệt bằng câu trả lời; mỗi việc hỏi model có chữ riêng, không chữ
nào là cái tên, không bao giờ rỗng với việc chưa có chữ, và tool thì không có chữ nào; số
agent kia tiêu ghi tới bốn chữ số thập phân như thẻ của việc giao trực tiếp, lời gọi không
giao gì thì không có số; cuộc trò chuyện của việc đọc từ đầu của một dòng script giữ lại, bằng
bộ đọc riêng), state/activity-reducer.test.ts (tiền script trả cho model được cộng một lần
trên bước của script; lời gọi model hỏng không rõ giá được đếm trong số lời gọi chưa ai định
giá; việc giao đi không đổi số tiêu của run dù agent kia tiêu bao nhiêu, và không cộng gì khi
kết quả là nơi đầu tiên nêu nó; nhóm “children a script opened”: run con treo dưới lượt có
script đã giao, kể cả khi lượt ấy còn chạy và script đang chờ),
components/stats-panel.test.tsx (khoản script trả cho model được gọi bằng chữ, không bằng
khoá của sổ chi phí).script-model-delegate-canvas.spec.ts (ở 1440, 1000 và 390px: thẻ của script liệt
kê cả ba lời gọi, mỗi cái bằng chữ của nó, và thẻ canvas đứng dưới; ở màn rộng canvas vừa tạo
mở cạnh khung chat trong khi bàn phím vẫn ở ô nhập, và mở lại được từ thẻ sau khi đóng; ở màn
hẹp và điện thoại không canvas nào tự mở, thẻ canvas mở nó; lời gọi giao việc dẫn vào cuộc trò
chuyện việc ấy chạy trong đó; ở Hoạt động, run của việc treo dưới lượt đã giao và cả ba lời
gọi nằm trên một bước của script; trên điện thoại dòng mở danh sách, lối vào việc và thẻ canvas
đều đủ lớn cho ngón tay).tool_script ở bảng Công cụ, trình sửa agent và màn Kết nối:
components/tools-matrix.test.tsx (tool mở cho script được nói là tìm như các tool
khác và được lời mô tả của tool_script nêu tên khi nó chỉ đọc; chỉ agent có tool chỉ đọc giữ
tool_script, dù nó còn tìm được gì khác; ô của agent không giữ nó mang dấu ∅ và không bị
giải thích bằng danh sách cho phép, khoá, chế độ hay máy chủ; nó mang nhãn riêng “đi kèm công
cụ đọc”, lời giải thích của nhãn nói khi nào agent giữ nó, và tool_search không mang nhãn
ấy; chú giải có một dòng riêng cho dấu ∅, cả ở crew không có máy chủ nào, và chỉ khi bảng
có tool_script), components/agent-editor/tools-section.test.tsx (danh sách cho phép không
liệt kê tool_script cho agent đọc bằng tool của chính nó, và bật danh sách lên thì không
gửi tên đó), components/mcp-servers-card.test.tsx (tool mở cho script được nói là được
tool_script nêu tên khi chủ ghi nó chỉ đọc), test/fake-mcp.test.ts (máy chủ giả của test
giao tool_script đúng luật của server: cho agent có một tool có sẵn chỉ đọc dù không máy chủ
nào, từng tool trong bốn tool đọc đều được tính; cho agent có tool chỉ đọc của máy chủ ở mức
mở nào cũng được; không cho agent chỉ có tool hỏi trước dù tool ấy mở cho script; nêu mỗi
agent một lần, theo thứ tự tên, dù nó đọc bằng tool của mình, qua máy chủ hay cả hai; không
biết tool có sẵn nào khi chưa được trao đội; không cho tool ẩn hay máy chủ không ai dùng).mcp-servers.spec.ts (bảng Công cụ có dòng tool_script với agent mà máy chủ của
nó có một tool chỉ đọc, và ô ∅ ở agent chỉ có tool hỏi trước dù tool ấy mở cho script; agent
đọc bằng tool của chính nó, không máy chủ nào, cũng có dòng tool_script với nhãn của nó,
chú giải khi đó không nói gì về máy chủ, và trang của agent không đưa tool_script vào danh
sách để tích; trên điện thoại 390px nhãn của dòng tool_script không bị ép).⊘ ở bảng Công cụ:
components/agent-editor/script-controls.test.tsx (ô chọn script gọi được gì mời ba giá
trị tệp nhận, mỗi giá trị bằng lời riêng; mở ở điều tệp viết, và ở mức đội đang chạy agent khi
server không trả khoá nào trong hai khoá; lưu từng giá trị thì gửi đúng một khoá đã đổi, không
đòi khởi động lại; mở lại agent vẫn thấy giá trị ấy; chọn lại như cũ thì không còn gì để lưu;
không đổi được ở agent không sửa được ở đây; dòng dưới ô chọn là lời mô tả của ô và đổi theo lựa
chọn; với gated nó nói trước tự chủ sẽ làm gì khi agent chưa tự chủ, và nói thẳng về agent
này, như một cảnh báo, khi form đang bật cả hai; ô cho script hỏi model tắt bằng một lần bỏ chọn
và một lần lưu, không gửi gì khác; lời mô tả của nó nói cái giá và rằng script của một lịch cũng
thuộc về nó; ô vẫn có ở agent không được trao tool script nào, vì script của lịch vẫn chạy),
components/agent-editor/script-controls-undo.test.tsx (ở agent mà đội không trả thiết lập nào:
chọn rồi chọn lại, qua gated hay qua none, không để lại gì để lưu; ô hỏi model cũng vậy),
components/agent-editor/script-controls-store-link.test.tsx (sau hai thiết lập có một liên kết
tới màn “Script giữ lại” với agent này đã chọn; có cả ở agent không sửa được ở đây),
components/agent-editor/autonomy-unasked-scripts.test.tsx (dòng dưới ô “Tự chủ” nói không ai
được hỏi: hiện khi bật tự chủ ở agent mà script đã được đổi dữ liệu, khi cho script đổi dữ liệu
ở agent đã tự chủ, có sẵn ở agent được lưu với cả hai, có ở agent form không sửa được, và không
có ở agent tự chủ mà đội không trả thiết lập script nào),
components/tools-matrix-script-off.test.tsx (ô tool_script của agent mà tệp tắt script mang
dấu đã tắt, không phải dấu của agent không có gì cho script gọi; lý do ấy được nêu cả khi agent
không giữ gì script gọi được; vẫn là ô có khi server nói agent giữ tool; chú giải chỉ có dòng
cho dấu này ở đội có agent được đặt như thế; agent của một server không trả khoá ấy được đọc là
chưa từng tắt), components/tools-matrix.test.tsx (lời giải thích của nhãn tool_script nay
kết bằng ngoại lệ của agent được đặt “Không dùng script”), hooks/use-agent-draft.test.ts
(agent mẫu nay mang hai khoá script), test/schedule-contract.test.ts (nhóm
“the two script switches, as the double reads them”: máy chủ giả của test nhận từng giá trị tệp
nhận và trả lại nó; từ chối mọi thứ khác: một nhãn, chữ hoa chữ thường khác, một chuỗi thay cho
đúng/sai; khoá bị xoá đọc lại là mức đội đang chạy agent).script-controls.spec.ts (hai thiết lập được đặt, lưu, và còn đó sau khi tải lại;
màn rộng 1440px: liên kết dưới hai thiết lập mở những gì script của agent này giữ, và Back về
lại trình sửa; điện thoại 390px cảm ứng: hai thiết lập đặt được bằng ngón tay và không tràn
trang; bật tự chủ ở agent mà script được đổi dữ liệu thì ngay dưới ô có câu nói không ai được
hỏi; liên kết đủ cao để chạm, mở kho của đúng agent này, và Back quay về).components/agent-editor/schedule-script-edit.test.tsx (dòng mới chạy script được gửi
đi đúng như bộ đọc của server nhận; dấu tab dán vào, dấu cách đầu dòng và dấu xuống dòng cuối
còn nguyên qua một lần lưu và một lần mở lại; phím Tab rời ô script theo cả hai chiều và không
để lại dấu tab nào; mỗi ô gắn với dòng gợi ý của nó, ô script gắn với điều đang sai ở nó; script
vượt giới hạn giữ lần lưu lại ngay, cắt bớt thì lưu được; xoá trắng ô prompt nối tiếp thì khoá
được lấy ra khỏi tệp; server từ chối script thì lời của server hiện ra và chữ đã gõ còn đó; ở
agent form không sửa được thì script chỉ để đọc),
components/agent-editor/schedule-kind-switch.test.tsx (đổi việc của một dòng không mang chữ
của loại này sang ô của loại kia; mỗi loại nhận lại phần của nó: chữ, prompt nối tiếp, kỹ năng;
lúc lưu chỉ loại đang chọn được ghi; script đổi thành lời nhắc được lưu là lời nhắc, không kèm
prompt nối tiếp; script được mời chọn kỹ năng khi đã có prompt nối tiếp, và gửi chúng; dòng
thành lệnh thì bỏ kỹ năng, vì lệnh không mở lượt nào),
components/agent-editor/schedule-command-skills.test.tsx (kỹ năng mà tệp ghi ở một dòng lệnh:
được ghi lại như tệp có khi chữ của lệnh đổi sau một vòng đổi loại; đi cùng dòng sang lời nhắc,
nơi bỏ một kỹ năng thì nó được lưu là đã bỏ; về lại lệnh thì là danh sách của lệnh như cũ),
components/agent-editor/schedule-then-prompt-undo.test.tsx (prompt nối tiếp được gõ rồi lấy
lại: không ở lại trên một dòng lời nhắc từng là script trong chốc lát; xoá trắng ô ở script vốn
không có thì là không có prompt nào), components/agent-editor/schedule-landing.test.tsx (trình
sửa mở từ “Sửa lịch” của một job: dừng ở dòng của job ấy giữa các dòng khác; liên kết quay lại
nói về danh sách lịch; không job nào dẫn tới thì dừng ở tiêu đề và liên kết quay lại như thường;
vẫn ở dòng đã dừng khi danh sách quanh nó đổi),
components/agent-editor/schedule-script-id.test.tsx (dòng script không có mã không lưu được:
lần lưu bị giữ lại, và ô mã nói lý do khi đã thử lưu; ô mã chỉ có dấu cách cũng là không có mã;
dưới ô mã của nó là lời nói mã dùng để làm gì, nơi dòng loại khác được bảo là có thể để trống;
dòng thôi là script thì lại để trống mã được),
components/agent-editor/schedule-placed-id.test.tsx (dòng script mà server đánh số theo vị
trí: mở agent thì không có gì để lưu và không có gì phải điền; lần lưu lịch kế tiếp gửi lại đúng
mã ấy dù không ai gõ gì vào dòng; dòng giữ mã ấy khi một dòng phía trên bị xoá, qua từng lần
lưu), hooks/schedule-action-kind.test.ts (việc của một dòng là việc duy nhất nó mang; dòng vừa
đổi sang script là script trước khi gõ gì; script rỗng bên cạnh một lời nhắc hay một lệnh thì
không phải script; script rỗng giữ lần lưu lại, lặng lẽ lúc đầu và được nêu tên khi đã thử lưu,
ở đúng ô của loại dòng ấy; script vượt giới hạn được nêu ngay, kèm con số, đếm theo ký tự như bộ
đọc của server; prompt nối tiếp để trống không giữ lần lưu nào và được gửi như không có; dòng
đổi loại gửi đi đúng một việc nó đang làm dù các khoá khác còn giữ gì; hai khoá script là null
ở nơi tệp không có thì không tính là đã đổi, còn script hay prompt nối tiếp có thật thì tính, và
ở mọi khoá khác null vẫn khác một giá trị), hooks/agent-draft-checks.test.ts (nhóm
“a row that runs a script and has no id”: giữ lần lưu lại, chưa nêu tên, cho tới khi thử lưu;
được nêu ở ô mã khi đã thử lưu; không áp cho script có mã, cho lời nhắc hay lệnh không mã; không
đụng tới form mà lịch không ai đổi).schedule-script-editor.spec.ts (ở 1280px và 390px: một lịch chạy script được viết,
lưu và đọc lại; “Sửa lịch” của một job script dừng ở dòng của job ấy chứ không ở dòng đầu, khi
tới bằng liên kết lẫn khi tải lại; ngoài ra: script bị server từ chối được nói bằng lời của
server và không mất chữ nào đã gõ; script để trống hay dài quá giới hạn giữ lần lưu lại và nói
lý do; ở 390px, dòng script không có mã không lưu được và ô mã nói lý do),
manage-smoke.spec.ts (ca thêm lịch từ danh sách lịch nay kiểm bàn phím nằm ở dòng của job vừa
bấm, không ở tiêu đề).api/script-store.test.ts (liệt kê kho của một agent dù id của nó chứa gì; đọc một giá
trị theo tên kho và khoá, mỗi cái gửi trọn; xoá một khoá theo tên kho và khoá; chỉ xoá cả kho
khi không khoá nào được nêu; khoá rỗng vẫn được nêu, nên một dòng được hỏi tới chứ không bao giờ
là tất cả; lỗi mang mã và lời của server), hooks/use-script-store.test.ts (chưa có gì, cũng
chưa có lỗi nào, cho tới khi lần đọc đầu được trả lời; chỉ nhận câu trả lời của lần đọc sau
cùng, dù cái nào về sau; không báo lỗi của lần đọc đã bị lần sau thay; giữ danh sách đang có khi
lần đọc sau hỏng, và buông lỗi khi một lần đọc được; được báo là đã mất thì bỏ dòng ngay, bỏ cả
kho khi đó là khoá cuối, rồi đọc lại; khoá được báo mất mà chưa từng có thì danh sách như cũ;
server từ chối một lần xoá thì không gì đổi và lời từ chối về tay nơi đã hỏi; đọc một giá trị
trả về chữ kèm độ dài và giờ ghi, và không đọc thêm gì khi danh sách nói y như vậy; không đọc
thêm cho khoá danh sách không có, hay cho câu trả lời về sau khi màn đã đóng),
components/script-store-section.test.tsx (mỗi kho được gọi bằng lời, có lối tới nơi dùng nó,
và tên như bảng lưu vẫn trong tầm mắt; các khoá của một kho xếp theo thứ tự, mỗi khoá kèm phần
đầu giá trị, độ dài và giờ ghi; kho được đo với mức tối đa của một kho, bằng con số và bằng một
thanh; kho không còn gì đọc nữa thì được đánh dấu, được nói là xoá được, và không có lối tới nơi
dùng đã mất; kho không của lịch cũng không của cuộc trò chuyện mang tên như bảng lưu, một lần;
cái một server cũ gửi thì hiện thành tên như bảng lưu, độ dài không có mức tối đa, không dấu,
không thanh; script chưa giữ gì thì một câu nói màn này là gì; đang đọc thì nói đang đọc chứ
không nói chưa có gì; đọc hỏng thì hiện lỗi thường lệ và đọc lại khi được bảo; mời các agent của
đội và báo cho nơi chứa nó agent nào được chọn; không bao giờ hiện khoá của agent này dưới tên
agent kế), components/script-store-value.test.tsx (giá trị chỉ được đọc khi được hỏi, bày
thành JSON trong một ô riêng, và cất đi được; hiện trọn, quá chỗ danh sách đã cắt; không phải
JSON thì hiện như được giữ, kèm một dòng nói vậy; ký tự không ai thấy được viết ra, ở khoá lẫn ở
giá trị, còn lời hỏi server dùng khoá như được giữ; nói đang đọc trong lúc server trả lời; về
sau khi người dùng đã đóng thì vẫn cất; khoá đã mất: nói vậy, bỏ dòng, đọc lại danh sách và đưa
bàn phím sang khoá kế; không đọc được: hiện lỗi thường lệ ngay tại dòng, giữ dòng, đọc lại khi
được bảo; khoá được ghi lại thì giá trị được cất, để giá trị cũ không bao giờ bị đọc là của
khoá), components/script-store-value-rewritten.test.tsx (giá trị đã được ghi lại từ lúc danh
sách được đọc: danh sách được đọc lại, đúng một lần, để giá trị đứng cạnh độ dài và giờ ghi của
chính nó; giá trị ở yên trong ô trong lúc danh sách được đọc lại; nó được cất khi danh sách đọc
lại cho thấy một lần ghi muộn hơn lần ghi của giá trị; giá trị đúng như danh sách nói thì danh
sách không được đọc lần hai), components/script-store-value-overlap.test.tsx (hai lần đọc giá
trị của một khoá cùng lúc: lần trước về trước thì ô vẫn chờ, rồi nhận giá trị của lần sau; lần
trước về sau thì ô giữ giá trị của lần sau; lời từ chối của lần trước không hiện trong lúc chờ
lần sau; hiện lời từ chối của lần sau chứ không phải giá trị của lần trước; lần trước thấy khoá
đã mất thì khoá không bị bỏ, lần sau thấy vậy thì bị bỏ dù lần trước đọc được gì; người dùng đã
đóng ô thì không lần nào điền gì; một lần đọc bị đóng trước khi về vẫn bỏ khoá khi server nói
khoá đã mất), components/script-store-delete.test.tsx (xoá một khoá: hỏi trước, xoá đúng khoá
ấy, nói cái gì đã đi và đọc lại danh sách; nói không thì server không được hỏi gì; bàn phím sang
khoá thế chỗ, không có thì sang khoá cuối; khoá cuối đi thì kho đi theo và bàn phím sang tiêu đề
của kho thế chỗ; server từ chối thì lý do hiện ngay tại dòng được hỏi và không gì bị dời; server
không còn khoá ấy thì nói không còn gì để xoá và vẫn bỏ dòng; bấm xoá lần nữa trước khi có trả
lời thì người dùng và server chỉ được hỏi một lần; khoá khác vẫn xoá được trong lúc lần xoá đầu
chưa có trả lời; bàn phím ở lại nơi người dùng đã đưa nó tới trong lúc server trả lời; đọc lại
danh sách hỏng thì màn giữ cái đang có, trừ khoá vừa xoá),
components/script-store-delete-all.test.tsx (xoá cả một kho: hỏi trước và nêu bao nhiêu khoá
sẽ đi, xoá hết, đưa bàn phím sang kho thế chỗ; đó là kho cuối thì còn lại trạng thái trống và
bàn phím về ô chọn agent; không hỏi về khoá nào của một kho đang đi cả; nói không thì server
không được hỏi gì; server từ chối thì lời của nó hiện dưới kho, không ở dòng nào, và không gì bị
dời), components/script-store-delete-all-recount.test.tsx (câu hỏi trước khi xoá cả kho nêu số
khoá server đang có, không phải số danh sách đã hiện; nói không thì trên màn là danh sách vừa
đọc; kho được gọi bằng tên nó đang mang; xoá một khoá thì không đọc lại trước khi hỏi, câu hỏi
ấy không nêu con số nào; kho đã mất lúc bấm xoá thì được nói là không còn gì và không ai bị hỏi,
và nếu là kho cuối thì còn trạng thái trống, bàn phím ở ô chọn agent; trong lúc đọc lại: bấm lần
nữa, hay bấm xoá một khoá của kho ấy, không hỏi thêm gì; người dùng sang agent khác được và sau
đó không bị hỏi gì; server từ chối lần đọc thì điều đó được nói dưới kho, không ai bị hỏi, và
lần bấm sau lại hỏi), components/script-store-delete-names.test.tsx (khoá trong câu hỏi xoá và
dòng báo sau đó: nằm trên một dòng giữa hai dấu ngoặc kép, dấu ngoặc kép và dấu xuống dòng của
chính nó được viết thành chuỗi thoát; cắt ở tám mươi ký tự mà vẫn bị xoá trọn; dấu cách ở cuối
và ký tự không ai thấy được viết ra giữa hai dấu ngoặc; dòng báo đã xoá gọi nó y như thế; lịch
hay cuộc trò chuyện được gọi bằng tiêu đề, bỏ dấu cách ở cuối tiêu đề; dấu ngoặc kép và dấu
xuống dòng của tiêu đề được viết thành chuỗi thoát, cả trong câu hỏi về một khoá; tiêu đề dài
thì cắt ở tám mươi ký tự; kho không thuộc hai loại ấy là tên như bảng lưu giữa hai dấu ngoặc,
được viết ra), components/script-store-written-names.test.tsx (những khoá chỉ khác nhau ở thứ
trang web gộp lại hay vẽ thành không gì: mỗi khoá được viết theo cách riêng ở dòng của nó, ở tên
của nút xem giá trị, ở tên của nút xoá, trên một giá trị đang mở và ở nút đóng nó; được hỏi tới,
và được báo là đã đi hay đã mất, theo đúng cách viết ở dòng; tên kho cũng vậy ở tiêu đề, dưới
tiêu đề, ở tên của thanh và của nút, trong câu hỏi và dòng báo đã xoá hết),
components/memory-panel-script-store.test.tsx (kho của script có tab riêng, sau wiki; mở ở
agent màn đang xem và ghi agent ấy vào địa chỉ; địa chỉ nêu nó thì mở nó, ở agent địa chỉ nêu,
không nêu agent thì ở agent của màn; agent đội không có thì về agent của màn mà không hỏi server
gì về nó; đội chưa đọc xong thì tin lời địa chỉ; chọn agent khác trong tab thì địa chỉ nêu agent
ấy và khoá của nó thay khoá kia; rời tab bằng tab khác thì địa chỉ thôi nêu nó và tab kế hiện
agent vừa xem; Back của trình duyệt về tab người dùng đã ở trước đó),
lib/script-store-namespace.test.ts (kho của lịch được gọi bằng tên lịch, có lối tới dòng của
nó trong Lịch chạy; kho của cuộc trò chuyện được gọi bằng tiêu đề, có lối tới cuộc ấy; không có
tiêu đề thì gọi bằng id; nơi dùng đã mất thì không có lối và nói đó là loại nơi dùng nào; không
thuộc hai loại, hay thuộc loại không biết, thì chỉ là tên như bảng lưu; server không gửi gì thêm
thì là tên như bảng lưu, không có chữ nào cho loại; thiếu id thì không có lối nào; ký tự không
ai thấy trong tên được viết ra, còn id trong địa chỉ thì giữ nguyên; câu nhắc tới một kho viết
dấu ngoặc kép và dấu xuống dòng của tiêu đề thành chuỗi thoát, ở tiêu đề cũng như trong câu; bỏ
dấu cách đầu và cuối của tiêu đề; cắt tiêu đề ở tám mươi ký tự trong câu và giữ trọn ở tiêu đề;
tên như bảng lưu nằm giữa hai dấu ngoặc với dấu cách cuối được viết ra; độ đầy là khoá và giá
trị trên mức tối đa của một kho, sắp đầy từ bốn phần năm và đầy khi chạm mức; không đo khi không
có mức tối đa), lib/quoted-name.test.ts (tên được viết ra: giữ nguyên khi không có gì trong nó
không ai thấy; dấu ngoặc kép và dấu \ của chính nó thành chuỗi thoát, để một chuỗi thoát không
bị đọc thành hai ký tự viết nên nó; LF, CR, tab và các ký tự cùng lớp thành chuỗi thoát của
chúng; không ký tự điều khiển, dấu ngắt dòng hay dấu cách loại khác nào còn nguyên; một dấu cách
giữa hai chữ thì để nguyên; tên dài mấy cũng còn trọn; tên trong một câu: nằm giữa hai dấu ngoặc
kép, dấu ngoặc đóng là dấu cuối cùng; ở trên một dòng; dấu cách đầu và cuối được viết ra từng
cái để đếm được; trọn tới tám mươi ký tự, quá thì cắt ở đó với dấu cắt trước dấu ngoặc đóng; đếm
theo ký tự, không theo nửa ký tự; không cắt đôi một chuỗi thoát; một dấu cách được viết như cả
tên viết nó, dù chỗ cắt rơi vào đâu), lib/pretty-json.test.ts (JSON được bày mỗi phần tử một
dòng, thụt hai dấu cách mỗi mức, và đọc lại vẫn là đúng giá trị ấy; list rỗng và object rỗng nằm
trên một dòng; mỗi con số được chép như nó được viết; những gì một chuỗi chứa thì để nguyên; chữ
tới đã có dấu cách riêng được bày cùng một cách; giá trị không phải list hay object thì để
nguyên; chữ không phải JSON thì không có cách bày nào), test/fake-script-store.test.ts (máy
chủ giả mà vitest và Playwright dùng chung trả lời các route của kho: liệt kê kho theo tên và
khoá theo khoá, mỗi giá trị là JSON viết gọn; dòng của agent nào chỉ thuộc agent ấy; preview
cắt ở 200 ký tự có dấu cắt, còn độ dài đo trên cả giá trị; nói kho của ai từ đội và các cuộc trò
chuyện nó được trỏ tới; trả lời được như server trước khi nó nói một kho là gì; đọc trọn một giá
trị, 404 cho khoá hay kho nó không giữ; xoá một khoá, và thôi liệt kê kho khi khoá cuối đã đi;
xoá cả kho khi không khoá nào được nêu, và chỉ kho ấy; khoá rỗng vẫn là nêu một khoá; 200 với
không gì bị xoá khi không gì khớp; 404 cho agent đội không có, ở mọi route; từ chối một loại
request chừng nào còn được bảo vậy và không đổi gì trong lúc ấy; không nhận request không nói
kho nào hay khoá nào; mọi đường dẫn khác để lại cho máy chủ giả chứa nó, còn phương thức khác
thì không ai nhận).script-store.spec.ts (ở 1440px, 1000px và 390px: khoá dài, tiêu đề dài và nhiều
khoá đều nằm trong màn; một giá trị dài cuộn trong ô của nó, cả bằng bàn phím; script chưa giữ
gì thì màn nói nó là gì; nói đang tải cho tới khi server trả lời; đọc hỏng thì hiện lỗi thường
lệ kèm lối đọc lại), script-store-actions.spec.ts (ở 1440px và 390px: xoá một khoá thì hỏi
trước, bỏ dòng và đưa bàn phím sang khoá kế; nói không thì không gì bị xoá; xoá một kho nêu bao
nhiêu khoá sẽ đi, và bàn phím dừng ở tiêu đề của kho thế chỗ; khoá đã mất lúc giá trị của nó
được hỏi thì rời danh sách và bàn phím sang khoá kế; kho của một lịch dẫn tới dòng của job, kho
của một cuộc trò chuyện dẫn tới cuộc ấy; màn được mở từ Ghi nhớ bằng bàn phím, đi theo agent
được chọn, và Back lùi từng tab), script-store-written-names.spec.ts (ở 1440px và 390px, với
những khoá mà trang web vẽ giống nhau: mỗi khoá được vẽ theo cách riêng, trong bề ngang của
trang; mỗi khoá có nút riêng theo tên mà trình đọc màn hình nhận; câu hỏi xoá dùng đúng cách
viết ở dòng, và chỉ khoá được hỏi tới bị xoá).components/failed-step-output.test.tsx (ở thẻ run: cái một bước hỏng trả về nằm ngay
trên dòng, không cần bấm, trọn vẹn, trong một ô không cuộn; gấp lại khi được bảo và mở lại được;
mở ra khi một bước đang chạy kết thúc bằng lỗi; bước đi qua thì vẫn gấp, trong ô có cuộn; trọn
vẹn ở lời gọi của một script khi các lời gọi của script được mở; ở thẻ tool trong khung chat: có
ngay trên thẻ của lời gọi lỗi và lời gọi bị từ chối, trọn vẹn; dài hơn mức một bước giữ thì mở
sẵn trong ô có cuộn; là câu trả lời của một việc được giao đã hỏng, không cần bấm; thẻ đi qua
thì vẫn gấp), lib/step-preview.test.ts (cái một bước giữ của một đoạn chữ là cả đoạn khi ngắn,
và 160 ký tự đầu kèm dấu cắt khi dài; lời gọi đi qua vẫn giữ đúng phần đầu ấy như trước; lời gọi
hỏng giữ trọn lý do khi nó vừa 600 ký tự, dài hơn thì 200 ký tự đầu, dấu cắt và 400 ký tự cuối,
nên lời khuyên ở cuối một lời từ chối còn đó; lý do được đo như server đo, khoảng trắng của nó
thành từng dấu cách đơn; đếm theo ký tự, không theo nửa ký tự; khoảng trắng là đúng những ký tự
mà phép tách của server tách, không hơn: U+FEFF là một phần của chữ; lý do chỉ vừa sau khi
khoảng trắng của server thành một dấu cách thì còn trọn, và lý do chỉ vừa nếu U+FEFF là khoảng
trắng thì bị cắt), state/activity-reducer-failed-output.test.ts (bước tool nghe trực tiếp giữ
trọn lý do của lời gọi hỏng; giữ đầu và cuối của một lý do quá dài; lời gọi đi qua vẫn chỉ giữ
phần đầu như trước), components/activity-cards.test.tsx (bước hỏng của thẻ run nay mở sẵn, nút
là “Ẩn kết quả”), components/tool-call-card-canvas.test.tsx (thẻ thường của một lần ghi canvas
hỏng nay hiện sẵn lý do, nút là “Ẩn kết quả”).failed-step-output.spec.ts (ở 1440px, 1000px và 390px: trang của run nói lý do
không cần bấm, trọn vẹn, còn bước đi qua thì vẫn gấp; thẻ trong khung chat và phần Hoạt động của
chính khung chat cũng nói trọn lý do).tests/test_script_handoff_words.py (ba phép so chữ giữa web và server: web giữ hai
dòng đánh dấu đúng như server viết; giữ câu đứng trên cái script in ra, từng chữ; và điền câu ấy
vào đúng dòng server viết).lib/script-handoff.test.ts (tin của job script được đọc thành prompt và cái script in
ra; prompt nhiều dòng, nhiều đoạn còn trọn; cái được in giữ đúng từng dòng trống, chỗ thụt lề và
dòng nó kết thúc; dòng giống dấu đánh dấu mà script in ra là một phần của cái nó in; dòng mở đầu
tiên được lấy, nên dòng mở thứ hai thuộc về cái được in; tin chỉ giống thì để nguyên như được
viết: không xét theo hai dấu mà một dòng nêu, dòng ấy phải là đúng câu của server; không nhận
câu của server có thêm một dòng của người viết, không nhận câu lệch một ký tự, và phân biệt được
với tin thật có cùng con số; cái script in được đếm theo dòng, bỏ những dòng trống ở cuối, và là
dài khi quá mười hai dòng hay tám trăm ký tự),
components/message-thread-script-handoff.test.tsx (bong bóng chỉ mang prompt của lịch, không
dấu đánh dấu, không chữ nào của câu dành cho model; cái script in ra nằm ngay dưới bong bóng, có
nhãn, đúng như được in; ngắn thì mở sẵn, nói nó dài bao nhiêu, gấp và mở khi bấm; dài thì gấp
cho tới khi được hỏi, rồi có đủ; mở rồi thì tới được và cuộn được bằng bàn phím; prompt nhiều
đoạn và các tệp một kênh lưu kèm vẫn như ở mọi tin; đứng trên ghi chú canvas của tin cũng mang
một cái; ký tự không ai thấy trong cái script in được viết thành mã của nó khi khối mở, và lại
như thế mỗi lần mở; khối dài còn gấp thì không vẽ chúng ở đâu; chỗ các mã ấy chiếm không làm một
đoạn thành dài; tin không đúng hẳn là tin chuyển tiếp, như câu trả lời mà agent viết y hệt, được
vẽ như chính nó; gửi lại hay chép một tin chuyển tiếp thì dùng tin như được lưu: nhánh mới nhận
cả tin, và bản xuất cuộc trò chuyện có mọi dòng của nó).script-handoff.spec.ts (ở 1440px và 390px: prompt nằm trong bong bóng và cái
script in ra nằm dưới nó, không dấu đánh dấu nào trên màn; đoạn dài được giữ gấp, mở ra thì cuộn
trong ô của nó và xuống dòng; ngoài ra: gửi lại tin của job script bắt đầu từ tin như được lưu;
tin chỉ trích hai dấu đánh dấu vẫn là tin thường).read_only hay thiếu codemode; lời gọi không được phép không
làm dừng script, được ghi như một lời gọi, hay nói sai phải gọi thế nào thay; bỏ hook; không
giữ lời gọi, hay giữ sai thời gian, giá và kết quả của nó; đổi mức cắt câu trả lời, số tool và
số tham số lời mô tả nêu, độ dài script; đọc schema sai hình). Năm mươi lăm ở tiến trình con
(bỏ từng luật của sandbox, từng cờ khởi động, môi trường rỗng; đổi từng mức giây; kể sai vì sao
một tiến trình kết thúc; để tiến trình sống sau khi lượt bị huỷ; bỏ mức CPU, bộ đếm bộ nhớ hay
độ sâu của tiến trình con). Hai mươi bốn ở chỗ nối (giao tool_script khi không có gì mở cho
script, sau tool của máy chủ hay trước tool_search; script giữ danh sách tool lúc được giao
thay vì lúc chạy; bỏ calls khỏi step; tính tiền lời gọi miễn phí hay không tính lời gọi có
trả). Bốn trăm bảy mươi bảy ở mười một module của ngôn ngữ (cho qua từng thứ bị từ chối trước
khi chạy; đổi mỗi mức lên một và xuống một; bỏ từng chỗ đo và từng chỗ tính bước; đổi nghĩa
toán tử, hàm dựng sẵn và phương thức; đổi cách gán, bắt lỗi, finally và cách một giá trị
thành JSON).read_only mà không ẩn): mười một sửa đổi,
đều bị bắt. Chín ở script/tool.py (cho tool ẩn qua; quay về luật cũ chỉ codemode; bỏ điều
kiện chỉ đọc; lời mô tả nêu mọi tool thay vì chỉ tool codemode; dòng “và N công cụ nữa” hiện
cả khi không còn gì; đếm số tool còn lại theo tool đã mở; đổi lời khuyên gọi trực tiếp với nạp
rồi gọi; đổi lý do từ chối; cho bốn tool nói thay agent qua). Hai ở mcp/handout.py (giao
tool_script chỉ cho tool codemode; giao cả khi không có tool nào).script/namespaces.py (mốc bốn mươi lời tra và mốc 1.000 ký tự lệch một; không đếm
lời tra hay đếm nó như lời gọi; bỏ hạn độ dài chữ; coi True là số; cho qua tham số thừa,
tham số đưa hai lần hay thiếu tham số bắt buộc; bỏ tham số viết theo vị trí, hay đòi viết đủ
mọi tham số; không tính câu trả lời vào giá trị script cầm; đem lời tra đi gọi như tool). Mười
ba ở script/discovery.py
(liệt kê mọi tool thay vì tool gọi được, hay xếp lại thứ tự; mặc định, trần và sàn của limit,
cho limit bằng không; không xét kiểu của query, limit, name, hay trả lời câu hỏi lạ;
mô tả không gộp một dòng; describe bỏ lời từ chối hay không gợi ý tên gần nhất). Mười ba ở
script/scope.py (cho tool ẩn, tool chỉ codemode, tool không read_only, tool nói thay
agent hay tên của một lời tra qua; đổi lời khuyên với lý do từ chối; bỏ điều kiện hỏi trước,
không gọi lại được hay có lý do hỏi; không lọc tool gọi được; gợi ý tên nào cũng được hay
không gợi ý tên nào). Hai mươi bốn ở script/signature.py (cắt lệch một ký tự hay không cắt;
mất tiền tố của tham số lồng nhau, đường dẫn mảng thành đường dẫn object, hay kiểu phần tử của
mảng; enum không hạn số giá trị hay độ dài giá trị, liệt kê một phần hay mọi giá trị, viết giá
trị không theo JSON; tên chỉ khớp phần đầu hay dài quá; thừa một tham số, bỏ dấu “…”; đọc sâu
thêm một mức; không đánh dấu bắt buộc; không cắt lời tham số hay lời mô tả; bỏ dòng không có
tham số; ba cách xét lời gọi không khớp tool; kiểu lạ không thành any). Chín ở
script/tool.py (thêm dòng chỉ cách gọi sau lời gọi thành công hay sau mọi lỗi; thay lời của
tool bằng dòng ấy, hay bỏ nó khỏi thẻ run hoặc khỏi câu trả lời; tên không có làm dừng script
hay không gợi ý tên gần nhất; lời từ chối không bắt được; không nối lời tra vào script). Năm ở
script/runner.py (nay ở script/replies.py; đọc lời tra như lời gọi; gửi lời từ chối như câu trả lời; không xét kiểu
tên và tham số của một lời gọi; chạy không có nơi tra mà vẫn trả lời), một ở script/child.py
(bỏ qua lời từ chối) và một ở mcp/tool_search.py (tool có sẵn không đưa tên vào xếp hạng).
Vòng đầu sót mười ba: bốn ở script/discovery.py (kiểu của limit, query và name, và câu
hỏi lạ vẫn được trả lời; tiến trình con không bao giờ gửi những câu ấy nên không test nào chạm
tới, trong khi tiến trình cha vẫn phải từ chối chúng) và chín ở script/signature.py (mốc cắt
của describe, hạn số giá trị và độ dài giá trị của enum, enum có giá trị không phải chữ hay
số nguyên, tên chỉ khớp phần đầu, độ sâu, lời của tham số và lời mô tả không bị cắt). Mỗi cái
được thêm test, rồi cả bảy mươi chín đều bị bắt.tools.batch): bảy mươi hai sửa đổi, bảy mươi mốt bị bắt, một tương đương. Ba
mươi bốn ở script/calls.py (mốc 100.000 ký tự và mốc 25 lần lệch một; tham số không đi qua
JSON; cho qua cách viết lẫn vị trí với tên, tuple, lô rỗng, lô dài quá, phần tử thừa khoá, thiếu
args hay sai kiểu tool và args; lỗi không nêu số thứ tự của phần tử, nêu luôn số 0 hay đếm
từ một; chỗ còn lại tính lệch một, hết sạch chỗ mà vẫn chỉ là lỗi bắt được, thiếu chỗ mà làm
dừng script; lô đếm như một lời gọi hay không đếm; câu trả lời của lô tính theo cái lớn nhất
hay không tính; câu trả lời và lời gọi gửi đi bị đảo). Hai ở script/namespaces.py (đem batch
đi gọi như một tool, hay không giữ tên ấy) và một ở script/scope.py (tool đặt tên giống một
lời tra vẫn gọi được). Mười một ở script/replies.py (không xét kiểu của cặp, tên, tham số, lô
hay thông điệp; lô rỗng hay lô dài tối đa không được trả lời; lô không giới hạn độ dài; lời gọi
sai hình bị bỏ qua; lô bị từ chối không được nói ra; câu trả lời bị đảo). Ba ở script/child.py
(lô bị từ chối không thành lỗi cho script bắt; cờ dừng của lô bị bỏ qua; câu trả lời bị đảo). Hai ở
script/runner.py (dòng nói về lô bị giới hạn ở 4 MiB và 5 MiB, trong khi lô dài nhất mà mỗi
lời gọi mang tham số dài nhất, nặng nhất khi viết ra, cần hơn 5 MiB). Mười ba ở script/tool.py
(nhiều hơn bốn lời gọi cùng lúc, chỉ một, hay không giới hạn; từ chối chỉ khi mọi lời gọi bị từ
chối, không làm dừng script, coi lời gọi chưa chạy là thành công, hay chỉ xét trong số tool
script được gọi thay vì mọi tool agent giữ; giữ lời gọi theo thứ tự xong; tên không có bị ghi như một
lời gọi hay được coi là thành công; tính giờ từ lúc lô được hỏi; lời mô tả không nêu mức tối đa
hay ví dụ) và hai ở texts_script.py (lời mô tả không nói lô vẫn tính vào số lần gọi, hay nói
một lời gọi lỗi làm hỏng cả lô). Vòng đầu sót bốn. Ba trong số đó là chỗ test hở: t8 (giữ lời
gọi theo thời gian giảm dần, mà test viết lời gọi lâu nhất trước nên trùng đúng thứ tự viết), và
hai câu trong lời mô tả vì test so lời mô tả với chính hằng số tạo ra nó nên đổi chữ vẫn qua.
Test thứ tự nay viết ba lời gọi theo thứ tự mà không cách xếp nào khác trùng (không ngắn nhất
trước, không dài nhất trước, không theo tên, không đảo của những thứ ấy), và test lời mô tả so
hai câu ấy nguyên văn; thêm bốn sửa đổi quanh t8 (ngắn nhất trước, theo lúc xong, đảo, theo
tên) và cả bảy đều bị bắt. Cái còn lại là tương đương: except (TypeError, ValueError) thành
except TypeError quanh json.dumps ở script/calls.py. ValueError không tới được đó:
Budget.measure đã báo TOO_NESTED với giá trị chứa chính nó trước khi tới đây, json.dumps cho
NaN và vô cực qua, và số nguyên của script bị chặn ở 256 bit nên không đủ dài để bị từ chối. Vế
ấy được giữ lại như lớp phòng thủ, đúng như đoạn code cũ mà nó được chuyển từ đó sang.activity/tool_steps.py (lời gọi nghe hai lần được ghi hai lần; kết quả tính tiền mọi lời gọi
hay không tính lời gọi nào; hai lời gọi giống nhau bị trừ cả hai; script chạy lại giữ danh sách
cũ; lời gọi vừa nghe không được tính tiền; kết quả giữ thứ tự đã nghe thay vì thứ tự đã viết;
lời gọi không thuộc step nào tự mở một step), ba ở agent/tool_run.py (không huỷ task khi lời
gọi bị bỏ; index đứng yên; không trao chỗ nói cho task), một ở script/nested.py (script
không nói gì khi một lời gọi xong), một ở agent/tool_calls.py (lượt bỏ sự kiện của lời gọi lồng) và
một ở agent/events.py (đổi tên loại sự kiện). Mười lăm ở trần của lượt: sáu ở
agent/nested_calls.py (mốc lệch một; cho một phần của lô; không bao giờ đánh dấu đã hết; chín
lời gọi mỗi bước thay vì mười; không đặt bộ đếm cho lượt; trần bằng số bước thay vì nhân mười),
ba ở script/nested.py (đếm cả tên không có; hết trần mà không dừng script; script không hỏi
bộ đếm), hai ở agent/loop.py (lượt không xét bộ đếm, hay xét mà không dừng), một ở
agent/tool_calls.py (những lời gọi đứng sau vẫn chạy), hai ở agent/step_limit.py (lý do
dừng sai; không đóng những lời gọi chưa chạy) và một ở texts_halt.py (lý do dừng không có
lời). Mười sáu nữa ở script/nested.py cho câu trả lời được giữ (mốc lệch một; giữ cả lỗi, giữ cả
cái tool_output_read trả về; không xét agent có tool_output_read, không xét id lời gọi;
ghi hỏng mà vẫn chỉ tới chỗ giữ; số ký tự còn lại nói thành cả độ dài; id thiếu số thứ tự hay
thiếu id của script; số thứ tự không tăng; đọc tiếp từ không; phần đầu ngắn hơn mức; registry
của script tự cắt câu trả lời ở mức script nhận, nên không còn bản đầy đủ để giữ; không giữ
được thì không cắt; đếm cái script nhận trước
khi cắt; giữ bản đã cắt thay vì bản đầy đủ). Mười chín ở phần in ra: mười bảy ở
script/printed.py (phần đầu còn một nửa, phần cuối thiếu một ký tự; đếm sai tổng; mảnh nằm
vắt qua chỗ cắt bị giữ cả, bị đếm cả, mất phần còn lại hay không bị cắt; không bao giờ bỏ gì
hay bỏ quá sớm; phần cuối không nhường chỗ cho dòng báo; đúng mức đã tính là quá, quá một ký tự
vẫn tính là nguyên; dòng báo được tính hẹp hơn thật hay không được trừ vào phần cuối; số ký tự
đã bỏ được đoán thay vì đếm; không có dòng báo; chỉ giữ phần đầu) và hai ở script/machine.py
(cái script in không tới chỗ giữ; cái trả về rỗng). Mười lăm ở phần lưu kèm: ba ở
store/message_models.py (ghi chú canvas được đọc ở mọi vai, không vai nào, hay ở vai tool),
hai ở agent/payload_trim.py (xét theo việc có phần lưu kèm, hay lấy ghi chú từ phần lưu kèm,
nên model nhận được cái script đã gọi), một ở activity/trajectory.py (bản ghi quỹ đạo chép
phần lưu kèm vào tin nhắn), một ở agent/tool_calls.py (kết quả không lưu gì kèm), sáu ở
agent/tool_events.py (chữ có dấu bị viết thành mã; script không có số đo hay không có lời
gọi thì không lưu gì; một số đo cũng đủ để bỏ trống; hai số đo đổi chỗ; bỏ danh sách lời gọi),
một ở store/messages.py và một ở store/message_log.py (chỗ lưu hay chỗ đọc lại bỏ phần lưu
kèm).script/limits.py, một ở script/tool.py, và max(times, 0) trong phép lặp ở
script/operators.py. Vòng ấy cũng cho thấy test đầu tiên cho “tiến trình bỏ đi trước khi nhận
câu trả lời” không thể hỏng; nó được thay bằng hai test cho hai thời điểm mà việc gửi thật sự
báo lỗi. Sau đó sáu trăm bốn mươi lăm bị test bắt. Cái còn lại là tương đương:
outcome if outcome is not None else … thành outcome or …, mà Outcome thì luôn được coi
là đúng.tool_script; đọc ở bất kỳ dòng nào; đọc cả ở dòng cuối; đọc khi còn chữ trên cùng dòng;
không đọc khi không có gì theo sau; chỉ đọc khi không có gì theo sau; bỏ qua khoảng trắng
đứng trước; không phân biệt hoa thường; đổi câu; đổi tên tool). Sáu ở chỗ hai bên phải khớp
nhau (web đọc câu khác hay tool khác; server nói câu khác, không nói, nói với script chạy
xong, nói ở cuối).calls; dòng của
script bị gộp; danh sách không hiện, hiện khi rỗng, hay đứng sau cái script in ra; dòng tóm tắt
đếm sai; danh sách mở sẵn hay không đánh số; tên, dấu lỗi, thời gian, tham số, giá và câu trả
lời của từng lời gọi mất hay sai; máy chủ giả của test giao tool_script sai luật). Mười ba qua
Playwright: năm cái trong số trên lặp lại trong trình duyệt, tám cái ở CSS (cột của đầu dòng,
chỗ đứng của thời gian, vùng chạm trên điện thoại, chỗ xuống dòng của tên và của từng dòng,
cột của danh sách, bề rộng của dòng mở danh sách). Vòng đầu sót bốn: thứ tự tên người giữ
tool_script ở máy chủ giả, và ba cái ở CSS. Phép kiểm cũ đo hộp của thời gian, mà hộp vẫn
chạm mép phải khi cột bị đổi, nên giờ nó đo chỗ chữ kết thúc; chỗ danh sách đứng và bề rộng của
dòng mở danh sách thì trước đó chưa có phép kiểm nào. Sau khi thêm test, cả sáu mươi ba đều bị
bắt.state/activity-reducer.ts (lấy step đầu
tiên của một id thay vì step mới nhất; lời gọi không rõ giá không được đếm hay bị cộng thành
tiền; lời gọi không tính tiền bị cộng; step đã trả lời bị dùng lại; step mở lại giữ danh sách
cũ hay thêm một step nữa; kết quả cộng lại mọi lời gọi, không cộng gì, giữ thứ tự đã nghe hay
bỏ việc của script; lời gọi nghe hai lần cộng hai lần, không bao giờ cộng, hay không được liệt
kê), hai mươi hai ở lib/nested-calls.ts (một lời gọi đã liệt kê trả lời thay cho mọi cái sau
nó, hay cái đầu tiên không bao giờ được trả lời; hai lời gọi bị coi là một dù khác giá, tham
số, tên, câu trả lời hay việc có tính tiền; chỗ kế tiếp bị coi là đã nghe; nghe lại bị nói là
mới và ngược lại; danh sách đưa vào bị sửa; danh sách rỗng được giữ; số đo bằng không bị bỏ;
lời gọi hỏng hay nửa danh sách vẫn được liệt kê; dòng null được đọc; JSON hỏng ném lỗi; giá,
câu trả lời hay tham số sai kiểu vẫn được nhận), hai ở lib/run-rows.ts, một ở
lib/run-summary.ts, hai ở lib/tool-reply.ts (câu từ chối vì hết lời gọi lồng không còn là
lỗi, hay lời mở thiếu khoảng trắng), bốn ở state/thread-reducer.ts (không đọc phần lưu kèm;
không giữ việc của script khi kết quả tới; mọi thẻ tool đều nhận lời gọi; nghe hai lần liệt kê
hai lần), sáu ở components/script-calls.tsx (đếm khi còn chạy; step cũ có lời gọi mà không
đếm; tool nào xong cũng đếm; hai số đo đổi chỗ; dòng đếm đứng trên danh sách; chỉ một số đo thì
không đếm), ba ở components/tool-call-card.tsx và hai ở components/run-timeline.tsx (chỉ
đếm khi thành công, đếm ngay, hay không có phần báo), hai ở i18n/vi.ts (số không tách hàng
nghìn; số chưa đếm đọc thành không) và mười một ở lib/conversation-markdown.ts cùng
lib/script-calls-markdown.ts, nơi viết từng dòng của danh sách (mỗi danh
sách một tiêu đề hay không bao giờ có tiêu đề; danh sách không mở lượt của agent; lời nói không
mở lượt, hay lượt nào cũng đọc là của agent; dấu lỗi đặt vào lời gọi thành công; giá ghi cho
lời gọi không tính tiền; đánh số từ không; dòng đầu không đếm lỗi hay đếm lỗi từ lời gọi thành
công; tên không nằm trong dấu code). Cái tương đương là phép xét kiểu của một số đo trong
storedWork: bỏ nó thì workOf ngay sau vẫn xét đúng điều ấy. Bốn sửa đổi ở CSS qua
Playwright, đều bị bắt (dòng đếm không xuống dòng, hiển thị nối dòng, đứng ở cột đầu của lưới,
hay sát mép trên ở thẻ trong khung chat).tool_script cho mọi agent có tool đọc: năm mươi bảy sửa đổi, đều bị bắt ngay vòng
đầu. Ba mươi lăm ở server: hai mươi bốn ở script/tool.py (quay về luật cũ chỉ giao khi máy chủ
có tool chỉ đọc; giao cho mọi agent; bỏ qua tool của máy chủ khi xét; đọc hộp đồ lúc dựng thay
vì lúc script chạy; quên tool của máy chủ trong lời mô tả; tool có sẵn chỉ nêu tên hay kèm cả
mô tả; bỏ trần của tool có sẵn hay đặt nó ở hai mươi chín; tool của máy chủ có trần riêng hay
trần lệch một; dòng “và N công cụ nữa” quên tool có sẵn, quên tool của máy chủ, đếm theo tool
đã mở, đứng sai nhóm, hay chỉ hiện khi có dòng của máy chủ; nêu mọi tool của máy chủ thay vì
tool codemode; mang with_mcp, không mang dấu nào, hay là một tool thường; tool của máy chủ
lẫn vào nhóm có sẵn; nhóm có sẵn nêu cả tool script không gọi được; không gọi lại được sau
restart), tám ở mcp/handout.py (không giao lại sau khi thu về; giao lại mà quên tool của máy
chủ hay tính mọi tool của máy chủ; không thu bản cũ về; không thu tool_search về; đặt
tool_script sau tool của máy chủ hay trước tool_search), một ở server/agent_assembly.py
(agent được dựng không có nó) và hai ở texts_script.py (tiêu đề của nhóm có sẵn, dòng đếm).
Hai mươi hai ở web qua vitest: hai ở components/agent-editor/tools-section.tsx (danh sách cho
phép liệt kê tool_script hay tool_search), mười một ở components/tools-matrix.tsx (ô
không có nghĩa riêng, mang nghĩa của tool_search hay ngược lại; dấu trùng với dấu của
tool_search; dòng chú giải hiện ở mọi crew hay theo tool_search; mất nhãn, nhãn hiện cả ở
tool_search, nhãn và lời giải thích mang chữ của tool_search), tám ở test/fake-mcp.ts
(máy chủ giả đánh dấu sai, không biết agent đọc bằng tool của mình, coi mọi agent hay mọi tool
là đọc, đòi mọi tool đều đọc, quên agent đọc qua máy chủ, đổi thứ tự hai dòng, không xếp tên
agent) và một ở i18n/vi.ts (chữ của dòng chú giải).Dưới dòng của mỗi agent trong roster (“Đội của bạn”), system prompt của master mang các dòng hệ thống tự đọc từ đội đang chạy mỗi lần lắp prompt: máy chủ MCP kèm mức với tới, kỹ năng, job đang bật, công cụ, model và model leo thang. Không lưu ở đâu, nên thứ một agent vừa được thêm hay vừa mất có mặt ngay ở lần gọi model kế tiếp (agents.md).
tests/test_crew_abilities_told.py (tên được kể so với của người đọc khi số tên khác
nhau không quá một nửa số tên phải kể, đúng ở mốc một nửa, và kể cả danh sách khi khác nhiều
hơn hay khi không có người đọc để so; mỗi loại một dòng theo một thứ tự; loại không có thì
không có dòng; model leo thang không tự đứng một mình; bốn mức với tới của một máy chủ; skill
người đọc cũng có thì chỉ nêu tên, skill người đọc có mà agent không có thì nêu tên không kèm
mô tả; mô tả gộp khoảng trắng và cắt ở 80 ký tự, đúng 80 thì giữ nguyên; tên job, tên skill
và tên công cụ có xuống dòng vẫn chỉ là một mục của dòng nó thuộc về, dù được kể cả danh sách
hay so với người đọc, ở phía “thêm” hay phía “không có”; các dòng đứng ngay
dưới dòng của agent và lời dặn đứng cuối; chỉ một agent có gì để kể cũng đủ để có lời dặn, dù
nó đứng đầu danh sách; đội không kể gì thì roster chỉ còn dòng của chủ, không có lời dặn;
không hỏi đội khi không có ai để liệt kê; lời dặn nói ai viết các dòng và rằng chúng đáng tin
hơn điều đã nói; lời dặn gọi đúng hai tình trạng ngoài tầm với bằng chữ mở đầu của chính câu
kể chúng, dặn không giao việc cần dịch vụ đó mà nói rõ với người dùng, và không nhắc tới hai
tình trạng dùng được; lời dặn nói dòng Công cụ không tính tool của dịch vụ ngoài và ai gọi
được dịch vụ nào thì chỉ xem ở dòng Dịch vụ ngoài của chính agent đó; agent mà script gọi
được cả tool ghi thì dòng Công cụ có thêm một câu ở cuối, câu ấy nêu shell_run chỉ khi
agent giữ nó, đứng sau cả khi tên công cụ được kể so với của người đọc, và agent không có
dòng Công cụ thì không có câu nào).tests/test_crew_roster_abilities.py (tool vừa được thêm hay vừa mất có ngay ở prompt
kế tiếp, không nối lại gì; delegate không bị đem ra so; skill mới kèm mô tả; skill thiếu lệnh
trên máy mang [thiếu: …] đứng trước mô tả nên phép cắt 80 ký tự không làm mất nó, kể đủ mọi
lệnh thiếu, và skill đủ lệnh thì không mang gì; chỉ job đang bật
và không phải job gom trí nhớ được kể, bật tắt là thấy ngay; job không tên được kể bằng id; job
tạo từ chat có ngay khi tồn tại và job của master không bị kể dưới agent khác; job tạo từ chat
với tên có xuống dòng chỉ thêm đúng một dòng vào prompt của master, không dựng được dòng agent
hay dòng công cụ giả; model là tuyến
đầu, model leo thang chỉ được kể khi tuyến đó dùng được; máy chủ agent nêu tên, kể cả nêu hai
lần, được kể một lần với mô tả và đọc và ghi, tool mcp__… không lẫn vào dòng công cụ và
master không cầm tool nào của nó; read_only toàn bộ thì chỉ đọc, và tool ghi bị hidden
cũng vậy; máy chủ chỉ có tool mang readOnlyHint thì chỉ đọc dù chủ không khai read_only
và tool ấy vẫn cần duyệt, còn tool không ai nói là chỉ đọc thì đọc và ghi; máy chủ hidden
cả thì không kể dù nối được; chờ đăng nhập và không nối được có câu riêng, nối lại được thì
quay về đọc và ghi; máy chủ chưa nối lần nào được kể là không nối được; phiên đăng nhập hết
hạn giữa chừng (token hết, máy chủ không cấp lại) được kể là chờ đăng nhập dù agent vẫn cầm
tool, và khoá riêng bị từ chối giữa chừng được kể là không nối được, cả hai đi qua đúng lời gọi
tool phát hiện ra chuyện đó; máy chủ không có trong cấu hình thì bỏ qua; một agent được thêm
vào giữa lúc đội đang được đọc không làm hỏng prompt; một
lượt thật của master mang các dòng và lời dặn trong system message, còn lượt được giao việc
thì không; agent bật script_calls: gated được kể là script gọi được cả tool ghi ngay ở lần
đọc kế tiếp, câu ấy nêu shell_run chỉ khi agent còn giữ nó và mất hẳn khi agent không còn
tool_script, và điều master tự bật cho mình không nói gì về agent khác).tests/test_crew_roster.py::test_an_agent_added_edited_or_removed_while_running_is_told_as_it_now_is
(runtime dựng thật trên home tạm: agent thêm khi đang chạy được kể với công cụ của nó, sửa
tools là dòng đổi theo, mọi agent đều được nối với đội, agent bị xoá thì mất dòng).delegate ra so, kể tool MCP như công cụ, kể job gom trí nhớ hay job đang tắt,
kể job bằng id hay chỉ bằng tên, kể job của agent khác, đổi chỗ “chờ đăng nhập” với “không nối
được”, bỏ qua trạng thái kết nối khi agent còn cầm tool, chỉ coi link hỏng là không nối được,
luôn hay không bao giờ đọc và ghi, bỏ qua gợi ý chỉ đọc của máy chủ hay bỏ qua lời của chủ,
kể máy chủ hidden, không gộp tên ở từng chỗ tên được kể, không kể lệnh thiếu, đặt lệnh thiếu
sau mô tả, chỉ kể lệnh thiếu đầu tiên, đọc đội không qua bản chép,
kể hai lần máy chủ nêu hai lần, bỏ mô tả máy chủ, lấy tuyến cuối, bỏ kỹ năng,
hỏi đội khi không có ai để liệt kê, không biết người đọc là ai, luôn hay không bao giờ có lời
dặn, lời dặn theo agent đứng cuối, bỏ các dòng, chỉ nối đội cho agent giao việc được, đọc đội
một lần lúc nối rồi giữ lại, prompt không đưa đội vào roster). Vòng đầu sót năm; bốn cái được
thêm test (job không tên, máy chủ nêu hai lần, tuyến thứ hai, lời dặn khi agent có dòng không
đứng cuối). Cái còn lại là tương đương: đưa tên “không có” qua hàm mô tả skill, mà một tên
như thế thì người đọc luôn có, nên hàm chỉ trả lại tên. Sáu mươi mốt cái còn lại đều bị bắt.scripts/run_evals.py trên một home tạm có master và một agent nêu mcp: [notion] trỏ tới
một máy chủ MCP giả ở loopback, mô tả của agent không nhắc Notion, model của đội thật, mỗi ca ba
lượt. Hai yêu cầu: tạo một trang Notion và tìm một trang trong Notion. Bản phát hành trước
(roster chỉ có mô tả): 0/6 lượt giao việc, cả sáu trả lời là không có công cụ Notion. Bản này,
máy chủ đã nối: 6/6 lượt giao cho agent cầm Notion, agent gọi tool của máy chủ và master báo
lại kết quả; câu hỏi thường vẫn tự trả lời 3/3. Bản này, máy chủ ngoài tầm với: 5/6 lượt
master nói rõ dịch vụ chưa kết nối được mà không giao việc, một lượt vẫn giao. Trước khi lời
dặn có câu về dịch vụ ngoài tầm với thì cả 6/6 lượt đều giao và 5 lượt thuật lại là “không có
công cụ”. Chạy lại cả hai trạng thái sau khi lời dặn có thêm câu về dòng Công cụ: máy chủ đã
nối 6/6 lượt giao đúng agent và câu hỏi thường 3/3, máy chủ ngoài tầm với 6/6 lượt nói rõ mà
không giao. Mỗi ca chỉ ba lượt, nên các con số này cho biết chiều hướng chứ không phải tỉ lệ.--no-schedule nên không tự nối máy chủ MCP lúc khởi động: muốn có
trạng thái đã nối thì gọi POST /api/mcp/<tên>/reconnect ngay khi server lên, và đặt một ca
không dính tới máy chủ ở đầu tệp ca.Agent có browser: read hay act trong tệp của nó, trên máy đã bật browser_enabled, giữ các tool
browser_*: một Chromium riêng cho mỗi agent, tab thuộc về cuộc trò chuyện, trang được kể thành dàn ý
có ref giữa hai dòng rào, và mọi kết nối của trình duyệt đi qua một proxy chỉ cho tới địa chỉ công
khai. Chi tiết ở tools.md. Test chạy trên chính Chromium mà gói Playwright
ghim (CI cài nó trước khi chạy pytest; trên máy mình thì chạy uv run playwright install chromium
một lần, thiếu nó các test này đỏ chứ không bị bỏ qua), với các trang do máy chủ giả trên loopback
phục vụ
(tests/browser_servers.py); cổng của máy chủ giả được miễn rào mạng theo từng cổng, chỉ trong test.
Không test nào ra mạng. tests/browser_tools.py dựng bộ tool cho một agent, tests/browser_crew.py
dựng một đội có agent giữ trình duyệt.
tests/test_browser_config.py (máy không có trình duyệt cho tới khi tệp nói vậy; tệp đặt
và biến môi trường thắng; danh sách từ dừng để rỗng là tắt rào mềm có chủ ý, chỉ khoá vắng mới về
mặc định; giá trị lạ làm dừng khởi động; agent chỉ đọc hay thao tác khi chính tệp của nó nói, không
bật được thứ máy chưa bật, không thừa hưởng mức từ cấu hình chung; off trần của YAML là tắt, on
trần bị từ chối vì không nói tới đâu, mức lạ bị từ chối; danh sách host chỉ nhận tên host; lần lưu
từ web là điều lần khởi động sau đọc, off được ghi thành chữ, xoá hai khoá là bỏ khỏi tệp, giá
trị sai từ web bị từ chối và tệp giữ nguyên).tests/test_browser_install.py (Chromium đúng bản ghim, tải xong trọn vẹn, mới là đã cài
và nói được phiên bản; bản khác hay lần tải dở thì chưa; thiếu gói thì không có gì; kênh chrome
là Chrome của máy, phiên bản đọc từ ứng dụng mà không khởi động nó; thư mục trình duyệt tìm đúng
nơi Playwright để).tests/test_browser_tool_set.py, tests/test_browser_runtime.py (máy tắt hay agent không
xin thì không có tool nào; read được sáu tool, act thêm hai (browser_act,
browser_download), và chỉ browser_navigate cùng hai tool ấy cần duyệt; master nhận theo tệp
của nó như mọi agent; tools: không đem tới cũng không lấy đi tool trình duyệt; bật mà thiếu
trình duyệt là một dòng cảnh báo và server vẫn chạy; pool dựng theo cấu hình của máy và tìm đúng
kênh; server đang chạy thì quét trình duyệt nhàn rỗi theo chu kỳ, một vòng hỏng không làm dừng
việc quét, và khi tắt thì đánh dấu pool đã đóng trước rồi mới dừng mọi trình duyệt).tests/test_browser_pool.py (chưa ai gọi thì chưa có gì chạy; hai lời gọi cùng lúc chờ
một trình duyệt; đăng nhập của agent này không phải của agent kia và còn qua lần khởi động lại;
trình duyệt chỉ tới được trang qua proxy của nó, loopback cũng vậy; nhàn rỗi đủ lâu thì bị dừng
và lời gọi sau khởi động lại; trình duyệt đang phục vụ một lời gọi không bị dừng; lần khởi động
hỏng không giữ chỗ; tên không phải id agent không khởi động gì; trình duyệt tự biến mất được
quên và khởi động lại; lời gọi đến lúc đang dừng thì chờ; lệnh dừng đến trước khi lần khởi động
chạy bước đầu vẫn dừng được trình duyệt ấy; mọi thứ dừng được cả khi một trình duyệt còn đang
khởi động; lệnh dừng thứ hai chờ lệnh thứ nhất; cổng duyệt đọc tab mà không khởi động gì; trình
duyệt không tự chạy việc riêng nào ra mạng; pool đã bị đánh dấu đóng thì không khởi động trình
duyệt nào và không để lại hồ sơ, trình duyệt đang chạy vẫn là của agent cho tới khi mọi thứ
dừng; dừng mọi thứ chưa phải là đóng; trình duyệt không bao giờ trả lời lệnh đóng vẫn trả lại
proxy của nó).tests/test_browser_launch.py (trang không gửi được gói UDP nào vòng qua proxy, kể cả
trên hồ sơ đã dùng trước đó; địa chỉ viết http được hỏi ở đúng cổng của nó, không thử cổng
https trước; trình duyệt nhận đúng một danh sách tính năng bị tắt, gồm của driver và của đội;
những tên chép lại từ driver đúng là thứ driver đang cài tắt; sandbox của chính Chromium được
giữ trên macOS và để như driver đặt ở nơi khác; hồ sơ mới là riêng tư và mở đầu bằng thiết lập
của đội, thứ trình duyệt đã ghi được giữ còn thiết lập của đội được ghi đè lên, tệp thiết lập
không đọc được thì được thay, tệp đã đúng thì không ghi lại; driver không khởi động được thì
không bị giữ và lần sau khởi động cái khác; driver biến mất kéo theo các trình duyệt của nó và
lời gọi sau bắt đầu lại từ đầu; trình duyệt của agent khởi động không ai thấy với trang một cỡ,
còn cửa sổ đăng nhập khởi động có giao diện, cỡ do người kéo, sau proxy và với cùng các công
tắc, thiết lập như trình duyệt của agent; tệp trang gửi về chỉ được giữ khi có một thư mục dành
cho nó; lần khởi động mà driver không bao giờ trả lời bị bỏ sau một lúc, không giữ chỗ, và lời
gọi sau khởi động cái mới; trình duyệt không trả lời lệnh đóng chỉ được chờ một lúc, còn trình
duyệt đã biến mất thì được đóng không một lời).tests/test_browser_network.py (địa chỉ không công khai là nội bộ, kể cả địa chỉ riêng
viết trong IPv6; dải mà fetch_url cho qua thì trình duyệt vẫn chặn; một tên chỉ tới được khi
mọi địa chỉ nó phân giải ra đều tới được; chỉ mở địa chỉ http/https đầy đủ; trang ở địa chỉ
riêng bị từ chối kèm tên; địa chỉ riêng viết thẳng ra bị từ chối mà không phân giải tên nào; địa
chỉ chỉ việc phân giải mới từ chối được thì không bị coi là đã bị từ chối từ trước; tên không ai
phân giải được nói đúng là vậy chứ không nói là riêng; ngoại lệ cho test nêu một cổng và không
che gì khác).tests/test_browser_proxy.py (máy chủ nội bộ bị từ chối và không bao giờ được nối tới;
tên trỏ vào trong bị từ chối theo địa chỉ nó phân giải ra; request đi tới đúng địa chỉ đã kiểm,
không hỏi lại tên; proxy ký câu trả lời của chính nó và không trang nào giả được chữ ký; trang
không được nghe điều nói với proxy; thân request đi trọn; request thứ hai trên cùng kết nối không
được chuyển; đường hầm chở byte hai chiều tới địa chỉ đã kiểm; không mật khẩu của lần khởi động
này thì không được phục vụ, hai lần khởi động không chung mật khẩu; nhiễu và máy khách bỏ ngang
không làm proxy ngừng; các lần từ chối gần đây đọc lại được; dừng thì đóng cửa và cả các đường hầm
đang mở).tests/test_browser_policy.py (host của một địa chỉ được đọc theo cách chứng chỉ viết
nó; địa chỉ mà trình duyệt có thể đọc theo cách khác thì không nêu host nào; trình duyệt được
giao đúng cách viết đã dùng để đọc host; host thuộc về agent chỉ khi danh sách nêu, bất kể hoa
thường, cổng hay dấu chấm cuối; từ là chuỗi chữ và số viết thường; tên viết bằng dấu rời vẫn là
cùng những từ ấy; từ dừng được tìm theo pattern khớp nó, tên chỉ trông giống thì không; từ có
dấu trong tên không khớp pattern viết không dấu; pattern đầu tiên của danh sách là cái được nêu;
ký tự trình duyệt không vẽ ra bị bỏ khỏi tên, chỗ trống vẽ bằng một chữ cái là khoảng trắng, chữ
vẽ ở dạng khác đọc thành chữ thường, dấu phụ lạc không được tính; chữ của bảng chữ khác trông
giống chữ Latin không khớp từ nào; mỗi từ của danh sách mặc định dừng được một cái tên).tests/test_browser_tabs.py (một cuộc thấy tab của chính nó, tab dùng sau cùng đứng
đầu; tab của lời gọi trước là đúng tab ấy hoặc không có, không bao giờ là tab khác; hỏi tab không
có thì được kể những tab đang có; nhãn trùng không được cấp hai lần, nhãn sai không mở gì; cuộc
giữ hết chỗ thì được bảo đóng bớt; tới giới hạn thì tab cuộc khác để lâu nhất nhường chỗ, tab
đang giữa một thao tác thì không; hai cuộc mở cùng lúc không cùng lấy chỗ cuối; tab không ai dùng
nửa giờ bị đóng; cửa sổ trang tự mở là tab kế tiếp của cùng cuộc; hộp thoại bị gạt đi và được giữ
lại để kể; trang hỏi có rời đi không thì được rời).tests/test_browser_kit.py (lời gọi ngoài mọi cuộc trò chuyện dùng tab của job; ref
là thứ model đã được thấy hoặc không tìm ra gì; trang đứng yên được vẽ một lần, trang chuyển đi
lúc đang vẽ được vẽ lại, chuyển trong cùng trang không tính, trang không ngừng chuyển được kể ở
địa chỉ nó vừa rời; bản vẽ trình duyệt đánh mất được vẽ lại khi trang đã yên, bản vẽ quá giờ
hay hỏng ở phía server thì không, lần vẽ cuối bị mất là một lỗi; lỗi của trình duyệt được nhận
ra theo nơi lớp của nó sống; tham số được đọc đúng kiểu tool nhận; schema nêu thứ bắt buộc; tên
host được kể mỗi tên một lần, và chỉ vài tên).tests/test_browser_snapshot.py (mỗi phần tử có ref được đọc cùng vai trò và tên;
ref viết trong tên hay trong giá trị không phải là ref; phần tử mang từ dừng của chính tên
nó, hộp mang từ dừng của thứ nó chứa, từ không lan sang thứ đứng bên cạnh hay phía sau; địa chỉ
của liên kết không làm dừng; ref vẽ hai lần thì không tìm ra gì; dàn ý vừa trần được đưa trọn,
quá trần thì rút về những gì thao tác được; tìm kiếm trả những dòng chứa từ, dưới các dòng cha
của chúng, xét cả giá trị, địa chỉ và hai cách viết dấu; mọi khung nhìn bị cắt đều kết ở một dòng
trọn và nói còn bao nhiêu).tests/test_browser_fence.py (rào được tạo mới cho mỗi câu trả lời; chỗ dành cho trang
là phần trần còn lại, và là không khi trần quá nhỏ; chỗ để dành đủ cho thứ registry và các tool
viết thêm sau đó; dòng được giữ trọn và mỗi dòng tính cả dấu xuống dòng; câu trả lời là các dòng
của tool rồi tới trang trong rào; không còn chỗ cho trang thì câu trả lời nói vậy thay cho
trang; ghi chú registry thêm vào không bao giờ làm mất dòng đóng rào; lời từ chối giữ trọn chi
tiết của nó hoặc bỏ hẳn; lỗi chỉ kể dòng đầu; ngoài rào, lỗi được kể bằng mã của trình duyệt
hoặc tên loại lỗi; dòng lỗi của trình duyệt nằm trong rào).tests/test_browser_answer_caps.py (qua registry thật, với hook thêm ghi chú, model
tóm tắt và thư mục cất bản đầy đủ, ở bảy mức trần từ 300 tới 8.000 ký tự: không câu trả lời nào
bị cắt, cất hay tóm tắt, rào đã mở thì đóng, không chữ nào của trang đứng ngoài rào; trang dài
được đưa tới chỗ trần còn chứa và nói đã đưa tới đâu; trần không đủ cho một cái rào thì tool
nói vậy và không rào gì; tab được liệt kê theo dòng trọn; tên host mà trang đẩy trình duyệt tới
được kể trong rào hoặc không kể; ảnh chỉ được đọc khi trần còn chỗ cho lời đọc; script nhận câu
trả lời trọn vẹn dù trần của agent cao tới đâu).tests/test_browser_navigate.py (trang được kể giữa hai dòng rào và được giữ làm thứ đã
cho xem; lời gọi tiếp tục ở tab vừa dùng trừ khi nêu tab khác; địa chỉ bị từ chối không khởi
động trình duyệt; trang dẫn tới địa chỉ riêng bị từ chối và để lại tab trắng; lời từ chối của
chính trang web là một trang như mọi trang; trang tự viết dấu của proxy chỉ tự từ chối mình; máy
chủ không trả lời được nêu tên; trang tải quá chậm bị bỏ mà tab vẫn dùng được; lỗi của trình
duyệt được kể không kèm địa chỉ; địa chỉ lẽ ra tải tệp về thì không tải gì, để lại tab trắng và
được nói là một tệp chứ không phải trang hỏng, kèm tool nào lấy được tệp; thứ trang với tới mà
bị từ chối được kể trong rào; agent có danh sách host mở các host ấy không hỏi và hỏi với host
khác; không ai bị hỏi về một địa chỉ bị từ chối bất kể họ trả lời gì; trình duyệt được giao địa
chỉ đúng như đã kiểm).tests/test_browser_read_tools.py (điều trang viết không đóng được rào cũng không đứng
được ngoài rào; trang dài hơn trần bị cắt ở dòng trọn và giữ dòng rào đóng; cửa sổ trang mở và
hộp thoại được kể đúng một lần; dàn ý chờ được một câu chữ và nói khi chữ không tới; tab chỉ
với tới được từ cuộc đã mở nó; chữ của cả trang hay của một phần tử; ref trang không còn nhận
thì bị từ chối; liệt kê tab không khởi động trình duyệt, tab không trả lời vẫn được kể và đóng
được; các tool đọc không đổi gì và không hỏi ai).[new] trên thứ vừa tới trang:
tests/test_browser_snapshot.py (phần tử mới có, hay đã đổi chữ, từ lần xem trước là
mới; trang vẽ lại được ghép với lần trước theo vai trò và chữ, theo thứ tự vẽ; ref nay tìm ra
phần tử khác là mới, còn phần tử đã đi không ghép với phần tử nào; lần xem đầu, lần xem ở địa
chỉ khác hay khi không gì ở lại thì không gì là mới; dấu là thuộc tính cuối của dòng và dòng vẫn
đọc là phần tử của nó; phần tử mới được đánh dấu ở khung nhìn nào đưa ra dòng của nó; dấu chiếm
chỗ trong trần và chỉ những dấu đã đưa ra mới được đếm).tests/test_browser_new_marks.py (trên Chromium thật: thứ một thao tác đem tới trang
được đánh dấu và được đếm ngoài rào; trang tự vẽ lại chỉ đánh dấu phần nó có thêm; trang tải lại
ở cùng địa chỉ đánh dấu phần nó hiện thêm; mở một trang không đánh dấu gì, thao tác dẫn sang nơi
khác cũng vậy; trang đã thành trang khác ở cùng địa chỉ không đánh dấu gì; thứ tới trong lúc
không ai thao tác được đánh dấu ở danh sách phần tử thao tác được lẫn ở kết quả tìm; trang dài
nói nó bị cắt thế nào, có gì mới, và nằm trong trần), tests/test_browser_fence.py (chỗ để
dành sau trang chứa cả dòng đếm dấu [new]).tests/test_browser_act_rules.py (mỗi kind được đọc với đúng thứ nó cần; kind lạ
và tham số thiếu được nêu tên; chỉ fill được viết rỗng; phím là một trong vài phím hay một ký
tự, nhiều nhất kèm Shift, tổ hợp phím bị từ chối; Enter gửi form dù có Shift hay không; ô chỉ
chủ được điền nhận ra qua điều nó khai hay tên nó mang, qua name và id trong markup, và qua
việc nó chỉ nhận một ký tự theo cách trình duyệt đọc maxlength; ô nhận nhiều ký tự hơn và ô
không nói gì thì không phải; thao tác dừng vì một từ trong tên, trong lựa chọn hay dưới phần
tử; tệp tải lên phải có trong workspace).tests/test_browser_act_page.py (phần tử còn như đã cho xem thì tìm được theo ref;
ref model chưa từng được cho không với tới trang nào; trang đã sang địa chỉ khác, phần tử đã
mất hay đổi vai trò, tên nêu không còn là tên phần tử đang mang, hay chữ dành cho người đã đổi
thì không thao tác; từ mà người đã được hỏi rồi không tính là đổi).tests/test_browser_act_tools.py (bấm đúng một lần và trang sau đó là điều model nhận
lại; mỗi kind làm đúng một việc với phần tử được nêu; ref ở lại với phần tử khi trang mọc
thêm quanh nó; từ dừng xuất hiện dưới phần tử sau khi model nhìn thì chặn thao tác; mật khẩu, mã
và số thẻ không bao giờ được gõ, kể cả ô chỉ markup nói nó là gì và ô mã một ký tự; phím tới
phần tử được nêu hoặc không tới đâu; Enter không gửi form mà người sẽ được hỏi; tệp của
workspace đi vào hộp chọn tệp mà phần tử mở; cổng duyệt không khởi động trình duyệt và hỏi về
tải lên ở mọi nơi; agent có danh sách host được hỏi cho mọi thao tác trên trang ngoài danh
sách; tên phần tử không đóng được cặp dấu «» đứng quanh nó trong lý do hỏi người; thao tác dành
cho tab được nêu hay tab của lời gọi trước và không bao giờ chuyển sang tab khác khi tab của nó
mất; thứ trang với tới và hộp thoại trang nói trong lúc thao tác được kể cùng trang sau thao
tác; phần tử không bao giờ sẵn sàng bị bỏ sau thời gian đã xin; thao tác đã làm được kể là đã
làm khi không đọc lại được trang).tests/test_tool_gate_conditional.py (lời gọi mà tool nêu lý do thì hỏi dù cuộc trò
chuyện cho phép gì; lời gọi tool không nêu thì theo quy tắc thường; lý do hiện ra đúng như tool
viết và đọc lại sau vẫn còn; tool hỏng khi xét có hỏi không thì vẫn hỏi, hỏng khi xét điều gì là
thường lệ thì không cho gì; lời gọi thường lệ chạy trong cuộc đang giám sát; lý do để hỏi đứng
trên điều thường lệ; lượt không ai trông dừng ở lời gọi tool nêu; script và script của lịch bị
kết thúc bởi lời gọi tool nêu; tool có thể hỏi không bao giờ được tính là tool chỉ đọc).tests/test_browser_screenshot.py (ảnh là một tệp trong workspace dưới tên tool tự đặt;
ảnh của đúng tab được nêu; ảnh quá lớn ở PNG được giữ ở JPEG, còn quá lớn thì bị từ chối; điều
model đọc từ ảnh được kể giữa hai dòng rào, bài đọc dài bị cắt sao cho rào vẫn đóng; ảnh không
model nào đọc được vẫn được giữ và tab rảnh trong lúc chờ; chỉ những ảnh gần nhất của tool được
giữ, thứ khác trong thư mục không bị đụng, kể cả một thư mục mang tên như một ảnh; ảnh được ghi
trong workspace hoặc không ghi; agent bị giữ trong vài thư mục thì ảnh nằm dưới thư mục đầu
tiên; thư mục hoá thành liên kết sau khi đã được xét thì không có ảnh nào được chụp và không gì
bên ngoài bị xoá; model đọc đúng ảnh đã chụp, dù lúc ấy tên của nó chứa gì; không bao giờ ghi
qua một tên đã có).tests/test_browser_download_tool.py (tool là một thao tác, người được hỏi như với một
lần bấm; lần bấm giữ đúng một tệp nó gây ra, dưới tên đã làm sạch; tên đã có thì được đánh số,
và model đặt được tên; tệp tới từ cửa sổ lần bấm mở, từ trang lần bấm mở gửi về một lúc sau,
hay bắt đầu một lúc sau lần bấm đều được lấy; trang không đặt được tên đưa tệp ra ngoài thư
mục; tên và host do trang chọn chỉ được kể dưới rào, và dưới trần không còn chỗ cho rào thì
tệp vẫn được giữ, tên không được nói, còn đuôi được thêm vẫn được nói; lần bấm không đem tệp
nào về nói là đã bấm; tệp vượt giới hạn bị dừng ngay lúc đang tới, tệp không tới kịp bị bỏ,
tệp không tới trọn không được giữ, và cả ba không để lại gì; tệp được chờ lâu đúng như xin,
từ năm giây tới năm phút; tệp không ai xin bị dừng ngay khi bắt đầu, tệp đã tới trọn trước khi
kịp dừng thì bị xoá; tệp mà tab khác, hay cửa sổ tab khác mở, gửi trong lúc ấy không phải của
lời gọi; trang chỉ được nghe chừng nào lời gọi còn chờ; lần tải trong lúc lời gọi khác đang
chờ tệp của nó không bấm gì; không tệp nào được nghe trước khi phần tử được xét là đúng phần
tử để bấm, và chỉ phần tử model đã được cho xem mới được bấm; người được hỏi về host và từ
dừng như với một lần bấm, tên không đóng được cặp dấu «» quanh nó trong lý do hỏi, và cổng
duyệt không khởi động trình duyệt; agent bị giữ trong vài thư mục giữ tệp dưới thư mục đầu;
thư mục dẫn ra ngoài workspace bị từ chối trước mọi lần bấm, thư mục hoá thành liên kết trong
lúc tệp đang tới thì không nhận tệp, thư mục đã giữ tới giới hạn thì không nhận tệp mới và
không gì được bấm, tệp đã tới mà không đặt được vào workspace thì được nói vậy; host đã gửi
tệp được kể cạnh host của trang; tên không tận cùng như dữ liệu được giữ với .bin thêm sau;
địa chỉ trả lời bằng một tệp không phải trang và không giữ tệp nào).tests/test_browser_downloads.py (thư mục giữ tạm là riêng tư và được dọn những gì
trình duyệt đã chết để lại; tệp không ai chờ bị dừng khi bắt đầu, tệp đã tới trọn trước khi
kịp dừng bị lấy đi; lời gọi chỉ được trao tệp đầu tiên nó nhận là của nó; bản của trình duyệt
bị xoá cả khi lời gọi hỏng, cả khi lời gọi bị dừng thêm lần nữa trên đường ra; tệp bắt đầu
sau khi lời gọi bỏ cuộc bị dừng; lời gọi thứ hai được nói ngay, và chỉ vào được khi lời gọi
đầu đã ra hẳn; tệp được đo trong lúc tới và bị bỏ khi vượt giới hạn, mọi tệp trình duyệt đang
giữ đều tính vào giới hạn; tệp không tới kịp bị bỏ; nửa tệp trình duyệt chưa kịp xoá dù đã
báo dừng (như Chromium khi máy bận) cũng hết khi lời gọi ra, nên lời gọi sau đo từ số không,
còn tệp bị dừng trong lúc lời gọi đang chờ thì không đụng tới nửa tệp của lời gọi; tệp trình
duyệt báo hỏng nói lý do trong một dòng ngắn; thư mục giữ tạm đã mất thì không giữ gì).tests/test_browser_pdf.py (tool chỉ nhìn nên không ai được hỏi và script gọi được;
cuộc trò chuyện chưa có tab thì không có gì để in; trang được in dưới tiêu đề viết bằng chữ
không dấu; tên luôn tận cùng .pdf dù được gọi là gì và không bao giờ chiếm chỗ tệp khác;
trang không đặt được tên ra ngoài thư mục, trang không có tiêu đề vẫn có tên; tên do tiêu đề
trang chọn chỉ được kể dưới rào, và dưới trần không còn chỗ cho rào thì bản in vẫn được giữ
còn tên không được nói; in đúng tab được nêu; bản in quá cỡ một tệp được phép có thì không
được giữ; agent bị giữ trong vài thư mục giữ bản in dưới thư mục đầu; bản in được ghi trong
workspace hoặc không ghi; thư mục hoá thành liên kết trong lúc in thì không nhận bản in, thư
mục đã giữ tới giới hạn thì không nhận bản mới; bản in không đặt được vào workspace thì được
nói vậy).tests/test_browser_saved.py (tên là phần cuối của điều được nói, viết bằng chữ không
dấu, không mở đầu bằng thứ giấu tệp hay đọc như một tuỳ chọn; tên tận cùng bằng đuôi tool nói,
dù được gọi là gì; tên không tận cùng như dữ liệu thì thêm .bin, và chỉ thêm một lần; đuôi
của dữ liệu là một danh sách ngắn có cả thứ các tool làm ra; thư mục được tạo cho tệp; tệp
được chép trọn từ tệp giữ nó; tên đã có thì được đánh số và không gì bị ghi đè; không bao giờ
ghi qua một liên kết đã nằm ở tên ấy; tệp không ghi trọn được thì bị xoá đi; thư mục không tạo
được, hay hoá thành liên kết sau khi được xét, thì không nhận tệp; liên kết tới thư mục khác
của workspace không phải thư mục để đi qua; đường đi không bao giờ rời thư mục nó bắt đầu;
workspace tới qua một liên kết được ghi ở nơi nó thật sự nằm; không descriptor nào bị để mở dù
tệp được giữ hay không; thư mục giữ bao nhiêu là tổng các tệp nằm ngay trong nó; tệp được giữ
mang dấu của tệp tới từ bên ngoài, dấu không đặt được thì chỉ được ghi vào log, còn ngoài
macOS thì không có dấu và không làm gì; thư mục là của workspace hay của thư mục đầu agent bị
giữ trong đó; thư mục mà một liên kết dẫn ra ngoài workspace, hay ra ngoài các thư mục của
agent, không nhận tệp).tests/test_browser_runtime.py (script gọi được các tool chỉ nhìn, kể cả browser_pdf;
browser_navigate, browser_act và browser_download chỉ gọi được từ script của agent
script_calls: gated, qua cổng duyệt).browser-use:
tests/test_browser_skill.py (docs/examples/skills/browser-use/SKILL.md nêu đúng các
tool mà agent mức act giữ, có lời gọi mẫu cho browser_navigate, browser_act và
browser_tabs, và mỗi tham số trong lời gọi mẫu là tham số tool ấy nhận; phần thân dưới 4.000
ký tự và có nói tới dấu [new]).tests/test_private_paths.py (chưa giữ thư mục nào thì chưa gì là riêng; thư mục được
giữ và mọi thứ dưới nó là riêng, thứ bên cạnh thì không; liên kết dẫn vào thư mục được giữ là
riêng dù nằm đâu; thư mục được nhận ra theo chính nó chứ không theo cách viết tên; thư mục giữ
trước khi được tạo vẫn được nhận ra; các tool tệp không đọc, không liệt kê, không ghi; tìm kiếm
bắt đầu phía trên thì đi vòng qua, có hay không có ripgrep). Server giữ tên các thư mục riêng
suốt đời tiến trình, nên một fixture trong tests/conftest.py cho mỗi test bắt đầu khi chưa
thư mục nào được giữ.tests/test_shell_private_folder.py (lệnh trong sandbox không với tới thư mục riêng;
thứ nằm bên cạnh vẫn đọc và ghi như trước; thư mục nêu qua một liên kết bị đóng ở nơi nó thật sự
nằm; lời từ chối nói cả hai quy tắc; shell chưa từng bị giữ thì không vì thư mục riêng mà bị
giữ). tests/test_browser_runtime.py giữ phần của server: thư mục đóng dù trình duyệt bật hay
tắt, và được đóng trước khi server tìm trình duyệt.tests/test_shell_server_port.py (profile không đóng cổng nào cho tới khi server
nêu cổng của nó, rồi đóng cổng ấy ở cuối profile, có mạng hay không; cổng nêu hai lần được
đóng một lần và profile luôn là cùng một dòng chữ; lệnh trong sandbox còn mạng không tới được
cổng ấy qua 127.0.0.1, ::1 hay localhost, với lời từ chối của sandbox, còn cổng khác
thì vẫn tới; server nêu đúng cổng --port trước khi dựng đội). Server giữ cổng đã nêu suốt
đời tiến trình, nên một fixture trong tests/conftest.py cho mỗi test bắt đầu khi chưa cổng
nào bị đóng.tests/test_browser_routes.py (hỏi trạng thái không khởi động gì; agent không có trong
đội là 404; tab được kể theo thứ tự dùng gần nhất, theo host và tiêu đề, không bao giờ theo địa
chỉ; tab được gọi bằng số của nó; thời gian nhàn rỗi là số giây trọn; máy thiếu phần nào thì nói
đúng phần ấy; lời gọi đang chạy được kể là bận và kết thúc khi người đóng tab của nó; dừng thì
kết thúc lời gọi đang chạy, giữ đăng nhập và để lời gọi sau khởi động lại; agent bị đưa về off
thì trình duyệt nó để chạy bị dừng; gỡ agent thì mất cả trình duyệt lẫn những gì nó đã đăng nhập,
của agent khác còn nguyên; lượt đang chạy lúc agent bị đưa về off hay bị gỡ không duyệt thêm
được, còn bộ tool mới của agent được bật lại thì duyệt được; agent bị tệp của nó đưa về off
thì bị dừng trình duyệt ở lần đội được dựng lại, hồ sơ còn nguyên, agent khác không bị đụng;
agent thêm vào hay dựng lại lúc server đang chạy dùng cùng một pool; lời gọi đến sau khi server
đã dừng không khởi động trình duyệt nào và nhận đúng câu báo không khởi động được; bảng tool
của đội đánh dấu with_browser cho đúng tám tool, và chỉ browser_navigate, browser_act,
browser_download cần duyệt; trạng thái kể thêm login (chưa có cửa sổ đăng nhập nào mở và mở
được ngay, còn agent không có trình duyệt thì không mở được với lý do off) cùng logins,
logins_more (chưa trình duyệt nào đóng trên hồ sơ thì chưa biết gì; sau khi một trình duyệt
đóng hay đang chạy mà chưa đăng nhập đâu thì là danh sách rỗng); dòng đầu của
browser_screenshot nêu một đường dẫn mà GET /api/agents/{id}/files trả về như một ảnh, cả
khi dòng ấy đã bị rút thành bản xem trước của step).tests/test_browser_retire.py (bộ tool có từ trước khi agent bị gỡ không khởi động
lại trình duyệt và không để lại hồ sơ, còn agent mới cùng mã và agent khác thì dùng được;
dừng thường để chính lượt ấy khởi động lại, còn tắt hẳn thì không và đăng nhập được giữ; lời
gọi đang khởi động trình duyệt lúc agent bị gỡ chỉ khởi động đúng một lần, không để lại gì, và
được nói ngay là agent đã bị gỡ; lời gọi mà người bấm dừng trình duyệt của nó đúng lúc trình
duyệt đang khởi động được nói ngay như vậy, và lời gọi kế tiếp của cùng lượt khởi động lại; lời
gọi đến lúc trình duyệt của agent bị gỡ còn đang đóng thì bị từ chối ngay).tests/test_browser_login.py (để mặc định, cửa sổ là một cửa sổ người thấy trên chính
màn hình của máy; cửa sổ dùng hồ sơ của agent và trong lúc ấy agent không có trình duyệt;
trình duyệt nhàn rỗi của agent bị dừng trước và các đăng nhập còn nguyên; không cửa sổ nào mở
dưới một lời gọi, trong lúc trình duyệt đang khởi động cho một lời gọi, hay giữa lúc trình
duyệt lên và lúc lời gọi giữ nó; nơi không ai thấy được thì không thử mở; hai lần xin cùng lúc
mở một cửa sổ, lần xin sau được trao cửa sổ ấy như nó đang là; cửa sổ không ai đóng tự đóng
sau một lúc; người đóng trang cuối thì với đội cửa sổ cũng đã đóng; người thoát trình duyệt
thì cửa sổ kết thúc và điều nó giữ được ghi lại, trình duyệt thôi trả lời thì điều nó được
thấy giữ lần cuối được ghi; cửa sổ không mở được để lại trình duyệt cho agent, trình duyệt đã
lên mà không thành cửa sổ được thì bị đóng lại; yêu cầu bỏ chờ giữa chừng vẫn để lại một cửa
sổ đội biết; địa chỉ đầu được kiểm như một lời gọi của agent, và cửa sổ là của người trước khi
địa chỉ ấy trả lời; agent rời đội trong lúc tra địa chỉ thì không được cửa sổ nào; lệnh đóng
dành cho cửa sổ đã mất để nguyên cửa sổ đang mở; cửa sổ không tới được nơi nào trình duyệt của
agent không tới; đóng tất cả là đóng cả cửa sổ còn đang mở dở; lời gọi của agent được nói là
có người đang đăng nhập, và sau đó đọc được trang đã đăng nhập; lời gọi đã bỏ trình duyệt không
cản cửa sổ nào về sau; server đang tắt thì không mở cửa sổ nào; driver mất thì với đội cửa sổ
đóng ngay, và không bị hỏi gì khi đóng; cửa sổ không bao giờ trả lời lệnh đóng vẫn trả trình
duyệt lại cho agent).tests/test_browser_login_routes.py (mở cửa sổ trả thời điểm nó tự đóng, giờ UTC tới
từng giây, mở lại thì nhận đúng cửa sổ ấy, và người nói xong thì đóng; agent chỉ đọc cũng có
hồ sơ để đăng nhập; cửa sổ mở ở địa chỉ được gửi, không có thì mở trang trống; nơi không ai
thấy được thì không mở và nói lý do; không mở dưới lời gọi của agent, và màn hình khoá được nói
trước; agent không có trình duyệt, hay máy không có trình duyệt để khởi động, thì không có hồ
sơ để đăng nhập; agent không có trong đội là 404; agent rời đội trong lúc tra địa chỉ thì không
được cửa sổ; địa chỉ mà trình duyệt nào của đội cũng không tới là 422; trình duyệt không chịu
hiện cửa sổ được kể bằng chính những chữ đầu của nó; server đang tắt thì không mở và nói vậy;
dừng trình duyệt, đưa agent về off hay gỡ agent thì cửa sổ bị đóng; server tắt thì đóng cửa
sổ lúc nó còn nói được nó giữ gì; trong lúc cửa sổ mở lời gọi của agent được nói là có người
đang đăng nhập; cửa sổ tự đóng không một lời được kể là đã đóng và agent duyệt tiếp; nơi trình
duyệt có thể còn đăng nhập được kể bằng host và thời điểm, không gì hơn; host danh sách dài bỏ
ra được kể bằng một con số; agent có danh sách host riêng được kể từng host, đã đăng nhập hay
chưa).tests/test_browser_console.py (ba trạng thái màn hình web dựa vào; cửa sổ chỉ hiện
trong phiên đang ở trước màn hình và khi màn hình không khoá; hệ không chịu nói thì không thử
mở cửa sổ; ngoài macOS thì DISPLAY hay WAYLAND_DISPLAY trong môi trường là có màn hình;
phiên của chính máy Mac được đọc mà không xin quyền gì).tests/test_browser_logins.py (một cookie được tính khi không script nào đọc được nó,
nó sống lâu hơn trình duyệt và không đi theo sang site khác; mỗi host được giữ một lần, viết
thường, với thời điểm muộn nhất mà một cookie của nó hết hạn; của cookie chỉ lấy host và thời
điểm; trình duyệt không chịu nói thì không kể gì, điều đã ghi được để nguyên hay điều nó được
thấy giữ lần cuối được ghi; trình duyệt đã mất thì không bị hỏi, còn điều đã thấy được ghi;
trình duyệt không có hồ sơ thì không ghi và không bị hỏi; hồ sơ không ghi được thì vào log và
không làm dừng gì; hồ sơ chưa trình duyệt nào đóng trên đó, hay tệp không đúng dạng, thì không
nhớ ra gì; agent có host riêng được kể từng host, kể cả host không giữ gì; host chỉ trùng phần
đuôi không tính là nằm dưới host khác; agent không có host riêng được kể bằng tên các host
đang giữ gì đó; danh sách dài kể phần bỏ ra bằng một con số; điều trình duyệt giữ được ghi cạnh
hồ sơ mỗi lần nó đóng).web/src/components/connections-browser-card.test.tsx (server không nói gì về trình
duyệt thì không vẽ thẻ; máy tắt thì thẻ nói chỗ bật; máy bật nói kênh và phiên bản, không có
phiên bản thì bỏ; thiếu gói hay thiếu Chromium nói đúng phần thiếu kèm lệnh cài, thiếu Chrome
thì không có lệnh nào để đưa; phần thiếu được nói cả khi máy đang tắt; agent nào đang có trình
duyệt chạy được kể bằng tên, không có tên thì bằng mã; thẻ không có gì để bấm, gõ hay mở;
dòng “ai đang chạy” được đọc lại lúc thẻ được vẽ, dù trang đã đọc máy từ bao lâu, nên agent
vừa bị dừng trình duyệt không còn được kể và agent vừa khởi động thì được kể; đọc lại năm
giây một lần bằng đúng một đồng hồ; tab khuất thì không hỏi, trở lại thì hỏi ngay; máy tắt
hay chưa cài thì không hỏi lần nào; server không trả lời thì thẻ giữ nguyên điều đang nói và
nhận câu trả lời kế tiếp; câu trả lời không nói gì về trình duyệt không xoá dòng ấy; câu trả
lời đến chéo sau một câu hỏi muộn hơn không thay nó; chỉ đúng một dòng ấy được lấy, phần còn
lại của trang giữ như trang đã đọc; trang đọc lại máy thành máy tắt thì thẻ nói theo trang và
thôi hỏi; rời trang thì không còn đồng hồ, không còn câu hỏi).web/e2e/browser.spec.ts (thẻ khi máy bật và có trình duyệt đang chạy; thẻ khi máy
tắt và khi thiếu trình duyệt, kèm lệnh cài; dừng trình duyệt ở trang agent rồi sang Kết nối
bằng thanh điều hướng, không tải lại, thì thẻ nói chưa agent nào chạy, và một trình duyệt khởi
động lúc trang đang mở thì được kể; ở 390px thẻ không làm trang tràn ngang).web/src/components/agent-editor/browser-controls.test.tsx (ba mức, mỗi mức một dòng
gợi ý: dòng của mức đọc nêu sáu tool mà không nêu browser_act hay browser_download, dòng
của mức thao tác nêu cả hai; lần lưu chỉ gửi khoá đã đổi, và về Tắt chỉ gửi mức, để nguyên
danh sách đã lưu; ô host hiện ở hai mức khác Tắt, danh sách còn nguyên như đã lưu và như đã gõ
khi về Tắt rồi quay lại; host được gửi viết thường và trần, ô xoá trắng là danh sách rỗng; dòng
không phải host được nêu tên dưới ô và giữ lần lưu, sửa xong thì lưu được, và ô vẫn hiện khi
mức về Tắt; dòng sai đã có sẵn trong tệp không giữ lần lưu của thứ khác; máy tắt hay chưa cài
được nói ngay khi chọn mức, trước mọi lần lưu, máy tắt nêu trước, và không nói gì khi máy chưa
được đọc; agent không sửa được ở đây thì chỉ xem; server chưa có khoá thì không vẽ gì và không
gửi khoá nào),
web/src/hooks/browser-hosts.test.ts (một dòng một host, viết thường, bỏ dòng trống; luật của
server cho một tên host, một địa chỉ dán vào bị từ chối trọn),
web/src/hooks/agent-draft-browser.test.ts (hai khoá đọc từ tệp, server không biết trình
duyệt thì bản nháp không giữ khoá nào; dòng sai đầu tiên được nêu tên; danh sách chưa đổi thì
không bị xét; lần lưu là mức, danh sách, cả hai hoặc không gì).web/src/components/agent-editor/tool-picker-browser.test.tsx (tám tool browser_*
không có ô chọn trong danh sách công cụ, dù server có trả listable_tools hay không; dưới
danh sách có dòng nói vì sao, agent ở mức nào cũng vậy).web/e2e/browser.spec.ts (chọn mức và gõ host thì lần lưu gửi đúng browser và
browser_hosts, tải lại trang vẫn còn; ở 390px ô chọn và ô host đủ lớn cho ngón tay, lời cảnh
báo của máy và dòng nêu host sai xuống dòng trong trang).web/src/components/agent-editor/browser-card.test.tsx (agent ở Tắt và không có gì
chạy thì không có thẻ, còn agent đã về Tắt mà trình duyệt còn chạy thì vẫn có thẻ để dừng;
dòng trạng thái theo thứ tự đang chạy, máy tắt, chưa cài, chưa chạy; tab theo thứ tự của
server, mỗi tab kể nhãn, tiêu đề, host, cuộc trò chuyện hay việc theo lịch, đang dùng hay rảnh;
đóng tab và dừng làm ngay rồi đọc lại, và khi không còn gì để dừng hay đóng thì nút duy nhất
là nút mở cửa sổ đăng nhập; trong lúc một yêu cầu đang đi mọi nút chờ; tab đã mất được nêu
bằng câu của server; câu trả lời tới lệch thứ tự không ghi đè cái mới hơn; lần đọc hỏng giữ
điều đang hiện và nói vậy; đọc lại mỗi 5 giây, cả với agent chưa có thẻ, trang bị che thì thôi
và đọc ngay khi được xem lại; lần lưu đổi mức thì đọc lại ngay; server chưa có route thì không
có thẻ và không hỏi nữa).web/e2e/browser.spec.ts (các tab được liệt kê; “Đóng tab” gửi DELETE tới đúng
tab và “Dừng trình duyệt” gửi POST …/browser/stop; ở 390px mục và thẻ nằm trong trang, mọi
nút đủ lớn cho ngón tay).web/src/components/agent-editor/browser-card-login.test.tsx (thẻ mời mở cửa sổ ở nơi
mở được, với ô địa chỉ không bắt buộc, nút, và câu nói việc mở làm gì; cửa sổ đang mở được kể
kèm giờ nó tự đóng theo đồng hồ người xem, hay không kèm giờ khi server không nêu, cùng nút
“Xong”; trong lúc ấy dòng trạng thái nói agent không dùng được trình duyệt, còn trình duyệt
đang chạy cạnh cửa sổ vẫn được kể trước và dừng được; màn hình không có phiên, màn hình khoá
hay một lời gọi đang chạy thì không có ô, không có nút, chỉ một câu nói điều đang cản, chữ lạ
đọc là chưa mở được lúc này; máy không cho agent trình duyệt, agent ở Tắt, hay server không
nói gì về cửa sổ thì không có gì về cửa sổ và thẻ như trước; lần mở gửi địa chỉ đã bỏ khoảng
trắng hai đầu, hay thân rỗng khi không gõ gì, rồi đọc lại, và ô được xoá khi cửa sổ mở; mở thì
trình duyệt của agent dừng và tab đi khỏi thẻ; 422 và 409 hiện đúng câu của server như một lời
cảnh báo và giữ điều đã gõ, kể cả qua lần màn hình khoá rồi mở lại; “Xong” đóng cửa sổ, đọc lại
và nút mở trở về, cửa sổ đã tự đóng trước khi bấm thì không nói gì; mọi nút của thẻ chờ từ lúc
bấm mở, đóng, dừng hay đóng tab cho tới khi lần đọc sau đó về; cửa sổ tự đóng hay được mở từ
trang khác hiện ra ở lần đọc kế tiếp, không cần yêu cầu riêng).web/src/components/agent-editor/browser-card-signed-in.test.tsx (agent có danh sách
host riêng thấy mọi host theo thứ tự trong tệp, bao nhiêu cũng vậy, kèm ngày đăng nhập hết hạn
theo lịch người xem, host không giữ đăng nhập nào thì mờ đi, dưới một câu nói các ngày ấy đọc
từ đâu, và tên host hiện đúng như chữ, kể cả markup; agent không có danh sách thấy tám host đầu
bằng tên cùng nút nói còn bao nhiêu, bấm thì hiện hết, nút đi mất và bàn phím nằm ở danh sách,
chưa bấm thì bàn phím không bị lấy, và danh sách giữ nguyên mở qua các lần đọc sau; số host
danh sách dài bỏ ra được nói trong một dòng dưới danh sách và nút, theo lần đọc kế tiếp, không
đọc to, và không có dòng ấy khi không gì bị bỏ, khi agent có danh sách riêng hay khi server
không nêu số; trình duyệt đã đóng trên hồ sơ mà không giữ đăng nhập nào, hay mọi đăng nhập đã
hết hạn, thì có một câu, còn hồ sơ chưa trình duyệt nào đóng trên đó, máy không cho agent trình
duyệt hay server không kể host thì không vẽ gì; các host đổi theo lần đọc kế tiếp mà không đọc
to, và hiện cả cạnh trình duyệt đang chạy lẫn dưới cửa sổ đang mở),
web/src/lib/relative-time.test.ts (calendarDate: ngày/tháng/năm theo múi giờ người xem;
chuỗi không đọc được thì trả lại nguyên).web/src/test/fake-browser-login.test.ts (server giả của test và của Playwright trả
lời về cửa sổ như server thật: mở mười lăm phút, giờ UTC tới từng giây, mở lại thì trả đúng
cửa sổ ấy, dừng trình duyệt của agent khi mở, không mở dưới lời gọi đang chạy, nói màn hình
trước lời gọi, nói agent không có trình duyệt trước khi xét địa chỉ và từ chối địa chỉ sai
trước khi xét cửa sổ hay màn hình, tự đóng khi tới giờ, đóng theo lần dừng hay lần lưu về
off, mỗi agent một cửa sổ; kể host như server thật, tối đa năm mươi tên cùng con
số phần còn lại, agent có danh sách riêng thì không bỏ host nào; server có trình duyệt mà chưa
có cửa sổ đăng nhập thì không có hai khoá, không có hai route và chỉ có sáu tool).web/e2e/browser-sign-in.spec.ts (màn hình khoá thì thẻ không có cách mở và nói
vì sao, mở khoá thì lần đọc kế tiếp có; cửa sổ mở ở địa chỉ đã gõ, nói giờ tự đóng, và đóng
bằng một lần bấm; không gõ địa chỉ thì yêu cầu không mang địa chỉ nào; địa chỉ bị từ chối hiện
bằng câu của server và ở lại trong ô; host của danh sách riêng hiện từng host với ngày hết hạn
hay là không giữ gì; agent không có danh sách thấy tám host rồi phần còn lại sau một lần bấm;
trình duyệt không giữ đăng nhập nào nói vậy trong một câu, hồ sơ chưa trình duyệt nào đóng thì
không nói gì; server chưa có cửa sổ đăng nhập thì thẻ như trước; ở 390px ô, nút và danh sách
host nằm trong trang, mọi nút đủ lớn cho ngón tay, và cửa sổ đang mở hay điều đang cản xuống
dòng trong trang).web/src/components/tools-matrix-browser.test.tsx (tám dòng mang nhãn “đi kèm trình
duyệt”; browser_navigate, browser_act, browser_download hỏi khi cuộc trò chuyện không
autonomous, các tool khác thì không; ô của agent đặt Tắt, và ô của từng tool thao tác ở agent
chỉ đọc (“công cụ này cần mức Thao tác”), nói lý do bằng khoá browser, không bằng danh sách
cho phép hay chế độ; ô browser_pdf của agent chỉ đọc là có; agent của server bản trước đọc là
không có trình duyệt; đội không có tool trình duyệt nào thì không có dòng, đội không agent nào
thao tác thì không có dòng của tool thao tác; chú thích chỉ có dòng cho dấu đang dùng).web/src/lib/browser-call.test.ts (mỗi kind của browser_act và browser_navigate
thành một câu; browser_download thành “Bấm «…» để tải tệp về”, thêm “, lưu thành …” khi lời
gọi đặt tên tệp, không có câu nào khi lời gọi không nêu phần tử, và câu ấy chỉ đọc từ đúng tool
này; địa chỉ chỉ được kể bằng host; mỗi phần trên một dòng và cắt ở 80 ký tự; ký tự đảo chiều
hay giấu chữ được viết bằng mã của nó; dấu «» trong một phần bị bỏ, để tên không đóng sớm được
cặp dấu quanh nó; tham số thiếu hay sai kiểu thì không có câu nào),
web/src/components/approval-bar-browser.test.tsx (câu ấy đứng dưới lý do của server và trên
phần tham số thô, ở thanh duyệt của khung chat, kể cả khi cuộc trò chuyện mở lên từ một yêu cầu
đã lưu, lẫn ở dòng của thẻ “Cần bạn xử lý”, với lần tải tệp cũng vậy; tool khác, kể cả
browser_pdf, không có dòng này).web/e2e/browser.spec.ts (“Bấm «Thanh toán»” phía trên tham số thô ở thanh duyệt;
“Mở trang shop.example” dưới lý do của server ở mục Duyệt; lần tải tệp đang chờ đọc thành câu
nêu phần tử và tên tệp ở thanh duyệt, và thành câu không có tên tệp ở danh sách yêu cầu khi lời
gọi không đặt tên; ở 390px câu dài xuống dòng).web/src/lib/screenshot-path.test.ts (đường dẫn đọc từ dòng đầu của
browser_screenshot, cả khi dòng đã bị rút; tool khác hay dòng khác thì không có gì),
web/src/components/screenshot-thumb.test.tsx (ảnh là liên kết tới tệp trong workspace của
agent đã chạy, mở tab riêng; chỉ lời gọi đã xong mới có ảnh, trên dòng step của run và thẻ
tool trong khung chat; ảnh không tải được thì không để lại gì, và dòng sang ảnh khác thì thử
ảnh ấy; không ai nói workspace của ai thì không có ảnh).web/e2e/browser.spec.ts (ảnh nhỏ ở bước của run là liên kết tới
/api/agents/{id}/files?path=…, không lớn quá khung của nó; tệp không còn thì không có gì; ở
390px ảnh chụp cả trang dài vẫn nằm trong khung và đủ lớn cho ngón tay).web/src/app-browser-surfaces.test.tsx (mục Kết nối kể tên agent đang có trình duyệt
chạy; agent bị dừng trình duyệt ở trang của nó không còn được kể ở Kết nối, và agent khởi động
sau khi trang đã đọc máy thì được kể, cả hai không cần tải lại; trạng thái của máy tới được
trình sửa từ lần đọc Kết nối đã có, chỉ hỏi một lần; thẻ của trang agent mở đúng cuộc trò chuyện
của một tab; lần lưu về Tắt dừng trình duyệt và thẻ đi mất; ảnh nhỏ được hỏi ở agent của cuộc
trò chuyện hay agent đã chạy; cửa sổ đăng nhập mở ở địa chỉ đã gõ rồi đóng khi người bấm xong,
và trình sửa không có gì để lưu; thẻ của agent có danh sách host riêng hiện mọi host trong
danh sách, theo tệp của agent chứ không theo điều đang gõ trong form; bảng công cụ có tám dòng
mang nhãn trình duyệt; trên server bản trước không có thẻ ở Kết nối, không có mục và thẻ ở
agent, không có dòng, nhãn hay chú thích ở bảng công cụ; trên server có trình duyệt mà chưa có
cửa sổ đăng nhập, thẻ của agent như trước, không ô, không host, và bảng công cụ có sáu tool),
web/src/components/agent-editor/editor-on-older-server.test.tsx (server không trả browser
thì trình sửa không có mục, không có thẻ, không có dòng dưới danh sách công cụ, và lần lưu thứ
khác không gửi khoá nào của trình duyệt).web/e2e/older-server.spec.ts (trên server của bản trước không gì của trình
duyệt được vẽ: không ô đặt, không thẻ, không nhãn, và lần lưu không mang khoá nào của nó).web/src/test/fake-browser.test.ts, web/src/test/schedule-contract.test.ts (server
giả của test và của Playwright trả lời như server thật: tab mới dùng đứng trước, tab đã mất là
404 bằng câu của server, mã tab không phải số là 422, browser: true trần bị từ chối, host sai
bị từ chối; trạng thái mang login, logins, logins_more; agent không có trong đội là 404
trên cả năm route, GET tới hai route của cửa sổ là 405; tám tool, ba tool hỏi, mức đọc giữ
sáu tool).Agent có computer: "on" trong tệp của nó giữ bốn tool computer_*, nói chuyện với trình trợ
giúp desktop qua một Unix socket; chi tiết ở tools.md. Không test nào chạm
vào màn hình thật hay hỏi macOS một quyền nào. Phía server, pytest nói chuyện với một trình trợ
giúp giả (tests/desktop_helper.py): một server Unix socket trả lời các lệnh theo trạng thái test
đặt và giữ mọi yêu cầu nó nhận, với socket trong một thư mục ngắn riêng dưới /tmp vì đường dẫn
thư mục của pytest dài quá 103 byte. tests/desktop_tools.py dựng bốn tool của một agent trên
trình trợ giúp giả ấy và tách phần trong rào khỏi phần ngoài. Phía trình trợ giúp, gói Swift
desktop-helper/ có bộ XCTest riêng ở desktop-helper/Tests, chạy bằng
scripts/desktop-helper-check.sh (thêm tên một lớp test để chỉ chạy lớp đó, ví dụ
scripts/desktop-helper-check.sh SocketServerTests); test chỉ tới máy qua một hệ thống giả
(FakeSystem.swift), nên không bao giờ bật hộp thoại. CI chạy bộ này ở job desktop-helper trên
macOS; ./scripts/gates.sh không chạy nó, nên ai sửa desktop-helper/ chạy script ấy trên máy
Mac trước khi đẩy. Phía web, web/src/test/fake-desktop.ts giữ một bộ quy tắc của các route
desktop mà cả server giả của vitest lẫn mock của Playwright dùng.
tests/test_desktop_profile.py (máy tìm trình trợ giúp dưới home của nó; tệp đặt
socket và biến môi trường thắng; để trống là socket dưới home; khoá lạ làm dừng khởi động;
đường dẫn dài đúng giới hạn vẫn được nhận; agent dùng desktop chỉ khi chính tệp của nó nói,
không thừa hưởng từ cấu hình chung; off trần là tắt, on trần bị từ chối kèm cách viết
chữ ấy, mức lạ bị từ chối; danh sách ứng dụng chỉ nhận tên, giữ một lần và bỏ khoảng trắng
hai đầu, danh sách rỗng là không ứng dụng nào; lần lưu từ web là điều lần khởi động sau đọc,
off được ghi thành chữ và lấy tool đi, xoá hai khoá là bỏ khỏi tệp, danh sách xoá trắng
để agent chỉ nhìn, giá trị sai bị từ chối và tệp giữ nguyên; agent mới tạo được với hai
khoá; danh sách nêu một trình duyệt web, theo tên hay bundle id, bị từ chối kèm tên ấy: tệp
viết tay làm dừng khởi động, lần lưu từ web là 422 kèm lý do).tests/test_desktop_runtime.py (mỗi agent nhận đúng bốn tool khoá của nó xin, có
hay không có trình trợ giúp; không có trình trợ giúp thì tool vẫn ở đó và nói không có;
tools: không đem tới cũng không lấy đi tool desktop; thư mục của trình trợ giúp, và socket
đặt ở chỗ khác, đóng với mọi tool tệp, kể cả khi đường dẫn đi vòng ra ngoài thư mục ấy;
agent làm việc ngay trong home không để ảnh cạnh socket; không lệnh nào trong sandbox kết
nối được tới socket ấy).tests/test_desktop_client.py (mỗi yêu cầu một dòng, một id và một kết nối riêng;
không có socket, không ai nghe, đường dẫn quá dài hay tệp không phải socket là absent;
socket hay thư mục của người khác, thư mục người khác ghi được, hay symlink tới socket thì
không được nói chuyện; hạn 10 giây và câu trả lời tối đa 64 MiB, quá thì bỏ chứ không đọc;
kết nối đóng im lặng, dòng không phải câu trả lời hay câu trả lời dưới id khác đều không phải
câu trả lời; lời từ chối mang mã và câu của trình trợ giúp, câu dài bị cắt).frame_id:
tests/test_desktop_screenshot.py (ảnh là tệp trong workspace mang tên khung hình,
hoặc dưới đường dẫn đầu của write_paths, không bao giờ ghi qua một tên đã có hay qua
symlink, và ảnh ghi dở bị xoá; màn hình không đổi thì không ảnh và không model nào được gọi,
khung hình được nhớ theo hội thoại và chỉ khi tệp còn; agent có ứng dụng chỉ nhìn khi một
ứng dụng của nó ở trước, xét theo tên hay bundle id, và ảnh chụp lúc ứng dụng khác lên trước
bị bỏ; agent không có ứng dụng nào nhìn được mọi thứ; lời model đọc từ ảnh và lời lỗi của
tuyến đọc ảnh nằm trong rào dưới mọi trần, không đủ chỗ thì không trả tiền cho model; chỉ giữ
40 ảnh mới nhất của tool, tệp và thư mục khác không bị đụng; màn hình khoá hay không thấy được
thì nói vậy và không chụp).ref:
tests/test_desktop_observe.py (dàn ý nằm giữa hai dòng rào, mỗi phần tử một ref
mang đầu tên của lần đọc; ứng dụng trong danh sách đọc theo tên hay bundle id, ứng dụng ngoài
danh sách bị từ chối trước khi trình trợ giúp nghe; agent không có ứng dụng nào không đọc dàn
ý nào; ứng dụng lên trước giữa chừng hay tiến trình lạ thì không kể; ô bảo mật không bao giờ
hiện giá trị; chữ của màn hình không bao giờ đứng ở chỗ ref hay ghi chú được đọc; dàn ý dài
bị cắt theo dòng trọn và luôn đóng rào, không đủ chỗ thì không để lại ref nào).tests/test_desktop_seen.py (ref chỉ có nghĩa với dàn ý hội thoại vừa được xem;
ref của dàn ý khác hay của dòng không vừa câu trả lời bị từ chối; ô bảo mật không bao giờ
được đặt chữ, nhưng bấm được theo tên; bấm phải kèm đúng tên dàn ý ghi, không phân biệt hoa
thường, khoảng trắng và cách mã hoá dấu, còn tên khác một chữ hay một dấu là tên khác; ô được
đặt giá trị giữ theo tên lời gọi nêu, và không theo tên nào khi lời gọi không nêu).tests/test_desktop_state_tool.py (computer_state chỉ nêu tên ứng dụng của agent;
ứng dụng ngoài danh sách ở trước được nói mà không nêu tên; agent không có ứng dụng nào được
kể mọi ứng dụng; màn hình khoá hay tắt, quyền thiếu và chế độ nhập bảo mật được nói, cùng lúc;
tên ứng dụng nằm trong rào, nhiều ứng dụng thì bị cắt mà rào vẫn đóng; máy không có màn hình
không được gọi là sẵn sàng; phiên bản chỉ được nêu khi có dạng một phiên bản; không có trình
trợ giúp hay bị từ chối thì một câu theo mã).tests/test_desktop_act_rules.py (mỗi kind được đọc và gửi đúng phần nó dùng;
focus_app không cần khung hình, mọi kind khác cần; ứng dụng xét trước khung hình; bấm theo
ref hay theo điểm, không cả hai; kéo cần đủ bốn số; điểm phải nằm trên ảnh mà chính
frame_id nêu kích thước; cuộn trong tầm của trình trợ giúp và phải đi đâu đó; giới hạn độ
dài chữ là của trình trợ giúp; type không nhận ref; nửa cặp UTF-16 bị từ chối trước khi
ai bị hỏi; những chữ không bao giờ gõ, những tổ hợp không bao giờ gửi, Spotlight chỉ cho agent
được giao nó; mỗi lý do luôn hỏi (đưa lên trước, Enter hay Space một mình, tổ hợp ngoài nhóm
thường dùng, từ dừng, dấu xuống dòng khi gõ nhưng không khi đặt giá trị, điểm, bấm đúp, tên
phần tử hay tên ô có từ dừng) và những lời gọi không có lý do nào; bấm theo ref chỉ đọc tên
đi kèm, không đọc gì khác lời gọi mang theo; ký tự đắt nhất còn được nhận (4 byte) vẫn vừa
giới hạn của trình trợ giúp; toạ độ trên thẻ làm tròn tới phần trăm).tests/test_desktop_keys.py (tổ hợp phím đọc đúng như trình trợ giúp đọc: bảng phím
và phím giữ được đọc thẳng từ mã Swift của trình trợ giúp, nên một phím thêm ở đó mà không ở
đây làm test đỏ; mỗi tổ hợp một cách viết và đọc lại ra chính nó; tổ hợp không bao giờ gửi và
những tổ hợp sát chúng; nhóm phím thường dùng đúng là những phím ấy).tests/test_desktop_policy.py (ứng dụng khớp theo tên trọn vẹn, không phân biệt hoa
thường hay cách mã hoá dấu, còn dấu khác là ứng dụng khác, hay theo bundle id đúng từng chữ;
danh sách rỗng không nêu ứng dụng nào; dạng của frame_id;
sudo, rm vừa đệ quy vừa không hỏi, và tải về đưa thẳng vào shell không bao giờ được gõ, kể
cả viết theo cách shell vẫn đọc ra như thế, còn những dòng gần giống thì gõ được).tests/test_desktop_act_asking.py (chữ của type hay set_value có một ký tự điều
khiển ngoài tab và hai dấu xuống dòng bị từ chối trước khi ai bị hỏi, xét từng ký tự; tab gõ
được, dấu xuống dòng khi gõ vẫn chờ người; set_value xét theo tên ô mà dàn ý ghi: tên có từ
dừng thì chờ dù chữ đặt vào là gì, từ dừng trong chữ thì lý do nêu tên ô, không có dàn ý thì
đọc tên lời gọi nêu; tên lấy từ màn hình hiện mọi ký tự điều khiển thành mã; bấm đúp lên phần
tử luôn chờ và nêu tên nó, lên một điểm thì chờ và nói ở đâu).tests/test_desktop_browsers.py (các trình duyệt server biết đúng là danh sách của
trình trợ giúp, đọc thẳng từ mã Swift; trình duyệt được nhận theo tên, không phân biệt hoa
thường, theo bundle id hay một id đi tiếp từ nó sau dấu chấm; ứng dụng chỉ giống trình duyệt
thì không phải; danh sách nêu trình duyệt theo tên hay bundle id không bao giờ cho nó; mọi tên
danh sách ấy biết đều là trình duyệt).tests/test_desktop_act_tool.py (thao tác được gửi đúng như đã đọc và câu trả lời để
ngỏ kết quả; set_value nói đã đặt; focus_app không cần khung hình và dặn chụp lại; khung
hình cũ hơn lần chụp cuối, điểm ngoài ảnh, chữ cho ô bảo mật và ref sai dạng không tới trình
trợ giúp; dàn ý một hội thoại khác hay agent khác vừa đọc làm ref cũ hết hiệu lực; ứng dụng
ngoài danh sách bị để lại ở trước được nói mà không nêu tên; mỗi mã của trình trợ giúp có câu
riêng, câu của trình trợ giúp bị bỏ khi trần không đủ chỗ, mã lạ chỉ được nêu khi đúng dạng;
set_value dưới một tên bị giữ theo tên của dàn ý, và tên ấy không bao giờ được gửi tới trình
trợ giúp).tests/test_desktop_gate.py (chỉ computer_act qua cổng duyệt; thao tác chờ người
trừ khi hội thoại autonomous, và các trường hợp luôn hỏi thì chờ dù hội thoại cho phép gì;
lời gọi chắc chắn bị từ chối không tới ai và không tới trình trợ giúp; ref chỉ được đưa ra
hỏi khi dàn ý của nó vẫn là dàn ý vừa được xem; thao tác được đồng ý sau khi màn hình đã được
chụp lại, hay sau khi hội thoại khác đọc dàn ý, vẫn bị từ chối; lượt không ai xem dừng ở thao
tác cần hỏi và chạy phần còn lại; script chỉ đọc được nhìn mà không được thao tác, script
gated gặp cùng cổng, và script của một lịch dừng ở thao tác cần hỏi; bấm đúp và đặt giá trị
vào ô có tên chứa từ dừng chờ người cả trong hội thoại autonomous).tests/test_desktop_act_helper_guards.py (điểm có cửa sổ khác nằm trên được nói như
thế, không bao giờ như việc đã làm; trình duyệt ở trước dưới một tên riêng là việc trình trợ
giúp từ chối; trình duyệt mà danh sách nêu bị từ chối trước khi trình trợ giúp nghe; trình trợ
giúp giả từ chối chữ có ký tự điều khiển như trình trợ giúp thật, cho yêu cầu gửi vòng qua
tool).tests/test_desktop_routes.py (khối desktop của trang Kết nối nói đúng điều trình trợ
giúp nói; không có trình trợ giúp không phải lỗi; trình trợ giúp không trả lời giữ trang tối
đa một giây; điều nó không nói hay nói sai kiểu là null, không đoán; desktop của một agent
là mức, ứng dụng và máy, agent lạ là 404; lần chụp thử của người là đúng ảnh agent sẽ được
thấy, không là khung hình của agent, và mỗi lần không chụp được là 409 kèm một câu; nút xin
quyền là thứ duy nhất xin quyền, và là 409 khi không có trình trợ giúp hay màn hình khoá; danh
sách tool đánh dấu bốn tool đi theo khoá computer; dòng đầu của câu trả lời
computer_screenshot nêu ảnh và khung hình, đọc được cả từ dòng ngắn mà bước của run giữ, và
route tệp trả ảnh ấy dạng PNG; màn hình không đổi được đọc theo khung hình vì dòng của bước
có thể hết trước đường dẫn sâu dưới write_paths).tests/test_desktop_picture_route.py (thẻ duyệt nhận đúng ảnh tool đã chụp, dạng ảnh
không tin cậy; ảnh người thử chụp cũng là ảnh tool chụp; ảnh agent ghi lại là 404 cho tới khi
byte của nó lại đúng như lúc chụp; liên kết đặt vào chỗ ảnh là 404, kể cả khi nó dẫn tới đúng
các byte ấy; ảnh đã mất hay không phải tệp là 404; khung hình tool không chụp cho agent ấy là
404; tên không có dạng khung hình không bao giờ được tìm; agent lạ là 404; chỉ ảnh mới nhất
của mỗi agent được nhớ).tests/test_api_model_routes.py (trang Kết nối trả về sau khi lưu tuyến mang cả khối
desktop).SocketServerTests (thư mục thiếu được tạo chỉ cho chủ, thư mục mở cho người khác hay
của người khác bị từ chối và để nguyên; socket chỉ chủ dùng được; socket không ai trả lời được
thay, socket có trình trợ giúp trả lời và tệp không phải socket thì để nguyên; đường dẫn 103
byte được nhận, 104 thì không, đo bằng byte; nhiều yêu cầu trên một kết nối, yêu cầu tới từng
mảnh; dòng quá 64 KiB bị trả bad_request và kết nối đóng; kết nối của người dùng khác bị
đóng không lời; 64 kết nối dồn tới khi trình trợ giúp chưa kịp nhận đều được giữ chờ, không bị
từ chối như khi hàng chờ chỉ có 16 chỗ; dừng thì xoá socket).RequestParsingTests, HelperPiecesTests (mỗi yêu cầu một đối tượng JSON trên một
dòng; id tối đa 64 ký tự; bảy lệnh và mười sáu mã lỗi đúng như hợp đồng; khung hình là đầu
SHA-256 của ảnh, kích thước và màn hình, sống 300 giây và chỉ khi còn mới nhất; ảnh thu nhỏ
tới khi cạnh dài vừa và không bao giờ phóng to; chữ gõ chia từng mảnh tối đa 20 đơn vị UTF-16
mà không mất gì; --socket, --version và đối số lạ).PreconditionTests (màn hình khoá trước màn hình tắt trước quyền thiếu; mỗi lệnh chỉ
cần quyền nó dùng; lệnh lạ được trả lời mà không nhìn máy).StateTests (state trả lời cả khi máy khoá, tắt và chưa được cấp gì, và chỉ đọc
những thứ không bật hộp thoại; request_permissions không hỏi gì khi màn hình khoá, hỏi một
lần rồi đọc lại quyền đang giữ).ListingTests (ứng dụng xếp theo tên, đánh dấu ứng dụng ở trước và ứng dụng ẩn; cửa
sổ từ trước ra sau, tối đa một trăm, tiêu đề cắt ở 200 ký tự).ScreenshotTests (cỡ mặc định và giới hạn của cạnh dài; màn hình chính là màn hình
hệ thống đánh dấu là chính, không phải màn hình đứng đầu danh sách; cùng khung hình thì không
gửi lại ảnh; ảnh không trọn và máy không có màn hình là capture_failed).AXTests (dàn ý của ứng dụng ở trước hay của ứng dụng được nêu theo tên hay bundle id;
ref đếm từ một theo thứ tự cây; tiêu đề và giá trị cắt ở 200 ký tự; ô bảo mật được đánh dấu
và không bao giờ cho giá trị; số phần tử giữ trong khoảng cho phép và nói khi bị cắt; lần đọc
sau làm ref cũ thành ref_unknown).ActAppTests (mọi kind trừ focus_app là app_mismatch khi ứng dụng khác ở trước;
focus_app chờ tối đa hai giây, đưa được ứng dụng ẩn ra trước, và ứng dụng chưa chạy là
app_not_running).ActBrowserTests (mọi kind là browser_app khi một trình duyệt ở trước, kể cả
trang web lưu thành ứng dụng, bundle id viết hoa khác và ứng dụng web của Safari, và không gì
được gửi; focus_app không bao giờ đưa trình duyệt lên trước, theo tên hay bundle id, nhưng
đưa được ứng dụng khác lên trước khi trình duyệt đang ở trước; ứng dụng mà bundle id chỉ bắt
đầu giống trình duyệt vẫn thao tác được; trình duyệt ở trước vẫn được kể, chụp và đọc dàn ý;
type dừng khi trình duyệt lên trước giữa hai mảnh; danh sách bundle id khớp không phân biệt
hoa thường và cả phần sau một dấu chấm, và không có dòng trùng).ActHitTests (bấm, bấm đúp, bấm phải theo điểm và cuộn là point_covered khi ứng
dụng khác nằm trên cùng tại điểm ấy, còn trúng bất kỳ phần tử nào của ứng dụng ở trước thì
gửi; cửa sổ nằm cạnh điểm không giữ cú bấm lại; kéo hỏi cả hai đầu trước khi nhấn chuột và
dừng ở đầu hỏng đầu tiên; bấm theo ref phải trúng chính phần tử hay một phần tử con, không
phải phần tử khác của cùng ứng dụng; không có gì tại điểm và mọi lỗi của máy là
point_covered; type, key và set_value không hỏi điểm).ActControlCharacterTests (type và set_value có ký tự điều khiển là
bad_request và không hỏi máy gì; ký tự bị từ chối đúng là nhóm điều khiển của Unicode trừ
tab, LF và CR, xét trên mọi điểm mã; ký tự ở bất kỳ đâu trong chữ dài đều bị tìm thấy và ký
tự đầu tiên được nêu; chữ chỉ có tab và xuống dòng được gõ và đặt nguyên vẹn).ActPointerTests (điểm của ảnh rơi đúng chỗ trên màn hình có gốc khác không và trên
màn hình Retina; kéo, cuộn trong 50 dòng mỗi chiều; khung hình không còn mới nhất, quá 300
giây hay của màn hình khác bị từ chối; điểm ngoài ảnh không gửi gì).ActRefTests (bấm theo ref vào giữa phần tử ở chỗ nó đang đứng; ref của ứng dụng
không ở trước là ref_unknown; set_value vào ô bảo mật là secure_input, nhưng ô ấy vẫn
bấm được).ActKeyboardTests (chế độ nhập bảo mật chặn type, key và set_value; khoá màn
hình, đăng xuất và buộc thoát bị chặn theo mọi thứ tự phím giữ, kể cả thêm một phím giữ;
type dừng khi ứng dụng khác lên trước hay chế độ nhập bảo mật bật giữa hai mảnh; 2 000 ký tự
gõ được, 2 001 thì không).ActOrderTests (thứ tự các phép kiểm của act: hình dạng kể cả ký tự điều khiển,
điều kiện của máy, phím bị chặn, ứng dụng, trình duyệt ở trước, nhập bảo mật, khung hình,
ref và điểm ngoài ảnh, rồi thứ nằm trên cùng tại điểm; mỗi phép kiểm một lần, rồi thao tác
một lần).SourceBudgetTests (không tệp Swift nào quá 200 dòng; thư viện lõi mà test chạy
không nêu gì của máy thật và chỉ import thứ không bật được hộp thoại; máy thật chỉ được nêu
trong phần chạy được của trình trợ giúp).web/src/components/connections-desktop-card.test.tsx (server không nói gì về desktop
thì không có thẻ; không có trình trợ giúp thì thẻ nói vậy, nói đó là việc của chủ máy và không
có gì để bấm; có trình trợ giúp thì nêu phiên bản, màn hình khoá hay tắt, và từng quyền đã cấp,
chưa cấp hay chưa rõ; quyền thiếu hay chưa rõ đều có nút “Xin quyền”; lúc vẽ không gửi gì; mỗi
lần bấm gửi đúng một POST /api/desktop/permissions, giữ nút trong lúc chờ, rồi đọc lại trang;
quyền vẫn thiếu là việc chủ cấp trong Cài đặt hệ thống; 409 hiện đúng câu của server).web/e2e/desktop.spec.ts (không có trình trợ giúp thì trang không gửi gì; quyền chỉ
được xin khi bấm, rồi trình trợ giúp được đọc lại; quyền còn thiếu là việc của chủ; màn hình
khoá hiện đúng câu từ chối).web/src/components/agent-editor/desktop-controls.test.tsx (hai mức mở ra theo đúng
tệp, dòng của mức Bật nêu bốn tool và cổng duyệt; Bật được lưu là chữ "on", Tắt là "off"
một mình và danh sách trong tệp giữ nguyên; dòng nói trình trợ giúp là việc của chủ máy và vì
sao bốn tool không có ô chọn; agent không sửa được thì chỉ xem; ô ứng dụng chỉ hiện khi Bật,
hay khi danh sách vừa bị từ chối; danh sách lưu đã cắt khoảng trắng, mỗi tên một lần, bỏ dòng
trống, ô xoá trắng gửi []; lỗi 422 hiện cạnh ô của nó và giữ chữ đã gõ, kể cả lời từ chối
một trình duyệt web, dưới dòng nói vì sao; server chưa biết desktop thì không có ô nào và
không gửi khoá nào).web/src/hooks/agent-draft-desktop.test.ts (bản nháp mang hai khoá của tệp; có mức mà
không có danh sách là chỉ nhìn; server chưa biết desktop thì không có khoá nào; lần lưu chỉ
gửi khoá đã đổi, và [] cho danh sách đã xoá trắng).web/src/components/agent-editor/tool-picker-desktop.test.tsx (danh sách công cụ không
có ô cho tool desktop nào, dù server có nói listable_tools hay không, và không ghi tool
desktop nào vào; dòng dưới danh sách nói bốn tool đi theo desktop, cả khi mức Tắt, và không có
với server chưa biết desktop).web/e2e/desktop.spec.ts (Bật và gõ ứng dụng thì lần lưu gửi "on" và một danh
sách, còn sau khi tải lại; xoá trắng ô thì gửi []; danh sách bị từ chối thì lỗi hiện cạnh ô
và ô giữ chữ đã gõ).web/src/components/agent-editor/desktop-card.test.tsx (Tắt thì không mời chụp; không
có trình trợ giúp thì chỉ nói đó là việc của chủ; màn hình khoá, sẵn sàng, và từng thứ đang
cản, quyền thiếu tách khỏi quyền chưa rõ; không có ứng dụng nào thì nói agent chỉ nhìn; server
cũ hay route 404 thì không có thẻ; ảnh thử hiện qua route tệp, theo đúng đường dẫn server nói,
cả dưới write_paths, và dòng dưới nút nói ảnh nằm ở thư mục desktop/ nào, rồi máy được đọc
lại; các câu 409 hiện đúng câu của server; một lần bấm
là một lần chụp; quá giới hạn của trình trợ giúp thì thôi chờ và câu trả lời muộn không được
vẽ; lưu mức hay danh sách thì máy được đọc lại, câu trả lời cũ đến sau không thay câu mới
hơn).web/e2e/desktop.spec.ts (chụp thử thì ảnh hiện; không chụp được thì thẻ hiện đúng
câu vì sao).computer_* trong bảng công cụ:
web/src/components/tools-matrix-desktop.test.tsx (mỗi dòng có nhãn desktop và lý do
khi rê chuột, không bị coi là tool của danh sách; chỉ tool thao tác chờ duyệt; agent server nói
có giữ tool thì ô là có; agent ở mức Tắt được giải thích bằng khoá computer; không agent nào
Bật thì không có dòng và không có chú giải).computer_act:
web/src/lib/desktop-call.test.ts (mỗi kind thành một câu; thao tác trên phần tử nói
bằng phần tử, không bằng điểm; dấu ngoặc trong tên không lọt ra ngoài phần trích; tham số
thiếu, sai kiểu hay của tool khác thì không có câu; toạ độ làm tròn tới phần trăm; mỗi ký tự
điều khiển được viết thành mã, còn tab và dấu xuống dòng đọc là khoảng trắng; set_value có
tên ô thì câu nêu tên ấy).web/src/components/approval-bar-desktop.test.tsx (câu nằm trên tham số thô, lý do của
server trên câu, lý do rỗng thì không bịa; ảnh luôn hỏi server theo khung hình, không bao giờ
là tệp trong thư mục của agent; lần chụp của hội thoại có khung hình ấy thì kèm giờ chụp, không
có thì không nói giờ; agent và khung hình được viết vào địa chỉ như một phần của nó, không bao
giờ thành thêm đường dẫn; ảnh không tải được thì bỏ; điểm đánh
dấu theo kích thước trong frame_id, kéo có hai dấu; thao tác theo ref, điểm ngoài ảnh hay
kind không có điểm thì không có dấu nhưng vẫn có ảnh; không bao giờ hiện ảnh của khung hình
trước; ảnh có ở thanh duyệt của hội thoại và ở dòng của danh sách yêu cầu).web/src/lib/desktop-frame.test.ts (frame_id phải là trọn giá trị và cho ra kích
thước; câu “đã lưu” và câu “không đổi” được đọc cả nguyên văn lẫn trong dòng 160 ký tự của
bước, ở workspace và dưới write_paths sâu; thư mục dự phòng đọc write_paths như server;
trong tin nhắn, lần lưu được ghép với lời gọi của nó, không bao giờ với tool khác).web/e2e/desktop.spec.ts (thao tác chờ trong chat có câu, ảnh có dấu điểm, rồi
tham số, ảnh và liên kết đều qua GET …/desktop/pictures/{frame_id}; kéo ở danh sách yêu cầu
dùng ảnh khung dọc của khung hình ấy, có dấu ở hai đầu, dưới lý do của server; route trả 404
thì thẻ có câu và tham số, không có ảnh).web/src/lib/hidden-chars.test.ts (mọi ký tự điều khiển trừ tab và hai dấu xuống dòng
được viết bằng mã; một backspace làm chữ này thành chữ khác hiện ra; nhóm ký tự mà khung xem mã
hiện vẫn là của khung ấy), web/src/lib/browser-call.test.ts (mỗi ký tự điều khiển được viết
bằng mã, kể cả những ký tự trắng trong số đó), web/src/components/tool-args-detail.test.tsx
(mỗi ký tự điều khiển được viết bằng mã, tab và dấu xuống dòng giữ nguyên; trên thanh duyệt cả
lý do, câu và tham số đều viết mã).computer_screenshot trên bước của run và thẻ tool:
web/src/components/desktop-shot-thumb.test.tsx (ảnh đã lưu đọc từ dòng bước giữ lại
và từ câu trả lời đầy đủ trên thẻ chat, mở trong tab mới qua route tệp; màn hình không đổi lấy
ảnh của cùng khung hình mà bước hay lời gọi trước đã lưu, không có thì lấy thư mục của agent;
bước hay lời gọi hỏng, chưa xong, hay của tool khác thì không có ảnh; các bước cùng tìm thư mục
của một agent đọc dòng của agent ấy một lần, lần tìm sau đọc lại).web/e2e/desktop.spec.ts (bước chụp hiện ảnh đã lưu, bước “không đổi” hiện cùng
ảnh ấy).web/e2e/desktop.spec.ts (ở 390px ô cài đặt, thẻ agent cùng ảnh, thẻ của máy và
ảnh của thao tác chờ duyệt cùng dấu điểm đều nằm trong trang, mọi nút đủ lớn cho ngón tay, và
bundle id dài xuống dòng).