shell-redesign V1 pre-release CLI/daemon Item 2: rig send email-style envelope wrap

Wraps every local rig send body with a header / body / reply-hint
envelope so the recipient pane shows who the message is from, who it
is for, and the exact command shape needed to reply. Resolves the
sender's session from OPENRIG_SESSION_NAME / RIGGED_SESSION_NAME and
falls back to a "<unknown sender>" marker when neither is present.

Envelope shape:

    From: <sender>
    To: <recipient>
    ---
    <body verbatim>
    ---
    ↩ Reply: rig send <sender> "..."

Cross-host sends (--host) intentionally do NOT wrap locally — the
remote rig wraps when it runs the same command, and double-wrapping
would nest envelopes. The body inside the dashes is the original
text verbatim, so multi-line input passes through unchanged.

New unit test packages/cli/test/send-header.test.ts covers header
presence, body preservation, empty-body edge case, sender fallback,
and recipient-string preservation. Existing send.test.ts assertion
updated to match the wrapped POST payload.

Verification:
- npx vitest run send.test.ts send-header.test.ts: 13/13 PASS
- npx tsc --noEmit --project packages/cli/tsconfig.json: clean

SC-29 EXCEPTION #4 continues for this slice.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-07 04:32:09 -07:00
1 parent 7a026128a0
commit ec43d48af9
3 files changed
+73 -2

No files matched your search

+27 -1
View File
@@ -6,9 +6,34 @@ import type { StatusDeps } from "./status.js";
import { loadHostRegistry, resolveHost } from "../host-registry.js";
import { runCrossHostCommand, type RunCrossHostCommandOpts } from "../cross-host-executor.js";
import { emitCrossHostError, emitCrossHostFailure } from "../cross-host-cli-helpers.js";
import { readOpenRigEnv } from "../openrig-compat.js";
const WAIT_FOR_IDLE_REQUEST_OVERHEAD_MS = 5_000;
const SENDER_FALLBACK = "<unknown sender>";
/**
* Wrap a `rig send` body with an email-style envelope so the recipient
* pane has both the sender's identity and a copy-pasteable reply hint.
* Cross-host sends do NOT wrap locally: the remote rig wraps when it
* runs the same command, and double-wrapping would nest envelopes.
*/
export function wrapSendBody(sender: string | undefined, recipient: string, body: string): string {
const senderLabel = sender && sender.trim().length > 0 ? sender : SENDER_FALLBACK;
return [
`From: ${senderLabel}`,
`To: ${recipient}`,
"---",
body,
"---",
`↩ Reply: rig send ${senderLabel} "..."`,
].join("\n");
}
function resolveSenderSession(): string | undefined {
return readOpenRigEnv("OPENRIG_SESSION_NAME", "RIGGED_SESSION_NAME");
}
export interface SendDeps extends StatusDeps {
/**
* Cross-host hooks. Both default to the production loaders/executors; tests
@@ -86,8 +111,9 @@ via single-hop ssh. SSH success is NOT verify success: the remote rig's
}
const client = deps.clientFactory(getDaemonUrl(status));
const wrappedText = wrapSendBody(resolveSenderSession(), session, text);
const res = await client.post<Record<string, unknown>>("/api/transport/send", {
session, text, verify: opts.verify, force: opts.force, waitForIdleMs,
session, text: wrappedText, verify: opts.verify, force: opts.force, waitForIdleMs,
}, waitForIdleRequestOptions(waitForIdleMs));
if (opts.json) {
+41
View File
@@ -0,0 +1,41 @@
import { describe, it, expect } from "vitest";
import { wrapSendBody } from "../src/commands/send.js";
describe("wrapSendBody — pre-release CLI/daemon Item 2 (email-style envelope)", () => {
it("renders From / To / body / reply hint with both session names", () => {
const out = wrapSendBody("driver-3@my-rig", "guard-3@my-rig", "Status: ready.");
expect(out).toContain("From: driver-3@my-rig");
expect(out).toContain("To: guard-3@my-rig");
expect(out).toContain("Status: ready.");
expect(out).toContain('↩ Reply: rig send driver-3@my-rig "..."');
});
it("preserves the original body verbatim between the dash separators", () => {
const body = "Multi-line\nbody with\nthree lines.";
const out = wrapSendBody("a@r", "b@r", body);
const segments = out.split("\n---\n");
expect(segments).toHaveLength(3);
expect(segments[1]).toBe(body);
});
it("wraps cleanly when the body is empty", () => {
const out = wrapSendBody("a@r", "b@r", "");
expect(out).toContain("From: a@r");
expect(out).toContain("To: b@r");
expect(out).toContain("---\n\n---");
expect(out).toContain('↩ Reply: rig send a@r "..."');
});
it("falls back to a marker when the sender is undefined or empty", () => {
const undef = wrapSendBody(undefined, "b@r", "hi");
expect(undef).toContain("From: <unknown sender>");
expect(undef).toContain('↩ Reply: rig send <unknown sender> "..."');
const blank = wrapSendBody(" ", "b@r", "hi");
expect(blank).toContain("From: <unknown sender>");
});
it("uses the literal recipient string in the To header so cross-rig addresses survive", () => {
const out = wrapSendBody("from@a", "to@b", "x");
expect(out).toMatch(/^From: from@a\nTo: to@b\n---\n/);
});
});
+5 -1
View File
@@ -137,11 +137,15 @@ describe("Send CLI", () => {
"/api/transport/send",
expect.objectContaining({
session: "dev-impl@my-rig",
text: "hello",
text: expect.stringContaining("hello"),
waitForIdleMs: 30000,
}),
{ timeoutMs: 35000 },
);
const sentText = postFn.mock.calls[0]?.[1] as { text: string } | undefined;
expect(sentText?.text).toContain("To: dev-impl@my-rig");
expect(sentText?.text).toContain("---\nhello\n---");
expect(sentText?.text).toContain('↩ Reply: rig send');
});
it("send without wait-for-idle uses default client timeout path", async () => {