



























@@ -86,7 +86,14 @@ export class CodexAppServerEventProjector {
86868787async handleNotification(notification: CodexServerNotification): Promise<void> {
8888const params = isJsonObject(notification.params) ? notification.params : undefined;
89-if (!params || !this.isNotificationForTurn(params)) {
89+if (!params) {
90+return;
91+}
92+if (isHookNotificationMethod(notification.method)) {
93+if (!this.isHookNotificationForCurrentThread(params)) {
94+return;
95+}
96+} else if (!this.isNotificationForTurn(params)) {
9097return;
9198}
9299@@ -120,6 +127,10 @@ export class CodexAppServerEventProjector {
120127case "item/autoApprovalReview/completed":
121128this.handleGuardianReviewNotification(notification.method, params);
122129break;
130+case "hook/started":
131+case "hook/completed":
132+this.handleHookNotification(notification.method, params);
133+break;
123134case "thread/tokenUsage/updated":
124135this.handleTokenUsage(params);
125136break;
@@ -413,6 +424,35 @@ export class CodexAppServerEventProjector {
413424});
414425}
415426427+private handleHookNotification(method: string, params: JsonObject): void {
428+const run = isJsonObject(params.run) ? params.run : undefined;
429+if (!run) {
430+return;
431+}
432+const durationMs = readNumber(run, "durationMs");
433+const entries = readHookOutputEntries(run.entries);
434+const hookTurnId = readNullableString(params, "turnId");
435+this.emitAgentEvent({
436+stream: "codex_app_server.hook",
437+data: {
438+phase: method === "hook/started" ? "started" : "completed",
439+threadId: this.threadId,
440+turnId: hookTurnId === undefined ? this.turnId : hookTurnId,
441+hookRunId: readString(run, "id"),
442+eventName: readString(run, "eventName"),
443+handlerType: readString(run, "handlerType"),
444+executionMode: readString(run, "executionMode"),
445+scope: readString(run, "scope"),
446+source: readString(run, "source"),
447+sourcePath: readString(run, "sourcePath"),
448+status: readString(run, "status"),
449+statusMessage: readNullableString(run, "statusMessage"),
450+ ...(durationMs !== undefined ? { durationMs } : {}),
451+ ...(entries.length > 0 ? { entries } : {}),
452+},
453+});
454+}
455+416456private async handleTurnCompleted(params: JsonObject): Promise<void> {
417457const turn = readTurn(params.turn);
418458if (!turn || turn.id !== this.turnId) {
@@ -690,6 +730,16 @@ export class CodexAppServerEventProjector {
690730const turnId = readNotificationTurnId(params);
691731return threadId === this.threadId && turnId === this.turnId;
692732}
733+734+private isHookNotificationForCurrentThread(params: JsonObject): boolean {
735+const threadId = readString(params, "threadId");
736+const turnId = params.turnId;
737+return threadId === this.threadId && (turnId === this.turnId || turnId === null);
738+}
739+}
740+741+function isHookNotificationMethod(method: string): method is "hook/started" | "hook/completed" {
742+return method === "hook/started" || method === "hook/completed";
693743}
694744695745function readNotificationTurnId(record: JsonObject): string | undefined {
@@ -719,6 +769,25 @@ function readNumber(record: JsonObject, key: string): number | undefined {
719769return typeof value === "number" && Number.isFinite(value) ? value : undefined;
720770}
721771772+function readHookOutputEntries(
773+value: JsonValue | undefined,
774+): Array<{ kind?: string; text: string }> {
775+if (!Array.isArray(value)) {
776+return [];
777+}
778+return value.flatMap((entry) => {
779+if (!isJsonObject(entry)) {
780+return [];
781+}
782+const text = readString(entry, "text");
783+if (!text) {
784+return [];
785+}
786+const kind = readString(entry, "kind");
787+return [{ ...(kind ? { kind } : {}), text }];
788+});
789+}
790+722791function readFirstJsonObject(record: JsonObject, keys: readonly string[]): JsonObject | undefined {
723792for (const key of keys) {
724793const value = record[key];
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。