@syengup/friday-channel-next 1.0.1 → 1.0.3

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.
@@ -343,6 +343,17 @@ export function forwardAgentEventRaw(evt) {
343
343
  });
344
344
  }
345
345
  }
346
+ // Codex app-server projects every tool/command call onto BOTH the standard `tool` stream
347
+ // (carrying args + the real result) AND a redundant `item` event (kind:"tool"/"command"),
348
+ // and core flags that item `suppressChannelProgress: true` ("do not surface in channel
349
+ // progress"). Forwarding the suppressed item anyway double-renders every non-exec tool in
350
+ // the app — the `tool`-stream row plus a second `item kind:tool` row, with the result landing
351
+ // only on the first. Honor the flag and drop suppressed items; the `tool` stream (and, for
352
+ // exec, the synthesized `command_output`) already carries everything the app renders. Codex
353
+ // reasoning items (preamble/analysis) are NOT suppressed, so this never touches thinking.
354
+ if (evt.stream === "item" && evt.data.suppressChannelProgress === true) {
355
+ return;
356
+ }
346
357
  // Register sessionKey → runId so we can resolve parentRunId
347
358
  if (sk && evt.stream === "lifecycle" && evt.data.phase === "start") {
348
359
  registerSessionKeyForRun(sk, evt.runId);
@@ -467,6 +467,16 @@ export async function handleMessages(req, res) {
467
467
  runId,
468
468
  suppressTyping: true,
469
469
  disableBlockStreaming: true,
470
+ // friday-next is a direct device channel: the final assistant reply auto-delivers
471
+ // to the app live over SSE (sendText), and the channel already declares ChatType
472
+ // "direct" + outbound.deliveryMode "direct". But OpenClaw core's source-reply policy
473
+ // (resolveSourceReplyDeliveryMode) can still resolve `message_tool_only` for this
474
+ // channel from its own defaults — when it does, the agent prompt tells the model
475
+ // "visible replies are NOT auto-delivered; use message(action=send) for everything",
476
+ // which makes Codex route its whole answer through the `message` tool (and that tool
477
+ // crashes on friday-next). Pin `automatic` so core honors the channel's own direct
478
+ // delivery and never instructs the model to deliver via the message tool.
479
+ sourceReplyDeliveryMode: "automatic",
470
480
  // A1: feed the chosen thinking level into the run as a one-shot override so the model
471
481
  // request asks for a reasoning summary. The session-stored `thinkingLevel` alone is NOT
472
482
  // honored by the reply dispatch; `thinkingLevelOverride` has top priority in OpenClaw's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syengup/friday-channel-next",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "OpenClaw Friday Next Apple channel plugin",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -175,6 +175,37 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
175
175
  expect(thinking[1].data.reasoningPrefixChars).toBe(2);
176
176
  });
177
177
 
178
+ it("drops item events flagged suppressChannelProgress (Codex tool/command duplicates)", () => {
179
+ // Codex app-server projects every tool/command onto both the `tool` stream and a redundant
180
+ // `item` event flagged suppressChannelProgress:true. Forwarding the item double-renders the
181
+ // tool in the app. We honor the flag and drop it.
182
+ forwardAgentEventRaw({
183
+ runId,
184
+ seq: 1,
185
+ stream: "item",
186
+ sessionKey,
187
+ data: {
188
+ itemId: "call_abc",
189
+ kind: "tool",
190
+ phase: "start",
191
+ name: "web_search",
192
+ suppressChannelProgress: true,
193
+ },
194
+ });
195
+ expect(sseEmitter.broadcastToRun).not.toHaveBeenCalled();
196
+ });
197
+
198
+ it("forwards item events that are not suppressed (e.g. reasoning analysis markers)", () => {
199
+ forwardAgentEventRaw({
200
+ runId,
201
+ seq: 1,
202
+ stream: "item",
203
+ sessionKey,
204
+ data: { itemId: "rs_1", kind: "analysis", phase: "start", title: "Reasoning" },
205
+ });
206
+ expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(1);
207
+ });
208
+
178
209
  it("does not translate preamble items from a non-Codex source", () => {
179
210
  forwardAgentEventRaw({
180
211
  runId,
@@ -398,6 +398,18 @@ export function forwardAgentEventRaw(evt: ForwardAgentEventArgs): void {
398
398
  }
399
399
  }
400
400
 
401
+ // Codex app-server projects every tool/command call onto BOTH the standard `tool` stream
402
+ // (carrying args + the real result) AND a redundant `item` event (kind:"tool"/"command"),
403
+ // and core flags that item `suppressChannelProgress: true` ("do not surface in channel
404
+ // progress"). Forwarding the suppressed item anyway double-renders every non-exec tool in
405
+ // the app — the `tool`-stream row plus a second `item kind:tool` row, with the result landing
406
+ // only on the first. Honor the flag and drop suppressed items; the `tool` stream (and, for
407
+ // exec, the synthesized `command_output`) already carries everything the app renders. Codex
408
+ // reasoning items (preamble/analysis) are NOT suppressed, so this never touches thinking.
409
+ if (evt.stream === "item" && evt.data.suppressChannelProgress === true) {
410
+ return;
411
+ }
412
+
401
413
  // Register sessionKey → runId so we can resolve parentRunId
402
414
  if (sk && evt.stream === "lifecycle" && evt.data.phase === "start") {
403
415
  registerSessionKeyForRun(sk, evt.runId);
@@ -635,6 +635,16 @@ export async function handleMessages(req: IncomingMessage, res: ServerResponse):
635
635
  runId,
636
636
  suppressTyping: true,
637
637
  disableBlockStreaming: true,
638
+ // friday-next is a direct device channel: the final assistant reply auto-delivers
639
+ // to the app live over SSE (sendText), and the channel already declares ChatType
640
+ // "direct" + outbound.deliveryMode "direct". But OpenClaw core's source-reply policy
641
+ // (resolveSourceReplyDeliveryMode) can still resolve `message_tool_only` for this
642
+ // channel from its own defaults — when it does, the agent prompt tells the model
643
+ // "visible replies are NOT auto-delivered; use message(action=send) for everything",
644
+ // which makes Codex route its whole answer through the `message` tool (and that tool
645
+ // crashes on friday-next). Pin `automatic` so core honors the channel's own direct
646
+ // delivery and never instructs the model to deliver via the message tool.
647
+ sourceReplyDeliveryMode: "automatic",
638
648
  // A1: feed the chosen thinking level into the run as a one-shot override so the model
639
649
  // request asks for a reasoning summary. The session-stored `thinkingLevel` alone is NOT
640
650
  // honored by the reply dispatch; `thinkingLevelOverride` has top priority in OpenClaw's