mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-08 17:31:56 -10:00
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:
1 parent
7a026128a0
commit
ec43d48af9
3 files changed
+73
-2
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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/);
|
||||
});
|
||||
});
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in new issue
Block a user