parseSettingsResponse and applySettingsUpdate read/wrote a dead
`autoDiscover` key, while every consumer (monitor/settings-helpers.ts)
and the settings migration use `autoDiscoverChannels`. As a result the
settings-store hot-reload override for auto-discover-channels never took
effect and the migration re-fired on every restart. Align the field name
and add a regression test covering both the load and subscription paths.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tlon): stop reconnecting after SSE client close
* fix(tlon): cancel SSE reconnect timers when monitoring stops
Preserve the original Tlon SSE reconnect fix while canceling both retry timers at stopReceiving(), the real monitor shutdown boundary.
Verified against an authenticated, SSRF-guarded loopback SSE server; both reconnect waits settle immediately without changing replay, close cleanup, or uninterrupted reconnection.
Co-authored-by: wahaha1223 <0668001153@xydigit.com>
* fix(tlon): prepare conflict-free reconnect refresh
Prepare a conflict-free refresh of the original Tlon SSE shutdown correction without changing its ownership or losing the contributor commit.
Co-authored-by: wahaha1223 <0668001153@xydigit.com>
* fix(tlon): cancel reconnect timers when monitoring stops
Cancel both Tlon SSE reconnect waits at stopReceiving(), the monitor-owned shutdown boundary, while preserving guarded HTTP, durable same-channel replay, and the original contributor's fix.
Verified with authenticated loopback SSE, both real retry delays, normal reconnection, all 252 Tlon tests, extension type checks, lint, and focused owner regressions.
Co-authored-by: wahaha1223 <0668001153@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* [AI] fix(tlon): use waitUntilAbort to fix monitor abort-race hang
Replace ad-hoc abort listener with the shared waitUntilAbort
primitive, hoist pollInterval for unified finally cleanup, and
add .unref() per codebase convention.
The original code registered an abort listener inside a Promise
constructor without checking signal.aborted first. Per the
AbortSignal contract, a listener attached after abort has fired
is never called, so the monitor hung without running cleanup.
The fix reuses waitUntilAbort from plugin-sdk/channel-outbound,
which correctly handles already-aborted, normal, and absent
signals. pollInterval is hoisted and cleared in finally as a
single cleanup owner.
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes#114886
* fix(tlon): prove abort-safe Urbit monitor cleanup
---------
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
* build(deps): remove npm shrinkwrap; mirror pnpm lock into transient package locks
npm 12 removed shrinkwrap (command + tarball/root loading). Delete all 82
committed npm-shrinkwrap.json files and stop publishing lockfiles; keep
pnpm-lock.yaml as the single reviewed dependency boundary. The generator
becomes scripts/generate-npm-package-lock.mjs and feeds plugin bundling via
a transient package-lock.json + npm ci (works on npm 11 and 12). Tarball
validation treats the published 2026.7.2 beta train as a shrinkwrap
transition; self-update npm detection now uses install topology instead of
the shipped shrinkwrap.
* fix(deps): repair lint, deadcode, and test-type lanes for the npm 12 migration
- sort integrity comparisons with an explicit comparator (oxlint)
- keep resolveBunGlobalNodeModules module-local (knip unused-export gate)
- model npm pack --json as npm<=11 array / npm 12 name-keyed object
- default calver destructuring in the tarball test fixture
* feat(channels): add channel-owned setup contracts
* test(channels): align legacy setup fixtures
* chore(channels): regenerate config and SDK baselines after rebase
* fix(update): run fresh doctor after current-process core changes
* fix(channels): align add pre-scan with execution precedence
* style(cli): format channels-cli test additions
* fix(channels): restore option-before-positional channel resolution via metadata arity scan
* fix(channels): keep help flags out of metadata arity escalation
* test(update): mock fresh post-update doctor in current-process suites
* style: format review fixes and correct entrypoint mock type
* fix(channels): register only modern contract options for dual-publishing plugins
* test(update): align downgrade suites with fresh-doctor child invocation
* docs(channels): record empty-contract and input-forwarding invariants
* fix(line): keep the shipped --token switch as a channel access token alias
* fix(signal): stop treating exact cross-family loopback endpoints as bind-aligned
* chore(config): regenerate docs config baselines after second rebase
* style: format rebased channels add tests
* fix(channels): enforce field-key and flag-name agreement in setup contracts
* fix(signal): detect container endpoints for bare --http-url setup
* fix(signal): ignore unconfigured accounts in transport collision checks
* fix(channels): validate negated setup flags in contract and normalizer
* fix(signal): preserve existing transport kind when setup detection is unreachable
* style(signal): use direct boolean check in collision guard
* style(signal): type test config literals
* docs(update): record two-read design of fresh-doctor validation gate
* fix(channels): satisfy post-rebase architecture gates
* docs: refresh channel setup map
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Media-facts program PR 5 — the planned additive bridge. Structured
facts travel alongside existing prompt text through prompt prelude,
GetReplyOptions, FollowupRun, queue collect/defer/retry, active
steering, embedded/CLI runs, Gateway offloads (offloadedRefs no longer
discarded; AgentContentPhaseResult carries media), Tlon, and late-media
projection — with prompt bytes golden-equal everywhere. Facts follow
the same adoption/idempotency/late-append lifecycle as prompt text.
Intentionally +175 prod LOC per the audit; the facts-first
hydration/prune deletion PR consumes this carrier next.
* refactor(channels)!: shrink ChannelSetupInput to a generic envelope with a deprecated compatibility tier
* fix(channels): keep ChannelSetupInput structurally assignable without an index signature
* docs: regenerate docs map
* fix(tlon): bound Urbit auth response body drain to prevent OOM
Replace the unbounded response.text() body drain (discarded after reading
to finalize set-cookie headers) with a streaming reader capped at 64 KiB.
When no body stream is available, fall back to text() for compatibility.
A hostile or misconfigured Urbit endpoint could previously stream an
arbitrarily large body into memory during login.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(tlon): add curly braces to drain loop for lint compliance
* fix(tlon): remove unbounded text() fallback, cancel reader in finally
Remove the unbounded response.text() body-less fallback path. When no body
stream is available, there is nothing to drain — cookie headers are already
finalised. Move reader.cancel() into a finally block so the reader is
released even when read() throws.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(channels): shared supplemental sender gating, allowlist-match adoption, outbound mop-ups
* fix(plugin-sdk): skip-aware media sequence with text fallback for empty URLs
* chore(plugin-sdk): align surface budgets after rebase
* test(qqbot): type media sender mock calls
* fix(plugin-sdk): distinguish empty media sequences
* fix(plugin-sdk): track void media sends
* feat(mattermost): add thread.requireExplicitMention to opt out of thread auto-follow
Mattermost treats any reply in a thread the bot has participated in as an
implicit mention, so requireMention only gates the first message and the bot
then answers follow-ups addressed to other people for the participation TTL.
Slack exposes channels.slack.thread.requireExplicitMention for exactly this;
Mattermost had no equivalent and its strict schema rejected the key.
Add channels.mattermost.thread.requireExplicitMention (channel + per-account),
mirroring Slack. When set, thread participation no longer counts as a mention.
Default (unset/false) keeps today's auto-follow behavior unchanged.
Related: #108269
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(channels): add implicit mention policy foundation
* feat(channels): unify implicit mention policy
Co-authored-by: leon <dodoma0919@gmail.com>
* refactor(channels): keep implicit policy inside evaluator
* fix(channels): use exported implicit mention type
* chore(channels): satisfy extension lint
* fix(config): break implicit mention type cycle
* fix(plugin-sdk): account for implicit mention config export
* refactor(config): isolate implicit mention schema
* chore(plugin-sdk): align implicit mention surface budget
* fix(config): remove unused schema re-export
---------
Co-authored-by: leon <dodoma0919@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* chore(plugins): gate deprecation hygiene in CI and purge internal deprecated usage
* fix(agents): migrate auth-profiles oauth import to OAuthProviderId
* fix(whatsapp): use durability param in delivery-recovery test
* chore(scripts): declare shouldRunDeprecationHygieneChecks in check-changed types
* test(plugins): update runtime-api guard for ssrf policy rename
* fix(tlon): keep fragment image URLs as media
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
* test(tlon): prove fragment image media send path
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
* test(tlon): remove mocked fragment outbound proof
Keep the PR evidence scoped to the helper-level regression test until live Tlon proof is available. The mocked outbound test was not sufficient for the reviewer-requested live behavior proof and triggered serialized-state review noise.
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
* docs: refresh docs map for CI
Regenerate docs/docs_map.md after syncing with upstream/main so the docs map check passes on this PR branch.
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
* fix(tlon): constrain fragment image URL matching
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
* fix(tlon): harden image URL classification
Co-authored-by: VectorPeak <garrufariw@gmail.com>
---------
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
Co-authored-by: VectorPeak <VectorPeak@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(tlon): cap SSE payload JSON.parse at 16 MiB to prevent OOM
* fix(tlon): cap SSE stream buffer and JSON.parse at 16 MiB to prevent OOM
* chore(tlon): add production-style SSE bounded proof script (#101274)
Adds extensions/tlon/proof-sse-bounded.mts which drives the real
UrbitSSEClient.processStream and processEvent against Node Readable streams
(not unit-test mocks) to demonstrate:
- normal SSE events are still delivered through the stream path;
- an unterminated stream that would grow beyond 16 MiB is rejected before
unbounded accumulation;
- a single SSE payload above 16 MiB is rejected before JSON.parse.
The script passes on this branch and fails 2/3 assertions when run against
origin/main's sse-client.ts, providing the before/after proof ClawSweeper
requested.
* chore(tlon): fix oxlint catch type in proof script (#101274)
* fix(tlon): reject oversized SSE chunk before buffer concatenation
Move the stream byte-limit check before buffer += chunkStr so a
single oversized chunk never lands in the pending buffer. The old
guard ran after concatenation, which still allowed the memory spike
this hardening is meant to prevent.
Add a single-oversized-chunk test to prove the guard fires before
the chunk is concatenated into the pending buffer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(tlon): remove PR-specific SSE bounded proof script
The proof script was review-only validation for #101274. Durable behavior
coverage lives in sse-client.test.ts (stream buffer bounding, oversized
chunk rejection before concatenation, 16 MiB boundary, normal delivery,
1000 small events). Per ClawSweeper P3 finding, drop the one-off script
from the plugin tree.
* fix(tlon): bound SSE event buffering safely
* test(tlon): avoid unsafe optional chaining
* fix(tlon): count split Unicode at SSE limit
* fix(tlon): parse split SSE delimiters at limit
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(extensions): make indexed access explicit across channel plugins
Transport-payload-safe burn-down: malformed Telegram/Discord/QQ/LINE
and sibling channel input keeps existing skip paths; no synthesized
fields, no new throws in delivery loops. Zalo escape sentinels preserve
literal matches instead of undefined replacements.
* fix(extensions): make indexed access explicit across provider and memory plugins
Stream and model iteration, tool-block guards, capture guards, and
sparse accumulators; singleton model reads carry named invariants.
* fix(extensions): make indexed access explicit across tooling plugins, flip the extensions lane
Remaining plugins (oc-path, qa-lab, browser, logbook, and siblings) plus
the tsconfig.extensions.json flag flip. Cleanup: logbook sampleFrames
NaN index at max=1, QA retry clamp at non-positive attempts, dead Canvas
probe and OpenShell no-op slice removed, twitch test setup leak excluded
from the prod lane.
* refactor(plugin-sdk): expose expectDefined via a focused SDK subpath
Extensions imported @openclaw/normalization-core directly, crossing the
external-plugin packaging boundary (it only worked because the runtime
builder bundles undeclared workspace helpers). expect-runtime joins the
canonical entrypoints JSON, generated exports, API baseline, docs, and
subpath contract test; all 78 extension imports now use the SDK seam.
Two scanner-shaped locals renamed for review-bundle hygiene.
* chore(plugin-sdk): raise surface budgets for the expect-runtime subpath
One new entrypoint with one callable export, added intentionally as the
packaging-honest seam for extension invariant helpers.
Route assistant-visible Tlon output through the shared sanitizer and prove the rendered Urbit poke payload for DMs, groups, and media captions.
Co-authored-by: masatohoshino <g515hoshino@gmail.com>