feat(omo-codex): batch 103 (19 files)
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
import type { UlwLoopScope } from "./paths.js";
|
||||
import { parseUlwLoopSteeringDirective, steerUlwLoop } from "./steering.js";
|
||||
|
||||
export interface UserPromptSubmitPayload {
|
||||
readonly cwd: string;
|
||||
readonly hook_event_name: "UserPromptSubmit";
|
||||
readonly model?: string;
|
||||
readonly permission_mode?: string;
|
||||
readonly prompt: string;
|
||||
readonly session_id: string;
|
||||
readonly transcript_path?: string;
|
||||
readonly turn_id?: string;
|
||||
}
|
||||
|
||||
export interface PreToolUsePayload {
|
||||
readonly cwd: string;
|
||||
readonly hook_event_name: "PreToolUse";
|
||||
readonly model: string;
|
||||
readonly permission_mode: string;
|
||||
readonly session_id: string;
|
||||
readonly tool_input: unknown;
|
||||
readonly tool_name: string;
|
||||
readonly tool_use_id: string;
|
||||
readonly transcript_path: string | null;
|
||||
readonly turn_id: string;
|
||||
}
|
||||
|
||||
interface PreToolUseHookOutput {
|
||||
readonly hookSpecificOutput: {
|
||||
readonly hookEventName: "PreToolUse";
|
||||
readonly permissionDecision: "deny";
|
||||
readonly permissionDecisionReason: string;
|
||||
readonly additionalContext: string;
|
||||
};
|
||||
}
|
||||
|
||||
const CREATE_GOAL_TOOL_NAME = "create_goal";
|
||||
const GOAL_BUDGET_WARNING =
|
||||
"Do not set token_budget on create_goal. Omit the budget field so the goal stays unlimited; ultrawork and ulw-loop runs must always use unlimited goals.";
|
||||
|
||||
export function parseUserPromptSubmitPayload(raw: string): UserPromptSubmitPayload | null {
|
||||
if (raw.trim().length === 0) return null;
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
return isUserPromptSubmitPayload(parsed) ? parsed : null;
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) return null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePreToolUsePayload(raw: string): PreToolUsePayload | null {
|
||||
if (raw.trim().length === 0) return null;
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
return isPreToolUsePayload(parsed) ? parsed : null;
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) return null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function applyUserPromptUlwLoopSteering(payload: UserPromptSubmitPayload): Promise<string> {
|
||||
try {
|
||||
if (payload.hook_event_name !== "UserPromptSubmit") return "";
|
||||
const proposal = parseUlwLoopSteeringDirective(payload.prompt);
|
||||
if (proposal === null) return "";
|
||||
const result = await steerUlwLoop(payload.cwd, proposal, payloadScope(payload));
|
||||
if (!result.accepted) return "";
|
||||
return JSON.stringify({
|
||||
status: "accepted",
|
||||
kind: result.audit.kind,
|
||||
source: result.audit.source,
|
||||
deduped: result.deduped,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error) return "";
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function payloadScope(payload: UserPromptSubmitPayload): UlwLoopScope {
|
||||
return { sessionId: payload.session_id };
|
||||
}
|
||||
|
||||
export function applyPreToolUseGoalBudgetGuard(payload: PreToolUsePayload): string {
|
||||
if (payload.hook_event_name !== "PreToolUse") return "";
|
||||
if (payload.tool_name !== CREATE_GOAL_TOOL_NAME) return "";
|
||||
if (!hasGoalBudgetInput(payload.tool_input)) return "";
|
||||
const output: PreToolUseHookOutput = {
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PreToolUse",
|
||||
permissionDecision: "deny",
|
||||
permissionDecisionReason: GOAL_BUDGET_WARNING,
|
||||
additionalContext: GOAL_BUDGET_WARNING,
|
||||
},
|
||||
};
|
||||
return `${JSON.stringify(output)}\n`;
|
||||
}
|
||||
|
||||
export async function runUlwLoopHookCli(stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream): Promise<void> {
|
||||
try {
|
||||
const payload = parseUserPromptSubmitPayload(await readAll(stdin));
|
||||
if (payload === null) return;
|
||||
const output = await applyUserPromptUlwLoopSteering(payload);
|
||||
if (output.length > 0) stdout.write(output);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export async function runPreToolUseGoalBudgetGuardCli(
|
||||
stdin: NodeJS.ReadableStream,
|
||||
stdout: NodeJS.WritableStream,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const payload = parsePreToolUsePayload(await readAll(stdin));
|
||||
if (payload === null) return;
|
||||
const output = applyPreToolUseGoalBudgetGuard(payload);
|
||||
if (output.length > 0) stdout.write(output);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function isUserPromptSubmitPayload(value: unknown): value is UserPromptSubmitPayload {
|
||||
if (!isRecord(value)) return false;
|
||||
return (
|
||||
value["hook_event_name"] === "UserPromptSubmit" &&
|
||||
typeof value["cwd"] === "string" &&
|
||||
typeof value["prompt"] === "string" &&
|
||||
typeof value["session_id"] === "string" &&
|
||||
["model", "permission_mode", "transcript_path", "turn_id"].every((key) => optionalString(value[key]))
|
||||
);
|
||||
}
|
||||
|
||||
function isPreToolUsePayload(value: unknown): value is PreToolUsePayload {
|
||||
if (!isRecord(value)) return false;
|
||||
return (
|
||||
value["hook_event_name"] === "PreToolUse" &&
|
||||
typeof value["cwd"] === "string" &&
|
||||
typeof value["model"] === "string" &&
|
||||
typeof value["permission_mode"] === "string" &&
|
||||
typeof value["session_id"] === "string" &&
|
||||
typeof value["tool_name"] === "string" &&
|
||||
typeof value["tool_use_id"] === "string" &&
|
||||
(value["transcript_path"] === null || typeof value["transcript_path"] === "string") &&
|
||||
typeof value["turn_id"] === "string" &&
|
||||
Object.hasOwn(value, "tool_input")
|
||||
);
|
||||
}
|
||||
|
||||
function hasGoalBudgetInput(value: unknown): boolean {
|
||||
return isRecord(value) && (Object.hasOwn(value, "token_budget") || Object.hasOwn(value, "tokenBudget"));
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function optionalString(value: unknown): boolean {
|
||||
return value === undefined || typeof value === "string";
|
||||
}
|
||||
|
||||
function readAll(stdin: NodeJS.ReadableStream): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let data = "";
|
||||
stdin.setEncoding("utf8");
|
||||
stdin.on("data", (chunk: unknown) => {
|
||||
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
|
||||
});
|
||||
stdin.once("error", reject);
|
||||
stdin.once("end", () => resolve(data));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user