Commit Graph

263 Commits

Author SHA1 Message Date
zengLingbiao
e517d187d2 test(utils): add unit tests for parseJsonWithJson5Fallback (#98801)
* test(utils): add unit tests for parseJsonWithJson5Fallback

* test(utils): verify JSON.parse fast path with spy, per ClawSweeper review

- Add vi.spyOn to prove JSON.parse is called (not JSON5) for strict JSON
- Add spy to prove JSON5.parse is called on fallback path
- Run full src/utils test suite (263 tests passed)
2026-07-17 17:20:16 -07:00
zengLingbiao
f64c35be30 test(utils): add unit tests for safeJsonStringify (#98667) 2026-07-17 17:20:03 -07:00
zengLingbiao
bd966771a7 test(utils): add unit tests for formatTokenCount (#98541)
* test(utils): add unit tests for formatTokenCount

* fix(test): use Number.NaN/Number.POSITIVE_INFINITY per oxlint unicorn/prefer-number-properties

* test(utils): fold formatTokenCount boundary tests into existing usage-format test

Move edge case coverage (invalid inputs, zero/negative, exact boundaries,
thousands overflow) into src/utils/usage-format.test.ts instead of a
separate colocated test file per reviewer feedback.
2026-07-17 17:08:42 -07:00
Peter Steinberger
ec8f6e5e03 feat(browser): add secure per-tab copilot panel (#109817)
* feat(browser): add copilot security contracts

* fix(gateway): expose verified client identity to handlers

* feat(browser): add secure per-tab copilot panel

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* refactor(browser): separate copilot gateway hint custody

* fix(browser): preserve legacy pairing parse shape

* fix(browser): harden copilot lifecycle custody

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): enforce copilot lifecycle boundaries

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* style(browser): format copilot sources

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): preserve copilot consent revocation

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* refactor(browser): split copilot custody owners

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(browser): normalize websocket array buffers

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* chore(protocol): regenerate Swift gateway models

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* refactor(browser): model copilot runtime entrypoints

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): honor extension build boundaries

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(gateway): assert targeted chat delivery

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(gateway): cover targeted delivery calls

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): declare copilot build dependencies

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(ci): clear browser copilot gate failures

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(ci): cover copilot lint exclusion

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): gate copilot on relay custody

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(browser): bound copilot relay frames

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

---------

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>
2026-07-18 01:00:23 +01:00
Peter Steinberger
03a8a7dd17 refactor(json): share JSON5 fallback parsing 2026-07-14 01:35:48 -07:00
Peter Steinberger
877af700bf feat(codex): continue paired-node Codex catalog sessions from the Control UI (#106927)
* feat(codex): continue paired-node Codex catalog sessions from the Control UI

Codex catalog rows on paired nodes were view-only. Node rows are now
continuable when the node advertises and permits the codex threads.list,
thread.turns.list, and cli.session.resume commands. Continue adopts the
session into a locked codex OpenClaw session (deterministic
harness:codex:node-session key, bounded native-history import, two-phase
initializing marker) and installs a webchat conversation binding
(codex-cli-node-session) through the new generic
bindPluginSessionConversation seam with attempt-ID guarded rollback, so
each web turn runs codex exec resume on the owning node via the existing
inbound_claim path. Local continue/archive semantics are unchanged;
archived rows are no longer offered as continuable.

* fix(plugins): serialize plugin session binds per session

Review P2: the bind rollback checked the attempt id and then awaited the
unbind, so a concurrent Continue could install a newer binding between the
two and lose it to the older attempt's rollback. All session binds for one
session key now queue through an in-process tail, so a failing attempt can
only ever roll back its own binding; the attempt-id guard stays as defense
against out-of-band rebinds.

* refactor(codex): extract node-continue and session-bind modules for the LOC ratchet

Pure movement to satisfy the changed-file TypeScript size ratchet: the
paired-node continue flow moves to session-catalog-node-continue.ts and
session-catalog-node-adoption.ts, catalog paging/parsing helpers to
session-catalog-parsing.ts, the Control UI session-bind seam to
session-conversation-binding.ts (+ session-key helpers), and the internal
conversation-id/channel-prefix helpers to utils. transcript-mirror.ts is
restored to main byte-for-byte; the node history builder constructs a plain
CodexThread (only id is required) instead of widening signatures.

* fix(plugins): satisfy preserve-caught-error in the session-bind rollback

* fix(codex): gate node continue on operator.admin and resume by CLI session id

Second review round: (1) bound node turns pass canMutateCodexHost only for
owners/admins, while sessions.catalog.continue is operator.write — a
write-scoped Control UI client could adopt a node session whose every turn
would then be rejected. The gateway now forwards the caller's scopes to the
provider (SessionCatalogContinueProviderParams.clientScopes) and codex fails
node continues closed without operator.admin, before the dedupe join so a
non-admin cannot ride an admin's in-flight operation. (2) The node binding
stored the app-server threadId, but codex exec resume takes the CLI session
id and forked threads share a session tree where the two differ; the binding
now prefers record.sessionId.

* fix(codex): defer node-session restore until the binding is durable

Third review round: continuing an archived adopted node session unarchived
it before the gateway installed the conversation binding, so a failed bind
left a visible session with no node routing. The unarchive now rides
afterConversationBound; finalizeNodeAdoptedSession already covers both the
initializing and finalized marker states, so the eager restore helper is
deleted.
2026-07-13 19:54:31 -07:00
Vincent Koc
1b43d71440 refactor(channels): privatize approval channel list (#106447) 2026-07-13 22:35:26 +08:00
Vincent Koc
60e8d4f021 refactor(queue): remove dead summary reset (#106440) 2026-07-13 22:14:37 +08:00
Peter Steinberger
26312fd0b0 refactor: remove gateway and channel dead exports (#106038)
* refactor: remove gateway and channel dead exports

* style: format config reload test

* refactor: remove newly dead gateway exports

* test: preserve approval audience coverage

* refactor: preserve gateway contracts while pruning exports

* test: retain gateway regression coverage

* test: restore gateway policy coverage

* test: close dead-export CI gaps

* fix: reconcile dead exports with latest main

* refactor: remove newly dead gateway runner export

* test: remove private worker verifier coverage

* test: preserve weak secret docs coverage

* fix: avoid gateway health import cycle

* fix: preserve node pairing type contract

* style: format talk relay test

* fix: preserve gateway protocol generation contract

* chore: refresh dead export baseline

* fix: preserve loaded target compatibility exports

* fix: preserve plugin SDK declaration resolution

* chore: refresh dead export baseline

* chore: refresh dead export baseline

* test(gateway): derive private worker service options
2026-07-13 05:47:05 -07:00
Peter Steinberger
1136757174 refactor: consolidate bounded concurrency on p-limit and p-map (#106002)
* refactor: adopt p-limit and p-map for bounded concurrency

* chore: normalize lockfile ordering

* fix: preserve memory host concurrency export

* refactor: keep media apply within size budget

* fix: drain memory concurrency before failure

* fix: satisfy memory concurrency guards

* chore: scope memory host dead-code checks

* chore: lower concurrency LOC baselines

* fix: preserve concurrency API surfaces

* chore: reconcile concurrency LOC baseline

* test: keep memory concurrency gate portable

* style: simplify concurrency drain

* ci: refresh deadcode export baseline
2026-07-13 01:53:28 -07:00
Peter Steinberger
e2ec8283c4 refactor(deadcode): trim mid-size src exports (#105888)
* refactor(deadcode): trim auto-reply and CLI exports

* refactor(deadcode): trim cron and task exports

* refactor(deadcode): trim fleet and process exports

* test(deadcode): exercise live task and process seams

* test(fleet): cover stream redaction through owner module

* refactor(security): trim dead internal exports

* refactor(secrets): trim dead internal exports

* refactor(deadcode): trim remaining src exports

* refactor(deadcode): remove test-only runtime exports

* refactor(deadcode): trim pairing test exports

* refactor(deadcode): reconcile refreshed baseline

* test(auto-reply): deduplicate queue state imports
2026-07-13 00:42:56 -07:00
Peter Steinberger
8c7f3d087f refactor: use native abort signal composition (#105947)
* refactor: use native abort signal composition

* style(discord): preserve media module LOC baseline

* chore(ci): lower abort refactor LOC baselines
2026-07-12 22:39:43 -07:00
Peter Steinberger
1b313dc4d4 chore(tooling): enforce indexed access in core tests (#105375)
* test(tooling): enforce indexed access in core tests

* test(tui): constrain theme environment overrides

* test(doctor): enforce indexed access in migration fixtures
2026-07-12 14:29:52 +01:00
Peter Steinberger
d325b6c76b fix: preserve parent abort reasons through timeouts (#105071) 2026-07-12 09:02:12 +01:00
Peter Steinberger
6b95f98fe7 fix(core): make indexed access explicit across remaining src (NUIA phase 3b) (#104773)
* fix(core): make indexed access explicit in auto-reply, infra, and config

Part 1/3 of the src NUIA phase-3b burn-down (#104600): iteration and
destructuring over index reads, boundary guards on parsed input, and
named invariants. Config path walkers bind the path head once; SQLite
migration key handling is hoisted without query-shape changes.

* fix(core): make indexed access explicit in cli, gateway, commands, security, shared

Part 2/3: argv/token selection restructured, gateway event/attachment
invariants named, security parsers stay fail-closed (invariant
violations throw), edit-distance matrices access checked entries.

* fix(core): make indexed access explicit across remaining src surfaces

Part 3/3: channels, plugins, process, cron, plugin-sdk, media, logging,
tui, hooks, daemon, and small directories. Latent bug fixed: a tailnet
resolver could leak undefined through a string|null contract and now
fails with a descriptive local error.

* fix(core): keep optional boundaries optional after per-commit review

Review findings: expectDefined misused where absence is a legitimate
state. CLI --profile/route-args missing next tokens take their existing
miss paths; help normalization compares --help against the last
positional again; first-time plugin install spreads absent cfg.plugins;
denylist scan iterates manifest dependency entries instead of throwing
on omitted sections; tailnet resolver returns a guaranteed string at
the source instead of a caller-side undefined throw.

* refactor(core): closed-key provider labels and honest optional passthroughs

PROVIDER_LABELS becomes a satisfies-typed closed record (static reads
provably defined; dynamic lookups go through providerUsageLabel with
honest string|undefined). Status-scan overview passes its optional
params through unchanged instead of asserting them.

* fix(channels): make getChatChannelMeta honestly optional

The original signature claimed ChatChannelMeta while leaking undefined
on bundled channel id metadata drift; three of four callers already
handled absence. The return type now says so, and the one assuming
caller falls back to the raw channel label.

* fix(core): index-safety for post-rebase main drift

Covers the sqlite-sessions flip and auth-source-plan code that landed
mid-phase, plus the channel-validation test consuming the now honestly
optional getChatChannelMeta.

* refactor(channels): split chat-meta accessors along the SDK contract

getChatChannelMeta keeps its shipped plugin-SDK signature (defined for
bundled ids, fail-loud on impossible misses); new findChatChannelMeta
carries the drift-tolerant optional contract for core auto-enable and
formatting paths.

* fix(qa-channel): own channel metadata instead of a guaranteed-undefined catalog lookup

qa-channel spread getChatChannelMeta over an id that is never in the
bundled catalog, shipping an empty setup meta by accident; the fail-loud
SDK accessor exposed it. The channel now declares its metadata once.

* fix(gateway): heartbeat projection lookahead is optional at the transcript tail

expectDefined wrapped messages[i + 1] whose absence on the final message
is the normal case; the adjacent ternary already handled it. Restores
the plain optional read with an explicit guard in the pair condition.

* fix(plugin-sdk): channel plugin factory tolerates non-bundled channel ids again

createChannelPluginBase spreads bundled catalog meta for ANY channel id,
where absence is the normal case for external plugins; the resolver is
honestly optional again while the exported bundled-id accessor keeps the
fail-loud contract.

* fix(core): spreads of optional config sections stay optional

Fresh-setup and first-install paths (crestodian setup inference, hook
installs, agent config base, target agent models) legitimately lack the
section being rebuilt; spreading undefined is the shipped {} semantics.
Removes the remaining gratuitous assertion wraps found by tree audit.
2026-07-11 20:47:34 -07:00
Peter Steinberger
c48ed95533 feat(ui): merge Instances into a unified Devices page (#104561)
* chore(i18n): regenerate locale bundles for Devices rename

* chore(i18n): regenerate locale bundles for Devices rename

* chore(i18n): sync locale metadata for Devices rename

* fix(gateway): stop tracking ephemeral control-plane clients in presence

* feat(ui): merge Instances into a unified Devices page

* docs: point Instances references at the unified Devices page

* chore(i18n): reconcile locale metadata after rebase

* fix(gateway): keep test-mode clients tracked in presence; refresh docs map
2026-07-11 13:36:07 -07:00
Song Zhenlin
416d113a2c fix(utils): use UTF-16 safe truncation for long URL timeout logs (#104301)
* fix(utils): use UTF-16 safe truncation for long URL timeout logs

* test(utils): cover UTF-16 timeout log boundaries

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-11 12:27:03 -07:00
Peter Steinberger
1bcc4c5e70 feat(gateway): add cooperative host suspension (#103618)
* feat(gateway): add cooperative suspension preparation

* style: satisfy suspension lint checks

* test(gateway): reset work admission between shared suites

* fix(gateway): reject upgrades during suspension

* fix(gateway): preserve admitted work during suspension

* test(gateway): isolate suspension and restart state

* fix(gateway): close suspension false-ready gaps

* refactor(protocol): slim suspension declaration graph

* refactor(plugin-sdk): sever protocol registry edges

* fix(gateway): preserve admitted restart follow-ups

* fix(gateway): make suspension recovery fail closed

* fix(protocol): keep validation formatter re-export only

* test(gateway): simplify deferred fixture type

* style(gateway): clarify suspension entry name

* fix(gateway): retain detached work admission
2026-07-10 20:24:53 +01:00
Vincent Koc
f79e885985 fix(telegram): harden spooled turn adoption (#103695)
* fix(telegram): harden spooled turn adoption

* fix(telegram): retain fence through adoption

* fix(auto-reply): await queued admission hooks

* fix(telegram): clean up adoption integration

* fix(telegram): break spool retry import cycle

* fix(telegram): serialize spool timeout with adoption
2026-07-10 07:08:35 -07:00
Eva
ae63a48e94 fix(auto-reply): recover stranded message-tool finals by default (#99536)
In messages.visibleReplies "message_tool" sessions, a successful agent turn that produced a substantive private final without calling message(action=send) previously left the user with silence and only an operator log. The gateway now enqueues one protected front-of-queue retry prompting the model to deliver the reply, and falls back to a sanitized visible diagnostic when the retry cannot be enqueued or also strands. Queue overflow protection is unified with the in-flight-aware drop policy (skip in-flight or protected items, reject when nothing is droppable), rejected overflow no longer refreshes the drain debounce, heartbeat turns are excluded from recovery, and recovery retries no longer share the client turn's queued-turn lifecycle.

Fixes #85714

Thanks to Eva (@100yenadmin) for the contribution.
2026-07-10 14:01:01 +05:30
tzy-17
2f7e2aee15 fix(queue): prevent applyQueueDropPolicy from selecting in-flight items as overflow victims (#103284)
* fix(queue): prevent applyQueueDropPolicy from selecting in-flight items as overflow victims

When a burst of inbound messages hits the followup queue cap while the
head item is mid-delivery, applyQueueDropPolicy can select that same
in-flight item as an overflow victim. With the default
dropPolicy: "summarize", the in-flight item ends up recorded in the
overflow summary as dropped even though it is still being delivered,
producing a contradictory record where the same message is both
answered and reported as unanswered-due-to-overflow.

The fix introduces an optional `inFlight` Set parameter to
applyQueueDropPolicy and drainNextQueueItem. The followup queue state
now owns a shared inFlight set that is:
- Populated by drainNextQueueItem during the await run(next) window
- Populated by the collect-merge drain path for activeGroupItems
- Passed to applyQueueDropPolicy in enqueueFollowupRun

The drop policy now computes an effective queue length that excludes
in-flight items, and skips them when selecting splice victims.

Fixes #103246

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(queue): align pending depth with active deliveries

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-10 06:53:29 +01:00
ANIRUDDHA ADAK
991262e2e7 fix(utils): fetchWithTimeout ignores caller-provided AbortSignal in RequestInit (#102951)
* fix(utils): fetchWithTimeout ignores caller-provided AbortSignal in RequestInit

* fix(fetch): preserve caller abort through response body

* test(fetch): satisfy abort rejection lint

* test(mattermost): satisfy promise executor lint

---------

Co-authored-by: Aniruddha Adak <aniruddhaadak80@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-10 00:39:18 +01:00
Peter Steinberger
5ef269c2bb fix: keep bounded Unicode text valid across UTF-16 boundaries (#102823)
* fix(text): keep bounded output UTF-16 safe

Co-authored-by: 黄剑雄0668001315 <huang.jianxiong@xydigit.com>
Co-authored-by: 0668000539 <shu.zongyu@xydigit.com>
Co-authored-by: 张鹊平0668001085 <zhang.queping@xydigit.com>
Co-authored-by: 赵旺0668001248 <zhao.wang1@xydigit.com>
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
Co-authored-by: lizeyu-xydt <li.zeyu@xydigit.com>

* chore(changelog): defer release note to automation

* refactor(qa-lab): keep text helper behind SDK

---------

Co-authored-by: 黄剑雄0668001315 <huang.jianxiong@xydigit.com>
Co-authored-by: 0668000539 <shu.zongyu@xydigit.com>
Co-authored-by: 张鹊平0668001085 <zhang.queping@xydigit.com>
Co-authored-by: 赵旺0668001248 <zhao.wang1@xydigit.com>
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
Co-authored-by: lizeyu-xydt <li.zeyu@xydigit.com>
2026-07-09 15:21:23 +01:00
Bryan Tegomoh, MD, MPH
3d442153f6 fix(agents): avoid false unscheduled note after shell cron add (#101807)
* fix(agents): count shell cron reminder adds

* fix(agents): narrow shell cron reminder tracking

* fix(agents): handle shell runner variants

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-07 14:45:10 -07:00
Vincent Koc
f565138ddc refactor(deadcode): localize core helpers (#101869) 2026-07-07 13:16:57 -07:00
Vincent Koc
2e7ffca8ff fix(core): preserve UTF-8 byte truncation boundaries 2026-07-04 17:48:21 -07:00
Dallin Romney
194dd6fe50 refactor(security): consolidate secret primitives (#99746)
* refactor(security): consolidate secret primitives

* fix(plugin-sdk): align secret display metadata

* chore(plugin-sdk): refresh API baseline after rebase

* refactor(security): trim secret primitive consolidation
2026-07-04 16:38:13 -07:00
Dallin Romney
1a04c9c2ad refactor: consolidate string reader mechanics (#99676)
* refactor: consolidate string reader mechanics

* fix: declare model catalog normalization dependency

* fix: keep model catalog dependency-free

* refactor(core): keep string readers internal
2026-07-03 18:00:07 -07:00
Dallin Romney
c7aca4f029 refactor: consolidate async timing helpers (#99721) 2026-07-03 17:58:15 -07:00
Peter Lee
4abdf0f3b5 fix(logging): redact Telegram bot tokens from timeout URLs (#99428)
* fix(net-policy): redact Telegram bot tokens from timeout URLs

* fix(net-policy): extend Telegram bot token redaction to all hostnames

* refactor(net-policy): centralize bot token URL redaction

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-03 05:05:38 -07:00
Yuval Dinodia
150ca2fedd fix(agents): keep merged delivery routes account-bound (#98240)
* fix(agents): keep merged delivery routes account-bound

mergeDeliveryContext gated route-field crossing on channel only, so a
completion origin that knew its account but not a concrete target
inherited a different account's to/threadId on the same channel. A
subagent, cron, or media completion for bot-a could be addressed to
bot-b's chat but sent through bot-a (cross-account misroute) or dropped.

This restores the account-bound guard added in 1ed8592467 and removed as
collateral by 025db6cf9e (PR #89949); same-account and missing-account
merges still backfill so the media route-pin path is preserved. Restores
the deleted regression test.

* fix(agents): centralize account-bound completion routes

---------

Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
2026-06-30 18:32:35 -07:00
zengLingbiao
09ebc1d566 test(utils): add unit tests for chunkItems (#98219)
Add 8 test cases covering fixed-size array splitting, empty input,
size <= 0, size=1, size > length, exact division, readonly input
preservation, and fractional size behavior.
2026-06-30 18:15:24 -07:00
ly-wang19
6285f5c776 fix(utils): keep reply directive ids unicode-safe (#96938)
* fix(utils): keep reply directive ids unicode-safe

* test(utils): catch trailing lone surrogate in hasUnpairedSurrogate helper

Per PR #96938 review: the test helper missed a high surrogate at end of
string because charCodeAt(out-of-bounds) returns NaN, and NaN comparisons
are always false. Guard bounds explicitly so the truncation test actually
proves what it claims, plus two cases pinning helper behavior.

* chore(utils): rerun QA smoke to confirm memory-index timeout flake on #96938

---------

Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
2026-06-30 17:47:39 -07:00
lin-hongkuan
2906d6c38f fix(auth): strip controls from masked API keys (#96445)
* fix(auth): strip controls from masked API keys

* test(auth): cover DEL and C1 masked key controls

* chore: retrigger PR checks

---------

Co-authored-by: lin-hongkuan <lin-hongkuan@users.noreply.github.com>
2026-06-29 11:15:55 -07:00
lin-hongkuan
bc7f0f1223 fix(secrets): strip control characters from secret input (#96444)
* fix(secrets): strip control characters from secret input

* chore: retrigger PR checks

* fix(web-content): strip controls from provider secrets

---------

Co-authored-by: lin-hongkuan <lin-hongkuan@users.noreply.github.com>
2026-06-29 10:51:08 -07:00
linhongkuan
8f9beb7766 fix(reply): sanitize explicit reply directive ids 2026-06-28 18:51:38 -07:00
linhongkuan
31e941c3fc fix(context): count fullwidth chars in token estimates (#96442) 2026-06-25 14:36:04 +08:00
Peter Steinberger
025db6cf9e fix(agents): pin media requester route at task start 2026-06-23 00:09:20 +05:30
Peter Steinberger
1ed8592467 fix(agents): keep fallback routes account-bound 2026-06-23 00:09:20 +05:30
NianJiu
1bfa2787b5 fix(exec): resume agent turn for native chat exec approvals (#93949)
* fix(exec): resume agent turn for native chat exec approvals (issue #93918)

Extend the inline approval-pending path that PR #85239 added for webchat to
every bundled chat channel that ships an `approval-handler.runtime`
adapter (Telegram, Discord, Slack, Signal, WhatsApp, iMessage, Matrix,
Google Chat, QQ Bot, plus webchat). When the originating turn can be
approved in the same chat, the gateway resolves the approval in place and
the agent waits inline for the command output instead of terminating the
run on the "approval-pending" tool result.

Before this fix, native chat approvals landed in the fire-and-forget
`sendExecApprovalFollowup` path. The followup either failed silently
against the agent dispatch and fell through to a direct delivery to the
operator, or never reached the agent at all; either way the model never
saw an "Exec running / Exec finished / Exec denied" event. The operator
had to send a follow-up message to recover the turn, and a new approval
was minted because the original run had already ended.

The change:

- Introduces `NATIVE_APPROVAL_CHANNELS` and `isNativeApprovalChannel`
  in `src/utils/message-channel-constants.ts`, listing the channels that
  ship a native chat approval client. `webchat` is included so the
  single-channel check inside `shouldAwaitGatewayApprovalInline` can
  move from "this one id" to "any native approval client".
- Replaces the `INTERNAL_MESSAGE_CHANNEL` equality check in
  `shouldAwaitGatewayApprovalInline` with `isNativeApprovalChannel`,
  preserving the `approvalFollowupMode` opt-out and the existing
  `unavailableReason === null` gate.
- Adds unit tests asserting inline resolution and inline denial for
  every native approval channel, plus a regression test that
  non-native channels (e.g. feishu) and explicit `approvalFollowupMode`
  settings still take the fire-and-forget path.
- Adds a `NATIVE_APPROVAL_CHANNELS` test in
  `src/utils/message-channel.test.ts` to lock the membership and the
  negative cases.

Refs https://github.com/openclaw/openclaw/issues/93918

* fix(lint): restore InternalMessageChannel type export lost during rebase

Rebase on upstream/main dropped the InternalMessageChannel type alias
from message-channel-constants.ts, breaking the plugin-sdk boundary
.dts check ('has no exported member named InternalMessageChannel').
message-channel.ts was also re-importing the type only to re-export
it, triggering the oxlint no-unused-vars rule.

- Re-add 'export type InternalMessageChannel = typeof INTERNAL_MESSAGE_CHANNEL'
  in message-channel-constants.ts so the public re-export is valid.
- Drop the redundant 'type InternalMessageChannel' from the local
  import in message-channel.ts; the value-side import is what the
  file body actually needs.

* test(exec): align native approval routing expectations
2026-06-18 16:41:04 -04:00
Vincent Koc
1075f3819c refactor(utils): narrow helper exports 2026-06-17 17:13:29 +08:00
Vincent Koc
c09ed1954f refactor(utils): trim delivery queue helpers 2026-06-17 17:10:12 +08:00
Vincent Koc
08e0b8cf6b refactor(utils): hide usage pricing types 2026-06-17 17:02:24 +08:00
Vincent Koc
102c1f4ec7 refactor(utils): trim helper type exports 2026-06-17 16:42:06 +08:00
苹果小姐
a34ddce9a2 refactor: remove dead code and improve string concatenation
Refs #91117
2026-06-16 00:11:18 +08:00
Jayesh Betala
e8b35a8280 fix(status): render sub-1000 token counts as plain integers (#89736)
* fix(status): render sub-1000 token counts as plain integers

formatKTokens always divided by 1000 and appended "k", so token counts
below 1000 rendered as misleading fractional k in `openclaw status`
output (e.g. 999 rounded up across the boundary to "1.0k", 420 -> "0.4k",
a 300-token cache write -> "write 0.3k").

Guard value < 1000 to render the plain rounded integer, matching the
canonical formatTokenCount convention (src/utils/usage-format.ts). The
>=1000 "k" behavior is unchanged. Adds focused regression tests for the
0/420/999/1000/12000 boundary and small-session/small-cache status lines.

Fixes #89735

* fix(status): reuse canonical token formatter

* refactor(status): extract lightweight token formatter

---------

Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
2026-06-14 10:24:42 +08:00
Ayaan Zaidi
47fc1c288b test(reply-queue): cover overflow mutation during drain 2026-06-08 22:03:11 +05:30
yetval
51dbc2c60f fix(reply-queue): remove the drained item by reference instead of front index
drainNextQueueItem captured items[0], awaited the run, then shift()-ed
index 0 assuming it still held the item it ran. Concurrent inbound
messages mutate the same shared items array, and at or over cap
applyQueueDropPolicy splices items off the front, so a burst arriving
while item[0] is in flight can shift a different, still-undelivered
survivor into index 0. shift() then deletes that survivor: it is never
run and is not counted in the overflow summary, so the agent silently
ignores a message it should have answered.

Remove the item that actually ran by identity via a new
removeQueuedItemsByRef helper, and apply the same reference-based
removal to the collect path in drain.ts, which had the same positional
splice(0, groupItems.length) assumption after an awaited group run.
2026-06-08 22:03:11 +05:30
Vincent Koc
e2524e0438 fix(ci): break plugin import cycles 2026-06-07 19:03:38 +09:00
Peter Steinberger
ef08c83e17 docs: document utility helpers 2026-06-04 20:12:49 -04:00