diff --git a/packages/conversation/tests/flows.test.mjs b/packages/conversation/tests/flows.test.mjs index acd84aa35..e1bbce8fa 100644 --- a/packages/conversation/tests/flows.test.mjs +++ b/packages/conversation/tests/flows.test.mjs @@ -492,7 +492,7 @@ test("terminal: engine control characters are made visible; a lost connection re const h = await started(); try { const term = new Terminal({ client: h.client }); - h.engine.script([{ text: "\x1b]52;c;ZXZpbA==\x07 bell‮", stop: "stop" }]); + h.engine.script([{ text: "\x1b]52;c;ZXZpbA==\x07 bell\u202e", stop: "stop" }]); await term.key("go\r"); await receiptState(h.client, term.lastReceipt.id, "finished"); await until(() => term.transcript.lines().some((l) => l.includes("bell")), 2000, "the reply"); diff --git a/packages/tasks/src/fields.mjs b/packages/tasks/src/fields.mjs index 1c1d61fbc..e790430f0 100644 --- a/packages/tasks/src/fields.mjs +++ b/packages/tasks/src/fields.mjs @@ -2,7 +2,10 @@ import { readFileSync } from 'node:fs'; import { TASK_VERBS } from '../../bus/src/broker.mjs'; // Row 61 (#1550): the slice 1 field table as data, in ../fields.json. The Field table in // docs/plans/2026-10-04_slice-1.md is the reviewed prose it must match, and tests/fields.test.mjs -// checks that prose and this table against what the verbs do. Two layers, as lead decision 113 rules: +// checks that prose and this table against what the verbs do. For the prose, it reads the rows of +// the Field table and requires that every field name in a row is a label here and that the +// task verbs a row names are exactly the verbs this table lists for that field. +// Two layers, as lead decision 113 rules: // - `verbs`: the writer verbs.mjs checks before anything else. `pm` is the pm definition // (field-writer otherwise), `assigned` a role whose bot is an assignee (not-assigned // otherwise), `any` no writer check. diff --git a/packages/tasks/tests/fields.test.mjs b/packages/tasks/tests/fields.test.mjs index ef81f3385..1c2e396d4 100644 --- a/packages/tasks/tests/fields.test.mjs +++ b/packages/tasks/tests/fields.test.mjs @@ -10,7 +10,8 @@ import { loadFields, checkFields, FieldTableError, FIELDS_FILE } from '../src/fi import { TASK_VERBS } from '../../bus/src/broker.mjs'; import { loadRole } from '../../business/src/index.mjs'; // Row 61 (#1550): fields.json, and the Field table in docs/plans/2026-10-04_slice-1.md, against what -// the verbs in src/verbs.mjs do, with the role files as given. The writer layer is read from the table; whether a write needs a decision, and who +// the verbs in src/verbs.mjs do, with the role files as given. The writer layer is read from the +// table; whether a write needs a decision, and who // resolves it, from roles/*.json as the business loader reads them (lead decision 113). A version 1 // file, the researcher's, grants nothing. Neither expectation is written out here, so a change to // the table, the prose, a role file or the verbs that leaves them disagreeing fails this file. diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js index 108df3b98..0cc841bbc 100644 --- a/packages/webui/src/public/bus.js +++ b/packages/webui/src/public/bus.js @@ -10,7 +10,7 @@ const esc = v => String(v ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); // The CHAT-02 rules: C0 controls show as control pictures, C1 and bidi controls as // [U+XXXX]. Newlines and tabs stay. Markdown stays as its source. - const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f]/g, UNSEEN = /[\u0080-\u009f؜‎‏‪-‮⁦-⁩]/g; + const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f]/g, UNSEEN = /[\u0080-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g; const inert = v => String(v ?? '').replace(CONTROL, c => c === '\u007f' ? '␡' : String.fromCharCode(0x2400 + c.charCodeAt(0))).replace(UNSEEN, c => `[U+${c.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}]`); const txt = v => esc(inert(v)); // every agent-, tracker- or bus-authored string goes through this const announce = text => { $('announce').textContent = text; }; diff --git a/packages/webui/tests/bus-browser.test.mjs b/packages/webui/tests/bus-browser.test.mjs index 106a8dd26..bf09119f1 100644 --- a/packages/webui/tests/bus-browser.test.mjs +++ b/packages/webui/tests/bus-browser.test.mjs @@ -108,7 +108,7 @@ test('S5 pages in a browser: real broker rows, inert text, Copy, routes, failure assert.equal(await b.evaluate('!!window.injected'), false, where); assert.equal(await b.evaluate('document.querySelectorAll("#bus-view script, #bus-view img, #bus-view iframe, #bus-view object, #bus-view embed, #bus-view svg, #bus-view style, #bus-view link").length'), 0, where); const shown = await text('#bus-view'); - assert.doesNotMatch(shown, /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/, where); + assert.doesNotMatch(shown, /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/, where); }; const noOverflow = async where => { for (const width of [320, 1440]) { diff --git a/packages/webui/tests/bus.test.mjs b/packages/webui/tests/bus.test.mjs index 6fb74ccec..d1bb55e42 100644 --- a/packages/webui/tests/bus.test.mjs +++ b/packages/webui/tests/bus.test.mjs @@ -17,7 +17,7 @@ import { startServer } from '../src/serve.mjs'; import { BusReadError, busReader, humanCall } from '../src/bus.mjs'; import { close } from './fixture.mjs'; -const HOSTILE = ' ‮evil'; +const HOSTILE = ' \u202eevil'; const at = '2026-10-08T12:00:00.000Z'; const businesses = { demo: {