@rubytech/create-maxy-code 0.1.443 → 0.1.444

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.
Files changed (59) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  3. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  4. package/payload/platform/plugins/admin/mcp/dist/index.js +40 -24
  5. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  6. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +7 -7
  7. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -1
  8. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +7 -7
  9. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -1
  10. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +8 -4
  11. package/payload/platform/plugins/docs/references/admin-session.md +2 -0
  12. package/payload/platform/plugins/docs/references/admin-ui.md +4 -0
  13. package/payload/platform/plugins/docs/references/cross-account-authority.md +3 -3
  14. package/payload/platform/plugins/outlook/references/auth.md +8 -0
  15. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +3 -0
  16. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-attachment-suffix.test.d.ts +2 -0
  17. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-attachment-suffix.test.d.ts.map +1 -0
  18. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-attachment-suffix.test.js +44 -0
  19. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-attachment-suffix.test.js.map +1 -0
  20. package/payload/platform/plugins/whatsapp/mcp/dist/format.d.ts +7 -0
  21. package/payload/platform/plugins/whatsapp/mcp/dist/format.d.ts.map +1 -1
  22. package/payload/platform/plugins/whatsapp/mcp/dist/format.js +27 -0
  23. package/payload/platform/plugins/whatsapp/mcp/dist/format.js.map +1 -1
  24. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +6 -19
  25. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  26. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
  27. package/payload/platform/plugins/work/mcp/dist/tools/work-create.d.ts.map +1 -1
  28. package/payload/platform/plugins/work/mcp/dist/tools/work-create.js +10 -7
  29. package/payload/platform/plugins/work/mcp/dist/tools/work-create.js.map +1 -1
  30. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +1 -0
  31. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  32. package/payload/platform/services/claude-session-manager/dist/http-server.js +99 -8
  33. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  34. package/payload/platform/services/claude-session-manager/dist/index.js +0 -13
  35. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  36. package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts +8 -0
  37. package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts.map +1 -1
  38. package/payload/platform/services/claude-session-manager/dist/jsonl-path.js +11 -0
  39. package/payload/platform/services/claude-session-manager/dist/jsonl-path.js.map +1 -1
  40. package/payload/platform/services/claude-session-manager/dist/passive-admin-assignment.d.ts +20 -0
  41. package/payload/platform/services/claude-session-manager/dist/passive-admin-assignment.d.ts.map +1 -0
  42. package/payload/platform/services/claude-session-manager/dist/passive-admin-assignment.js +66 -0
  43. package/payload/platform/services/claude-session-manager/dist/passive-admin-assignment.js.map +1 -0
  44. package/payload/platform/services/claude-session-manager/dist/passive-spawn-tools-tail.d.ts +19 -0
  45. package/payload/platform/services/claude-session-manager/dist/passive-spawn-tools-tail.d.ts.map +1 -0
  46. package/payload/platform/services/claude-session-manager/dist/passive-spawn-tools-tail.js +50 -0
  47. package/payload/platform/services/claude-session-manager/dist/passive-spawn-tools-tail.js.map +1 -0
  48. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  49. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +14 -1
  50. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  51. package/payload/server/public/assets/{chat-DhD-NES_.js → chat-Dq-X-ia8.js} +1 -1
  52. package/payload/server/public/assets/{operator-zcIfeB14.js → operator-88m0DMl2.js} +1 -1
  53. package/payload/server/public/assets/page-BRfxFXyi.js +32 -0
  54. package/payload/server/public/assets/{public-C0ehZCdE.js → public-BX2MjsFY.js} +1 -1
  55. package/payload/server/public/chat.html +2 -2
  56. package/payload/server/public/operator.html +2 -2
  57. package/payload/server/public/public.html +2 -2
  58. package/payload/server/server.js +65 -13
  59. package/payload/server/public/assets/page-6fCnIb45.js +0 -32
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Chat — Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/chat-DhD-NES_.js"></script>
8
+ <script type="module" crossorigin src="/assets/chat-Dq-X-ia8.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-Ck5AOu5v.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/useSubAccountSwitcher-s6WlRn52.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/file-download-Px4s0Bhp.js">
@@ -17,7 +17,7 @@
17
17
  <link rel="modulepreload" crossorigin href="/assets/useCopyFeedback-ByGUMN94.js">
18
18
  <link rel="modulepreload" crossorigin href="/assets/chevron-left-B3xSeEDR.js">
19
19
  <link rel="modulepreload" crossorigin href="/assets/rotate-ccw-C7BqkMec.js">
20
- <link rel="modulepreload" crossorigin href="/assets/page-6fCnIb45.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/page-BRfxFXyi.js">
21
21
  <link rel="stylesheet" crossorigin href="/assets/useSubAccountSwitcher-1izQvPiD.css">
22
22
  <link rel="stylesheet" href="/brand-defaults.css">
23
23
  </head>
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Operator — Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/operator-zcIfeB14.js"></script>
8
+ <script type="module" crossorigin src="/assets/operator-88m0DMl2.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-Ck5AOu5v.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/useSubAccountSwitcher-s6WlRn52.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/file-download-Px4s0Bhp.js">
@@ -19,7 +19,7 @@
19
19
  <link rel="modulepreload" crossorigin href="/assets/useCopyFeedback-ByGUMN94.js">
20
20
  <link rel="modulepreload" crossorigin href="/assets/chevron-left-B3xSeEDR.js">
21
21
  <link rel="modulepreload" crossorigin href="/assets/rotate-ccw-C7BqkMec.js">
22
- <link rel="modulepreload" crossorigin href="/assets/page-6fCnIb45.js">
22
+ <link rel="modulepreload" crossorigin href="/assets/page-BRfxFXyi.js">
23
23
  <link rel="stylesheet" crossorigin href="/assets/useSubAccountSwitcher-1izQvPiD.css">
24
24
  <link rel="stylesheet" crossorigin href="/assets/AdminLoginScreens-CWMpccrR.css">
25
25
  <link rel="stylesheet" href="/brand-defaults.css">
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/public-C0ehZCdE.js"></script>
8
+ <script type="module" crossorigin src="/assets/public-BX2MjsFY.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-Ck5AOu5v.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/useSubAccountSwitcher-s6WlRn52.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/file-download-Px4s0Bhp.js">
@@ -17,7 +17,7 @@
17
17
  <link rel="modulepreload" crossorigin href="/assets/useCopyFeedback-ByGUMN94.js">
18
18
  <link rel="modulepreload" crossorigin href="/assets/chevron-left-B3xSeEDR.js">
19
19
  <link rel="modulepreload" crossorigin href="/assets/rotate-ccw-C7BqkMec.js">
20
- <link rel="modulepreload" crossorigin href="/assets/page-6fCnIb45.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/page-BRfxFXyi.js">
21
21
  <link rel="stylesheet" crossorigin href="/assets/useSubAccountSwitcher-1izQvPiD.css">
22
22
  <link rel="stylesheet" href="/brand-defaults.css">
23
23
  </head>
@@ -4179,8 +4179,9 @@ function resolveStoredAttachment(accountId, attachmentId) {
4179
4179
  return null;
4180
4180
  }
4181
4181
  if (!meta || typeof meta !== "object") return null;
4182
- const { filename, mimeType } = meta;
4182
+ const { filename, mimeType, sizeBytes } = meta;
4183
4183
  if (typeof filename !== "string" || typeof mimeType !== "string") return null;
4184
+ const resolvedSizeBytes = typeof sizeBytes === "number" ? sizeBytes : void 0;
4184
4185
  let entries;
4185
4186
  try {
4186
4187
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -4191,7 +4192,7 @@ function resolveStoredAttachment(accountId, attachmentId) {
4191
4192
  if (!dataFile) return null;
4192
4193
  const readableSibling = `${attachmentId}${EXTRACTED_TEXT_SUFFIX}`;
4193
4194
  const readableTextPath = entries.some((e) => e.isFile() && e.name === readableSibling) ? resolve6(dir, readableSibling) : void 0;
4194
- return { attachmentId, filename, mimeType, path: resolve6(dir, dataFile.name), readableTextPath };
4195
+ return { attachmentId, filename, mimeType, sizeBytes: resolvedSizeBytes, path: resolve6(dir, dataFile.name), readableTextPath };
4195
4196
  }
4196
4197
  async function writeAttachment(accountId, subPath, filename, mimeType, sizeBytes, buffer, explicitId) {
4197
4198
  const attachmentId = explicitId ?? randomUUID3();
@@ -7842,17 +7843,30 @@ function projectionFor(requestedAccountId) {
7842
7843
  houseAccountManagers
7843
7844
  });
7844
7845
  }
7845
- function callerScopeDenial(c, requestedAccountId) {
7846
+ function evaluateCallerScope(c, requestedAccountId) {
7846
7847
  const callerAccountId = c.req.header("x-maxy-caller-account")?.trim() || null;
7847
7848
  const houseAdminScope = c.req.header("x-maxy-house-admin-scope")?.trim() || null;
7848
7849
  const decision = authorizeRecallRead({ requestedAccountId, callerAccountId, houseAdminScope });
7850
+ return { decision, callerAccountId, houseAdminScope };
7851
+ }
7852
+ function callerScopeDenial(c, requestedAccountId) {
7853
+ const { decision, callerAccountId, houseAdminScope } = evaluateCallerScope(c, requestedAccountId);
7849
7854
  if (decision.authorized) return null;
7850
7855
  console.error(`[whatsapp-read-tool] op=authz-deny requested=${requestedAccountId} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
7851
7856
  return c.json({ error: decision.reason }, 403);
7852
7857
  }
7858
+ function houseSocketScopeDenial(c, op) {
7859
+ const houseAccountId = resolveAccount()?.accountId ?? "";
7860
+ const { decision, callerAccountId, houseAdminScope } = evaluateCallerScope(c, houseAccountId);
7861
+ if (decision.authorized) return null;
7862
+ console.error(`${TAG21} op=authz-deny route=${op} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
7863
+ return c.json({ ok: false, error: "Not authorized. The WhatsApp connection and its settings are house-scoped; this session may only act on its own account." }, 403);
7864
+ }
7853
7865
  var app2 = new Hono();
7854
7866
  app2.get("/status", (c) => {
7855
7867
  try {
7868
+ const denial = houseSocketScopeDenial(c, "status");
7869
+ if (denial) return denial;
7856
7870
  const live = getStatus();
7857
7871
  const liveIds = new Set(live.map((a) => a.accountId));
7858
7872
  const activeLoginIds = new Set(listActiveLoginAccountIds());
@@ -7880,6 +7894,8 @@ app2.get("/status", (c) => {
7880
7894
  });
7881
7895
  app2.post("/login/start", async (c) => {
7882
7896
  try {
7897
+ const denial = houseSocketScopeDenial(c, "login/start");
7898
+ if (denial) return denial;
7883
7899
  const body = await c.req.json().catch(() => ({}));
7884
7900
  const accountId = validateAccountId(body.accountId);
7885
7901
  const force = body.force ?? false;
@@ -7899,6 +7915,8 @@ app2.post("/login/start", async (c) => {
7899
7915
  });
7900
7916
  app2.post("/login/wait", async (c) => {
7901
7917
  try {
7918
+ const denial = houseSocketScopeDenial(c, "login/wait");
7919
+ if (denial) return denial;
7902
7920
  const body = await c.req.json().catch(() => ({}));
7903
7921
  const accountId = validateAccountId(body.accountId);
7904
7922
  const timeoutMs = body.timeoutMs ?? 6e4;
@@ -7917,6 +7935,8 @@ app2.post("/login/wait", async (c) => {
7917
7935
  });
7918
7936
  app2.post("/disconnect", async (c) => {
7919
7937
  try {
7938
+ const denial = houseSocketScopeDenial(c, "disconnect");
7939
+ if (denial) return denial;
7920
7940
  const body = await c.req.json().catch(() => ({}));
7921
7941
  const accountId = validateAccountId(body.accountId);
7922
7942
  await stopConnection(accountId);
@@ -7928,6 +7948,8 @@ app2.post("/disconnect", async (c) => {
7928
7948
  });
7929
7949
  app2.post("/reconnect", async (c) => {
7930
7950
  try {
7951
+ const denial = houseSocketScopeDenial(c, "reconnect");
7952
+ if (denial) return denial;
7931
7953
  const body = await c.req.json().catch(() => ({}));
7932
7954
  const accountId = validateAccountId(body.accountId);
7933
7955
  await startConnection(accountId);
@@ -7944,6 +7966,8 @@ app2.post("/config", async (c) => {
7944
7966
  if (!action || typeof action !== "string") {
7945
7967
  return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
7946
7968
  }
7969
+ const denial = houseSocketScopeDenial(c, `config action=${action}`);
7970
+ if (denial) return denial;
7947
7971
  const account = resolveAccount();
7948
7972
  if (!account) {
7949
7973
  return c.json({ ok: false, error: "No account configured." }, 500);
@@ -8983,6 +9007,16 @@ function asString(content) {
8983
9007
  function tsOf(row) {
8984
9008
  return typeof row.timestamp === "string" ? row.timestamp : null;
8985
9009
  }
9010
+ function hasWebchatReply(content) {
9011
+ if (!Array.isArray(content)) return false;
9012
+ for (const block of content) {
9013
+ if (block && typeof block === "object") {
9014
+ const b = block;
9015
+ if (b.type === "tool_use" && b.name === WEBCHAT_REPLY_TOOL) return true;
9016
+ }
9017
+ }
9018
+ return false;
9019
+ }
8986
9020
  function joinTextBlocks(content) {
8987
9021
  if (!Array.isArray(content)) return "";
8988
9022
  const parts = [];
@@ -9075,8 +9109,10 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
9075
9109
  out.push({ kind: "agent-error", code, raw, ts });
9076
9110
  continue;
9077
9111
  }
9112
+ if (hasWebchatReply(msg?.content)) turnCtx.repliedThisTurn = true;
9078
9113
  const isEnd = msg?.stop_reason === "end_turn";
9079
- out.push(...assistantTurns(msg?.content, ts, isEnd && turnCtx.lastChannelSource === "webchat"));
9114
+ const promoteFinal = isEnd && turnCtx.lastChannelSource === "webchat" && !turnCtx.repliedThisTurn;
9115
+ out.push(...assistantTurns(msg?.content, ts, promoteFinal));
9080
9116
  continue;
9081
9117
  }
9082
9118
  if (row.type === "queue-operation" && row.operation === "enqueue") {
@@ -9084,6 +9120,7 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
9084
9120
  if (content !== null && CHANNEL_WRAPPER2.test(content)) {
9085
9121
  const u = unwrapChannel(content);
9086
9122
  turnCtx.lastChannelSource = u.source;
9123
+ turnCtx.repliedThisTurn = false;
9087
9124
  if (!isScheduleSource(content)) {
9088
9125
  out.push(operatorInboundTurn(u, ts));
9089
9126
  queuedPendingSuppress.set(u.text, (queuedPendingSuppress.get(u.text) ?? 0) + 1);
@@ -9097,6 +9134,7 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
9097
9134
  if (isChannelQueued) {
9098
9135
  const u = unwrapChannel(att.prompt);
9099
9136
  turnCtx.lastChannelSource = u.source;
9137
+ turnCtx.repliedThisTurn = false;
9100
9138
  if (!isScheduleSource(att.prompt)) {
9101
9139
  renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress);
9102
9140
  }
@@ -9126,6 +9164,7 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
9126
9164
  if (isChannel) {
9127
9165
  const u = unwrapChannel(text);
9128
9166
  turnCtx.lastChannelSource = u.source;
9167
+ turnCtx.repliedThisTurn = false;
9129
9168
  if (!isScheduleSource(text)) {
9130
9169
  renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress);
9131
9170
  }
@@ -10873,6 +10912,7 @@ function createWebchatRoutes(deps) {
10873
10912
  let text;
10874
10913
  let target;
10875
10914
  let files = [];
10915
+ let clearSignal = false;
10876
10916
  const contentType = c.req.header("content-type") ?? "";
10877
10917
  const isMultipart = contentType.startsWith("multipart/form-data");
10878
10918
  if (isMultipart) {
@@ -10893,6 +10933,7 @@ function createWebchatRoutes(deps) {
10893
10933
  }
10894
10934
  target = rawTarget;
10895
10935
  }
10936
+ clearSignal = formData.get("clearSignal") === "true";
10896
10937
  } else {
10897
10938
  const body = await c.req.json().catch(() => null);
10898
10939
  const typed = body?.text;
@@ -10907,14 +10948,25 @@ function createWebchatRoutes(deps) {
10907
10948
  }
10908
10949
  target = rawTarget;
10909
10950
  }
10951
+ clearSignal = body?.clearSignal === true;
10910
10952
  }
10911
10953
  let deliveryKey = WEBCHAT_ADMIN_KEY;
10912
10954
  let deliverySessionId;
10913
10955
  let deliveryAccountId = accountId;
10914
10956
  const explicitTarget = target !== void 0;
10957
+ let sendIdFromResolve;
10915
10958
  if (target === void 0) {
10916
10959
  const latest = latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccountId, multiAccount);
10917
- if (latest !== null) target = latest;
10960
+ let sendTargetMode = "bootstrap";
10961
+ if (clearSignal) {
10962
+ sendTargetMode = "spawn-new";
10963
+ } else if (latest !== null) {
10964
+ target = latest;
10965
+ sendTargetMode = "resume-latest";
10966
+ }
10967
+ const sendId2 = ++sendSeq;
10968
+ sendIdFromResolve = sendId2;
10969
+ console.error(`[webchat:inbound] op=send-target-resolve seq=${sendId2} mode=${sendTargetMode} hadLatest=${latest !== null} clearSignal=${clearSignal}`);
10918
10970
  }
10919
10971
  if (explicitTarget && target !== void 0) {
10920
10972
  const t = readTargetOwner(target);
@@ -11029,7 +11081,7 @@ ${note}` : note;
11029
11081
  if (deliveredIntId !== null) {
11030
11082
  console.log(`[webchat-interrupt] op=delivered intId=${deliveredIntId} sessionId=${deliverySessionId.slice(0, 8)}`);
11031
11083
  }
11032
- const sendId = ++sendSeq;
11084
+ const sendId = sendIdFromResolve ?? ++sendSeq;
11033
11085
  let turnSessionId = deliverySessionId;
11034
11086
  if (turnSessionId === void 0) {
11035
11087
  let canonAccount;
@@ -11199,7 +11251,7 @@ ${note}` : note;
11199
11251
  }
11200
11252
  }
11201
11253
  const indicators = await fetchComposerIndicators(sessionId);
11202
- return c.json({ sessionId, projectDir, sizeBytes: jsonlSizeBytes(projectDir, sessionId), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${sessionId}`) ?? null, deliveryFailure: deps.deliveryFailureFor?.(`session:${sessionId}`) ?? null, ...indicators, ...readLevers(account) });
11254
+ return c.json({ sessionId, projectDir, source, sizeBytes: jsonlSizeBytes(projectDir, sessionId), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${sessionId}`) ?? null, deliveryFailure: deps.deliveryFailureFor?.(`session:${sessionId}`) ?? null, ...indicators, ...readLevers(account) });
11203
11255
  });
11204
11256
  app62.post("/permission-verdict", requireAdminSession, async (c) => {
11205
11257
  const body = await c.req.json().catch(() => null);
@@ -23472,15 +23524,15 @@ function createWebchatChannelRoutes(deps) {
23472
23524
  const finalBytes = typeof body?.finalBytes === "number" && Number.isFinite(body.finalBytes) ? body.finalBytes : 0;
23473
23525
  const k = key.slice(0, 8);
23474
23526
  const sid = sessionId.slice(0, 8);
23475
- if (interactive && finalBytes > 0) {
23476
- console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} replied=${replied ? "yes" : "no"} delivered=fallback bytes=${finalBytes}`);
23477
- return c.json({ ok: true, delivered: "fallback" });
23478
- }
23479
23527
  if (replied) {
23480
- console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} delivered=reply`);
23528
+ console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} replied=yes delivered=reply bytes=${finalBytes}`);
23481
23529
  return c.json({ ok: true, delivered: "reply" });
23482
23530
  }
23483
- console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} delivered=none`);
23531
+ if (interactive && finalBytes > 0) {
23532
+ console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} replied=no delivered=fallback bytes=${finalBytes}`);
23533
+ return c.json({ ok: true, delivered: "fallback" });
23534
+ }
23535
+ console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} replied=no delivered=none`);
23484
23536
  return c.json({ ok: true, delivered: "none" });
23485
23537
  });
23486
23538
  return app62;