@syengup/friday-channel-next 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -321,6 +321,28 @@ export function forwardAgentEventRaw(evt) {
321
321
  if (typeof evt.stream === "string" && evt.stream.startsWith("codex_app_server")) {
322
322
  codexRunIds.add(evt.runId);
323
323
  }
324
+ // Codex app-server reasoning: newer OpenClaw cores stopped invoking the dispatch
325
+ // `onReasoningStream` callback (the A2 path in messages.ts) and instead stream the
326
+ // reasoning summary on the agent-event bus as `stream:"item" kind:"preamble"` with a
327
+ // cumulative `progressText` (source "codex-app-server"). The Friday app only renders
328
+ // `stream:"thinking"`, so translate it here — synthesize a thinking event reusing the
329
+ // cumulative→delta rewrite below. The raw preamble item is still forwarded but the app
330
+ // ignores unknown item kinds. (The onReasoningStream callback stays as a harmless
331
+ // fallback for cores that still fire it.)
332
+ if (evt.stream === "item" &&
333
+ evt.data.kind === "preamble" &&
334
+ evt.data.source === "codex-app-server") {
335
+ codexRunIds.add(evt.runId);
336
+ const reasoningText = typeof evt.data.progressText === "string" ? evt.data.progressText : "";
337
+ if (reasoningText) {
338
+ forwardAgentEventRaw({
339
+ runId: evt.runId,
340
+ stream: "thinking",
341
+ data: { text: reasoningText },
342
+ sessionKey: evt.sessionKey ?? sk,
343
+ });
344
+ }
345
+ }
324
346
  // Register sessionKey → runId so we can resolve parentRunId
325
347
  if (sk && evt.stream === "lifecycle" && evt.data.phase === "start") {
326
348
  registerSessionKeyForRun(sk, evt.runId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syengup/friday-channel-next",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "OpenClaw Friday Next Apple channel plugin",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -139,6 +139,56 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
139
139
  expect(third.delta).toBe(t1);
140
140
  });
141
141
 
142
+ it("translates a Codex preamble item (progressText) into streamed thinking deltas", () => {
143
+ // Newer OpenClaw cores stopped invoking dispatch onReasoningStream for Codex and instead
144
+ // stream the reasoning summary on the agent-event bus as item/preamble with cumulative
145
+ // progressText. We translate it back into stream:"thinking" so the app renders it.
146
+ forwardAgentEventRaw({
147
+ runId,
148
+ seq: 1,
149
+ stream: "item",
150
+ sessionKey,
151
+ data: { kind: "preamble", source: "codex-app-server", phase: "update", progressText: "先把" },
152
+ });
153
+ forwardAgentEventRaw({
154
+ runId,
155
+ seq: 2,
156
+ stream: "item",
157
+ sessionKey,
158
+ data: {
159
+ kind: "preamble",
160
+ source: "codex-app-server",
161
+ phase: "update",
162
+ progressText: "先把标准",
163
+ },
164
+ });
165
+
166
+ const thinking = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls
167
+ .map((c) => c[1].data)
168
+ .filter((d: { stream?: string }) => d.stream === "thinking");
169
+ expect(thinking).toHaveLength(2);
170
+ expect(thinking[0].data.text).toBe("先把");
171
+ expect(thinking[0].data.delta).toBe("先把");
172
+ expect(thinking[0].data.reasoningPrefixChars).toBe(0);
173
+ expect(thinking[1].data.text).toBe("先把标准");
174
+ expect(thinking[1].data.delta).toBe("标准");
175
+ expect(thinking[1].data.reasoningPrefixChars).toBe(2);
176
+ });
177
+
178
+ it("does not translate preamble items from a non-Codex source", () => {
179
+ forwardAgentEventRaw({
180
+ runId,
181
+ seq: 1,
182
+ stream: "item",
183
+ sessionKey,
184
+ data: { kind: "preamble", source: "something-else", progressText: "x" },
185
+ });
186
+ const thinking = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls
187
+ .map((c) => c[1].data)
188
+ .filter((d: { stream?: string }) => d.stream === "thinking");
189
+ expect(thinking).toHaveLength(0);
190
+ });
191
+
142
192
  it("merges run metadata into lifecycle.end (model, tokens, context usage)", () => {
143
193
  forwardAgentEventRaw({
144
194
  runId,
@@ -373,6 +373,31 @@ export function forwardAgentEventRaw(evt: ForwardAgentEventArgs): void {
373
373
  codexRunIds.add(evt.runId);
374
374
  }
375
375
 
376
+ // Codex app-server reasoning: newer OpenClaw cores stopped invoking the dispatch
377
+ // `onReasoningStream` callback (the A2 path in messages.ts) and instead stream the
378
+ // reasoning summary on the agent-event bus as `stream:"item" kind:"preamble"` with a
379
+ // cumulative `progressText` (source "codex-app-server"). The Friday app only renders
380
+ // `stream:"thinking"`, so translate it here — synthesize a thinking event reusing the
381
+ // cumulative→delta rewrite below. The raw preamble item is still forwarded but the app
382
+ // ignores unknown item kinds. (The onReasoningStream callback stays as a harmless
383
+ // fallback for cores that still fire it.)
384
+ if (
385
+ evt.stream === "item" &&
386
+ evt.data.kind === "preamble" &&
387
+ evt.data.source === "codex-app-server"
388
+ ) {
389
+ codexRunIds.add(evt.runId);
390
+ const reasoningText = typeof evt.data.progressText === "string" ? evt.data.progressText : "";
391
+ if (reasoningText) {
392
+ forwardAgentEventRaw({
393
+ runId: evt.runId,
394
+ stream: "thinking",
395
+ data: { text: reasoningText },
396
+ sessionKey: evt.sessionKey ?? sk,
397
+ });
398
+ }
399
+ }
400
+
376
401
  // Register sessionKey → runId so we can resolve parentRunId
377
402
  if (sk && evt.stream === "lifecycle" && evt.data.phase === "start") {
378
403
  registerSessionKeyForRun(sk, evt.runId);
@@ -469,8 +494,7 @@ export function forwardAgentEventRaw(evt: ForwardAgentEventArgs): void {
469
494
  if (evt.stream === "lifecycle" && evt.data.phase === "start") {
470
495
  const announced = parseAnnounceRunId(evt.runId);
471
496
  if (announced) {
472
- const entry =
473
- lookupByChildSessionKey(announced.childSessionKey) ?? lookupByRunId(evt.runId);
497
+ const entry = lookupByChildSessionKey(announced.childSessionKey) ?? lookupByRunId(evt.runId);
474
498
  sseEmitter.broadcast(
475
499
  {
476
500
  type: "subagent",
@@ -1,13 +0,0 @@
1
- /**
2
- * Bearer token authentication middleware for Friday HTTP routes.
3
- *
4
- * Validates that the bearer token matches the gateway's configured auth token.
5
- * This ensures plugin HTTP endpoints use the same token as gateway WS connections.
6
- */
7
- import type { IncomingMessage } from "node:http";
8
- /**
9
- * Extract and validate bearer token from Authorization header.
10
- * Returns the token only if it matches the gateway's configured auth token.
11
- * Returns null if token is missing, malformed, or doesn't match.
12
- */
13
- export declare function extractBearerToken(req: IncomingMessage): string | null;
@@ -1,29 +0,0 @@
1
- /**
2
- * Bearer token authentication middleware for Friday HTTP routes.
3
- *
4
- * Validates that the bearer token matches the gateway's configured auth token.
5
- * This ensures plugin HTTP endpoints use the same token as gateway WS connections.
6
- */
7
- import { resolveFridayNextConfig } from "../../config.js";
8
- import { getHostOpenClawConfigSnapshot } from "../../host-config.js";
9
- import { getFridayNextRuntime } from "../../runtime.js";
10
- /**
11
- * Extract and validate bearer token from Authorization header.
12
- * Returns the token only if it matches the gateway's configured auth token.
13
- * Returns null if token is missing, malformed, or doesn't match.
14
- */
15
- export function extractBearerToken(req) {
16
- const auth = req.headers.authorization;
17
- if (!auth || typeof auth !== "string")
18
- return null;
19
- const parts = auth.trim().split(/\s+/);
20
- if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer")
21
- return null;
22
- const token = parts[1];
23
- // Validate token matches the gateway's configured auth token.
24
- const cfg = getHostOpenClawConfigSnapshot(getFridayNextRuntime().config);
25
- const runtimeConfig = resolveFridayNextConfig(cfg);
26
- if (!runtimeConfig.authToken || token !== runtimeConfig.authToken)
27
- return null;
28
- return token;
29
- }
@@ -1,2 +0,0 @@
1
- import type { IncomingMessage } from "node:http";
2
- export declare function readJsonBody(req: IncomingMessage, maxBytes?: number): Promise<Record<string, unknown> | null>;
@@ -1,24 +0,0 @@
1
- export async function readJsonBody(req, maxBytes = 2 * 1024 * 1024) {
2
- return await new Promise((resolve) => {
3
- const chunks = [];
4
- let total = 0;
5
- req.on("data", (chunk) => {
6
- total += chunk.length;
7
- if (total > maxBytes) {
8
- resolve(null);
9
- req.destroy();
10
- return;
11
- }
12
- chunks.push(chunk);
13
- });
14
- req.on("end", () => {
15
- try {
16
- resolve(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
17
- }
18
- catch {
19
- resolve(null);
20
- }
21
- });
22
- req.on("error", () => resolve(null));
23
- });
24
- }
@@ -1,2 +0,0 @@
1
- import type { ServerResponse } from "node:http";
2
- export declare function applyCorsHeaders(res: ServerResponse): void;
@@ -1,11 +0,0 @@
1
- import { resolveFridayNextConfig } from "../../config.js";
2
- import { getHostOpenClawConfigSnapshot } from "../../host-config.js";
3
- import { getFridayNextRuntime } from "../../runtime.js";
4
- export function applyCorsHeaders(res) {
5
- const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(getFridayNextRuntime().config));
6
- if (!cfg.corsEnabled)
7
- return;
8
- res.setHeader("Access-Control-Allow-Origin", cfg.corsAllowOrigin || "*");
9
- res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Last-Event-ID");
10
- res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
11
- }