botmux 3.12.0 → 3.12.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.
Files changed (229) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/adapters/backend/riff-backend.d.ts +18 -0
  3. package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
  4. package/dist/adapters/backend/riff-backend.js +143 -0
  5. package/dist/adapters/backend/riff-backend.js.map +1 -1
  6. package/dist/adapters/cli/kimi.d.ts.map +1 -1
  7. package/dist/adapters/cli/kimi.js +22 -8
  8. package/dist/adapters/cli/kimi.js.map +1 -1
  9. package/dist/adapters/cli/pi.d.ts.map +1 -1
  10. package/dist/adapters/cli/pi.js +3 -1
  11. package/dist/adapters/cli/pi.js.map +1 -1
  12. package/dist/bot-registry.d.ts.map +1 -1
  13. package/dist/bot-registry.js +4 -1
  14. package/dist/bot-registry.js.map +1 -1
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +65 -27
  17. package/dist/cli.js.map +1 -1
  18. package/dist/core/command-handler.d.ts.map +1 -1
  19. package/dist/core/command-handler.js +16 -2
  20. package/dist/core/command-handler.js.map +1 -1
  21. package/dist/core/dashboard-ipc-server.js +1 -1
  22. package/dist/core/dashboard-ipc-server.js.map +1 -1
  23. package/dist/core/dashboard-rows.d.ts +17 -1
  24. package/dist/core/dashboard-rows.d.ts.map +1 -1
  25. package/dist/core/dashboard-rows.js +13 -1
  26. package/dist/core/dashboard-rows.js.map +1 -1
  27. package/dist/core/maintenance.d.ts.map +1 -1
  28. package/dist/core/maintenance.js +5 -0
  29. package/dist/core/maintenance.js.map +1 -1
  30. package/dist/core/skills/cli-admin-command.d.ts.map +1 -1
  31. package/dist/core/skills/cli-admin-command.js +142 -3
  32. package/dist/core/skills/cli-admin-command.js.map +1 -1
  33. package/dist/core/skills/frontmatter.d.ts.map +1 -1
  34. package/dist/core/skills/frontmatter.js +44 -7
  35. package/dist/core/skills/frontmatter.js.map +1 -1
  36. package/dist/core/skills/im-command.d.ts +6 -0
  37. package/dist/core/skills/im-command.d.ts.map +1 -1
  38. package/dist/core/skills/im-command.js +34 -7
  39. package/dist/core/skills/im-command.js.map +1 -1
  40. package/dist/core/skills/install-command.d.ts +4 -0
  41. package/dist/core/skills/install-command.d.ts.map +1 -0
  42. package/dist/core/skills/install-command.js +89 -0
  43. package/dist/core/skills/install-command.js.map +1 -0
  44. package/dist/core/skills/package.d.ts.map +1 -1
  45. package/dist/core/skills/package.js +10 -3
  46. package/dist/core/skills/package.js.map +1 -1
  47. package/dist/core/skills/policy.d.ts +5 -1
  48. package/dist/core/skills/policy.d.ts.map +1 -1
  49. package/dist/core/skills/policy.js +66 -3
  50. package/dist/core/skills/policy.js.map +1 -1
  51. package/dist/core/skills/references.d.ts +9 -1
  52. package/dist/core/skills/references.d.ts.map +1 -1
  53. package/dist/core/skills/references.js +35 -2
  54. package/dist/core/skills/references.js.map +1 -1
  55. package/dist/core/skills/registry-paths.d.ts +1 -0
  56. package/dist/core/skills/registry-paths.d.ts.map +1 -1
  57. package/dist/core/skills/registry-paths.js +3 -0
  58. package/dist/core/skills/registry-paths.js.map +1 -1
  59. package/dist/core/skills/session-resolver.d.ts +2 -1
  60. package/dist/core/skills/session-resolver.d.ts.map +1 -1
  61. package/dist/core/skills/session-resolver.js +1 -0
  62. package/dist/core/skills/session-resolver.js.map +1 -1
  63. package/dist/core/skills/session-runtime.d.ts.map +1 -1
  64. package/dist/core/skills/session-runtime.js +2 -0
  65. package/dist/core/skills/session-runtime.js.map +1 -1
  66. package/dist/core/skills/sources.d.ts +23 -0
  67. package/dist/core/skills/sources.d.ts.map +1 -1
  68. package/dist/core/skills/sources.js +128 -12
  69. package/dist/core/skills/sources.js.map +1 -1
  70. package/dist/core/skills/types.d.ts +22 -1
  71. package/dist/core/skills/types.d.ts.map +1 -1
  72. package/dist/core/types.d.ts +2 -0
  73. package/dist/core/types.d.ts.map +1 -1
  74. package/dist/core/types.js.map +1 -1
  75. package/dist/core/worker-pool.d.ts.map +1 -1
  76. package/dist/core/worker-pool.js +52 -1
  77. package/dist/core/worker-pool.js.map +1 -1
  78. package/dist/dashboard/bot-onboarding.d.ts.map +1 -1
  79. package/dist/dashboard/bot-onboarding.js +2 -114
  80. package/dist/dashboard/bot-onboarding.js.map +1 -1
  81. package/dist/dashboard/connector-api.d.ts.map +1 -1
  82. package/dist/dashboard/connector-api.js +66 -6
  83. package/dist/dashboard/connector-api.js.map +1 -1
  84. package/dist/dashboard/public-redact.d.ts.map +1 -1
  85. package/dist/dashboard/public-redact.js +3 -0
  86. package/dist/dashboard/public-redact.js.map +1 -1
  87. package/dist/dashboard/skill-install-request.js +10 -11
  88. package/dist/dashboard/skill-install-request.js.map +1 -1
  89. package/dist/dashboard/skill-pack-response.d.ts +15 -0
  90. package/dist/dashboard/skill-pack-response.d.ts.map +1 -0
  91. package/dist/dashboard/skill-pack-response.js +32 -0
  92. package/dist/dashboard/skill-pack-response.js.map +1 -0
  93. package/dist/dashboard/web/connectors-page.d.ts +69 -0
  94. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  95. package/dist/dashboard/web/connectors-page.js +47 -7
  96. package/dist/dashboard/web/connectors-page.js.map +1 -1
  97. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  98. package/dist/dashboard/web/i18n.js +301 -26
  99. package/dist/dashboard/web/i18n.js.map +1 -1
  100. package/dist/dashboard/web/preferences.d.ts +4 -2
  101. package/dist/dashboard/web/preferences.d.ts.map +1 -1
  102. package/dist/dashboard/web/preferences.js +14 -6
  103. package/dist/dashboard/web/preferences.js.map +1 -1
  104. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  105. package/dist/dashboard/web/sessions-page.js +99 -3
  106. package/dist/dashboard/web/sessions-page.js.map +1 -1
  107. package/dist/dashboard/web/sessions.d.ts +3 -1
  108. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  109. package/dist/dashboard/web/sessions.js +13 -6
  110. package/dist/dashboard/web/sessions.js.map +1 -1
  111. package/dist/dashboard/web/skills/bot-assignments-tab.d.ts +27 -0
  112. package/dist/dashboard/web/skills/bot-assignments-tab.d.ts.map +1 -0
  113. package/dist/dashboard/web/skills/bot-assignments-tab.js +257 -0
  114. package/dist/dashboard/web/skills/bot-assignments-tab.js.map +1 -0
  115. package/dist/dashboard/web/skills/delivery-settings-tab.d.ts +25 -0
  116. package/dist/dashboard/web/skills/delivery-settings-tab.d.ts.map +1 -0
  117. package/dist/dashboard/web/skills/delivery-settings-tab.js +20 -0
  118. package/dist/dashboard/web/skills/delivery-settings-tab.js.map +1 -0
  119. package/dist/dashboard/web/skills/shared.d.ts +86 -0
  120. package/dist/dashboard/web/skills/shared.d.ts.map +1 -0
  121. package/dist/dashboard/web/skills/shared.js +178 -0
  122. package/dist/dashboard/web/skills/shared.js.map +1 -0
  123. package/dist/dashboard/web/skills/skill-library-tab.d.ts +75 -0
  124. package/dist/dashboard/web/skills/skill-library-tab.d.ts.map +1 -0
  125. package/dist/dashboard/web/skills/skill-library-tab.js +108 -0
  126. package/dist/dashboard/web/skills/skill-library-tab.js.map +1 -0
  127. package/dist/dashboard/web/skills/skill-packs-tab.d.ts +21 -0
  128. package/dist/dashboard/web/skills/skill-packs-tab.d.ts.map +1 -0
  129. package/dist/dashboard/web/skills/skill-packs-tab.js +185 -0
  130. package/dist/dashboard/web/skills/skill-packs-tab.js.map +1 -0
  131. package/dist/dashboard/web/skills/types.d.ts +95 -0
  132. package/dist/dashboard/web/skills/types.d.ts.map +1 -0
  133. package/dist/dashboard/web/skills/types.js +2 -0
  134. package/dist/dashboard/web/skills/types.js.map +1 -0
  135. package/dist/dashboard/web/skills/use-skills-data.d.ts +31 -0
  136. package/dist/dashboard/web/skills/use-skills-data.d.ts.map +1 -0
  137. package/dist/dashboard/web/skills/use-skills-data.js +86 -0
  138. package/dist/dashboard/web/skills/use-skills-data.js.map +1 -0
  139. package/dist/dashboard/web/skills-page.d.ts +34 -31
  140. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  141. package/dist/dashboard/web/skills-page.js +321 -176
  142. package/dist/dashboard/web/skills-page.js.map +1 -1
  143. package/dist/dashboard/webhook-routes.d.ts +18 -0
  144. package/dist/dashboard/webhook-routes.d.ts.map +1 -1
  145. package/dist/dashboard/webhook-routes.js +35 -1
  146. package/dist/dashboard/webhook-routes.js.map +1 -1
  147. package/dist/dashboard-web/app.js +1 -1
  148. package/dist/dashboard-web/chunks/{bot-defaults-page-USC6EZXP.js → bot-defaults-page-HL655CNR.js} +1 -1
  149. package/dist/dashboard-web/chunks/{chunk-KOPISPHN.js → chunk-5PAYQ3KT.js} +1 -1
  150. package/dist/dashboard-web/chunks/{chunk-RXRJB5SO.js → chunk-5ZL7NLR5.js} +1 -1
  151. package/dist/dashboard-web/chunks/{chunk-A7VC4BXR.js → chunk-BFUTTYVK.js} +1 -1
  152. package/dist/dashboard-web/chunks/chunk-ELL53D62.js +55 -0
  153. package/dist/dashboard-web/chunks/connectors-page-3QQACF23.js +1 -0
  154. package/dist/dashboard-web/chunks/{groups-page-C6NSDTIN.js → groups-page-SDZMJ7UV.js} +1 -1
  155. package/dist/dashboard-web/chunks/{insights-page-UTOVYLLY.js → insights-page-ZZ3JEJ6L.js} +1 -1
  156. package/dist/dashboard-web/chunks/{monitor-room-UKTJYTA2.js → monitor-room-ROJ5GZ4N.js} +1 -1
  157. package/dist/dashboard-web/chunks/{monitoring-page-Q3CEZIVX.js → monitoring-page-IATV7KJF.js} +1 -1
  158. package/dist/dashboard-web/chunks/{office-page-GYCZOOVJ.js → office-page-TYO5OIIT.js} +1 -1
  159. package/dist/dashboard-web/chunks/{overview-page-W2VBJ6OH.js → overview-page-VJUULZF2.js} +1 -1
  160. package/dist/dashboard-web/chunks/{roles-page-U4SHER4K.js → roles-page-7RJJKZGK.js} +1 -1
  161. package/dist/dashboard-web/chunks/{schedules-page-TNPW3N3Q.js → schedules-page-JTEJ5IXW.js} +1 -1
  162. package/dist/dashboard-web/chunks/sessions-page-KD7HDT3U.js +8 -0
  163. package/dist/dashboard-web/chunks/{settings-page-VKW5ZK5K.js → settings-page-7CTTFBRS.js} +1 -1
  164. package/dist/dashboard-web/chunks/skills-page-57NZECJ3.js +4 -0
  165. package/dist/dashboard-web/chunks/{team-federation-page-OMFGGBT2.js → team-federation-page-6YYIMJG3.js} +1 -1
  166. package/dist/dashboard-web/chunks/{v3-page-PLXWK4KO.js → v3-page-ZGKBSBK3.js} +1 -1
  167. package/dist/dashboard-web/chunks/{whiteboards-page-NJQOEO2P.js → whiteboards-page-AACJF6DB.js} +1 -1
  168. package/dist/dashboard-web/style.css +1623 -160
  169. package/dist/dashboard.js +200 -16
  170. package/dist/dashboard.js.map +1 -1
  171. package/dist/i18n/en.d.ts.map +1 -1
  172. package/dist/i18n/en.js +2 -0
  173. package/dist/i18n/en.js.map +1 -1
  174. package/dist/i18n/zh.d.ts.map +1 -1
  175. package/dist/i18n/zh.js +2 -0
  176. package/dist/i18n/zh.js.map +1 -1
  177. package/dist/index-daemon.js +6 -1
  178. package/dist/index-daemon.js.map +1 -1
  179. package/dist/services/connector-store.d.ts +14 -1
  180. package/dist/services/connector-store.d.ts.map +1 -1
  181. package/dist/services/connector-store.js.map +1 -1
  182. package/dist/services/connector-topic-template.d.ts +7 -0
  183. package/dist/services/connector-topic-template.d.ts.map +1 -0
  184. package/dist/services/connector-topic-template.js +210 -0
  185. package/dist/services/connector-topic-template.js.map +1 -0
  186. package/dist/services/project-scanner.d.ts +33 -0
  187. package/dist/services/project-scanner.d.ts.map +1 -1
  188. package/dist/services/project-scanner.js +40 -1
  189. package/dist/services/project-scanner.js.map +1 -1
  190. package/dist/services/skill-pack-store.d.ts +50 -0
  191. package/dist/services/skill-pack-store.d.ts.map +1 -0
  192. package/dist/services/skill-pack-store.js +270 -0
  193. package/dist/services/skill-pack-store.js.map +1 -0
  194. package/dist/services/skill-registry-store.d.ts +8 -0
  195. package/dist/services/skill-registry-store.d.ts.map +1 -1
  196. package/dist/services/skill-registry-store.js +100 -68
  197. package/dist/services/skill-registry-store.js.map +1 -1
  198. package/dist/services/todo-state.d.ts +74 -0
  199. package/dist/services/todo-state.d.ts.map +1 -0
  200. package/dist/services/todo-state.js +351 -0
  201. package/dist/services/todo-state.js.map +1 -0
  202. package/dist/setup/agent-preset.d.ts.map +1 -1
  203. package/dist/setup/agent-preset.js +2 -0
  204. package/dist/setup/agent-preset.js.map +1 -1
  205. package/dist/setup/owner-identity.d.ts +47 -0
  206. package/dist/setup/owner-identity.d.ts.map +1 -0
  207. package/dist/setup/owner-identity.js +196 -0
  208. package/dist/setup/owner-identity.js.map +1 -0
  209. package/dist/setup/register-app.d.ts +4 -3
  210. package/dist/setup/register-app.d.ts.map +1 -1
  211. package/dist/setup/register-app.js.map +1 -1
  212. package/dist/setup/setup-args.d.ts +1 -1
  213. package/dist/setup/setup-args.d.ts.map +1 -1
  214. package/dist/setup/setup-args.js +7 -3
  215. package/dist/setup/setup-args.js.map +1 -1
  216. package/dist/types.d.ts +11 -0
  217. package/dist/types.d.ts.map +1 -1
  218. package/dist/utils/child-env.d.ts +25 -0
  219. package/dist/utils/child-env.d.ts.map +1 -1
  220. package/dist/utils/child-env.js +47 -0
  221. package/dist/utils/child-env.js.map +1 -1
  222. package/dist/worker.js +55 -16
  223. package/dist/worker.js.map +1 -1
  224. package/dist/workflows/definition.d.ts +44 -44
  225. package/package.json +1 -1
  226. package/dist/dashboard-web/chunks/chunk-M6BHWQFO.js +0 -51
  227. package/dist/dashboard-web/chunks/connectors-page-4Z76HWRH.js +0 -1
  228. package/dist/dashboard-web/chunks/sessions-page-YWWDDV57.js +0 -6
  229. package/dist/dashboard-web/chunks/skills-page-ZCEDFGZZ.js +0 -4
package/dist/worker.js CHANGED
@@ -28,7 +28,7 @@ import { readProcessStartIdentity } from './core/session-marker.js';
28
28
  import { roleLibraryRoot, roleLibrarySubtree } from './core/role-library.js';
29
29
  import { drainTranscript, joinAssistantText, trailingAssistantText, findJsonlContainingFingerprint, findJsonlsContainingExactContent, findLatestJsonl, extractLastAssistantTurn, extractTurnStartText, splitTranscriptEventsByCutoff, isTranscriptRateLimitEvent, apiErrorMessageText } from './services/claude-transcript.js';
30
30
  import { BridgeTurnQueue, makeFingerprint, normaliseForFingerprint } from './services/bridge-turn-queue.js';
31
- import { bridgePostText, shouldEmitEmptyCompletedBridgeFallback, shouldEmitFailedBridgeFallback, shouldSuppressBridgeEmit, stripTrailingBridgeSentinelLine } from './services/bridge-fallback-gate.js';
31
+ import { bridgePostText, isBridgeNothingToSendFinal, shouldEmitEmptyCompletedBridgeFallback, shouldEmitFailedBridgeFallback, shouldSuppressBridgeEmit, stripTrailingBridgeSentinelLine } from './services/bridge-fallback-gate.js';
32
32
  import { buildSubmitMessagePreview } from './services/submit-notification.js';
33
33
  import { decideHardTimeoutAction, decidePostHookPromptEvidence, decideSettleMarkReady, shouldArmPostHookPromptEvidenceFallback, shouldReleaseFirstPromptTimeout, shouldWaitForPostSessionStartPromptEvidence, shouldWriteNow, POST_HOOK_EVIDENCE_FALLBACK_MS, POST_HOOK_EVIDENCE_RETRY_MS, } from './utils/input-gate.js';
34
34
  import { decideCodexRunnerFreshness, CodexRunnerFreshnessInputQueue, shouldHoldCodexRunnerInput, } from './services/codex-runner-freshness.js';
@@ -128,7 +128,7 @@ import { mergeHerdrWebSnapshot, renderHerdrWebHistory, } from './utils/herdr-web
128
128
  import { parseWorkerRequestUrl } from './utils/worker-http.js';
129
129
  import { detectCliUsageLimit, usageLimitStateKey, structuredRateLimitState, isStructuredRateLimitAuthoritative } from './utils/cli-usage-limit.js';
130
130
  import { uploadImageBuffer } from './utils/lark-upload.js';
131
- import { redactChildEnv, scrubClaudeSessionMarkerEnv, scrubSessionCliHomeEnv } from './utils/child-env.js';
131
+ import { applySessionOwnerEnv, redactChildEnv, scrubClaudeSessionMarkerEnv, scrubSessionCliHomeEnv } from './utils/child-env.js';
132
132
  import { decideSubmitConfirmationAction, settleDeferredSubmitConfirmation, settleStaleWriteContinuation, } from './services/submit-confirmation.js';
133
133
  import { runAdoptQueuedWriteSequence, runAdoptRawInputSequence, runAdoptSessionRenameSequence, } from './services/adopt-input-sequence.js';
134
134
  import { config, resolveChatBotDiscoveryConfig } from './config.js';
@@ -770,10 +770,6 @@ async function engageCodexRpc(cfg) {
770
770
  engineEnv.BOTMUX_LARK_APP_ID = cfg.larkAppId;
771
771
  engineEnv.BOTMUX_ROOT_MESSAGE_ID = cfg.rootMessageId;
772
772
  engineEnv.BOTMUX_SESSION_SCOPE = cfg.rootMessageId?.startsWith('om_') ? 'thread' : 'chat';
773
- if (cfg.ownerOpenId)
774
- engineEnv.BOTMUX_OWNER_OPEN_ID = cfg.ownerOpenId;
775
- else
776
- delete engineEnv.BOTMUX_OWNER_OPEN_ID;
777
773
  // The app-server owns model execution in RPC mode. Its MCP gateway child
778
774
  // must inherit the trusted host socket just like a native CLI process does.
779
775
  if (sessionMcpGatewayHost) {
@@ -789,6 +785,9 @@ async function engageCodexRpc(cfg) {
789
785
  // injects into the TUI — else a 3rd-party-provider bot's app-server silently
790
786
  // falls back to the default provider. Re-sanitized (crossed IPC).
791
787
  Object.assign(engineEnv, sanitizePerBotEnv(cfg.env));
788
+ // Session identity is host-owned. Pin it after the config-controlled merge,
789
+ // matching every other backend and preventing stale owner resurrection.
790
+ applySessionOwnerEnv(engineEnv, cfg.ownerOpenId);
792
791
  engine = new CodexRpcEngine({
793
792
  cliBin, cwd: cfg.workingDir, env: engineEnv, sessionId: cfg.sessionId,
794
793
  model: cfg.model, reasoningEffort: cfg.reasoningEffort, log: (m) => log(m),
@@ -4619,6 +4618,10 @@ function emitReadyTurns(opts = {}) {
4619
4618
  const remainingPending = bridgeQueue.peek();
4620
4619
  const nextPendingMarkTimeMs = remainingPending.length > 0 ? remainingPending[0].markTimeMs : undefined;
4621
4620
  const cache = new Map();
4621
+ // Turns suppressed as GENUINE SILENCE — see emitReadyCodexTurns for the full
4622
+ // rationale. Tracked by object identity across this function's two loops so
4623
+ // the terminal can carry positive silence evidence for a durable/async caller.
4624
+ const nothingToSendTurns = new Set();
4622
4625
  for (let i = 0; i < ready.length; i++) {
4623
4626
  const turn = ready[i];
4624
4627
  const nextBoundaryMs = (i + 1 < ready.length ? ready[i + 1].markTimeMs : nextPendingMarkTimeMs);
@@ -4651,6 +4654,11 @@ function emitReadyTurns(opts = {}) {
4651
4654
  if (shouldSuppressBridgeEmit(gateInput, nextBoundaryMs, markers, adoptMode)) {
4652
4655
  const reason = turn.isLocal ? 'local-typed' : 'model called botmux send within window';
4653
4656
  log(`Bridge fallback suppressed for turn ${turn.turnId.substring(0, 8)} (${reason})`);
4657
+ // Positive silence evidence for the terminal — only a bare nothing-to-send
4658
+ // sentinel (no prose, no send), never "already sent" / local-typed.
4659
+ if (!adoptMode && isBridgeNothingToSendFinal(assistantText)) {
4660
+ nothingToSendTurns.add(turn);
4661
+ }
4654
4662
  notifyExplicitReplyObserved(turn.turnId, explicitReplyMarkerForTurnWindow(gateInput, nextBoundaryMs, markers, adoptMode));
4655
4663
  continue;
4656
4664
  }
@@ -4719,7 +4727,7 @@ function emitReadyTurns(opts = {}) {
4719
4727
  // only after all corresponding final_output IPC messages have been queued so
4720
4728
  // the daemon observes a stable per-turn ordering.
4721
4729
  for (const turn of ready)
4722
- emitTurnTerminal(turn.turnId, 'completed', undefined, turn.dispatchAttempt);
4730
+ emitTurnTerminal(turn.turnId, 'completed', undefined, turn.dispatchAttempt, nothingToSendTurns.has(turn) ? 'nothing_to_send' : undefined);
4723
4731
  }
4724
4732
  /** Drain `path` from `fromOffset` and feed the events to the bridge queue
4725
4733
  * with that path as the source stamp. Pure side-effects on bridgeQueue +
@@ -5977,6 +5985,14 @@ function emitReadyCodexTurns() {
5977
5985
  const ready = codexBridgeQueue.drainEmittable();
5978
5986
  if (ready.length === 0)
5979
5987
  return;
5988
+ // Turns suppressed as GENUINE SILENCE (model terminated with a bare
5989
+ // nothing-to-send sentinel, no `botmux send`). Tracked by object identity —
5990
+ // both the emit loop and the terminal loop below iterate this same `ready`
5991
+ // array — so the terminal for such a turn can carry positive silence evidence
5992
+ // (outputDisposition: 'nothing_to_send'). A durable/async caller uses ONLY
5993
+ // that flag to settle completed-with-empty; a bare `completed` terminal (e.g.
5994
+ // the RPC-hydration timeout path) must never be read as silence.
5995
+ const nothingToSendTurns = new Set();
5980
5996
  const adoptMode = lastInitConfig?.adoptMode === true;
5981
5997
  // Adopt mode: model is the user's external Codex, no botmux send to
5982
5998
  // gate against — every assistant turn (Lark-driven OR locally typed)
@@ -6016,6 +6032,16 @@ function emitReadyCodexTurns() {
6016
6032
  continue;
6017
6033
  if (shouldSuppressBridgeEmit(gateInput, nextBoundaryMs, markers, adoptMode)) {
6018
6034
  log(`Codex bridge fallback suppressed for turn ${turn.turnId.substring(0, 8)} (gate)`);
6035
+ // Distinguish DELIBERATE SILENCE (bare nothing-to-send sentinel, no prose,
6036
+ // no send) from other suppression reasons (already `botmux send`-ed this
6037
+ // turn, local-typed). Only the former is positive evidence that the model
6038
+ // chose to produce no output — the terminal below carries it so a durable/
6039
+ // async caller can settle completed-with-empty. "Already sent" is NOT
6040
+ // silence: its content went out via final_output/botmux send and the async
6041
+ // result is captured there, so it must not be stamped.
6042
+ if (!adoptMode && isBridgeNothingToSendFinal(turn.finalText)) {
6043
+ nothingToSendTurns.add(turn);
6044
+ }
6019
6045
  notifyExplicitReplyObserved(turn.turnId, explicitReplyMarkerForTurnWindow(gateInput, nextBoundaryMs, markers, adoptMode));
6020
6046
  continue;
6021
6047
  }
@@ -6065,7 +6091,7 @@ function emitReadyCodexTurns() {
6065
6091
  ? { dispatchAttempt: turn.dispatchAttempt }
6066
6092
  : {}),
6067
6093
  });
6068
- emitTurnTerminal(turn.turnId, turn.terminalStatus ?? 'completed', turn.terminalErrorCode, turn.dispatchAttempt);
6094
+ emitTurnTerminal(turn.turnId, turn.terminalStatus ?? 'completed', turn.terminalErrorCode, turn.dispatchAttempt, nothingToSendTurns.has(turn) ? 'nothing_to_send' : undefined);
6069
6095
  }
6070
6096
  }
6071
6097
  function stopCodexBridge() {
@@ -10700,9 +10726,6 @@ async function spawnCli(cfg, opts = {}) {
10700
10726
  sessionEnv.BOTMUX_DEFERRED_SCHEDULE_TOPIC_TITLE = cfg.deferredScheduleRun.topicTitle;
10701
10727
  }
10702
10728
  }
10703
- // Turn sender so `--mention-back` can resolve an @ target in the sandbox.
10704
- if (cfg.ownerOpenId)
10705
- sessionEnv.BOTMUX_OWNER_OPEN_ID = cfg.ownerOpenId;
10706
10729
  // Lark credentials so `botmux send` works inside the riff sandbox without a
10707
10730
  // local daemon or bots.json. The sandbox has no session data / bot config,
10708
10731
  // so cmdSend falls back to these env vars to call the Lark API directly.
@@ -10732,6 +10755,10 @@ async function spawnCli(cfg, opts = {}) {
10732
10755
  mergedEnv.BOTMUX_API_ONLY = '1';
10733
10756
  mergedEnv.BOTMUX_CHAT_ID = cfg.chatId; // host-owned; never from config
10734
10757
  }
10758
+ // Session identity is host-owned. Re-freeze it AFTER per-bot/riff config
10759
+ // merge so a stale or user-supplied backend env cannot impersonate another
10760
+ // owner (or resurrect an owner on an ownerless session).
10761
+ applySessionOwnerEnv(mergedEnv, cfg.ownerOpenId);
10735
10762
  riffBackendConfig = Object.assign({}, cfg.backendConfig, { env: mergedEnv, resumeParentTaskId: cfg.riffParentTaskId });
10736
10763
  // 复用本地仓库+分支:多仓只认会话上的显式 stamp(仓库选择卡多选流按用户
10737
10764
  // 顺序写入 cfg.riffRepoDirs,首仓=primary);否则仅对 workingDir 本身做单仓
@@ -11629,6 +11656,7 @@ async function spawnCli(cfg, opts = {}) {
11629
11656
  else
11630
11657
  delete childEnv.BOTMUX_API_ONLY;
11631
11658
  childEnv.BOTMUX_ROOT_MESSAGE_ID = cfg.rootMessageId;
11659
+ applySessionOwnerEnv(childEnv, cfg.ownerOpenId);
11632
11660
  // This bot's resolved brandLabel template, injected so a SANDBOXED `botmux
11633
11661
  // send` renders the role-name footer without reading bots.json (deny-by-
11634
11662
  // default → EPERM → role footer would silently fall back to the default
@@ -12917,8 +12945,19 @@ async function spawnCli(cfg, opts = {}) {
12917
12945
  log('Screen settle barrier degraded after bounded retries; finalizing from the last successful snapshot');
12918
12946
  }
12919
12947
  }
12920
- if (evidenceSource === 'screen' && idleBackend
12921
- && deferPromptReadyWhileBusy(`${cliName()} screen-idle`, idleBackend))
12948
+ // Pi's transcript final (assistant_final) is persisted asynchronously
12949
+ // from the TUI clearing its `Working...` busy marker — either order
12950
+ // occurs. An external idle landing while the authoritative viewport
12951
+ // still shows busy must defer exactly like a screen idle, or the card
12952
+ // flips to 等待输入 mid-turn. Scoped to Pi: Grok/Codex own their idle
12953
+ // via reliableTurnTerminal / lifecycle blocking, and their busy markers
12954
+ // legitimately lag behind the transcript final. deferPromptReadyWhileBusy
12955
+ // itself fail-opens on non-authoritative screens (ZMX), so a stale
12956
+ // `Working...` in ZMX history can never block a structured terminal.
12957
+ const busyGuardedIdle = evidenceSource === 'screen'
12958
+ || (evidenceSource === 'external' && structuredBridgeIsPi());
12959
+ if (busyGuardedIdle && idleBackend
12960
+ && deferPromptReadyWhileBusy(`${cliName()} ${evidenceSource}-idle`, idleBackend))
12922
12961
  return;
12923
12962
  drainBridgesThenMarkReady(evidenceSource);
12924
12963
  });
@@ -14709,7 +14748,7 @@ const emittedTurnTerminals = new TurnTerminalDeduper();
14709
14748
  /** Report CLI processing completion independently from user-visible output.
14710
14749
  * Keep a bounded worker-local dedup set because transcript watchers and app
14711
14750
  * runners may surface the same final boundary more than once. */
14712
- function emitTurnTerminal(turnId, status, errorCode, dispatchAttempt) {
14751
+ function emitTurnTerminal(turnId, status, errorCode, dispatchAttempt, outputDisposition) {
14713
14752
  if (!sessionId || !turnId)
14714
14753
  return;
14715
14754
  if (!emittedTurnTerminals.claim(sessionId, turnId, dispatchAttempt))
@@ -14730,6 +14769,7 @@ function emitTurnTerminal(turnId, status, errorCode, dispatchAttempt) {
14730
14769
  status,
14731
14770
  ...(dispatchAttempt !== undefined ? { dispatchAttempt } : {}),
14732
14771
  ...(errorCode ? { errorCode } : {}),
14772
+ ...(outputDisposition ? { outputDisposition } : {}),
14733
14773
  });
14734
14774
  if (terminalReleasesDurableTurn({ turnId: currentBotmuxTurnId, dispatchAttempt: currentBotmuxDispatchAttempt }, { turnId, dispatchAttempt })) {
14735
14775
  // Durable replay ownership moves back to the receiver/hub at terminal.
@@ -14942,8 +14982,7 @@ process.on('message', async (raw) => {
14942
14982
  sessionId = msg.sessionId;
14943
14983
  refreshTerminalViewToken();
14944
14984
  refreshTerminalWriteToken();
14945
- if (msg.ownerOpenId)
14946
- process.env.__OWNER_OPEN_ID = msg.ownerOpenId;
14985
+ applySessionOwnerEnv(process.env, msg.ownerOpenId);
14947
14986
  // Pin this worker's i18n locale early so every t() call below resolves
14948
14987
  // against the bot's chosen language without each callsite needing to
14949
14988
  // re-thread it.