instar 1.3.49 → 1.3.51
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.
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +16 -0
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +29 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +53 -20
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +85 -7
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +4 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +106 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/1.3.50.md +70 -0
- package/upgrades/1.3.51.md +62 -0
- package/upgrades/side-effects/a2a-inbox-http-transport.md +128 -0
- package/upgrades/side-effects/mentee-receiver-forward-dispatch.md +99 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Side-Effects Review — same-machine a2a transport via `/a2a/inbox`
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/MENTOR-LIVE-READINESS-SPEC.md` §Recipient side. Third
|
|
4
|
+
fast-follow to PR #462 (receiver wiring) + PR #464 (lifeline-forward
|
|
5
|
+
dispatch). Closes the bot-to-bot-block gap surfaced by dogfood.
|
|
6
|
+
|
|
7
|
+
**Root cause being addressed:** Telegram Bot API structurally blocks
|
|
8
|
+
bot-to-bot delivery: "Bots talking to each other could potentially get stuck
|
|
9
|
+
in unwelcome loops. To avoid this, we decided that bots will not be able to
|
|
10
|
+
see messages from other bots regardless of mode." (Bot API FAQ.) The spec
|
|
11
|
+
assumed `Echo's mentor bot → Codey's primary bot via shared chat → Codey's
|
|
12
|
+
lifeline forwards` would work. Empirically it doesn't — verified by sending
|
|
13
|
+
4 marker messages to topic 458, Codey's bot poll-offset never advances past
|
|
14
|
+
them, his lifeline-forward stays silent.
|
|
15
|
+
|
|
16
|
+
**Change:** Three production files + two test files.
|
|
17
|
+
- `src/server/routes.ts` (+ ~70 LoC: new `/a2a/inbox` POST route)
|
|
18
|
+
- `src/server/middleware.ts` (+ 2 LoC: skip the API bearer middleware for
|
|
19
|
+
`/a2a/inbox` — uses per-agent token like `/messages/relay-agent`)
|
|
20
|
+
- `src/server/AgentServer.ts` (+ ~75 LoC: same-machine path in
|
|
21
|
+
`deliverToMentee` via AgentRegistry + `fetch` to peer's `/a2a/inbox`,
|
|
22
|
+
before the Telegram bot fallback)
|
|
23
|
+
- `tests/integration/a2a-inbox-route.test.ts` (new, 8)
|
|
24
|
+
- `tests/e2e/a2a-inbox-lifecycle.test.ts` (new, 4)
|
|
25
|
+
|
|
26
|
+
## What changed
|
|
27
|
+
|
|
28
|
+
1. **`POST /a2a/inbox`** on every instar server. Accepts:
|
|
29
|
+
`{ text, topicId, senderAgent?, senderIsBot?, senderBotId?, fromUserId? }`.
|
|
30
|
+
Auth: Bearer token must match the target agent's per-agent token
|
|
31
|
+
(`verifyAgentToken` — same shape as `/messages/relay-agent`). Behaviour:
|
|
32
|
+
- 401 if bearer is missing or wrong
|
|
33
|
+
- 503 if no `ctx.telegram` adapter is configured at all (no hook surface)
|
|
34
|
+
- 400 if `text` (string) or `topicId` (number) are missing / malformed
|
|
35
|
+
- 200 `{ ok, agentMessage: true }` when the adapter's
|
|
36
|
+
`dispatchAgentMessageHook` claims the message
|
|
37
|
+
- 200 `{ ok, agentMessage: false, reason: 'not-routed' }` when the hook
|
|
38
|
+
refuses (no marker / malformed / not-allowlisted / not-allowed-role).
|
|
39
|
+
The route is dedicated to a2a — non-routable messages are NOT forwarded
|
|
40
|
+
to user-message handling.
|
|
41
|
+
- `senderIsBot` defaults to `true` if omitted — local peers holding our
|
|
42
|
+
agent token are bots by construction (the spoof defense is satisfied
|
|
43
|
+
by the bearer-token check upstream).
|
|
44
|
+
2. **Auth middleware bypass for `/a2a/inbox`** — alongside the existing
|
|
45
|
+
`/messages/relay-agent` and `/messages/relay-machine` bypasses, since the
|
|
46
|
+
inbox enforces its own per-agent-token auth in-handler. Without this
|
|
47
|
+
bypass the API bearer middleware (using `config.authToken`) would 403
|
|
48
|
+
the request, because the caller holds the TARGET's per-agent token,
|
|
49
|
+
not the API bearer.
|
|
50
|
+
3. **Same-machine routing in `deliverToMentee`** — before the existing
|
|
51
|
+
Telegram bot delivery, the method now:
|
|
52
|
+
- Imports `listAgents` from AgentRegistry + `getAgentToken` from the
|
|
53
|
+
token manager (lazy-imported inside the closure to avoid loading the
|
|
54
|
+
registry on every server boot).
|
|
55
|
+
- Looks up the mentee by `name === menteeAgent && name !== self.config.projectName`
|
|
56
|
+
(anti-self-target).
|
|
57
|
+
- If a local peer with a port is found AND we have the peer's token,
|
|
58
|
+
POSTs to `http://localhost:PORT/a2a/inbox` with the marker text +
|
|
59
|
+
sender context. On `agentMessage: true`, marks the outstanding-prompt
|
|
60
|
+
tracker as sent and audits a `transport: 'a2a-inbox-local'` row in
|
|
61
|
+
the sent-ledger.
|
|
62
|
+
- On any failure (no peer, no token, HTTP error, hook refused) logs and
|
|
63
|
+
falls through to the existing Telegram bot path (preserved unchanged
|
|
64
|
+
for cross-machine; currently unreachable due to the same bot-to-bot
|
|
65
|
+
block — a separate cross-machine transport is future work).
|
|
66
|
+
- The anti-ping-pong check (`outstanding.canSendTo`) runs ONCE before
|
|
67
|
+
either transport so the same correlation key gates both paths.
|
|
68
|
+
|
|
69
|
+
## The seven questions
|
|
70
|
+
|
|
71
|
+
1. **Over-block.** N/A. The inbox is a NEW dedicated endpoint; the route
|
|
72
|
+
refuses non-routable traffic with `agentMessage: false` but never
|
|
73
|
+
forwards to user handling. No existing route's behaviour changes.
|
|
74
|
+
2. **Under-block.** Spoof defense is intact: per-agent token bearer auth
|
|
75
|
+
gates the route. `senderIsBot` defaulting to `true` is correct because
|
|
76
|
+
the peer authenticated with the target's own token — the spoof defense
|
|
77
|
+
it would otherwise enforce is satisfied earlier by the bearer check.
|
|
78
|
+
Anti-ping-pong: `outstanding.canSendTo` runs before either transport so
|
|
79
|
+
one outstanding prompt blocks both paths.
|
|
80
|
+
3. **Level-of-abstraction fit.** Mirrors `/messages/relay-agent`'s pattern
|
|
81
|
+
exactly: own route, own auth, dispatch into existing in-process state.
|
|
82
|
+
No new abstraction.
|
|
83
|
+
4. **Signal vs authority.** Inbox DECIDES route-or-drop via the hook (the
|
|
84
|
+
hook's existing routing matrix is the spec); audit ledger captures the
|
|
85
|
+
result. `deliverToMentee` DECIDES transport (local vs Telegram). All
|
|
86
|
+
audited; no silent paths.
|
|
87
|
+
5. **Interactions.** Reuses AgentRegistry + AgentTokenManager (same APIs
|
|
88
|
+
already used by `/messages/relay-agent`). Reuses
|
|
89
|
+
`getOrCreateA2aLedger` + `getOrCreateMentorOutstanding` from the mentor
|
|
90
|
+
sender side. Reuses `dispatchAgentMessageHook` from PR #464. No new
|
|
91
|
+
shared mutable state.
|
|
92
|
+
6. **External surfaces.** One new HTTP route (`/a2a/inbox`). One new
|
|
93
|
+
audit-ledger transport value (`'a2a-inbox-local'`). No new config keys.
|
|
94
|
+
7. **Rollback cost.** Trivial — revert removes the route, the middleware
|
|
95
|
+
bypass, and the same-machine branch in `deliverToMentee`. The Telegram
|
|
96
|
+
bot path is untouched and preserved.
|
|
97
|
+
|
|
98
|
+
## Testing
|
|
99
|
+
|
|
100
|
+
12 new tests, all green (`tsc --noEmit` clean):
|
|
101
|
+
|
|
102
|
+
- **Tier 2 integration (8):** `a2a-inbox-route` covers the auth + input-
|
|
103
|
+
validation matrix (401 missing bearer, 401 wrong bearer, 503 no adapter,
|
|
104
|
+
400 missing text, 400 bad topicId, 200 routed, 200 not-routed, 200 with
|
|
105
|
+
default senderIsBot:true).
|
|
106
|
+
- **Tier 3 E2E lifecycle (4):** `a2a-inbox-lifecycle` covers full server
|
|
107
|
+
boot with the mentee receiver wiring + the inbox route alive (not 404),
|
|
108
|
+
auth-gated, and the full claim path that proves the inbox invokes the
|
|
109
|
+
adapter dispatcher end-to-end on the real production init path.
|
|
110
|
+
|
|
111
|
+
A unit-tier slice for the same-machine branch in `deliverToMentee` is
|
|
112
|
+
deferred — covered transitively by the E2E + the existing unit suite for
|
|
113
|
+
`OutstandingPromptTracker` + the new integration test for the inbox route.
|
|
114
|
+
A dedicated unit on the `deliverToMentee` branch would require mocking
|
|
115
|
+
`fetch` + AgentRegistry + getAgentToken — defensible to add later but the
|
|
116
|
+
behaviour is already tested at the boundary.
|
|
117
|
+
|
|
118
|
+
## Migration parity
|
|
119
|
+
|
|
120
|
+
No new config keys. The route registers unconditionally; if no
|
|
121
|
+
`config.mentee.enabled` block is set on the receiver, the adapter has no
|
|
122
|
+
hook installed and the route returns `agentMessage: false`. The
|
|
123
|
+
`/messages/relay-agent` precedent is identical (always-registered route,
|
|
124
|
+
behaviour depends on installed state).
|
|
125
|
+
|
|
126
|
+
The `deliverToMentee` change is purely additive — when no local peer is
|
|
127
|
+
registered, behaviour is byte-for-byte the same as before. No PostUpdateMigrator
|
|
128
|
+
change required.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Side-Effects Review — mentee receiver wiring fires on lifeline-forward path
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/MENTOR-LIVE-READINESS-SPEC.md` §Recipient side.
|
|
4
|
+
Fast-follow to PR #462 (mentee receiver wiring) — closes the dual-path
|
|
5
|
+
ingress gap surfaced by dogfood.
|
|
6
|
+
|
|
7
|
+
**Change:** Three production files + three test files.
|
|
8
|
+
- `src/messaging/TelegramAdapter.ts` (+ ~55 LoC: `dispatchAgentMessageHook`
|
|
9
|
+
public method; polling text-dispatch now calls it instead of inlining the
|
|
10
|
+
hook logic — pure extraction, no behavior change on the polling path)
|
|
11
|
+
- `src/server/routes.ts` (+ ~20 LoC: destructure `senderIsBot` /
|
|
12
|
+
`senderChatId` / `senderBotId` from forward body; invoke
|
|
13
|
+
`dispatchAgentMessageHook` before `onTopicMessage`; short-circuit on
|
|
14
|
+
`handled:true`)
|
|
15
|
+
- `src/lifeline/TelegramLifeline.ts` (+ ~15 LoC: derive + include
|
|
16
|
+
`senderIsBot`/`senderChatId`/`senderBotId` in the forward body)
|
|
17
|
+
- `tests/unit/TelegramAdapter-dispatchAgentMessageHook.test.ts` (new, 8)
|
|
18
|
+
- `tests/integration/telegram-forward-a2a-dispatch.test.ts` (new, 3)
|
|
19
|
+
|
|
20
|
+
## What changed
|
|
21
|
+
|
|
22
|
+
1. **`dispatchAgentMessageHook` public method** on `TelegramAdapter`:
|
|
23
|
+
wraps the existing hook invocation with the FAIL-OPEN try/catch (a
|
|
24
|
+
broken hook never freezes the dispatch loop) and the senderBotId
|
|
25
|
+
derivation logic (sender_chat wins; rawFromId iff senderIsBot;
|
|
26
|
+
undefined for real users). Polling path now calls the dispatcher
|
|
27
|
+
instead of inlining the same code — pure extraction.
|
|
28
|
+
2. **`/internal/telegram-forward` handler** destructures the new spoof-
|
|
29
|
+
defense fields from the request body. Before invoking `onTopicMessage`,
|
|
30
|
+
calls `dispatchAgentMessageHook`. On `handled:true`, returns
|
|
31
|
+
`{ ok: true, forwarded: true, agentMessage: true }` (NEW response shape
|
|
32
|
+
addition — additive, doesn't change the existing `{ ok, forwarded }`
|
|
33
|
+
contract for non-a2a forwards). On hook throw, logs + falls through
|
|
34
|
+
(same fail-open semantics as the polling path).
|
|
35
|
+
3. **`TelegramLifeline.forwardToServer`** derives `senderIsBot` from
|
|
36
|
+
`rawMsg.from.is_bot === true`, `senderChatId` from
|
|
37
|
+
`rawMsg.sender_chat?.id`, `senderBotId` from `senderChatId ?? (isBot ?
|
|
38
|
+
rawMsg.from.id : undefined)`. Includes them in the buildBody output
|
|
39
|
+
only when they have values (omitted fields → server treats as falsy →
|
|
40
|
+
marker-bearing forwards drop closed for spoof defense).
|
|
41
|
+
|
|
42
|
+
## The seven questions
|
|
43
|
+
|
|
44
|
+
1. **Over-block.** N/A — additive. The forward route now invokes the hook
|
|
45
|
+
before `onTopicMessage`; if no hook is installed (or no adapter has
|
|
46
|
+
`dispatchAgentMessageHook`), the route falls through to the existing
|
|
47
|
+
behavior. Backward-compatible test (`preserves existing behavior when
|
|
48
|
+
the adapter has NO dispatchAgentMessageHook`) covers this.
|
|
49
|
+
2. **Under-block.** Spoof defense is end-to-end on the forwarded path:
|
|
50
|
+
missing `senderIsBot` from older lifelines → server treats as falsy →
|
|
51
|
+
any marker drops as `agent-marker-spoofed-by-user`. This is the
|
|
52
|
+
fail-CLOSED side, matching spec invariant.
|
|
53
|
+
3. **Level-of-abstraction fit.** Extraction-then-reuse: the hook
|
|
54
|
+
invocation logic moves from inlined to a method on the adapter, called
|
|
55
|
+
from both polling and forward paths. No new abstraction; just exposes
|
|
56
|
+
what was already there.
|
|
57
|
+
4. **Signal vs authority.** The dispatcher returns a clean boolean to the
|
|
58
|
+
caller; the caller decides whether to short-circuit. Authority lives in
|
|
59
|
+
the caller (route or polling loop), not in the dispatcher. Same as the
|
|
60
|
+
prior inline version.
|
|
61
|
+
5. **Interactions.** Reuses `agentMessageHook` set by `setAgentMessageHook`
|
|
62
|
+
— no new state. The forward route reads three new optional fields from
|
|
63
|
+
the body; older clients (lifeline pre-this-PR) omit them and get the
|
|
64
|
+
fail-CLOSED spoof defense automatically.
|
|
65
|
+
6. **External surfaces.** The `/internal/telegram-forward` response gains
|
|
66
|
+
one additive flag (`agentMessage: true`) when the hook claims the
|
|
67
|
+
message; the existing `{ ok, forwarded }` contract is preserved
|
|
68
|
+
otherwise. No new routes. No new config.
|
|
69
|
+
7. **Rollback cost.** Trivial — revert restores the inline hook call in
|
|
70
|
+
the polling path, drops the dispatcher and the forward-route call,
|
|
71
|
+
drops the lifeline body additions. The new `agentMessage:true` flag is
|
|
72
|
+
an additive response field, so any consumer relying on it would need a
|
|
73
|
+
one-line fix back to checking `forwarded:true` alone.
|
|
74
|
+
|
|
75
|
+
## Testing
|
|
76
|
+
|
|
77
|
+
11 new tests, all green (`tsc --noEmit` clean):
|
|
78
|
+
|
|
79
|
+
- **Tier 1 unit (8):** `TelegramAdapter-dispatchAgentMessageHook` covers
|
|
80
|
+
no-op safe (no hook), handled:true short-circuit, handled:false
|
|
81
|
+
fall-through, FAIL-OPEN on hook throw, three senderBotId derivation
|
|
82
|
+
modes (sender_chat / rawFromId+isBot / undefined for users), explicit
|
|
83
|
+
override.
|
|
84
|
+
- **Tier 2 integration (3):** `telegram-forward-a2a-dispatch` boots
|
|
85
|
+
`createRoutes` with a mock adapter through the real Express pipeline;
|
|
86
|
+
asserts short-circuit when hook claims, fall-through when not,
|
|
87
|
+
backward-compat when adapter lacks the method.
|
|
88
|
+
- **Tier 3 E2E:** the existing `mentee-receiver-lifecycle.test.ts` from
|
|
89
|
+
PR #462 (which installs the hook end-to-end on server boot) plus the
|
|
90
|
+
live dogfood loop that motivated this PR are the lifecycle proof.
|
|
91
|
+
|
|
92
|
+
## Migration parity
|
|
93
|
+
|
|
94
|
+
No config changes; no new config keys. The lifeline-side change is
|
|
95
|
+
deployed via the same npm release as the server-side change, so the
|
|
96
|
+
mixed-version state is only momentary (server upgrades alongside its
|
|
97
|
+
lifeline via the existing AutoUpdater + LifelineDriftPromoter pair).
|
|
98
|
+
Mixed-version safety is preserved either direction (server new / lifeline
|
|
99
|
+
old: spoof-fail-closed; server old / lifeline new: unknown fields ignored).
|