Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 100 additions & 5 deletions packages/cli/bin/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ import {
renderHumanArchitecturePackageOutcome,
runHumanArchitecturePackage,
} from "./human-architecture-package.js";
import { renderHumanVersion } from "./human-version.js";
import {
renderHumanHandoffOutcome,
runHumanHandoff,
} from "./human-handoff.js";
import { createSystemFilePicker } from "./system-file-picker.js";
import {
consumeInvocationContext,
Expand Down Expand Up @@ -448,6 +453,19 @@ function print(value) {
return;
}

if (
value.operation === "version"
&& ["completed", "unavailable", "execution_error"].includes(value.status)
) {
const presentation = humanAuditPresentationOptions({
args,
env: process.env,
output: process.stdout,
});
process.stdout.write(`${renderHumanVersion(value, { styled: presentation.styled })}\n`);
return;
}

if (
value.operation === "audit"
&& ["needs_input", "needs_confirmation", "needs_permission"].includes(value.status)
Expand Down Expand Up @@ -601,7 +619,7 @@ async function main() {
if (command === "version") {
const authority = await resolveExecutionAuthority({
cwd: optionValue("--cwd"),
launcher_version: VERSION,
launcher_version: invocationContext.launcher_version,
});
const ready = authority.state === "ready";
const invalid = authority.state === "invalid_toolchain";
Expand All @@ -610,7 +628,7 @@ async function main() {
status: ready ? "completed" : invalid ? "execution_error" : "unavailable",
operation: "version",
...(ready ? { cli_version: authority.engine.version } : {}),
launcher_version: VERSION,
launcher_version: invocationContext.launcher_version,
authority,
...(!ready ? {
error: authority.state,
Expand Down Expand Up @@ -1207,6 +1225,7 @@ async function main() {
}

if (command === "handoff") {
const cwd = optionValue("--cwd") ?? process.cwd();
const resumeToken = optionValue("--resume");
const source = {};
const files = [
Expand All @@ -1224,13 +1243,15 @@ async function main() {
}
}
} catch {
print({
const failure = {
contract: CLI_INTERACTION_CONTRACT,
status: "execution_error",
operation: "handoff",
error: "invalid_handoff_input_file",
message: "Handoff requires readable Task Graph, Executor, tool, and review JSON files.",
});
};
if (json) print(failure);
else process.stdout.write(`${renderHumanHandoffOutcome(failure)}\n`);
return 2;
}
let receipt;
Expand All @@ -1239,13 +1260,87 @@ async function main() {
try {
receipt = JSON.parse(await readFile(receiptPath, "utf8"));
} catch {
print({
const failure = {
contract: CLI_INTERACTION_CONTRACT,
status: "execution_error",
operation: "handoff",
error: "invalid_execution_receipt_file",
message: "The execution receipt JSON could not be read and parsed.",
};
if (json) print(failure);
else process.stdout.write(`${renderHumanHandoffOutcome(failure)}\n`);
return 2;
}
}
if (!json) {
if (["--resume", "--select", "--confirm", "--choice"].some((option) =>
args.includes(option))) {
process.stderr.write([
"Human Mode does not accept structured Handoff decisions.",
"Use rally handoff --json with explicit resume and decision options for the Agent/CI protocol.",
].join("\n") + "\n");
return 2;
}
if (process.stdin.isTTY !== true) {
process.stderr.write([
"Non-TTY Human Mode cannot coordinate Handoff decisions safely.",
"Use rally handoff --json for the resumable Agent/CI protocol.",
].join("\n") + "\n");
return 2;
}
const { createClackPromptAdapter, createPlainPromptAdapter } = await import(
"./prompt-adapters.js"
);
const presentation = humanAuditPresentationOptions({
args,
env: process.env,
output: process.stdout,
});
const prompt = presentation.plain
? createPlainPromptAdapter({ input: process.stdin, output: process.stderr })
: await createClackPromptAdapter({ input: process.stdin, output: process.stderr });
const sourceReportId = source.task_graph?.source_report?.id;
const verifyAction = typeof sourceReportId === "string"
? createNextAction(invocationContext, [
"verify",
"--cwd",
path.resolve(cwd),
"--report",
path.resolve(cwd, ".launchrally", "reports", sourceReportId, "record.json"),
"--scope",
"full",
])
: null;
try {
const result = await runHumanHandoff({
source,
receipt,
prompt,
runHandoff,
verifyAction,
loadReceipt: async (receiptFile) => {
try {
return JSON.parse(await readFile(path.resolve(receiptFile), "utf8"));
} catch {
const error = new Error("The execution receipt JSON could not be read and parsed.");
error.code = "invalid_execution_receipt_file";
throw error;
}
},
});
return ["unavailable", "execution_error", "stale_input"].includes(result.status)
? 2
: 0;
} catch (error) {
if (error instanceof PromptCancelledError) {
process.stderr.write("Handoff cancelled. LaunchRally executed no external Task.\n");
return 130;
}
process.stdout.write(`${renderHumanHandoffOutcome({
status: "execution_error",
error: error.code ?? "handoff_human_mode_failed",
message: error.message,
})}\n`);
return 2;
}
}
Expand Down
226 changes: 226 additions & 0 deletions packages/cli/bin/human-handoff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { terminalSafeText } from "./human-architect.js";

function list(values, render = terminalSafeText, fallback = "None") {
return values?.length > 0
? values.map((value) => ` - ${render(value)}`)
: [` - ${fallback}`];
}

function candidateLines(candidate) {
return [
`${candidate.recommended ? "Recommended" : "Alternative"}: ${terminalSafeText(candidate.batch_id)}`,
` Executor: ${terminalSafeText(candidate.executor_id)}`,
` Tasks: ${candidate.task_ids.map(terminalSafeText).join(", ")}`,
` Authority: ${terminalSafeText(candidate.effect_class)} on ${terminalSafeText(candidate.target)}`,
` Availability: ${candidate.available ? "available" : terminalSafeText(candidate.unavailable_reason)}`,
` Tools: ${candidate.tools.map((tool) => `${terminalSafeText(tool.executable)}@${terminalSafeText(tool.exact_version)}`).join(", ")}`,
` Authentication: ${terminalSafeText(candidate.authentication_state)} (${candidate.auth_assumptions.map(terminalSafeText).join(", ")})`,
` Secret handling: ${terminalSafeText(candidate.secret_handling)}`,
` Cancellation: ${terminalSafeText(candidate.cancellation)}`,
` Partial failure: ${terminalSafeText(candidate.partial_failure)}`,
];
}

export function renderHumanHandoffDiscovery(result) {
const lines = [
"External Executor Discovery",
"LaunchRally does not install, log in, request credentials, or execute external Tasks.",
"Ready authority boundaries:",
...list(result.preview?.user_visible_effects),
];
if (result.candidates?.length > 0) {
lines.push(
"Reviewed Executor candidates:",
...result.candidates.flatMap(candidateLines),
);
}
if (result.recovery) {
lines.push(
"Executor recovery:",
` Reason: ${terminalSafeText(result.recovery.reason)}`,
` Tool: ${terminalSafeText(result.recovery.tool.executable)}@${terminalSafeText(result.recovery.tool.exact_version)}`,
` Official manual: ${terminalSafeText(result.recovery.official_manual.url)}`,
...(result.recovery.installation_instructions?.length > 0
? [
" User-managed commands (not executed):",
...result.recovery.installation_instructions.map(({ command }) =>
` - ${[command.executable, ...command.arguments].map(terminalSafeText).join(" ")}`),
]
: []),
);
}
if (result.manual_path) {
lines.push(
"Manual or custom Executor path is available without granting authority.",
`Required capabilities: ${result.manual_path.required_capabilities?.map(terminalSafeText).join(", ") || "none"}`,
);
}
return lines.join("\n");
}

export function renderHumanHandoffAuthority(result) {
const handoff = result.handoff_package;
const authority = handoff.authority_batch;
return [
"Exact External Authority Preview",
`Handoff Package: ${terminalSafeText(handoff.handoff_id)}`,
`Environment: ${terminalSafeText(handoff.environment)}`,
`Executor: ${terminalSafeText(handoff.executor.id)}`,
"Tasks:",
...list(handoff.task_ids),
"Allowed effects:",
...list(authority.allowed_effects),
"Prohibited effects:",
...list(authority.prohibited_effects),
"User-visible effects:",
...list(authority.user_visible_effects),
`Authentication: ${terminalSafeText(authority.executor_requirements.authentication_state)} (${authority.executor_requirements.auth_assumptions.map(terminalSafeText).join(", ")})`,
`Secret handling: ${terminalSafeText(authority.executor_requirements.secret_handling)}`,
`Cancellation: ${terminalSafeText(authority.coordination.cancellation)}`,
`Partial failure: ${terminalSafeText(authority.coordination.partial_failure)}`,
"Confirmation grants only this exact external authority. LaunchRally still performs no external execution.",
].join("\n");
}

export function renderHumanHandoffReceipt(result) {
const receipt = result.execution_receipt;
const outcomes = result.execution_outcomes ?? [];
return [
receipt ? "Execution Receipt Review" : "Approved Handoff Package",
`Handoff Package: ${terminalSafeText(result.handoff_package?.handoff_id)}`,
`Approval: ${terminalSafeText(result.handoff_package?.approval?.state)}`,
...(receipt ? [
`Receipt: ${terminalSafeText(receipt.receipt_id)}`,
"Classification: claim only; not Machine Evidence; verification remains unverified.",
"Reported Task outcomes:",
...list(outcomes, (outcome) => [
terminalSafeText(outcome.task_id),
terminalSafeText(outcome.receipt_state),
outcome.remaining_work?.state === "required"
? `remaining work ${terminalSafeText(outcome.remaining_work.coordination)}`
: "no remaining work reported",
].join(" — ")),
] : [
"Run the approved package with the selected external Executor outside LaunchRally, then submit its normalized Execution Receipt.",
"The receipt must be secret-free and exactly bound to this Handoff Package.",
]),
].join("\n");
}

export function renderHumanHandoffOutcome(result, { verifyAction } = {}) {
if (result.status === "completed" && result.next?.operation === "verify") {
return [
"Fresh Verify Requires a Separate Command",
"The Execution Receipt remains a claim and has not been promoted to Machine Evidence.",
`Verify scope: ${terminalSafeText(result.next.scope)}`,
"Task requests:",
...list(result.next.task_requests, (request) =>
`${terminalSafeText(request.task_id)} — ${terminalSafeText(request.scope)}; Evidence: ${request.evidence_targets.map(terminalSafeText).join(", ")}`),
"Handoff does not carry the source Report contents or Check IDs required to start targeted Verify safely.",
"Run this complete Human Verify command:",
terminalSafeText(verifyAction?.display ?? "A complete Verify command is unavailable."),
...(verifyAction?.disclosure
? [`Launcher entry: ${terminalSafeText(verifyAction.disclosure)}`]
: []),
].join("\n");
}
if (result.outcome === "manual_or_custom_selected") {
return [
"Manual or Custom Executor Selected",
"No external authority was granted and no external execution was started.",
].join("\n");
}
if (
["deferred", "receipt_deferred"].includes(result.outcome)
|| (result.status === "resumable" && result.state === "verification_pending")
) {
let detail = "No external authority was granted.";
if (result.state === "verification_pending") {
detail = "The Execution Receipt remains a claim and fresh Verify remains pending.";
} else if (result.outcome === "receipt_deferred") {
detail = "The approved package remains external; no receipt was accepted.";
}
return [
"Handoff Deferred",
detail,
"Run Handoff again with current inputs when you are ready to continue.",
].join("\n");
}
if (result.status === "denied") {
return [
"External Authority Declined",
"No external authority was granted and no external Task was executed by LaunchRally.",
].join("\n");
}
if (result.status === "cancelled") {
return [
"Handoff Cancelled",
`Authority granted before cancellation: ${result.safety?.authority_granted ? "yes" : "no"}`,
"LaunchRally did not execute an external Task.",
].join("\n");
}
if (result.status === "stale_input") {
return [
"Handoff Inputs Are Stale",
"Refresh the Task Graph and reviewed Executor inputs before granting authority.",
].join("\n");
}
if (result.status === "execution_error" || result.status === "unavailable") {
return [
"Handoff Could Not Continue",
`Error: ${terminalSafeText(result.error)}`,
...(result.message ? [`Message: ${terminalSafeText(result.message)}`] : []),
"No additional authority was granted by this failure.",
].join("\n");
}
return [
"Handoff Complete",
`Outcome: ${terminalSafeText(result.outcome ?? result.status)}`,
"LaunchRally did not execute an external Task.",
].join("\n");
}

export async function runHumanHandoff({
source,
receipt,
prompt,
runHandoff,
loadReceipt,
verifyAction,
}) {
let result;
let suppliedReceipt = receipt;
try {
await prompt.start?.("handoff");
result = await runHandoff(source, {});
while (["needs_input", "needs_confirmation", "resumable", "partial_completion"]
.includes(result.status)) {
const response = await prompt.respondHandoff(result, {
receiptAvailable: suppliedReceipt !== undefined,
});
const options = { resume_token: result.resume_token };
if (result.request?.kind === "executor_selection") {
options.selection = response.selection;
} else if (result.request?.kind === "authority_confirmation") {
options.confirmation = response.confirmation;
} else if (result.request?.kind === "execution_receipt") {
if (response.choice === "submit") {
if (suppliedReceipt === undefined) {
suppliedReceipt = await loadReceipt(response.receipt_path);
}
options.receipt = suppliedReceipt;
} else {
options.choice = response.choice;
}
} else {
options.choice = response.choice;
}
result = await runHandoff({}, options);
if (response.choice === "defer") break;
}
await prompt.finishHandoff(result, { verifyAction });
return result;
} finally {
await prompt.close();
}
}
Loading