166 lines
5.8 KiB
JavaScript
166 lines
5.8 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
import crypto from "node:crypto";
|
||
|
|
import fs from "node:fs";
|
||
|
|
import os from "node:os";
|
||
|
|
import path from "node:path";
|
||
|
|
|
||
|
|
function output(additionalContext = null) {
|
||
|
|
process.stdout.write(JSON.stringify(additionalContext ? {
|
||
|
|
hookSpecificOutput: {
|
||
|
|
hookEventName: "UserPromptSubmit",
|
||
|
|
additionalContext,
|
||
|
|
},
|
||
|
|
} : { continue: true }));
|
||
|
|
}
|
||
|
|
|
||
|
|
function atomicWrite(file, value) {
|
||
|
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||
|
|
const temporary = `${file}.${process.pid}.tmp`;
|
||
|
|
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
||
|
|
encoding: "utf8",
|
||
|
|
mode: 0o600,
|
||
|
|
});
|
||
|
|
fs.renameSync(temporary, file);
|
||
|
|
}
|
||
|
|
|
||
|
|
function safeToken(value, fallback) {
|
||
|
|
const token = String(value ?? "").trim().replaceAll(/[^a-zA-Z0-9._-]/gu, "-");
|
||
|
|
return token || fallback;
|
||
|
|
}
|
||
|
|
|
||
|
|
function classifySource(prompt) {
|
||
|
|
const value = String(prompt ?? "").trim();
|
||
|
|
if (/^<codex_delegation>[\s\S]*<\/codex_delegation>$/u.test(value)) {
|
||
|
|
return ["CODEX_THREAD_DELEGATION", false];
|
||
|
|
}
|
||
|
|
if (/^<(?:subagent_notification|agent_notification|automation_event)>[\s\S]*<\/(?:subagent_notification|agent_notification|automation_event)>$/u.test(value)) {
|
||
|
|
return ["SYSTEM_OR_AGENT_DELIVERY", false];
|
||
|
|
}
|
||
|
|
return ["DIRECT_HUMAN_NATURAL_LANGUAGE", true];
|
||
|
|
}
|
||
|
|
|
||
|
|
let input = {};
|
||
|
|
try {
|
||
|
|
const raw = fs.readFileSync(0, "utf8").trim();
|
||
|
|
if (raw) input = JSON.parse(raw);
|
||
|
|
} catch {
|
||
|
|
input = {};
|
||
|
|
}
|
||
|
|
if ((input.hook_event_name ?? input.hookEventName) !== "UserPromptSubmit") {
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
const prompt = typeof input.prompt === "string" ? input.prompt : "";
|
||
|
|
if (!prompt.trim()) {
|
||
|
|
output("GUANGHU_SOURCE_ENVELOPE state=SOURCE_MISSING; execution_authority=NONE");
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
const codexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
|
||
|
|
const stateRoot = process.env.GH_CODEX_BRIDGE_STATE_ROOT ??
|
||
|
|
path.join(codexHome, "runtime", "guanghu-codex-host-bridge", "state");
|
||
|
|
const controlRoot = process.env.GH_CODEX_CONTROL_ROOT ?? path.join(stateRoot, "control");
|
||
|
|
const subjectId = process.env.GH_HUMAN_SOURCE_ID ?? "HUMAN-LOCAL";
|
||
|
|
const subjectName = process.env.GH_HUMAN_SOURCE_NAME ?? "LOCAL_HUMAN";
|
||
|
|
const sourceRole = process.env.GH_HUMAN_SOURCE_ROLE ?? "HUMAN_HOST_ANCHOR";
|
||
|
|
const carrierRole = process.env.GH_CODEX_CARRIER_ROLE ?? "CODEX_EXECUTION_CARRIER";
|
||
|
|
const memoryAnchor = process.env.GH_HUMAN_MEMORY_ANCHOR ?? "CURRENT_DIRECT_HUMAN_MESSAGE";
|
||
|
|
const sessionId = safeToken(
|
||
|
|
input.session_id ?? input.sessionId ?? input.thread_id ?? input.threadId,
|
||
|
|
"unknown-session",
|
||
|
|
);
|
||
|
|
const turnId = safeToken(input.turn_id ?? input.turnId, crypto.randomUUID());
|
||
|
|
const [sourceKind, directHuman] = classifySource(prompt);
|
||
|
|
const occurredAtUnixMs = Date.now();
|
||
|
|
const occurredAt = new Date(occurredAtUnixMs).toISOString();
|
||
|
|
const rawTextSha256 = crypto.createHash("sha256").update(prompt).digest("hex");
|
||
|
|
|
||
|
|
if (!directHuman) {
|
||
|
|
const deliveryRoot = path.join(stateRoot, "sessions", sessionId, "delivery-events");
|
||
|
|
const eventPath = path.join(deliveryRoot, `event-${turnId}-${rawTextSha256.slice(0, 12)}.json`);
|
||
|
|
atomicWrite(eventPath, {
|
||
|
|
schema: "guanghu.codex-nonhuman-delivery-event/v1",
|
||
|
|
source_kind: sourceKind,
|
||
|
|
direct_human_natural_language: false,
|
||
|
|
may_claim_current_controller: false,
|
||
|
|
raw_text_sha256: rawTextSha256,
|
||
|
|
raw_text: prompt,
|
||
|
|
occurred_at: occurredAt,
|
||
|
|
});
|
||
|
|
atomicWrite(path.join(deliveryRoot, "current.json"), {
|
||
|
|
schema: "guanghu.codex-nonhuman-delivery-current/v1",
|
||
|
|
source_kind: sourceKind,
|
||
|
|
last_event_path: eventPath,
|
||
|
|
updated_at: occurredAt,
|
||
|
|
});
|
||
|
|
output([
|
||
|
|
"GUANGHU_MESSAGE_SOURCE_ENVELOPE v1",
|
||
|
|
`source=[CODEX|${sourceKind}|NOT_DIRECT_HUMAN_SPEECH]`,
|
||
|
|
"controller_effect=NONE",
|
||
|
|
"execution_authority=NONE",
|
||
|
|
`raw_text_sha256=${rawTextSha256}`,
|
||
|
|
].join("\n"));
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
const sourceRoot = path.join(stateRoot, "sessions", sessionId, "source-events");
|
||
|
|
const sourceCurrentPath = path.join(sourceRoot, "current.json");
|
||
|
|
let previous = null;
|
||
|
|
try {
|
||
|
|
previous = JSON.parse(fs.readFileSync(sourceCurrentPath, "utf8"));
|
||
|
|
} catch {
|
||
|
|
previous = null;
|
||
|
|
}
|
||
|
|
const controlEpoch = crypto.randomUUID();
|
||
|
|
const eventNumber = `${subjectId}/CODEX-${sessionId}/EVENT-${turnId}-${rawTextSha256.slice(0, 12)}`;
|
||
|
|
const eventPath = path.join(sourceRoot, `event-${turnId}-${rawTextSha256.slice(0, 12)}.json`);
|
||
|
|
atomicWrite(eventPath, {
|
||
|
|
schema: "guanghu.numbered-direct-human-input/v1",
|
||
|
|
event_number: eventNumber,
|
||
|
|
parent_event_number: previous?.last_event_number ?? null,
|
||
|
|
subject_id: subjectId,
|
||
|
|
subject_name: subjectName,
|
||
|
|
source_role: sourceRole,
|
||
|
|
source_kind: sourceKind,
|
||
|
|
carrier_role: carrierRole,
|
||
|
|
occurred_at: occurredAt,
|
||
|
|
raw_text_sha256: rawTextSha256,
|
||
|
|
raw_text: prompt,
|
||
|
|
});
|
||
|
|
atomicWrite(sourceCurrentPath, {
|
||
|
|
schema: "guanghu.numbered-direct-human-input-current/v1",
|
||
|
|
last_event_number: eventNumber,
|
||
|
|
last_event_path: eventPath,
|
||
|
|
updated_at: occurredAt,
|
||
|
|
});
|
||
|
|
atomicWrite(path.join(controlRoot, "current-controller.json"), {
|
||
|
|
schema: "guanghu.codex-current-controller/v1",
|
||
|
|
control_epoch: controlEpoch,
|
||
|
|
session_id: sessionId,
|
||
|
|
turn_id: turnId,
|
||
|
|
source_kind: sourceKind,
|
||
|
|
source_subject_number: subjectId,
|
||
|
|
direct_human_natural_language: true,
|
||
|
|
numbered_event_number: eventNumber,
|
||
|
|
numbered_event_path: eventPath,
|
||
|
|
raw_text_sha256: rawTextSha256,
|
||
|
|
claimed_at: occurredAt,
|
||
|
|
});
|
||
|
|
try {
|
||
|
|
fs.unlinkSync(path.join(controlRoot, "write-lease.json"));
|
||
|
|
} catch (error) {
|
||
|
|
if (error?.code !== "ENOENT") throw error;
|
||
|
|
}
|
||
|
|
output([
|
||
|
|
"GUANGHU_HUMAN_SOURCE_ENVELOPE v1",
|
||
|
|
`source=[${subjectName}|${subjectId}|DIRECT_HUMAN_NATURAL_LANGUAGE]`,
|
||
|
|
`carrier_role=${carrierRole}`,
|
||
|
|
`memory_anchor=${memoryAnchor}`,
|
||
|
|
"original_message=UNCHANGED_AND_NOT_COPIED_IN_THIS_ENVELOPE",
|
||
|
|
"effect=SOURCE_ROUTING_AND_CONTROLLER_EPOCH_NOT_PERSONA_BINDING_OR_EXECUTION_AUTHORITY",
|
||
|
|
`event=${eventNumber}`,
|
||
|
|
`controller_session=${sessionId}`,
|
||
|
|
`controller_turn=${turnId}`,
|
||
|
|
`controller_epoch=${controlEpoch}`,
|
||
|
|
`raw_text_sha256=${rawTextSha256}`,
|
||
|
|
].join("\n"));
|