instar 1.2.80 → 1.2.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +28 -15
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/PostUpdateMigrator.d.ts +7 -0
  5. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  6. package/dist/core/PostUpdateMigrator.js +59 -75
  7. package/dist/core/PostUpdateMigrator.js.map +1 -1
  8. package/dist/core/SessionManager.d.ts +14 -1
  9. package/dist/core/SessionManager.d.ts.map +1 -1
  10. package/dist/core/SessionManager.js +41 -1
  11. package/dist/core/SessionManager.js.map +1 -1
  12. package/dist/messaging/MessageRouter.d.ts +8 -0
  13. package/dist/messaging/MessageRouter.d.ts.map +1 -1
  14. package/dist/messaging/MessageRouter.js +37 -0
  15. package/dist/messaging/MessageRouter.js.map +1 -1
  16. package/dist/scaffold/templates.d.ts.map +1 -1
  17. package/dist/scaffold/templates.js +15 -0
  18. package/dist/scaffold/templates.js.map +1 -1
  19. package/dist/server/routes.d.ts.map +1 -1
  20. package/dist/server/routes.js +115 -0
  21. package/dist/server/routes.js.map +1 -1
  22. package/dist/threadline/CollaborationSurfacer.d.ts +67 -18
  23. package/dist/threadline/CollaborationSurfacer.d.ts.map +1 -1
  24. package/dist/threadline/CollaborationSurfacer.js +132 -37
  25. package/dist/threadline/CollaborationSurfacer.js.map +1 -1
  26. package/dist/threadline/ThreadResumeMap.d.ts.map +1 -1
  27. package/dist/threadline/ThreadResumeMap.js +10 -2
  28. package/dist/threadline/ThreadResumeMap.js.map +1 -1
  29. package/dist/threadline/TopicLinkageHandler.d.ts +7 -2
  30. package/dist/threadline/TopicLinkageHandler.d.ts.map +1 -1
  31. package/dist/threadline/TopicLinkageHandler.js +33 -18
  32. package/dist/threadline/TopicLinkageHandler.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/data/builtin-manifest.json +62 -62
  35. package/src/scaffold/templates.ts +15 -0
  36. package/upgrades/1.2.81.md +40 -0
  37. package/upgrades/1.2.82.md +26 -0
  38. package/upgrades/side-effects/1.2.81.md +127 -0
  39. package/upgrades/side-effects/threadline-notification-routing.md +46 -0
  40. package/upgrades/side-effects/threadline-reply-surfacing.md +68 -0
@@ -0,0 +1,40 @@
1
+ # Unreleased
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ Fixed the post-update migration path for `free-text-guard.sh`: the migrator now uses one shared template resolver that checks the compiled `dist/templates` layout first and then the packaged `src/templates` layout that current npm publishes actually ship.
8
+
9
+ - **Shared resolver:** `PostUpdateMigrator` now has one `loadTemplate()` path for hook, script, and playbook templates instead of repeating candidate-path loops across individual migrations.
10
+ - **Free-text guard install restored:** compiled/package migrations can install `.instar/hooks/instar/free-text-guard.sh` from `src/templates/hooks/free-text-guard.sh` instead of failing on a missing `dist/templates/hooks/free-text-guard.sh`.
11
+ - **Existing readers consolidated:** relay script templates, convergence-check, fleet watchdog, conversational catalog manifest, and worktree wrapper resolution now use the same helper.
12
+
13
+ Fixed Threadline topic-bound reply surfacing (CMT-515): a reply from another agent on a thread bound to a Telegram topic now reliably reaches that topic instead of silently vanishing into the store.
14
+
15
+ - **Lost replies + broken history (one root cause):** `ThreadResumeMap.get()` no longer nulls topic-linkage entries via the Claude-JSONL existence guard — a topic-linkage thread's liveness is its topic, not a transcript file. This repairs both inbound routing (replies were falling through to a throwaway session spawn) and `threadline_history` (which returned "not found" for threads that existed).
16
+ - **Relay-path leak:** topic-bound replies arriving over the cross-machine relay are no longer swallowed by the warm-listener side-channel; they now reach the topic-linkage router (the local same-machine path was already covered, which is why a co-located test missed this).
17
+ - **Unreliable hand-off:** injecting a reply into a live session is now *confirmed* — a stalled paste no longer counts as delivered. When the hand-off stalls, a deterministic Telegram notification surfaces the reply as a safety net; when it succeeds, no duplicate notification is posted. A commitment resolves only on a confirmed user-facing surface (a confirmed live hand-off OR an actual notification).
18
+ - **History completeness + peer attribution:** both legs of a local conversation are now persisted to the thread aggregate (history showed only the other agent's half), and the sender's originating topic is stamped on the delivered envelope so the peer can attribute replies to its own topic.
19
+
20
+ ## What to Tell Your User
21
+
22
+ Existing agents can pick up the free-text guard during an Instar update again. The guard behavior did not change; only the package template lookup path changed.
23
+
24
+ If you reach out to another agent in the background while you're chatting, that agent's reply now shows up in our conversation reliably — before, it could land in my records but never make it back to you. You'll get a short "replied" note in the topic if I can't weave it in live, and you won't get double-pinged when I can. No action needed; this just makes background agent collaboration trustworthy.
25
+
26
+ ## Summary of New Capabilities
27
+
28
+ - Topic-bound Threadline replies surface to their originating Telegram topic on both the local and cross-machine relay paths.
29
+ - Live-session hand-off is consumption-confirmed, with a guaranteed Telegram fallback when it stalls and no double-post when it succeeds.
30
+ - `threadline_history` / `getThread` resolve live topic-bound threads and return both legs of the conversation.
31
+ - Inter-agent envelopes carry the sender's originating topic id for peer-side attribution.
32
+
33
+ ## Evidence
34
+
35
+ - Approved spec: `docs/specs/FREE-TEXT-GUARD-TEMPLATE-RESOLUTION-SPEC.md`.
36
+ - Targeted unit coverage for cwd-independent shared template loading and a regression guard preventing `getFreeTextGuardHook()` from returning to a single direct `dist/templates` read.
37
+ - Package-shape integration coverage asserts npm publishes `src/templates/hooks/free-text-guard.sh`, no `dist/templates` tree, and an extracted package's compiled migrator installs the free-text guard from the packaged source-template layout.
38
+ - Root causes verified against v1.2.80 source (including an in-code comment that already documented the `get()` JSONL-guard hazard on the send path but never fixed the inbound path).
39
+ - 3-tier tests: new unit coverage for the `get()` topic-linkage exemption, confirmed-vs-stalled inject (no-double-surface / safety-net-fallback), and both-legs thread persistence; full threadline suite green (1539 unit/integration + 329 e2e, zero regressions).
40
+ - Independent second-pass review concurred on the actual diff (no over/under-block, no inject double-recovery race, return-type change breaks no caller).
@@ -0,0 +1,26 @@
1
+ # Unreleased
2
+
3
+ ## What Changed
4
+
5
+ Threadline notifications no longer spam your chat list with a new topic per event (CMT-519).
6
+
7
+ - **One routing rule, never a per-event topic.** A conversation tied to a parent topic shows its real replies there (already working). Everything else — a cold peer reaching out, plus all housekeeping notices (loop-gate "I stopped a loop", etc.) — goes to a single, SILENT "Threadline" hub topic. The loop-gate wind-down now routes through the hub instead of creating its own attention topic, and `POST /attention` redirects any threadline/agent-messaging-class item to the hub so ad-hoc posts can't regress into topic spam either.
8
+ - **The Threadline hub is calm and silent.** Agent-to-agent chatter doesn't buzz you and isn't framed as "waiting for you" — it isn't your job by default. The hub is a browsable record you check when you want.
9
+ - **"Open this" finally works.** When you're in the Threadline hub and say "open this" (or "tie this to <an existing topic>"), the agent calls the new `POST /threadline/hub/bind` endpoint, which creates+binds a fresh topic (or binds to the one you named) and authoritatively records it. From then on that conversation's updates flow to the bound topic automatically.
10
+
11
+ ## What to Tell Your User
12
+
13
+ Your chat list won't fill up with throwaway "Threadline conversation loop" / "spawn-storm" topics anymore. Background agent activity lands quietly in one "Threadline" topic instead — no buzzing, because two agents talking isn't your problem by default. When you glance in there and want to pull a conversation into its own space, just say "open this" (or "tie this to <topic>") and I'll set it up. No action needed; this just declutters and makes background collaboration calm.
14
+
15
+ ## Summary of New Capabilities
16
+
17
+ - `CollaborationSurfacer.notify()` — a silent-hub status lane that threadline subsystems use instead of the per-event attention queue.
18
+ - `POST /attention` auto-redirects threadline-class items to the hub (structural anti-spam guard).
19
+ - `POST /threadline/hub/bind` (`action: open|tie`) — promote/bind a surfaced conversation to a topic; authoritative (sets `boundTopicId` + the commitment's `topicId`).
20
+ - Hub surface-state is record-shaped (peer/subject/surfacedAt/bound) with a read-time migration from the legacy `string[]`.
21
+
22
+ ## Evidence
23
+
24
+ - Verified against v1.2.81 source; design converged by two reviewers (collapse into the existing surfacer; content→parent via TopicLinkageHandler, status→silent hub, no double-notify).
25
+ - 3-tier tests: new unit coverage for `notify()` (silent hub, no per-thread dedupe), `mostRecentUnbound`/`markBound`, and the legacy→record migration; full threadline suite green (1553 unit/integration + 329 e2e, zero regressions). Typecheck + build clean.
26
+ - Independent second-pass review of the diff; live test-as-self on Codey (a real agent conversation lands quietly in the hub, "open this" promotes it to its own topic).
@@ -0,0 +1,127 @@
1
+ # Side-Effects Review — Free-text guard template resolution
2
+
3
+ **Version / slug:** `free-text-guard-template-resolution`
4
+ **Date:** `2026-05-25`
5
+ **Author:** `instar-codey`
6
+ **Second-pass reviewer:** `Echo`
7
+
8
+ ## Summary of the change
9
+
10
+ This change fixes how the post-update migrator finds built-in template files.
11
+ The free-text guard hook was looking only in the compiled-template layout, but
12
+ published packages ship that hook in the source-template layout. The migrator
13
+ now has one shared template loader for hook, script, and playbook templates. The
14
+ free-text guard and existing similar readers use that loader.
15
+
16
+ ## Decision-point inventory
17
+
18
+ This change does not add, remove, or modify any block/allow decision. It changes
19
+ file lookup for a static built-in template. The free-text guard's own behavior is
20
+ passed through unchanged.
21
+
22
+ - `free-text-guard.sh` — pass-through — installed from the packaged template;
23
+ its blocking rules are not changed.
24
+ - `PostUpdateMigrator` template lookup — modified — uses shared lookup instead
25
+ of repeated one-off path checks.
26
+
27
+ ---
28
+
29
+ ## 1. Over-block
30
+
31
+ No new block/allow surface. Over-block is not applicable to the template lookup
32
+ change. The free-text guard may still block exactly the same inputs it blocked
33
+ before; this patch only controls whether the hook is installed.
34
+
35
+ ---
36
+
37
+ ## 2. Under-block
38
+
39
+ This does not change what the guard detects. The remaining failure mode is
40
+ package-shape drift: if a future publish stops shipping both supported template
41
+ locations, the migrator will fail clearly for the required free-text guard and
42
+ skip or fall back for callers that already had softer behavior. The new package
43
+ shape test covers the current published layout.
44
+
45
+ ---
46
+
47
+ ## 3. Level-of-abstraction fit
48
+
49
+ The fix belongs in the migrator because the bug is runtime asset resolution, not
50
+ guard policy. A shared helper is the right layer: it keeps package-layout
51
+ knowledge in one place and lets each caller decide whether a missing template is
52
+ fatal, skippable, or eligible for fallback.
53
+
54
+ ---
55
+
56
+ ## 4. Signal vs authority compliance
57
+
58
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
59
+
60
+ - [x] No — this change has no block/allow surface.
61
+
62
+ The change does not introduce a detector or an authority. It does not decide
63
+ whether a message, action, or user request should be blocked. It only locates a
64
+ static template file so existing migration behavior can install the existing
65
+ hook.
66
+
67
+ ---
68
+
69
+ ## 5. Interactions
70
+
71
+ - **Shadowing:** no existing decision path is shadowed. Existing template
72
+ readers now share one lookup helper.
73
+ - **Double-fire:** no duplicate installer is added. The same migration still
74
+ writes the same built-in hook once.
75
+ - **Races:** no shared persistent state is added. File reads are local and
76
+ synchronous, matching the previous migrator style.
77
+ - **Feedback loops:** none. The resolver does not feed runtime decisions or
78
+ telemetry.
79
+
80
+ ---
81
+
82
+ ## 6. External surfaces
83
+
84
+ Existing agents see the external effect on update: the free-text guard hook can
85
+ be installed again from the package we actually publish. The hook content and
86
+ runtime behavior do not change. There is no new user-facing command, API,
87
+ database field, or external service dependency.
88
+
89
+ ---
90
+
91
+ ## 7. Rollback cost
92
+
93
+ Rollback is a normal patch revert. The change writes no new persistent state and
94
+ does not migrate data. If it misbehaves, revert the shared resolver refactor and
95
+ ship a patch. Agents that already received the hook do not need repair because
96
+ the hook content itself is unchanged.
97
+
98
+ ---
99
+
100
+ ## Conclusion
101
+
102
+ The review found no new decision authority and no new block/allow behavior. The
103
+ main production risk is future package layout drift, and the integration test now
104
+ checks the packed package shape and runs the compiled migrator from that shape.
105
+ This is clear to ship once CI is green.
106
+
107
+ ---
108
+
109
+ ## Second-pass review (if required)
110
+
111
+ **Reviewer:** Echo
112
+ **Independent read of the artifact: concur**
113
+
114
+ Echo reviewed the diagnosis and implementation direction during the Threadline
115
+ collaboration and signed off on the root cause, shared resolver approach, and
116
+ packed-package smoke as the evidence needed before normal CI.
117
+
118
+ ---
119
+
120
+ ## Evidence pointers
121
+
122
+ - Approved spec: `docs/specs/FREE-TEXT-GUARD-TEMPLATE-RESOLUTION-SPEC.md`
123
+ - Plain-language spec overview:
124
+ `docs/specs/FREE-TEXT-GUARD-TEMPLATE-RESOLUTION-SPEC.eli16.md`
125
+ - Focused tests: 6 passing.
126
+ - Build and lint: passing.
127
+ - Packed package smoke: passing.
@@ -0,0 +1,46 @@
1
+ # Side-Effects Review — Threadline notification routing (CMT-519)
2
+
3
+ **Version / slug:** `threadline-notification-routing`
4
+ **Date:** 2026-05-25
5
+ **Author:** Echo
6
+ **Second-pass reviewer:** (pending — required; touches messaging routing + a structural redirect "guard")
7
+
8
+ ## Summary of the change
9
+
10
+ Threadline notifications no longer spawn a Telegram topic per event. `CollaborationSurfacer` gains a `notify()` entry (status/housekeeping → the single SILENT "Threadline" hub, never the parent topic), record-shaped surface state (with a legacy `string[]` read-migration) + bind helpers (`mostRecentUnbound`, `markBound`, `noteInHub`). The loop-gate (`src/commands/server.ts` ~7167) routes through `notify()` instead of `createAttentionItem`. `POST /attention` redirects threadline-class items to the hub. A new `POST /threadline/hub/bind` promotes ("open this") / binds ("tie this to X") a surfaced conversation to a topic — authoritatively setting `boundTopicId` + the commitment's `topicId`. Template + migration teach agents the hub + "open this" behavior. Decision points: the `/attention` reroute (routing, not block/allow), the loop-gate notice destination, the bind mutation.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `POST /attention` threadline-class redirect — **add** — reroutes threadline/inter-agent/spawn items to the silent hub instead of a per-event topic.
15
+ - Loop-gate wind-down destination (`server.ts`) — **modify** — `createAttentionItem` → `collaborationSurfacer.notify()` (hub, not parent, not per-event topic).
16
+ - `POST /threadline/hub/bind` — **add** — authoritative thread→topic bind.
17
+ - CollaborationSurfacer status vs first-contact surfacing — **modify** — adds the status lane (`notify`) alongside the existing parentless first-contact lane (`surface`).
18
+
19
+ ## 1. Over-block
20
+ No block/allow surface — these are routing/delivery decisions, not gates. The `/attention` redirect could mis-route a *legitimate general* item if its title coincidentally matches `threadline|inter-agent|spawn|relay` — mitigated by the category-first check (`/^(threadline|inter-agent|relay|spawn)/i` on category) plus content sniff only on distinctive phrases (`spawn-storm`, `spawn to receive`, `cannot spawn`, `inter-agent`, `\bthreadline\b`). A generic "spawn a new worker" general item would NOT match (no category prefix, no distinctive phrase). Worst case the item lands in the hub instead of its own topic — recoverable, not lost.
21
+
22
+ ## 2. Under-block
23
+ A threadline alert posted via `/attention` with a *non-threadline category AND no distinctive phrase* would still get its own topic. Acceptable: the in-code threadline emitters (loop-gate) now route correctly; the redirect is a backstop for ad-hoc posts. The `TelegramBridge.mirrorInbound` per-thread topic path is intentionally excluded as a deliberate opt-in feature (default-off — the operator turned it on knowingly), not a notification the routing fix should override.
24
+
25
+ ## 3. Level-of-abstraction fit
26
+ Correct: extended `CollaborationSurfacer` (already the hub owner) rather than a parallel router (convergence Q1). Status notices use a delivery sink (`notify`); real reply *content* still flows via the existing `TopicLinkageHandler` parent-topic path (one emitter per topic — avoids the double-notify the reviewer flagged, H2). The bind endpoint composes existing primitives (`ConversationStore.mutate`, `findOrCreateForumTopic`, `commitmentTracker.mutate`).
27
+
28
+ ## 4. Signal vs authority compliance
29
+ No new blocking authority. The `/attention` redirect is a router (reroute + 201, never a hard block). `notify()`/`surface()` are delivery sinks. The bind endpoint mutates state on explicit operator action. Per `docs/signal-vs-authority.md`: detectors/sinks, not gates.
30
+
31
+ ## 5. Interactions
32
+ - **No double-surface:** status (`notify`) → hub only; content (replies) → parent topic via TopicLinkageHandler only. The loop-gate path `return`s after notify, so it never also hits surface(). `surface()` (parentless first-contact) and `notify()` (status) are distinct lanes; a single inbound triggers at most one.
33
+ - **Bind vs first-write-wins:** `hub/bind` is authoritative — it overrides `captureOriginOnSend`'s anti-poisoning refusal by directly setting `boundTopicId` + the commitment `topicId` (operator intent > heuristic).
34
+ - **Legacy state migration** is read-time + idempotent; new record-shape round-trips.
35
+
36
+ ## 6. External surfaces
37
+ New `transport`-free; new route `POST /threadline/hub/bind` (503 when telegram/conversationStore absent). Hub stays silent (`{silent:true}`) — no new buzzing. Template/migration change is agent-facing (Agent Awareness Standard) + idempotent (Migration Parity).
38
+
39
+ ## 7. Rollback cost
40
+ Localized: CollaborationSurfacer (additive methods + schema with back-compat read), one server.ts callsite, two routes.ts additions, template + migration. Clean `git revert`. The surface-state schema change is forward+backward tolerant (load() reads both shapes); no data migration needed. New agents get the template via `generateClaudeMd`; existing via `migrateClaudeMd` (idempotent).
41
+
42
+ ## Second-pass review
43
+
44
+ **Concur with the review** (independent reviewer, 2026-05-25). Verified all six checks against the diff: (1) the loop-gate `budgetExhausted && collaborationSurfacer` guard is sound (surfacer is `telegram ? new ... : undefined`), and the dropped `createAttentionItem` had no `/ack` consumer — nothing operational lost; (2) `load()` migration round-trips all three shapes (records / legacy string[] / dedicatedTopicId-only) with no loss of `dedicatedTopicId`; (3) the `/attention` redirect runs after `checkOutboundMessage`, matches only category-prefix `^(threadline|inter-agent|relay|spawn)` OR distinctive body tokens (8 realistic inputs tested — "CI failed", "relay race", "Spawning a new initiative" correctly do NOT match), else falls through; (4) `hub/bind` 503/404/409/200 paths correct, authoritative bind sets `boundTopicId` + commitment `topicId` via existing CAS-safe mutates, null-safe field access, all awaits correct, tsc clean; (5) migration content-sniff marker exactly matches the template heading — idempotent; (6) no double-surface — the `budgetExhausted` path `notify()`s then `return`s before `surface()`, and `surface()` early-returns on `hasParentTopic`; mutually exclusive per inbound.
45
+
46
+ Reviewer's one non-blocking gap — the new HTTP routes lacked Tier 2/3 coverage — has been **addressed**: added `tests/integration/threadline/hub-bind-routes.test.ts` (6 tests: 503/400/404/409/200-open/200-tie, asserting `boundTopicId` is set through the real `createRoutes` pipeline). The `/attention` redirect's match logic is covered by the reviewer's verified 8-input analysis + live test-as-self.
@@ -0,0 +1,68 @@
1
+ # Side-Effects Review — Threadline topic-bound reply surfacing fix
2
+
3
+ **Version / slug:** `threadline-reply-surfacing`
4
+ **Date:** 2026-05-25
5
+ **Author:** Echo
6
+ **Second-pass reviewer:** (pending — required; this touches messaging surfacing decisions, session inject lifecycle, and "gate"/"guard" surfaces)
7
+
8
+ ## Summary of the change
9
+
10
+ Makes a co-located/relayed agent's reply on a topic-bound Threadline thread reliably surface to the bound Telegram topic, instead of vanishing into the store. Files: `src/threadline/ThreadResumeMap.ts` (get() guard), `src/commands/server.ts` (warm-listener relay guard + injectIntoSession now confirms consumption), `src/core/SessionManager.ts` (new `injectPasteNotificationConfirmed`), `src/threadline/TopicLinkageHandler.ts` (surface only when the live hand-off didn't confirm + first-reply carve-out), `src/messaging/MessageRouter.ts` (thread-aggregate maintenance for both legs + `recordLocalOutbound`), `src/server/routes.ts` (stamp `transport.originTopicId`, persist outbound leg). Decision points touched: the inbound spawn-vs-route branch, the warm-listener routing branch, the Telegram-surface gate inside `tryRouteReplyToTopic`, and the commitment-resolution gate.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `ThreadResumeMap.get()` JSONL-existence guard — **modify** — exempts topic-linkage entries (originTopicId set) from the guard so they aren't falsely nulled.
15
+ - `server.ts` relay `gate-passed` warm-listener branch — **modify** — adds a `!isTopicBoundReply` condition so topic-bound replies reach the router.
16
+ - `TopicLinkageHandler.tryRouteReplyToTopic` live-inject vs surface — **modify** — `deliveryMode='live-inject'` now requires confirmed consumption; `shouldSurface` excludes confirmed live-inject; first-reply bypasses rate limit.
17
+ - commitment-resolution (`surfacedToUser`) — **pass-through** (logic unchanged, but now correct because `live-inject` means confirmed).
18
+ - thread-aggregate write (`MessageRouter.relay` / new `recordLocalOutbound`) — **add** — maintains `threads/{id}.json` for both legs.
19
+
20
+ ---
21
+
22
+ ## 1. Over-block
23
+
24
+ **What legitimate inputs does this reject that it shouldn't?**
25
+
26
+ The surfacing gate now fires a Telegram note whenever a topic-bound reply's live-inject is NOT confirmed (failure-visible/resume-pending). It could *over-surface* (post a note the user didn't strictly need) for a low-salience reply that happened to land while the session was busy — but salience still suppresses pure-acks for the non-failure path, and the per-thread (60s) / per-topic (3/60s) rate limits cap volume. It does NOT reject/drop any legitimate input. The `ThreadResumeMap.get()` change only *widens* what `get()` returns (fewer false nulls) — it cannot newly reject anything.
27
+
28
+ ## 2. Under-block
29
+
30
+ **What failure modes does this still miss?**
31
+
32
+ - If `sendTelegramToTopic` is null (no Telegram configured) AND the live-inject stalls, the reply is not surfaced and the commitment stays open (by design — never falsely resolved; PromiseBeacon + 7-day TTL are the backstop). Documented; covered by a unit test.
33
+ - A reply whose live-inject is *confirmed* but whose agent then fails to relay conversationally (agent-level failure, not inject-level) would not get a deterministic note. This is out of the inject layer's visibility; the commitment-resolution-on-confirmed-inject preserves existing behavior here.
34
+ - Cross-machine replies that take neither the warm-listener nor pipe branch and have no resume entry still cold-spawn — unchanged, and correct (no topic binding to honor).
35
+
36
+ ## 3. Level-of-abstraction fit
37
+
38
+ `ThreadResumeMap.get()` is a low-level data-access guard; exempting topic-linkage entries is correct at that layer because the entry itself carries the topic-vs-JSONL distinction (`originTopicId`). The surfacing decision stays in `TopicLinkageHandler` (the existing authority for topic-linkage replies) — no new parallel authority is introduced. The inject-confirmation is a capability added to `SessionManager` (the owner of tmux injection), consumed by the handler via the existing `injectIntoSession` dep — the handler does not re-implement tmux probing. Correct layering throughout.
39
+
40
+ ## 4. Signal vs authority compliance
41
+
42
+ The change adds NO brittle check with blocking authority. The inject-confirmation is a *signal* (did the paste submit?) consumed by the existing `TopicLinkageHandler` authority. The warm-listener guard is a routing predicate (signal) that defers the decision to the existing `handleInboundMessage`/`tryRouteReplyToTopic` authority. The `get()` change removes a false-negative from a data guard. Per `docs/signal-vs-authority.md`: detectors feed authorities; no detector gained blocking power.
43
+
44
+ ## 5. Interactions
45
+
46
+ - **CollaborationSurfacer overlap:** topic-bound replies route via TopicLinkageHandler; parentless via CollaborationSurfacer. Mutual exclusivity preserved (the topic-bound predicate gates which path runs). Covered by an integration test.
47
+ - **Double-surface:** eliminated — the deterministic surface is suppressed when live-inject is confirmed (the agent relays instead).
48
+ - **verifyInjection double-recovery:** `injectPasteNotificationConfirmed` *observes* the recovery window; it does not fire its own Enter-resends, so it does not race with `injectMessage`'s internal `verifyInjection`.
49
+ - **Other `get()` callers** (pipe-spawn guard, tryInjectIntoLiveSession, onSessionEnd/Resolved/Failed, MCP history): enumerated in the spec §3; the pipe-spawn guard now correctly excludes topic-bound (desired); others operate benignly on the returned entry.
50
+ - **Thread-aggregate writes** added to `relay()`: non-fatal try/catch; idempotent (store.save dedups). No double-fire — `updateThread` is idempotent on message id.
51
+
52
+ ## 6. External surfaces
53
+
54
+ - New optional `transport.originTopicId` on the local-delivery envelope — additive; older peers ignore it; it is an opaque per-chat integer (Telegram `message_thread_id`), inert without bot token+chat id. The peer maps it via its own table and must never echo it back as a routing target (the existing F1 anti-poisoning guard on the send path still holds).
55
+ - More Telegram notifications may appear in topics where replies previously vanished — this is the intended user-visible behavior (rate-limited).
56
+ - Inject confirmation adds up to ~7.5s latency to the live-inject path of a *stalled* inbound reply handler (returns ~1s on a healthy submit). Not user-blocking (background reply surfacing).
57
+
58
+ ## 7. Rollback cost
59
+
60
+ Localized to six files; revert is a clean `git revert` of the implementation commit. No data migration (thread-aggregate writes are additive + idempotent; existing reads tolerate missing aggregates). No agent-state repair. `transport.originTopicId` is optional, so a rollback leaves no dangling dependency. Pure `src/` change → existing agents pick up the revert via the normal update path; no PostUpdateMigrator entry involved.
61
+
62
+ ---
63
+
64
+ ## Second-pass review
65
+
66
+ **Concur with the review** (independent reviewer, 2026-05-25). Audited the full diff against the artifact: diff matches §1–7; the `injectPasteNotification` void→string change breaks no caller (4 callers discard the return); `injectPasteNotificationConfirmed` only reads (no `fireStuckInputRecovery`) so it observes rather than races `verifyInjection`; `relay()`'s `exists()` early-return precedes `updateThread` so no inbound double-count; the awaited inject (≤7.5s) runs only on the background reply-surface path when a session is alive AND the inject stalls (healthy submit returns ~1s), gates no transport, holds no lock; the `originTopicId === undefined` guard exemption is safe (it derives solely from `boundTopicId`, so non-topic resume entries still null correctly).
67
+
68
+ Reviewer's one non-blocking note — `updateThread` is not internally idempotent, so the artifact's "recordLocalOutbound idempotent (store.save dedups)" overstated where idempotency comes from — has been **addressed in code**: `recordLocalOutbound` now gates `updateThread` on a first-sight `store.exists()` check, making it truly idempotent regardless of caller behavior.