Skip to content

fix(discord-bot): ignore unmentioned Discord replies - #419

Draft
omegent-app[bot] wants to merge 205 commits into
fork/devfrom
t3-discord/8262a804
Draft

omegent-app[bot] wants to merge 205 commits into
fork/devfrom
t3-discord/8262a804

Conversation

@omegent-app

@omegent-app omegent-app Bot commented Aug 21, 2026

Copy link
Copy Markdown

Discord reply pings put the parent author in mentions without an in-content <@id>. People use replies as quotes, so Omegent was starting turns from those.

Require an explicit @mention (or bot-role mention) on replies. Thread-talk also skips replies.

Test plan

  • vp test run ThreadTalkPolicy, MentionRouter, slashCommands
  • changed-file vp check + vpr typecheck on push
  • After deploy: reply to Omegent without @Omegent should stay silent; @Omegent on a reply still starts a turn

opened by patroza in chat thread Discord · Discord

github-actions Bot and others added 30 commits August 5, 2026 15:48
Add custom "Open with" applications

Source: tim-smart#4
Source head: 8c4bdfbc5b57f6b600233244d330f9efa41dc498
Source commits: 08e1a4fb949585c3c441d6d00455fe904f72cd7b,cd43a401c6c148f1fe26cff72104ac527ea189f3,a8370e7502c552ebb064436e42e1c00f86f0946b,8c4bdfbc5b57f6b600233244d330f9efa41dc498
Imported: complete product delta from the source PR.

(cherry picked from commit 9fae005)
Load direnv environments for provider sessions

Source: tim-smart#5
Source head: 8f5fc87c13f4628c179cda44d4f32f7fe4d316b2
Source commits: e4f07014d39964fde2498bcb35588974cc5e6232,0d1463af61e0bd174f698b2519ebf3b207a2eaca,a66e4160d5f4b79140ec8fbcbc6aa66af750a991,8f5fc87c13f4628c179cda44d4f32f7fe4d316b2
Imported: complete product delta from the source PR.

(cherry picked from commit 0da8bfe)
Add unsigned retry for commit signing failures

Source: tim-smart#6
Source head: 7d65c5a224e97a6b811b0a84892f1fda065c5963
Source commits: 18ee567ecfdb11c9372153127b26b5cf57213a76,72a6fae23c86708080c4fed346d5bf0f136f0221,6614b28239ed2330a8f601357a413f2d50da195a,ec169369daa554541511aa28f551b36f3dd26485,7d65c5a224e97a6b811b0a84892f1fda065c5963
Imported: complete product delta from the source PR.

(cherry picked from commit 03671a2)
Add /new command for contextual threads

Source: tim-smart#7
Source head: 2051a8003041fe2806fcb4bc7a0d8940579fc543
Source commits: 2051a8003041fe2806fcb4bc7a0d8940579fc543
Imported: complete product delta from the source PR.

(cherry picked from commit 4d94f31)
Add session dashboard board

Source: tim-smart#8
Source head: d9f8e4d0a8dc22231ca315f3c595c3597f3b13e5
Source commits: 268fb8df9863ffbda51b975a8dbe68f11c41500c,dde20f271f674da22dd8f3a08201c2acf5e58ee5,df4a145e7b2cd2dc17a7a595267d2d8eb0a2a3f0,ce5723ddb0bf630a18d4cb8227b5344d12626e72,ad8c1a6af41161e1fc38a52f681b306517c7b918,6281887e6125317da0c7b4252d59bfd41c9bf35e,550db6316c634febdbe1cb27334d1347c23c7b2a,d9f8e4d0a8dc22231ca315f3c595c3597f3b13e5
Imported: complete product delta from the source PR.

(cherry picked from commit cd0e281)
Recover interrupted provider turns after server restarts

Source: tim-smart#9
Source head: b181832560177250b90bbfe07b0882c9e5b93493
Source commits: 7f69028a25be21f1882ecba14b62f387ad60cf2a,1d52bce1376766d804ef884d7d50b8b6d1b48cf7,b181832560177250b90bbfe07b0882c9e5b93493
Imported: complete product delta from the source PR.

(cherry picked from commit 83de8f5)
Avoid repeated thread snapshot loads during subscription retries

Source: tim-smart#10
Source head: c8c9eadb9de3026706bc3a403ca05b12d0da8dd5
Source commits: c8c9eadb9de3026706bc3a403ca05b12d0da8dd5
Imported: complete product delta from the source PR.

(cherry picked from commit 9e400c3)
Add image upload button to compact chat composer

Source: tim-smart#11
Source head: 1ff63f9b9c418ef56a46c6422d22c01de97581a8
Source commits: 1ff63f9b9c418ef56a46c6422d22c01de97581a8
Imported: complete product delta from the source PR.

(cherry picked from commit 720ec65)
Truncate mobile branch toolbar controls

Source: tim-smart#12
Source head: 1b7d44428472511bc98d8f936654359ce2536901
Source commits: 1b7d44428472511bc98d8f936654359ce2536901
Imported: complete product delta from the source PR.

(cherry picked from commit dc2bbb4)
Clean up worktrees when archiving threads

Source: tim-smart#13
Source head: a23f42d6ac671ea36b8db5d03934c089a31be448
Source commits: 4a194707ed134f993502ac5fdf36a8425f1769cd,1b6688aa5b641010cb2e9dad23d36d87257403ad,9ed32aa3923fb674380564b1ffcb3268290069b9,a23f42d6ac671ea36b8db5d03934c089a31be448
Imported: complete product delta from the source PR.

(cherry picked from commit 7e02dc9)
Pass hosted app channel into Vercel web builds

Source: tim-smart#14
Source head: de6966a6784b4703145c20b84fc482703bca4fa2
Source commits: de6966a6784b4703145c20b84fc482703bca4fa2
Imported: complete product delta from the source PR.

(cherry picked from commit 6333d8d)
Allow worktrees to reuse the selected branch

Source: tim-smart#15
Source head: 2d3900ba36c9397dc4fbe879c613a809f6b45384
Source commits: cd60531253fbafc470f5a5ac18d3e44832d3376d,2d3900ba36c9397dc4fbe879c613a809f6b45384
Imported: complete product delta from the source PR.

(cherry picked from commit 5e7dff2)
Add optional worktree removal confirmation

Source: tim-smart#16
Source head: c3f509fe8f690b704bb34692d9c132c0644db777
Source commits: 76f063e983ca3c39b20f79d8ea83783ab034251a,c3f509fe8f690b704bb34692d9c132c0644db777
Imported: complete product delta from the source PR.

(cherry picked from commit 9886109)
Stop retrying unavailable thread subscriptions

Source: tim-smart#17
Source head: 1359af8ba0b146e3d49f89b72c250f681e86199d
Source commits: 1359af8ba0b146e3d49f89b72c250f681e86199d
Imported: complete product delta from the source PR.

(cherry picked from commit 7b37a7a)
…nd; green tip

Bring Tim layer tip to typecheck green by joining main ref-refresh VCS client
state with fork failureKind/worktree-cleanup contracts, restoring
filterBrowseEntries/reuse-base-branch surfaces Tim dropped, and fixing
ChatView/Board call-site type errors left by incomplete Tim joins.

(cherry picked from commit 0e24917)
Bring fork/tim typecheck/test green after main pingdotgg#2679 + Tim client-runtime
rewrite: rejoin EnvironmentSubscriptionRpcTag/localApi/ws scopes, wire
BackgroundPolicy/ResourceTelemetry layers, force openpgp for signing tests
on hosts with gpg.format=ssh, and treat TRACE2 child_exit without
child_class as hook finish (git 2.55+).
"work" is no longer an empty full-catalog query once Worktree remove
confirmation is searchable. Keep the word-wrap false-positive check and
assert the worktree setting is the sole full-catalog hit.
Source: pingdotgg#4018
Source SHA: de8fd65

Imported: bounded server activity snapshots, cursor pagination, lazy web history loading, reconnect-safe reset/dedup, and disabled eager browser sidebar hydration.
Adapted: preserved Tim thread lifecycle handling and Omega composer/minimap behavior while resolving current-stack conflicts.
Excluded: none of the source PR behavior; native mobile pagination remains separate because pingdotgg#4018 intentionally excludes it.
…#3510) (#35)

Source: pingdotgg#3510
Source SHA: 034f4936d7a1435887bb62ac3f2db61f08928cbf

Imported: native mobile lazy loading for older thread activity, a 1,000-event subscription catch-up ceiling with snapshot fallback, and synchronized stale snapshot watermarks.
Adapted: applied above the refreshed pingdotgg#4018 web/server candidate and preserved Tim lifecycle handling plus our mobile composer changes.
Excluded: pingdotgg#3510 server/web pagination duplicated by pingdotgg#4018, the later shared-hook refactor, formatting-only commits, and contract comments. The shared refactor can be revisited independently after production validation.
Source: pingdotgg#4176
Source SHA: 56b6615

Imported: O(1) command read-model maps, deleted-thread eviction, VCS cache cleanup, browser surface cleanup, preview idle TTL, and per-thread UI cleanup.
Adapted: preserved our thread settlement, snooze, and sequential worktree deletion actions while wiring upstream cleanup into the current hook.
Excluded: none.

Co-authored-by: Rusiru Sadathana <27785781+RusiruSadathana@users.noreply.github.com>
#44)

Source: pingdotgg#4506

Source SHA: f7eaa00

Imported unchanged as one candidate provenance commit.
…tgg#4558)

Imported from https://github.com/pingdotgg/t3code/pull/4558\n\nAdapted to retain our provider restart-recovery constants while replacing the local default-title check with the shared policy.
…gdotgg#4379) (#312)

Imported from pingdotgg#4379 at
a27510d060645809ae1472bba4dbb248dc624e25.

Open file previews revalidate on mount and subscribe to debounced
native filesystem watches so external edits (editors, git, agents)
show without a manual refresh.

Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Enrico Polanski <16064771+enricopolanski@users.noreply.github.com>
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
… (#328)

Imported from pingdotgg#5344 at source SHA
783fd02 (commits b623dc2 + 783fd02
squashed into one provenance commit).

Imported behavior:
- `reduceThreadStreamItems`, a pure reducer that folds a batch of thread
  stream items into one state and one persistable snapshot.
- `Stream.groupedWithin(64, 16ms)` on the live subscription so a burst of
  thread events publishes the `SubscriptionRef` once instead of per event,
  and web/mobile stop rebuilding large thread views per streamed event.
- `eventBatchSize` on `EnvironmentThreadStateOptions`, plus the upstream
  regression tests for ordered single-publication bursts and for persisting
  a settled snapshot when a batch ends with a non-persistable turn start.

Local adaptations:
- Kept our `httpSnapshotLoadAttempted` guard around the HTTP snapshot
  fallback; the call now goes through `applyItems([...])`.
- Restored `setDeleted` (removed upstream) for the terminal
  `thread-deleted` subscription failure, which never reaches the item
  stream and so cannot go through the batch reducer. Cache removal is
  shared with the reducer path via `removeCachedThread`.

Excluded:
- `tasks/todo.md`, the author's scratch checklist.

Follow-up (fork/changes, not this layer): our `reload-required` branch and
`reloadFromServer` are built on the deleted `setThread`, so rebasing
fork/changes onto this layer must re-express them against the reducer
(split the batch at the reload point, then re-enter `applyItems` with the
remainder).

Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
omegent-app Bot and others added 24 commits August 14, 2026 10:08
23 upstream commits. Sixteen conflicts — this batch is mostly
**mobile**, which is exactly where the fork carries the most product
divergence.

**Not for merging yet.** Opened for review while I investigate the
mobile slowness report; some of what's in here is a candidate
explanation.

## Resolutions

**Android sidebar header.** Upstream deleted the native header-button
module ([pingdotgg#6520](pingdotgg#6520)) so
Android falls back to the shared component. The fork's list-mode buttons
already live in that shared component, so the deletions were taken and
nothing was lost.

**ThreadComposer.** Upstream renamed `ComposerToolbarTrigger.tsx` →
`ComposerToolbar.tsx`, and the trigger to `ComposerInlineControl`
([pingdotgg#6224](pingdotgg#6224)). The fork's
usage-marked provider icon and quota note were ported onto the renamed
component; upstream's queue-count line taken (the fork had none).

**HomeScreen / ThreadNavigationSidebar.** Upstream extracted the v2-list
preference into `useThreadListV2Enabled`. Both surfaces now read that
hook with the fork's list-mode gate on top, so they still agree with
each other. Also picked up upstream's `autoSettleOnMerge`.

**Sidebar.tsx.** The fork hoists `isUnread`/`status` above this point,
so upstream's re-declaration would not have compiled. Kept the fork's,
carried upstream's explanatory note.

**Markdown tables.** Upstream's cell runs pass `props.skills`; the
fork's local `tableCellRuns` helper dropped them, so skills never
highlighted inside a table cell. Took upstream's and removed the
now-dead helper.

**build-desktop-artifact.** Kept the fork's extracted
`promoteDesktopBuildArtifacts` (it also copies directories, and has its
own test) and took upstream's new Windows self-containment check.

**AGENTS.md.** Took upstream's *How it works* / *Where code lives* /
*Taste* / *Additional tips*. Deliberately **not** its verification and
pull-request sections — upstream's "do not run repo-wide checks, CI owns
the full suite" contradicts this fork's automated ship gate, and two
contradictory policies in one agent file is worse than either alone.

## Two welds the auto-merge produced

Both compiled clean in one place and broke in another, which is worth
noting given how often this class recurs:

- `build-desktop-artifact.test.ts` got **duplicate `FileSystem`/`Path`
imports** — both sides added them. `vpr typecheck` did not flag it; only
running the test did.
- My own union in `mobile-preferences.ts` left `sanitizePreferences`
**unclosed**, taking down two unrelated mobile suites. Again typecheck
stayed silent — the transform error only surfaced under test.

## Verification

`pnpm typecheck` (0 errors) · `pnpm test` — **272 files, 0 failures** ·
`vp build` in `apps/web` · `vp check --fix`.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code) via [T3
Chat](https://t3.chat) on Discord

---------

Co-authored-by: Nick Anisimov <n.anisimov.23@gmail.com>
Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com>
Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com>
Co-authored-by: maria <maria@kuuro.net>
Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com>
Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Chris Deeming <chris@xenforo.com>
Co-authored-by: Simone <lucenz@proton.me>
Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com>
Co-authored-by: t3-code[bot] <236186684+t3-code[bot]@users.noreply.github.com>
Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
Co-authored-by: Adamulek123 <adam.bogucki@piekna.edu.pl>
Co-authored-by: Paul van Dyk <paul@vandyk.fr>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: maria <254055478+maria-rcks@users.noreply.github.com>
Co-authored-by: Taylor Bombay <taylor@warheadent.com>
Co-authored-by: Rakshith Bhat <88523594+RakshithBhat03@users.noreply.github.com>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: David Hu <davidhu314@gmail.com>
Co-authored-by: Tyler <tyler@southboundsoftware.com>
Co-authored-by: tsouth89 <tsouth89@users.noreply.github.com>
Co-authored-by: t3-code[bot] <t3-code[bot]@users.noreply.github.com>
Co-authored-by: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com>
Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
…nt (#402)

`refreshThreadShellSummary` runs on **every event in a thread** and
loaded every activity row that thread has ever produced — payloads
included — to compute one integer, `pendingUserInputCount`.

Those payloads are the tool timeline, and they are enormous. On this
deployment:

| | |
|---|---|
| Busiest thread | **10,652 activity rows / 467 MB** |
| Rows that thread's count actually derives from | **0** |
| Whole database: activity payloads | 5.01 GB |
| …rows carrying a user-input request id | **13 rows / 10 KB** (0.0002%)
|

`derivePendingUserInputCountFromActivities` only reacts to three kinds —
`user-input.requested`, `user-input.resolved`,
`provider.user-input.respond.failed` — and skips everything else, so the
read now filters on exactly those. Measured against the live database:

```
before:  10,652 rows      467.4 MB     348.8 ms
after :       0 rows        0.000 MB       6.9 ms
```

…and that is before the Effect Schema decode, array copy and sort that
followed it.

## Why this is the memory problem

systemd's per-run accounting for `t3code-server` shows every long-lived
run climbing to the 32 GB `--max-old-space-size`:

| CPU | wall | memory peak | disk read |
|---|---|---|---|
| 8h37m | 16h11m | **34.1 G** | 58.6 G |
| 2h11m | 4h02m | **32.4 G** | 23.7 G |
| 10h28m | 1d 1h | **31.8 G** | 28.8 G |

At the cap the process sits in back-to-back full GCs. That is why
**every** client — desktop and mobile alike — saw slow thread opens,
slow actions, stalled sync and dropped connections, and why restarting
the server fixed it for a while. The `stop` that preceded the last
restart had to be SIGKILLed after the 2-minute timeout.

It is also cumulative rather than sudden: the cost is per-thread and
grows with that thread's accumulated activity, which is why it felt fine
last week and bad this week.

## Not a merge casualty

Worth stating, since the suspicion was that a sync dropped something:
this hot path is **byte-identical to upstream's**. Of the ten upstream
perf/reconnect commits in range, eight are 100% present and the two
partials are files this fork deliberately rewrote. `fork/tim` has zero
perf-related commits among its 18 unmerged ones. Upstream has the same
code; our thread sizes are what make it pathological — so this is a
clean upstream candidate too.

## Changes

- `ProjectionThreadActivityRepository.listByThreadIdAndKinds` — same
ordering and row decoding as `listByThreadId`, short-circuits an empty
kind list without touching SQL.
- `refreshThreadShellSummary` passes the three kinds the deriver reads,
named next to the deriver so they stay in step.

## Verification

`pnpm typecheck` (0 errors) · `pnpm test` — 273 files, 0 failures · `vp
check --fix`. Five new repository tests cover the filtering, ordering
parity with the unfiltered list, payload decoding parity, thread
isolation, and the empty-kinds short circuit.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code) via [T3
Chat](https://t3.chat) on Discord

Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
8 upstream commits, nine conflicts, all mobile — concentrated in the composer
and outbox files this fork rewrote for its queue feature.

The notable one is pingdotgg#6543 "steer active turns by default": it removes upstream's
freshThreadBusy guard from the outbox drain, converging on what this fork has
been doing all along — hand ownership to the server, which queues follow-ups
during an active turn. The fork's comment predicted exactly that, so its version
is kept: it is now the same behaviour, plus a "deferred" drain outcome that
upstream lacks. Reporting an editor hold as success used to clear retry state
and spin beginDispatch → finish → effect forever, and upstream's boolean would
not typecheck against the DrainOutcome the rest of that function returns.

Other resolutions:

- composerImages: the fork's iOS guard (requestMediaLibraryPermissionsAsync
  hard-crashes without a usage string) and upstream's Android foreground handoff
  (pingdotgg#6324) are independent, so both are kept — including catch *and* finally, so
  a picker failure is still reported to the caller and the handoff still ends.
- T3ComposerEditor: took upstream's wrapped native view and Android
  selection-change fix (pingdotgg#6323); the fork's older block referenced a setter that
  upstream renamed, so it would not have compiled.
- SettingsRouteScreen: took upstream's runAppUpdateCheck and its new "ready"
  state, but kept the fork's bundleLabel fallback — upstream has no bundle label
  and falls through to null, which would blank that row here. Upstream replaced
  its local runUpdateCheck with the shared helper, so the fork's now-unreachable
  copy is removed rather than left to rot.
- ThreadComposer: dropped upstream's re-added queueCount prop; the fork already
  declares it from the previous sync and it would have been a duplicate.
- ThreadDetailScreen / ThreadRouteScreen / use-thread-composer-state: fork-only
  queue props and state upstream has no equivalent for; kept.
- thread-outbox-model: kept the fork's comment, which still documents the
  decision now that upstream's logic matches it.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
…changes

Two adversarial reviews found the merge left mobile with eight typecheck
errors, and the workspace typecheck I trusted had not actually run — the
`vpr` shim was not on PATH, so grepping its output for "error TS" counted an
empty stream and reported clean. `pnpm typecheck` reports them; vitest never
typechecks, so the suite stayed green throughout.

- T3ComposerEditor: the merge took upstream's wrapped native view (pingdotgg#6323) but
  none of what it calls — the `expo-paste-input` wrapper import, the
  `useNativePaste` import, and the `handlePaste` binding.
- SettingsRouteScreen: upstream's `runAppUpdateCheck` reports an "Update ready"
  state the fork's local `UpdateCheckState` union does not have, so the setter
  did not match the callback and the new `=== "ready"` branch compared
  non-overlapping types. It now uses the `AppUpdateCheckState` already imported.
  `reportUpdateFailure` went with the local helper it served.
- thread-outbox-model: upstream keeps `threadBusy` in the contract even though
  pingdotgg#6543 stopped gating on it, and its new tests still pass it. Accepted again as
  an explicitly unused field, rather than editing upstream's tests — their
  assertions then keep compiling across future syncs.
- activeThreadBusy: pingdotgg#6543 removed it upstream; the fork's copy was required by
  ThreadComposer, never read there or in ThreadDetailScreen, and superseded by
  `sendEntersQueue`. Removed end to end instead of passing a value nothing uses.

The drain comment predicted upstream would keep waiting on threadBusy; pingdotgg#6543
matched us, so it now says so.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
Merge upstream/main into fork/dev (8 commits)
…405)

Token stats were missing from Discord/GitHub turn footers. Grok was the
obvious case, but the hole was per-adapter: if a provider never emitted
`thread.token-usage.updated` (or only emitted a used-total), the footer
had model and duration and nothing else.

- **Grok / Cursor / Kimi (ACP):** parse `_meta.totalTokens` on ordinary
session updates (they often skip `usage_update` /
`PromptResponse.usage`)
- **OpenCode:** map assistant/session/step token rollups to last in/out
- **Claude:** copy input/output onto `last*` so the footer can show ↑/↓
- **Codex:** already emitted last in/out; unchanged
- **Footer:** if in/out are missing, show `usedTokens` (`grok-4.6 · 31m
31s · 140k`)

## Test plan
- [x] `vp test run` OpenCodeAdapter, ClaudeAdapter, AcpRuntimeModel,
AcpCoreRuntimeEvents, turnResponseStats, GrokAdapter, CursorAdapter
- [x] lint on the changed files

opened by [patroza](https://discord.com/users/95218063095377920) in chat
thread **Discord** ·
[Discord](https://discord.com/channels/1083767712431480922/1538080379636945017/1538080379636945017)
·
[T3](https://t3vm.tail86038f.ts.net/?thread=96f87e18-60db-4e7c-8b8e-d20f9bf54371)

Made with grok-4.6 in T3 Code.

---------

Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
## What Changed

`resolveProjectJumpTarget` now ranks matches instead of treating every
name a project answers to as equal:

- `primaryProjectNames()` — title, workspace folder basename, and the
project's *own* repository identity (`canonicalKey` / `displayName` /
`name` / `owner/name`).
- `remoteProjectNames()` — names derived from
`repositoryIdentity.remotes[]`.

A primary match always beats a remote-only match; thread recency only
breaks ties *within* a tier. Remote matches still resolve when nothing
else answers to the name, so a fork addressed by its upstream name keeps
working.

## Why

`t3code://open/project?project=<name>` folded all of those names into
one flat set and then picked whichever match had the most recent thread
activity. Remotes are deliberately part of the identity (see
`packages/contracts/src/environment.ts:122-124`: a fork answers to more
than one repository) — but so does any clone that happens to carry an
unrelated remote.

Concretely: a `macs-holding/scanner` checkout had an extra remote for a
subtree workflow:

```
effect-app-libs  https://github.com/effect-app/libs.git
origin           https://github.com/macs-holding/scanner.git
```

That made the scanner project answer to `libs`. Scanner is far busier
than libs, so the recency tie-break won and a `Hyper+i` shortcut bound
to `project=libs&action=latest` reliably opened a **scanner** thread
instead. Because matching spans every connected environment, the busiest
clone anywhere won — the jump landed in a different environment's
scanner from any machine.

Tiering keeps recency useful (it still picks between several real `libs`
projects across environments) while making a stray remote unable to
hijack the name.

## Downstream Fork Relationship

Base branch `fork/dev`. Touches `apps/web` only; no dependency on
another open PR.

## Verification

- `pnpm vp test run --project unit src/projectJump.test.ts` in
`apps/web` — 5 passed, including two new cases:
- a project matched *only* via a secondary remote still resolves (fork
fallback preserved);
- a busier scanner clone carrying an `effect-app/libs` remote no longer
outranks the real `libs` project, and the returned `latestThread` is
libs'.
- `pnpm exec tsgo --noEmit` in `apps/web` — clean.
- Reproduced the original bug end to end before the fix (deep link
landed on `scanner / Cut Empasa Release Rehearsal`) and confirmed
unaffected names (`project=.config`) always resolved correctly.

## Checklist

- [x] This PR is small and focused
- [x] I explained what changed and why
- [ ] I included before/after screenshots for any UI changes (n/a — no
UI change)
- [ ] I included a video for animation/interaction changes (n/a)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
15 upstream commits, fourteen conflicts. The bulk come from pingdotgg#6572 (open remote
environments in your local editor over SSH), which lands squarely on surfaces
this fork has rewritten.

The one that mattered: OpenInPicker. The fork replaced that component wholesale
with its own "open with" system (449 lines against upstream's 74), so neither
side could win outright. Upstream's remote path is ported into the fork's
dispatch instead: for a remote environment, openInEditorMutation launches the
editor on the *server*, which is the wrong machine — the viewing machine now
gets a Remote-SSH deep link. That matters more here than upstream, since this
fork routinely drives several environments at once.

Other resolutions:

- ElectronShell: upstream moved editor schemes into SAFE_EXTERNAL_PROTOCOLS,
  which returns any URL carrying one. The fork validates hostname and path
  first, and that check already permits upstream's
  `<scheme>://vscode-remote/ssh-remote+…` links, so the fork's two-tier check
  stays — with the scheme list now derived from upstream's constant rather than
  hardcoded, so a new remote-capable editor is covered automatically.
- LegacySidebar: the shared context after the conflict was the fork's, so
  upstream's block could not be spliced in; kept the fork's conditional
  structure and ported upstream's Button migration onto it by hand.
- ChatView: took upstream's glass Button styling for scroll-to-end, kept the
  fork's unread-activity dot and label.
- build-desktop-artifact: upstream's pingdotgg#5877 removed WINDOWS_ASAR_UNPACK, so the
  fork's assertion had no symbol left to assert against; took upstream's. Its
  linux assertion then exposed a real gap — the fork registered t3code-dev on
  mac but not linux, while declaring an explicit MimeType. Both now cover the
  dev scheme.
- PortExposure test: pingdotgg#6572 added hasListenerOnHost to NetServiceShape; the
  fork's stub predated it.
- IPC, ws, contracts, ChatHeader, toast, index.css: unions of independent
  additions, minus the duplicate imports the union produced.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
Merge upstream/main into fork/dev (15 commits)
## Summary

The PR panel mapped every unclassified `gh` exit to **GitHub CLI command
failed.**, so the real guest-wrapper reason never reached the UI.

That is what `pingdotgg#6613` (`pingdotgg/t3code`) showed this time. The live
t3vm image already has ops #80 (host-qualified `--repo`). The product
`#373` shim fix is still on `fork/dev`. App-only mint dies with:

```
t3-github-app-token: app is not installed on pingdotgg/t3code (or repo does not exist)
```

The App is installed on `patroza` / `macs-holding` / `effect-app` /
`aaaomega` — not `pingdotgg`.

This change passes through guest wrapper lines (`t3-github-app-token:` /
`gh-app-wrapper:`) as the command-failed detail, and keeps raw provider
stderr off the VCS error message (tokens stay out of logs).

Installing the App on `pingdotgg` (or using a user/SSH token for those
reads) is still required for the panel to actually load that PR.

## Test plan

- [x] `vp test run apps/server/src/vcs/VcsProcess.test.ts
apps/server/src/sourceControl/GitHubCli.test.ts`
- [ ] After deploy: open a PR the App is not installed on and confirm
the panel shows `app is not installed on …` instead of the generic CLI
line
- [ ] Confirm a `patroza/t3code` PR still loads on t3vm

Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Merges 86 upstream commits into fork/dev. Notable weld points:

- CommandPalette: ports upstream's pinned clone destination (pingdotgg#5989) onto the
  fork's synchronous browse path, which has no browseNavigation coordinator.
- ChatMarkdown/ChatComposer/ChatView: keeps the fork's richer renderers and
  send path while adopting upstream's oversized-prompt preflight (pingdotgg#6602) and
  markdown title stripping (pingdotgg#4133).
- ProjectionSnapshotQuery: trims the fork's lazy-load window before upstream's
  pinned-activity union (pingdotgg#6153) so hasMoreActivities still means what it says.
- ProviderRuntimeIngestion: upstream's canReplaceThreadTitle guard with the
  fork's title sanitisation and provider-initiated interaction-mode sync.
- usageProviders: upstream's PROVIDER_PRESENTATION record, extended to the
  fork's four providers.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
merge: sync upstream through 20a7042 (86 commits)
## Summary

The thread PR panel used the project's primary repository identity. On a
fork that prefers **upstream**, so `#410` was loaded as
`pingdotgg#410`.

That number exists only on the fork:

| Repo | #410 |
| --- | --- |
| `patroza/t3code` | open — docs(agents): drop obsolete fork stack
workflow |
| `pingdotgg/t3code` | GraphQL: could not resolve |

The agent on the left found the fork PR (`gh pr view 410` with `GH_REPO`
/ origin). The panel asked GitHub for the upstream number and showed
**Pull request not found**.

This is not the earlier App-install / wrapper issue. Ops #81 and product
#408 already landed; t3vm is minting public-read fallback tokens.

**Fix:** open the panel from the change-request URL (`patroza/t3code`),
and accept any of the checkout's remotes on the server so origin is
still this project.

## Test plan

- [x] `vp test run
apps/server/src/pullRequest/PullRequestService.test.ts
apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts`
- [ ] After deploy: open the `#410` thread panel and confirm it loads
`#410`
- [ ] Confirm an upstream PR on the same checkout still opens against
`pingdotgg/t3code`

Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
AGENTS.md is the single current fork workflow:

- change PRs target `fork/dev` and squash-merge
- update `main` from `upstream/main`, then classic-merge into `fork/dev`
- catching a change PR up is rebase or merge — pick one

Removed the historical stack/handover docs (`docs/fork-stack.md`,
`docs/fork-base.md`, `docs/stable-dev-release-branch-handover.md`) so
they cannot describe a past or future model.

Grok 4.6 / T3 Discord

opened by [patroza](https://discord.com/users/95218063095377920) in chat
thread **Discord** ·
[Discord](https://discord.com/channels/1083767712431480922/1538189075251601469/1538189075251601469)
· [T3](https://t3vm/?thread=866be431-d52a-48b6-bb4b-cfcd2399d79c)

---------

Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
#401 was squash-merged. That kept the code and threw the lineage away:
the 23 upstream commits stopped being ancestors, so `fork/dev` read as
**29 commits behind upstream when only 6 were genuinely outstanding**,
and the next sync would have re-merged and re-resolved all 23 — on the
same mobile files that took sixteen conflicts to land the first time.

Nobody noticed for two merges. It surfaced in a deploy alert that said
**"Commits (2)"** for a range that had carried 23.

## What this adds

A `push`-triggered check on `fork/dev` that fails when a commit which
carried a sync has fewer than two parents, and prints the `-s ours`
repair in the log.

Sync commits are identified by **the head branch of the PR they came
from**, not by their subject, because subjects vary by merge method:

```
Merge pull request #400 from patroza/sync/upstream-2026-08-12b
Merge upstream/main into fork/dev (23 commits) (#401)
merge: sync upstream through b73232b
```

A merge-button commit names the branch inline, so no API call is needed;
squash and rebase commits are resolved through the API, with the subject
line as a fallback when that is unavailable. **Ordinary fork PRs are
untouched** — they are expected to squash, and are never checked.

## Verified against the real commits

| commit | what it is | result |
|---|---|---|
| `5e63531b1` | #401, squash-merged sync | **fails**, exit 1 |
| `0bf7835cc` | #400, sync merged properly | recognised as a sync,
passes (2 parents) |
| `a76069bd9` | #402, an ordinary squashed PR | not flagged |

The third row is the one that matters most: the guard has to stay silent
on your normal workflow.

## This detects, it does not prevent

Worth being explicit, since it was the first question asked: **clicking
merge does not fail.** The check runs after the merge lands, because
GitHub has no per-PR merge-method control, and a repository-wide setting
cannot allow squash for ordinary fork PRs while requiring a merge commit
for syncs. Squash merges do fire `push` — `5e63531b1` triggered Fork CI
at 08:08 — so this turns a silent, weeks-later discovery into a red
check within a minute.

Actual prevention is `gh pr merge <n> --merge`, which is how #398, #399
and #400 all landed correctly. That is now written into the sync runbook
in `AGENTS.md`.

If you would rather it be enforced at the button, the next step is a
`sync:upstream` label workflow that merges the PR through the API once
checks pass — say the word and I will add it.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code) via [T3
Chat](https://t3.chat) on Discord

---------

Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
…PRs (#413)

Manual `/omegent today-recap` (alias `/agent today-recap`) starts a
recap thread for the repo bound to the calling Discord channel
(`t3-<shortName>` topic). No schedule — someone has to run it.

## Why
Daily recaps of PR opens/merges/closes (with Discord thread links) are
useful and cheap. Doing it as a slash command now lets us iterate on
format without a cron.

## What
- `/omegent today-recap` in a project channel (e.g. scanner) opens a new
thread and asks the agent for that repo's UTC-day recap
- Same from a child thread: recap still lands on the parent project
channel
- `@Omegent today-recap` / `today recap` as a mention fallback
- Prompt encodes the format we settled: what/why from PR descriptions,
`[PR #N](url)`, bare Discord thread URLs, `## 🟢 MERGED` / `## 🔴 CLOSED`
/ `## 🟠 OPEN`, `### fix` / `### feat` outline
- Recap turns run `--local` (no worktree)

## Test plan
- [x] `vp test run` todayRecap, slashCommands, channelInfoPin, mentions,
MentionRouter
- [x] `apps/discord-bot` typecheck
- [x] targeted lint
- [ ] After deploy: `/omegent today-recap` in the scanner channel

opened by [joshuadima](https://discord.com/users/593167616273809448) in
chat thread **Discord** ·
[Discord](https://discord.com/channels/1083767712431480922/1539325452873769041/1539325452873769041)
·
[T3](https://t3vm.tail86038f.ts.net/?thread=1c8e37cb-722d-405e-8071-5c8c3cd159fe)

---------

Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Joshua Dimaunahan <170177550+MindfulLearner@users.noreply.github.com>
Merges 66 upstream commits into fork/dev. Notable weld points:

- ChatComposer: upstream pingdotgg#7150 relocated the prompt editor, length validation
  and footer, leaving the fork's copies as a second live layout. Ported the
  fork-only pieces into upstream's copies first — the environment-unavailable
  placeholder, the model picker's usage snapshot, and the attach-images button
  that drives the hidden iOS file input — then dropped the stale layout.
- MessagesTimeline: upstream's new active-turn block indexes whatever it
  iterates; the fork walks its collapsed/interleaved list, so the block was
  rebased onto that list to keep the indices referring to one array.
- DesktopUpdates: upstream's action reservation and widened installable check,
  kept compatible with the fork's Linux dir-install mode.
- CommandPalette: dropped the `open` prop upstream removed while keeping the
  fork's file-picker / content-search overlays, and took upstream's newer
  reduceCommandPaletteUiState over the fork's relocated copy of the old one.
- mobile-showcase-screenshots: GitHub-hosted runners (this fork has no
  Blacksmith) with upstream's palette-driven timeout.
- ProviderRegistry.test: kept the fork's coverage for mergeProviderSnapshots /
  selectProvidersByKind, which upstream deleted while leaving both exported.

Upstream's new t3code/no-native-title-tooltip rule flagged seven fork-only
components; four had a title duplicating an existing aria-label, three needed
the label moved off the native tooltip.

rerere was disabled for this merge: it had replayed earlier resolutions onto
new upstream content, which is exactly how a weld goes silently wrong.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
merge: sync upstream through f708f63 (66 commits)
…one (#417)

Thread search restricted assistant matches to the single row a turn
names as its
terminal message:

```sql
messages.message_id IN (
  SELECT turns.assistant_message_id FROM projection_turns AS turns
  WHERE turns.assistant_message_id IS NOT NULL
)
```

Providers stream an answer as many assistant items — roughly ten per
turn now,
and climbing as chunking gets finer — and only the last is linked as
`turns.assistant_message_id`. Everything said before it was invisible to
search.
That is why full-text search appeared to "stop working" without anything
visibly changing: the query never changed, the shape of the data under
it did.

## Measured on a live `state.sqlite`

| | searchable |
|---|---|
| before | 7,118 / 20,094 settled messages (35%) |
| after | 20,094 (100%) |
| assistant messages before | 3,430 / 16,406 (21%) |

A real query for `worktree` goes from **138 to 187 threads**.

All 425 threads with assistant text had *some* searchable row, so the
symptom
was a search quietly missing most of what was said, not one missing
whole
threads — which is exactly why it was hard to pin down.

## Scope

- **Tool calls were never involved.** They are activities in
`projection_thread_activities`, not rows in
`projection_thread_messages`, so
  they stay out by construction.
- **`system` notices** are now excluded explicitly rather than
incidentally.
- **Per-thread dedupe is unchanged**: `thread_match_rank = 1` still
collapses a
  thread to one best row, preferring a user match, then the most recent.
- **Streaming rows stay excluded** via the existing `is_streaming = 0`.

## Known limit

A phrase that straddles a split boundary still will not match — each
message row
is matched independently, so "the quick brown" in one item and "fox" in
the next
is two rows, not one string. Fixing that means matching against a
per-turn
concatenation, which is a bigger change with its own cost; happy to do
it as a
follow-up if it matters in practice.

## Tests

The existing test asserted the old behaviour outright — its fixture
literally
read `'Interim needle must not be searchable.'` — so that expectation is
inverted here, and a split-answer case is added whose matching half is
not the
turn's `assistant_message_id`. Mutation-checked: restoring the old
restriction
fails both new assertions.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>

---------

Signed-off-by: aoright <102943475+aoright@users.noreply.github.com>
Co-authored-by: Rodrigo Brechard <rodrigobrechard@gmail.com>
Co-authored-by: Rodrigo Brechard <rodrigo@clubtidy.fr>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com>
Co-authored-by: Eddy Naboulet <93473191+eddy-naboulet@users.noreply.github.com>
Co-authored-by: maria <maria@kuuro.net>
Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
Co-authored-by: Pavlo Trinko <paul.trinko95@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com>
Co-authored-by: Chris Deeming <chris@xenforo.com>
Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
Co-authored-by: Francisco Arredondo <95440147+frarredondo@users.noreply.github.com>
Co-authored-by: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com>
Co-authored-by: Nitay Rabinovich <nitayr@wix.com>
Co-authored-by: Lars Nieuwenhuis <35393046+lnieuwenhuis@users.noreply.github.com>
Co-authored-by: Tristan Knight <admin@snappeh.com>
Co-authored-by: Nick Anisimov <n.anisimov.23@gmail.com>
Co-authored-by: Maslin Edwin <maslinje@gmail.com>
Co-authored-by: aoright <102943475+aoright@users.noreply.github.com>
Co-authored-by: Guilherme Barros <gbarros1095@gmail.com>
Co-authored-by: Rishet11 <154429365+Rishet11@users.noreply.github.com>
Co-authored-by: Augie <augie@luebbers.email>
Co-authored-by: Taras <Taras.Fomin@gmail.com>
Co-authored-by: Gianmarco <gianmarcosimone89@gmail.com>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: Inaya Yousfi <zied.essaber@gmail.com>
Co-authored-by: maria <254055478+maria-rcks@users.noreply.github.com>
Co-authored-by: Rakshith Bhat <88523594+RakshithBhat03@users.noreply.github.com>
Co-authored-by: GPT-5.6 <noreply@openai.com>
Co-authored-by: David Balderston <dbalders@gmail.com>
Co-authored-by: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com>
Co-authored-by: Luis Gustavo Couto Wacker <luis.wacker@pagar.me>
Co-authored-by: Jake Leventhal <jakeleventhal@me.com>
Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
## Why

Sentry short ids (`SCANNER-313`) have the same shape as Jira keys
(`PROJ-123`). The Discord bot extracted those tokens from sentry.io URLs
and Sentry alert embeds, pinned them under **Jira**, and injected `jira:
SCANNER-313` into agent turns — so the agent treated an obvious Sentry
issue as a Jira ticket.

## What

- Do not treat sentry.io URLs, Sentry-bot authors, or Sentry Discord
embeds as Jira. Atlassian browse / `selectedIssue` URLs are still
extracted.
- Drop already-stored false positives on pin refresh and backfill (and
skip mining our own Omegent Info pin, which echoed the misclassified
key).
- Persist sentry.io issue URLs and render them as **Sentry** on the
thread-info pin (above Jira).

## Test plan

- [x] `vp test run` jiraLinks, sentryLinks, threadInfoPin,
ThreadLinkStore, threadContext, MentionRouter
- [x] `apps/discord-bot` typecheck
- [ ] After deploy: Sentry alert / pasted
`https://*.sentry.io/issues/SCANNER-313` should pin **Sentry**, not
**Jira**

opened by [Patrick Roza](https://discord.com/users/95218063095377920) in
chat thread **Discord** · [what happened
here?](https://discord.com/channels/1083767712431480922)

---------

Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
…#414)

## Summary

Omegent on t3vm failed with `T3 did not become ready after a server
restart` because the Discord bot re-exchanges
`local-bootstrap-credential` on every process start, and that grant was
seeded with the **desktop 24h TTL**. After `t3code-server` had been up
more than a day, a bot-only restart (or a deploy that restarted only the
bot) looped `invalid_credential` forever.

- Keep the desktop IPC bootstrap seed at 24h.
- Seed the file-backed local grant with a long-lived TTL (colocated
trusted clients).
- Persist the 30-day bearer under the bot data dir and reuse it on the
next start; clear and re-bootstrap if T3 rejects it.
- Also include basename asar unpack globs (`*.node` as well as
`**/*.node`) so `packWindowsServerAsar` still creates
`server.asar.unpacked` on hosts where `@electron/asar` 3.4 does not
treat `**` as nested.

## Test plan

- [x] `PairingGrantStore` local-file grant still consumes after 25h and
400d
- [x] Desktop IPC grant still expires after 24h
- [x] Persisted bearer reuse / host mismatch / remaining-time floor /
disk round-trip
- [x] T3Session existence contract for persist + invalid_credential
fallback
- [x] `build-desktop-artifact` Windows asar unpack tests
- t3vm: rebuilt dist, restarted server then bot; new Discord bot session
issued 2026-08-20 06:49 UTC and HTTPS `/` is 200 again

---------

Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
…ap (#418)

`/omegent today-recap` posted the ack twice (channel starter + slash
reply) and opened a thread with no recap in it until/unless the agent
finished — so the thread looked empty.

## What
- Open one public thread with no starter message
- Slash ack is ephemeral (jump only)
- Recap is the only public bot message, inside that thread
- Mention-in-channel still threads off the mention; mention-in-thread
only posts a jump, not a second ack

opened by [joshuadima](https://discord.com/users/593167616273809448) in
chat thread **Discord** ·
[Discord](https://discord.com/channels/1083767712431480922/1539325452873769041/1539325452873769041)
·
[T3](https://t3vm.tail86038f.ts.net/?thread=1c8e37cb-722d-405e-8071-5c8c3cd159fe)

Co-authored-by: omegent-app[bot] <306514130+omegent-app[bot]@users.noreply.github.com>
Co-authored-by: Joshua Dimaunahan <170177550+MindfulLearner@users.noreply.github.com>
Discord reply pings put the parent author in `mentions` without an
in-content <@id>. People use replies as quotes, so Omegent was starting
turns from those.

Require an explicit @mention (or role mention) on replies. Thread-talk
also skips replies.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
@omegent-app
omegent-app Bot marked this pull request as ready for review August 21, 2026 09:04
@omegent-app
omegent-app Bot marked this pull request as draft August 21, 2026 09:54
@patroza
patroza force-pushed the fork/dev branch 4 times, most recently from 80434dc to fc6701e Compare October 3, 2026 10:00

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants