mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-10 03:31:40 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71389bef06 | ||
|
|
ebf20ae616 |
No files matched your search
@@ -179,26 +179,28 @@ export function composerContainsOwnedText(input: ComposerInput, expected: string
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Upper-bound a complete paste in the recognized Codex viewport before writing.
|
||||
* FirstFit may move a whole word to the next row; every adjacent pair of
|
||||
* non-final rows then consumes at least one row's width. Reserve another row
|
||||
* for the insertion cursor. Non-ASCII code points are charged two cells, so
|
||||
* combining marks/emoji cannot make this bound optimistic. Other readers keep
|
||||
* their existing policy; this is not ownership or permission to press Enter.
|
||||
/** Conservative uncertainty before a protected Codex paste, never Enter authority.
|
||||
* Codex 0.164 collapses >1000 code points to [Pasted Content N chars], which
|
||||
* the current ownership reader cannot own. Other wrapped text gets a row
|
||||
* upper bound; a line fitting the native width needs only one row.
|
||||
*/
|
||||
export function composerCanObservePaste(input: ComposerInput, text: string): boolean {
|
||||
export function composerPasteUncertainty(input: ComposerInput, text: string): "controls" | "long" | "viewport" | null {
|
||||
const viewport = input.pasteViewport;
|
||||
if (!viewport) return true;
|
||||
if (viewport.columns < 1 || viewport.rows < 1) return false;
|
||||
if (!viewport) return null;
|
||||
const normalized = text.replace(/\r\n/g, "\n");
|
||||
const lines = normalized.split("\n");
|
||||
if (lines.some(line => /[\x00-\x1f\x7f-\x9f]/.test(line))) return "controls";
|
||||
// rust-v0.164.0 chat_composer/paste_input.rs:184-192 (Unicode scalar count).
|
||||
if ([...normalized].length > 1000) return "long";
|
||||
if (viewport.columns < 1 || viewport.rows < 1) return "viewport";
|
||||
let rows = 0;
|
||||
for (const line of text.replace(/\r\n/g, "\n").split("\n")) {
|
||||
// Tabs/control sequences have no bounded printable layout here.
|
||||
if (/[\x00-\x1f\x7f-\x9f]/.test(line)) return false;
|
||||
for (const line of lines) {
|
||||
// Two cells per non-ASCII code point conservatively covers wide glyphs.
|
||||
const cells = [...line].reduce((sum, ch) => sum + (ch >= " " && ch <= "~" ? 1 : 2), 0);
|
||||
rows += cells < viewport.columns ? 1 : Math.ceil(2 * cells / viewport.columns) + 1;
|
||||
if (rows > viewport.rows) return false;
|
||||
rows += cells <= viewport.columns ? 1 : Math.ceil(2 * cells / viewport.columns) + 1;
|
||||
if (rows > viewport.rows) return "viewport";
|
||||
}
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Recognize this lease's paste immediately after pasting or before its Enter
|
||||
|
||||
@@ -2,7 +2,7 @@ import { hasOtherSeatHistory, isStoppedSeatPredecessor, resolveCurrentSessionAdd
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type Database from "better-sqlite3";
|
||||
import { composerCanObservePaste, composerContainsOwnedText, ownCollapsedPaste, type ComposerInput, type ComposerOwnership } from "./composer-prompts.js";
|
||||
import { composerPasteUncertainty, composerContainsOwnedText, ownCollapsedPaste, type ComposerInput, type ComposerOwnership } from "./composer-prompts.js";
|
||||
|
||||
/** A binding is captured before waiting. Never rebind an old operation to a new occupant. */
|
||||
export interface GuardTarget {
|
||||
@@ -433,8 +433,14 @@ export class SeatDeliveryGuard {
|
||||
} else if (input.state !== "empty") {
|
||||
throw new DeliveryGuardError("draft_input_busy", "The seat has an unsubmitted input. Automatic delivery is held.");
|
||||
} else {
|
||||
if (pasteText !== undefined && !composerCanObservePaste(input, pasteText)) {
|
||||
throw new DeliveryGuardError("draft_input_unknown", "Held before paste: the complete message cannot stay observable in this Codex viewport. Open a larger terminal or shorten the message, then retry this request.");
|
||||
const uncertainty = pasteText === undefined ? null : composerPasteUncertainty(input, pasteText);
|
||||
if (uncertainty) {
|
||||
const message = uncertainty === "controls"
|
||||
? "OpenRig couldn't confirm the layout of tabs or control characters. Send the message without them; nothing was pasted."
|
||||
: uncertainty === "long"
|
||||
? "OpenRig couldn't confirm ownership of Codex's collapsed long paste. Send a shorter message, or set this seat's typing guard to off for delivery without draft protection; nothing was pasted."
|
||||
: "OpenRig couldn't confirm the complete message would stay visible in this Codex viewport. Open a larger terminal or shorten the message, then retry this request; nothing was pasted.";
|
||||
throw new DeliveryGuardError("draft_input_unknown", message);
|
||||
}
|
||||
lease.inputFrame = input.frame;
|
||||
}
|
||||
|
||||
@@ -470,8 +470,8 @@ export function inspectComposerInput(snapshot: ComposerSnapshot | null, runtime:
|
||||
if (prompt.glyph !== "❯" && codexFooterSeparator(lines) === end) {
|
||||
// Codex 0.164 fullscreen: composer <= 2/3 of the pane (at least eight
|
||||
// rows, capped by the pane), less padding/status/hint rows. Leave the
|
||||
// right padding and scroll marker out of the printable width.
|
||||
input.pasteViewport = { columns: width - prefix - 2,
|
||||
// one-column right inset out of the native printable width.
|
||||
input.pasteViewport = { columns: width - prefix - 1,
|
||||
rows: Math.min(height, Math.max(8, Math.floor(height * 2 / 3))) - 4 };
|
||||
}
|
||||
candidates.push(input);
|
||||
|
||||
@@ -158,6 +158,20 @@ describe("Codex pre-paste observability through the real delivery path", () => {
|
||||
expect(f.submissions).toEqual([text]);
|
||||
});
|
||||
|
||||
it("owns the complete envelope after a native 62-column hard wrap at 65x45", async () => {
|
||||
const f = await codexDelivery("draft-aware", { width: 65, height: 45 });
|
||||
const body = "x".repeat(64);
|
||||
const text = wrapPaneEnvelope("sender@test", "worker@test", body);
|
||||
expect(text.split("\n").filter(row => row.length > 62)).toEqual([body]);
|
||||
// Codex's two-column prompt and one-column right inset leave 62 cells.
|
||||
// This ASCII word wraps without dropping or replacing any authored byte.
|
||||
vi.mocked(f.tmux.captureComposerSnapshot).mockImplementation(async () =>
|
||||
codexViewport((f.bodies.a ?? "").replace(body, `${body.slice(0, 62)}\n${body.slice(62)}`), 65, 45));
|
||||
expect(await f.send("native-wrap", text, f.options)).toMatchObject({ ok: true, sent: true });
|
||||
expect(f.submissions).toEqual([text]);
|
||||
expect(f.bodies.a).toBe("");
|
||||
});
|
||||
|
||||
it("retains a raw-fitting 1200-code-point envelope before Codex collapses it into an unowned label", async () => {
|
||||
const f = await codexDelivery("draft-aware", { width: 160, height: 45 });
|
||||
const empty = wrapPaneEnvelope("sender@test", "worker@test", "");
|
||||
|
||||
Reference in new issue
Block a user