instar 1.3.28 → 1.3.29

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.
@@ -0,0 +1,156 @@
1
+ /**
2
+ * AgentTelegramComms — agent-to-agent Telegram comms primitive.
3
+ *
4
+ * Spec: docs/specs/MENTOR-LIVE-READINESS-SPEC.md §Fix 2a (approved 2026-05-27).
5
+ *
6
+ * A robust, recipient-knows-it's-from-an-agent channel any agent can use to message
7
+ * another agent's bot over Telegram, with the indicator AND the anti-loop machinery as
8
+ * first-class infra. Justin's framing: "robust infra that indicates the message is from
9
+ * another agent… leverage infra to prevent the ping pong trap." The mentor is its first
10
+ * consumer (see MentorOnboarding*), but nothing here is mentor-specific.
11
+ *
12
+ * Every message carries a VISIBLE marker prefix in the body so (a) humans can audit at a
13
+ * glance and (b) the Telegram chat history alone reconstructs a round-trip when ledgers
14
+ * are unavailable:
15
+ *
16
+ * [a2a:from=<sender> to=<recipient> role=<role> id=<id> corr=<corr> ts=<unix-ms> v=1]
17
+ *
18
+ * <body>
19
+ *
20
+ * This file is the PURE logic (marker parse/format + routing decision + cycle-detection
21
+ * key). I/O (the actual Telegram send + the audit ledgers + the processed-id store) is
22
+ * injected so every branch is unit-testable. See §Fix 2a for the routing matrix and the
23
+ * anti-loop invariants this enforces.
24
+ */
25
+ /** Current marker schema version. Bumps are explicit (reader-first rule — see spec). */
26
+ export declare const A2A_VERSION = 1;
27
+ export interface A2aMarker {
28
+ from: string;
29
+ to: string;
30
+ role: string;
31
+ id: string;
32
+ corr: string;
33
+ ts: number;
34
+ v: number;
35
+ }
36
+ export interface A2aMessage extends A2aMarker {
37
+ body: string;
38
+ }
39
+ export interface A2aSendFields {
40
+ from: string;
41
+ to: string;
42
+ role: string;
43
+ /** Stable id; minted by the caller/sender if absent. */
44
+ id: string;
45
+ /** Correlation id. Prompts self-correlate (corr === id); replies carry the prompt's id. */
46
+ corr: string;
47
+ ts: number;
48
+ }
49
+ /**
50
+ * Result of parsing an inbound Telegram message body for an a2a marker.
51
+ * - `no-marker`: not an agent message → caller falls through to normal user handling.
52
+ * - `malformed`: marker-shaped but invalid → caller DROPS (security event), never falls
53
+ * through to user handling (spoof / broken-sender defense).
54
+ */
55
+ export type ParseResult = {
56
+ ok: true;
57
+ msg: A2aMessage;
58
+ } | {
59
+ ok: false;
60
+ kind: 'no-marker';
61
+ } | {
62
+ ok: false;
63
+ kind: 'malformed';
64
+ detail: string;
65
+ };
66
+ /** Format a marker + body for sending. The inverse of {@link parseMarker}. */
67
+ export declare function formatMarker(fields: A2aSendFields, body: string): string;
68
+ /** Parse an inbound message body. Strict — see {@link ParseResult}. */
69
+ export declare function parseMarker(raw: string): ParseResult;
70
+ /** Per-recipient routing configuration (spec §Fix 2a recipient + anti-loop). */
71
+ export interface RecipientConfig {
72
+ /** This agent's own name (the `to` an inbound message must target). */
73
+ localAgent: string;
74
+ /** Known agents → their Telegram bot identity. Only senders here are trusted. */
75
+ knownAgents: Record<string, {
76
+ botId: string;
77
+ }>;
78
+ /**
79
+ * Per-source role acceptance (round-2 adversarial F6): `{ fromAgent: allowedRoles[] }`.
80
+ * A compromised/buggy known agent sending an unexpected role is dropped, even if that
81
+ * role is accepted from a DIFFERENT source. Scoped admission.
82
+ */
83
+ acceptRoles: Record<string, string[]>;
84
+ /** Replay window: reject markers whose ts is outside ±this from now. Default 24h. */
85
+ skewWindowMs: number;
86
+ /** Highest marker version this recipient understands. Newer → dead-letter, not crash. */
87
+ maxVersion: number;
88
+ }
89
+ /**
90
+ * Inbound message context the caller resolves from the Telegram update. The bot-identity
91
+ * fields are the SPOOF DEFENSE: a human user typing a marker-shaped string has
92
+ * `senderIsBot === false` and no `senderChatId` → dropped (round-2 adversarial F1).
93
+ */
94
+ export interface IncomingContext {
95
+ /** The raw message body (marker + body, or arbitrary user text). */
96
+ raw: string;
97
+ /** `from.is_bot` from the Telegram update. */
98
+ senderIsBot: boolean;
99
+ /** `sender_chat.id` from the Telegram update when present (group bot-as-channel relay).
100
+ * Its mere presence is part of the spoof defense (a human user has none). */
101
+ senderChatId?: string;
102
+ /** The effective sender bot identity = `sender_chat.id ?? from.id`. Matched against the
103
+ * allowlist's `botId`. The caller resolves this from the Telegram update. */
104
+ senderBotId?: string;
105
+ /** Wall-clock now (ms) — injected for testability. */
106
+ now: number;
107
+ }
108
+ export type RouteDropReason = 'agent-marker-malformed' | 'agent-marker-stale-or-future' | 'agent-marker-spoofed-by-user' | 'agent-marker-wrong-recipient' | 'agent-marker-unsupported-version' | 'agent-marker-unknown' | 'agent-marker-duplicate' | 'agent-marker-role-not-allowed-from-source' | 'agent-marker-unexpected-role' | 'agent-marker-unknown-role';
109
+ export type RouteDecision = {
110
+ action: 'fall-through';
111
+ } | {
112
+ action: 'route';
113
+ msg: A2aMessage;
114
+ } | {
115
+ action: 'drop';
116
+ reason: RouteDropReason;
117
+ detail?: string;
118
+ msg?: A2aMessage;
119
+ };
120
+ /**
121
+ * The recipient routing matrix (spec §Fix 2a "Routing matrix"). PURE — every branch is a
122
+ * decision the caller then acts on (route to a role-handler, or drop + write an audit
123
+ * row). `knownRole` tells whether the role is one this recipient has a handler for at all;
124
+ * `isProcessed` is the idempotency check against the processed-id store.
125
+ */
126
+ export declare function decideRoute(ctx: IncomingContext, cfg: RecipientConfig, deps: {
127
+ isProcessed: (id: string) => boolean;
128
+ knownRole: (role: string) => boolean;
129
+ }): RouteDecision;
130
+ /**
131
+ * Cycle-detection key (spec §Fix 2a anti-loop #3): keyed on the full tuple so legitimate
132
+ * unrelated traffic never collides. `corr` is always present (required in the marker), so
133
+ * the key cannot collapse to `undefined`.
134
+ */
135
+ export declare function cycleKey(fields: {
136
+ fromBotId: string;
137
+ toBotId: string;
138
+ topicId: number | string;
139
+ role: string;
140
+ corr: string;
141
+ }): string;
142
+ /**
143
+ * In-memory cycle detector. A send is refused (unless `cycleOk`) when the same key was
144
+ * seen within `windowMs` — the structural guard against tight role↔reply ping-pong.
145
+ */
146
+ export declare class CycleDetector {
147
+ private readonly windowMs;
148
+ private readonly recent;
149
+ constructor(windowMs: number);
150
+ /** Returns true if sending this key now would trip cycle-detection. */
151
+ wouldTrip(key: string, now: number): boolean;
152
+ /** Record a send/receive of this key at `now`. */
153
+ mark(key: string, now: number): void;
154
+ private evict;
155
+ }
156
+ //# sourceMappingURL=AgentTelegramComms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentTelegramComms.d.ts","sourceRoot":"","sources":["../../src/messaging/AgentTelegramComms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,wFAAwF;AACxF,eAAO,MAAM,WAAW,IAAI,CAAC;AAuB7B,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAmBxE;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAkBpD;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB;kFAC8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;kFAC8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,eAAe,GACvB,wBAAwB,GACxB,8BAA8B,GAC9B,8BAA8B,GAC9B,8BAA8B,GAC9B,kCAAkC,GAClC,sBAAsB,GACtB,wBAAwB,GACxB,2CAA2C,GAC3C,8BAA8B,GAC9B,2BAA2B,CAAC;AAEhC,MAAM,MAAM,aAAa,GACrB;IAAE,MAAM,EAAE,cAAc,CAAA;CAAE,GAC1B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,UAAU,CAAA;CAAE,CAAC;AAEnF;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE;IAAE,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,GACnF,aAAa,CAoDf;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAET;AAED;;;GAGG;AACH,qBAAa,aAAa;IAEZ,OAAO,CAAC,QAAQ,CAAC,QAAQ;IADrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;gBACvB,QAAQ,EAAE,MAAM;IAE7C,uEAAuE;IACvE,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAM5C,kDAAkD;IAClD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAKpC,OAAO,CAAC,KAAK;CAKd"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * AgentTelegramComms — agent-to-agent Telegram comms primitive.
3
+ *
4
+ * Spec: docs/specs/MENTOR-LIVE-READINESS-SPEC.md §Fix 2a (approved 2026-05-27).
5
+ *
6
+ * A robust, recipient-knows-it's-from-an-agent channel any agent can use to message
7
+ * another agent's bot over Telegram, with the indicator AND the anti-loop machinery as
8
+ * first-class infra. Justin's framing: "robust infra that indicates the message is from
9
+ * another agent… leverage infra to prevent the ping pong trap." The mentor is its first
10
+ * consumer (see MentorOnboarding*), but nothing here is mentor-specific.
11
+ *
12
+ * Every message carries a VISIBLE marker prefix in the body so (a) humans can audit at a
13
+ * glance and (b) the Telegram chat history alone reconstructs a round-trip when ledgers
14
+ * are unavailable:
15
+ *
16
+ * [a2a:from=<sender> to=<recipient> role=<role> id=<id> corr=<corr> ts=<unix-ms> v=1]
17
+ *
18
+ * <body>
19
+ *
20
+ * This file is the PURE logic (marker parse/format + routing decision + cycle-detection
21
+ * key). I/O (the actual Telegram send + the audit ledgers + the processed-id store) is
22
+ * injected so every branch is unit-testable. See §Fix 2a for the routing matrix and the
23
+ * anti-loop invariants this enforces.
24
+ */
25
+ /** Current marker schema version. Bumps are explicit (reader-first rule — see spec). */
26
+ export const A2A_VERSION = 1;
27
+ /** Marker field-value charset (spec: deterministic parsing, avoids invisible/escaping
28
+ * cases). Agent/role/id/corr match this; ts and v are positive integers. */
29
+ const TOKEN = '[A-Za-z0-9._:-]+';
30
+ /**
31
+ * Strict marker regex — anchored to the start, consumes exactly the first line plus the
32
+ * required blank-line separator, then captures the body. Field ORDER is fixed (so the
33
+ * parse is deterministic and a reordered/extra-field marker is "malformed", not silently
34
+ * accepted). All of from/to/role/id/corr/ts/v are REQUIRED (corr + ts required per
35
+ * round-2/3 convergence — missing corr would collapse the cycle-detection key; missing ts
36
+ * disables replay-window defense).
37
+ */
38
+ const MARKER_RE = new RegExp(`^\\[a2a:from=(${TOKEN}) to=(${TOKEN}) role=(${TOKEN}) id=(${TOKEN}) corr=(${TOKEN}) ts=([0-9]+) v=([0-9]+)\\]\\n\\n([\\s\\S]*)$`);
39
+ /** A marker-shaped first line WITHOUT the strict full match — used only to distinguish
40
+ * "not an agent message at all" (fall through to user handling) from "looks like one but
41
+ * is malformed" (drop as a security event, NEVER fall through). */
42
+ const MARKER_PREFIX_RE = /^\[a2a:/;
43
+ /** Format a marker + body for sending. The inverse of {@link parseMarker}. */
44
+ export function formatMarker(fields, body) {
45
+ for (const [k, v] of Object.entries({
46
+ from: fields.from,
47
+ to: fields.to,
48
+ role: fields.role,
49
+ id: fields.id,
50
+ corr: fields.corr,
51
+ })) {
52
+ if (!new RegExp(`^${TOKEN}$`).test(String(v))) {
53
+ throw new Error(`AgentTelegramComms.formatMarker: field "${k}" value "${v}" violates the a2a charset ${TOKEN}`);
54
+ }
55
+ }
56
+ if (!Number.isInteger(fields.ts) || fields.ts <= 0) {
57
+ throw new Error(`AgentTelegramComms.formatMarker: ts must be a positive integer, got ${fields.ts}`);
58
+ }
59
+ return (`[a2a:from=${fields.from} to=${fields.to} role=${fields.role} id=${fields.id} ` +
60
+ `corr=${fields.corr} ts=${fields.ts} v=${A2A_VERSION}]\n\n${body}`);
61
+ }
62
+ /** Parse an inbound message body. Strict — see {@link ParseResult}. */
63
+ export function parseMarker(raw) {
64
+ if (typeof raw !== 'string' || !MARKER_PREFIX_RE.test(raw)) {
65
+ return { ok: false, kind: 'no-marker' };
66
+ }
67
+ const m = MARKER_RE.exec(raw);
68
+ if (!m) {
69
+ return { ok: false, kind: 'malformed', detail: 'marker prefix present but failed strict parse' };
70
+ }
71
+ const [, from, to, role, id, corr, tsStr, vStr, body] = m;
72
+ const ts = Number(tsStr);
73
+ const v = Number(vStr);
74
+ if (!Number.isInteger(ts) || ts <= 0) {
75
+ return { ok: false, kind: 'malformed', detail: `invalid ts "${tsStr}"` };
76
+ }
77
+ if (!Number.isInteger(v) || v <= 0) {
78
+ return { ok: false, kind: 'malformed', detail: `invalid v "${vStr}"` };
79
+ }
80
+ return { ok: true, msg: { from, to, role, id, corr, ts, v, body } };
81
+ }
82
+ /**
83
+ * The recipient routing matrix (spec §Fix 2a "Routing matrix"). PURE — every branch is a
84
+ * decision the caller then acts on (route to a role-handler, or drop + write an audit
85
+ * row). `knownRole` tells whether the role is one this recipient has a handler for at all;
86
+ * `isProcessed` is the idempotency check against the processed-id store.
87
+ */
88
+ export function decideRoute(ctx, cfg, deps) {
89
+ const parsed = parseMarker(ctx.raw);
90
+ if (!parsed.ok && parsed.kind === 'no-marker')
91
+ return { action: 'fall-through' };
92
+ if (!parsed.ok)
93
+ return { action: 'drop', reason: 'agent-marker-malformed', detail: parsed.detail };
94
+ const msg = parsed.msg;
95
+ // Replay / clock-skew window (round-2 adversarial F2).
96
+ if (Math.abs(ctx.now - msg.ts) > cfg.skewWindowMs) {
97
+ return { action: 'drop', reason: 'agent-marker-stale-or-future', msg };
98
+ }
99
+ // Unsupported version → dead-letter, never crash (reader-first rule).
100
+ if (msg.v > cfg.maxVersion) {
101
+ return { action: 'drop', reason: 'agent-marker-unsupported-version', msg };
102
+ }
103
+ // SPOOF DEFENSE (round-2 adversarial F1): a real user typing a marker-shaped string is
104
+ // not a bot and has no sender_chat. Drop before any allowlist/role check — a human-typed
105
+ // marker must NEVER reach a role-handler, even if from/id happen to match.
106
+ if (!ctx.senderIsBot && ctx.senderChatId === undefined) {
107
+ return { action: 'drop', reason: 'agent-marker-spoofed-by-user', msg };
108
+ }
109
+ // Recipient targeting.
110
+ if (msg.to !== cfg.localAgent) {
111
+ return { action: 'drop', reason: 'agent-marker-wrong-recipient', msg };
112
+ }
113
+ // Sender allowlist + bot-identity match.
114
+ const known = cfg.knownAgents[msg.from];
115
+ if (!known || (ctx.senderBotId !== undefined && known.botId !== ctx.senderBotId)) {
116
+ return { action: 'drop', reason: 'agent-marker-unknown', msg };
117
+ }
118
+ // Idempotency (round-2 adversarial: Telegram retry / adapter restart).
119
+ if (deps.isProcessed(msg.id)) {
120
+ return { action: 'drop', reason: 'agent-marker-duplicate', msg };
121
+ }
122
+ // Per-source role acceptance (round-2 adversarial F6): scoped admission.
123
+ const allowedFromSource = cfg.acceptRoles[msg.from] ?? [];
124
+ if (!allowedFromSource.includes(msg.role)) {
125
+ // Distinguish "this recipient has no handler for the role at all" from "the role
126
+ // exists but isn't allowed from THIS source" — both drop, different audit codes.
127
+ if (!deps.knownRole(msg.role)) {
128
+ return { action: 'drop', reason: 'agent-marker-unknown-role', msg };
129
+ }
130
+ return { action: 'drop', reason: 'agent-marker-role-not-allowed-from-source', msg };
131
+ }
132
+ return { action: 'route', msg };
133
+ }
134
+ /**
135
+ * Cycle-detection key (spec §Fix 2a anti-loop #3): keyed on the full tuple so legitimate
136
+ * unrelated traffic never collides. `corr` is always present (required in the marker), so
137
+ * the key cannot collapse to `undefined`.
138
+ */
139
+ export function cycleKey(fields) {
140
+ return `${fields.fromBotId}|${fields.toBotId}|${fields.topicId}|${fields.role}|${fields.corr}`;
141
+ }
142
+ /**
143
+ * In-memory cycle detector. A send is refused (unless `cycleOk`) when the same key was
144
+ * seen within `windowMs` — the structural guard against tight role↔reply ping-pong.
145
+ */
146
+ export class CycleDetector {
147
+ windowMs;
148
+ recent = new Map();
149
+ constructor(windowMs) {
150
+ this.windowMs = windowMs;
151
+ }
152
+ /** Returns true if sending this key now would trip cycle-detection. */
153
+ wouldTrip(key, now) {
154
+ this.evict(now);
155
+ const last = this.recent.get(key);
156
+ return last !== undefined && now - last < this.windowMs;
157
+ }
158
+ /** Record a send/receive of this key at `now`. */
159
+ mark(key, now) {
160
+ this.recent.set(key, now);
161
+ this.evict(now);
162
+ }
163
+ evict(now) {
164
+ for (const [k, t] of this.recent) {
165
+ if (now - t >= this.windowMs)
166
+ this.recent.delete(k);
167
+ }
168
+ }
169
+ }
170
+ //# sourceMappingURL=AgentTelegramComms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentTelegramComms.js","sourceRoot":"","sources":["../../src/messaging/AgentTelegramComms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,wFAAwF;AACxF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAE7B;6EAC6E;AAC7E,MAAM,KAAK,GAAG,kBAAkB,CAAC;AAEjC;;;;;;;GAOG;AACH,MAAM,SAAS,GAAG,IAAI,MAAM,CAC1B,iBAAiB,KAAK,SAAS,KAAK,WAAW,KAAK,SAAS,KAAK,WAAW,KAAK,+CAA+C,CAClI,CAAC;AAEF;;oEAEoE;AACpE,MAAM,gBAAgB,GAAG,SAAS,CAAC;AAsCnC,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,MAAqB,EAAE,IAAY;IAC9D,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC;QAClC,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;KAClB,CAAC,EAAE,CAAC;QACH,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,YAAY,CAAC,8BAA8B,KAAK,EAAE,CAAC,CAAC;QAClH,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,uEAAuE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IACtG,CAAC;IACD,OAAO,CACL,aAAa,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,EAAE,SAAS,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,EAAE,GAAG;QAC/E,QAAQ,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,EAAE,MAAM,WAAW,QAAQ,IAAI,EAAE,CACnE,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC1C,CAAC;IACD,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,+CAA+C,EAAE,CAAC;IACnG,CAAC;IACD,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1D,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QACrC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,KAAK,GAAG,EAAE,CAAC;IAC3E,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,IAAI,GAAG,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC;AACtE,CAAC;AAyDD;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,GAAoB,EACpB,GAAoB,EACpB,IAAoF;IAEpF,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACjF,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAEnG,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;IAEvB,uDAAuD;IACvD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAClD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,8BAA8B,EAAE,GAAG,EAAE,CAAC;IACzE,CAAC;IAED,sEAAsE;IACtE,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,kCAAkC,EAAE,GAAG,EAAE,CAAC;IAC7E,CAAC;IAED,uFAAuF;IACvF,yFAAyF;IACzF,2EAA2E;IAC3E,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACvD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,8BAA8B,EAAE,GAAG,EAAE,CAAC;IACzE,CAAC;IAED,uBAAuB;IACvB,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,UAAU,EAAE,CAAC;QAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,8BAA8B,EAAE,GAAG,EAAE,CAAC;IACzE,CAAC;IAED,yCAAyC;IACzC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QACjF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,GAAG,EAAE,CAAC;IACjE,CAAC;IAED,uEAAuE;IACvE,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAwB,EAAE,GAAG,EAAE,CAAC;IACnE,CAAC;IAED,yEAAyE;IACzE,MAAM,iBAAiB,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1D,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,iFAAiF;QACjF,iFAAiF;QACjF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,EAAE,GAAG,EAAE,CAAC;QACtE,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,2CAA2C,EAAE,GAAG,EAAE,CAAC;IACtF,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,MAMxB;IACC,OAAO,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;AACjG,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,aAAa;IAEK;IADZ,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpD,YAA6B,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;IAEjD,uEAAuE;IACvE,SAAS,CAAC,GAAW,EAAE,GAAW;QAChC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,OAAO,IAAI,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC1D,CAAC;IAED,kDAAkD;IAClD,IAAI,CAAC,GAAW,EAAE,GAAW;QAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC;IAEO,KAAK,CAAC,GAAW;QACvB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjC,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.28",
3
+ "version": "1.3.29",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-27T21:23:16.473Z",
5
- "instarVersion": "1.3.28",
4
+ "generatedAt": "2026-05-27T21:27:57.206Z",
5
+ "instarVersion": "1.3.29",
6
6
  "entryCount": 192,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,42 @@
1
+ # Instar Upgrade Guide — NEXT
2
+
3
+ <!-- bump: minor -->
4
+
5
+ ## What Changed
6
+
7
+ **New building block: agent-to-agent Telegram comms primitive (core logic).** This is the
8
+ first piece of a robust, reusable way for two Instar agents to talk over Telegram *knowingly*
9
+ — every agent-to-agent message carries a visible marker
10
+ (`[a2a:from=… to=… role=… id=… corr=… ts=… v=1]`) so the receiving side can tell it's from
11
+ another agent and apply anti-loop machinery, rather than treating it like a human user
12
+ message. This increment ships the pure, security-critical core: the strict marker
13
+ parser/formatter, the recipient routing decision (with the full drop matrix — malformed,
14
+ replay-window, user-spoof, wrong-recipient, unknown-sender, duplicate, and per-source role
15
+ admission), and the cycle-detection key. It ships **dark** — nothing wires it yet (the
16
+ TelegramAdapter integration and the first consumer, the mentor, land in follow-up PRs).
17
+
18
+ ## What to Tell Your User
19
+
20
+ - Nothing changes in how your agent behaves today — this is groundwork. It's the foundation
21
+ for agents being able to message each other over Telegram safely (with built-in defenses
22
+ so they can't spoof each other or get stuck in a reply loop), which a later update will
23
+ switch on for the mentor feature.
24
+
25
+ ## Summary of New Capabilities
26
+
27
+ | Capability | How to Use |
28
+ |-----------|-----------|
29
+ | `AgentTelegramComms` primitive (core) | Internal `parseMarker` / `formatMarker` / `decideRoute` / `CycleDetector` in `src/messaging/AgentTelegramComms.ts` — pure logic, I/O injected; not wired yet |
30
+
31
+ ## Evidence
32
+
33
+ **Net-new feature, not a bug fix** — a new pure-logic module that nothing imports yet
34
+ (ships dark). Proven by 20 unit tests covering every branch of the security-critical logic:
35
+ marker parse/format (valid; no-marker vs malformed incl. missing `corr`/`ts`, charset
36
+ violation, no separator; round-trip); the full routing matrix (route + fall-through + every
37
+ drop reason, including the **user-spoof defense** — a human typing a marker-shaped string is
38
+ dropped even when from/id match an allowlisted bot — and **per-source role acceptance** — a
39
+ known role from the wrong source is dropped); and cycle-detection (key never collapses,
40
+ trips within window, no false collision). `tsc --noEmit` clean. The integration + e2e tiers
41
+ (real TelegramAdapter wiring, the bidirectional contract test, the supervised live cycle)
42
+ land with the follow-up PRs per the spec's staged plan.
@@ -0,0 +1,75 @@
1
+ # Side-Effects Review — Agent-to-Agent Telegram comms primitive (core logic, PR 1)
2
+
3
+ **Spec:** `docs/specs/MENTOR-LIVE-READINESS-SPEC.md` §Fix 2a (converged, approved by Justin
4
+ 2026-05-27). Convergence report: `docs/specs/reports/mentor-live-readiness-convergence.md`.
5
+ **Change:** New module `src/messaging/AgentTelegramComms.ts` — the PURE, security-critical
6
+ core of the agent-to-agent Telegram comms primitive: strict marker parse/format, the
7
+ recipient routing matrix (every drop branch), and cycle-detection. I/O (the actual Telegram
8
+ send, audit ledgers, processed-id store) is **injected**, so this PR is pure logic + tests;
9
+ the TelegramAdapter wiring + ledger persistence is PR 2, the mentor consumer is PR 3.
10
+ **Files:** `src/messaging/AgentTelegramComms.ts` (new), `tests/unit/messaging/AgentTelegramComms.test.ts` (new).
11
+ **Ships dark:** nothing imports the module yet except its test — zero runtime behavior change.
12
+
13
+ ## Principle check (Phase 1)
14
+
15
+ Decision point? Yes — this is the recipient admission/routing decision (`decideRoute`) and
16
+ the anti-loop cycle key. It is built as PURE functions returning a decision the caller acts
17
+ on; the blocking authority (drop vs route) is explicit + exhaustively tested. No existing
18
+ gate is loosened (nothing wired yet).
19
+
20
+ ## The seven questions
21
+
22
+ 1. **Over-block.** `decideRoute` drops on every ambiguous/unrecognized shape (malformed,
23
+ stale, spoofed, wrong-recipient, unknown-version, unknown-sender, duplicate,
24
+ role-not-allowed). Correct posture for a security boundary — a marker-shaped string is
25
+ guilty until allowlisted. The only fall-through is a genuine no-marker (normal user msg).
26
+ 2. **Under-block.** The spoof defense (`!senderIsBot && senderChatId===undefined` → drop)
27
+ is the round-2 adversarial F1 closure — a human typing a marker can NEVER reach a
28
+ role-handler, even if from/id match. Tested explicitly. `corr`/`ts` are required in the
29
+ parser so neither the cycle key nor the replay window can be bypassed by omission.
30
+ 3. **Level-of-abstraction fit.** Pure logic separated from I/O — the security decisions are
31
+ unit-testable in isolation (20 tests, every branch). I/O injection deferred to PR 2.
32
+ 4. **Signal vs authority.** `decideRoute` IS the authority (drop/route); it returns a typed
33
+ decision + audit reason for every branch (no silent drops — caller writes the audit row).
34
+ 5. **Interactions.** Module is dark (imported only by its test). No interaction with the
35
+ running system yet. PR 2 wires it into TelegramAdapter (multi-instance + handler-chain);
36
+ PR 3 wires the mentor consumer. Each subsequent PR re-runs the gate against the same spec.
37
+ 6. **External surfaces.** None in this PR (no routes, no config consumed yet). The marker
38
+ format is the wire contract for PR 2/3 + Codey's side; `A2A_VERSION` is exported for
39
+ version pinning.
40
+ 7. **Rollback cost.** Trivial — revert removes an unused module. No data, no migration.
41
+
42
+ ## Build-time refinements beyond the spec text
43
+
44
+ - The spec mentioned `state/a2a-*.jsonl` (append-only JSONL audit) + a processed-id JSON
45
+ store. Convention note (MessageProcessingLedger header: "NOT a new ad-hoc JSON file") —
46
+ PR 2 will implement the processed-id store on the SQLite path (matching
47
+ PendingRelayStore/CommitmentTracker) rather than a JSON file; the append-only audit
48
+ ledgers stay JSONL (Codey designed the audit-row schema as JSONL, and append-only JSONL
49
+ is the right shape for a forensic trail). Flagged here so PR 2's choice is intentional.
50
+ - `senderBotId` resolution (`sender_chat.id ?? from.id`) + `senderIsBot` are passed in by
51
+ the caller (PR 2 resolves them from the Telegram update, after the Message type is
52
+ extended to expose `is_bot`/`sender_chat`). The pure core only consumes them.
53
+
54
+ ## Testing
55
+
56
+ - 20 unit tests (`tests/unit/messaging/AgentTelegramComms.test.ts`), all passing:
57
+ - **Marker parse/format**: valid parse; no-marker (fall-through) vs malformed (drop) —
58
+ incl. missing `corr`, missing `ts`, charset violation, no blank-line separator;
59
+ format↔parse round-trip; format rejects charset-violating fields.
60
+ - **Routing matrix**: route success; fall-through; and every drop branch — malformed,
61
+ stale/future (replay), **user-spoof** (human-typed marker dropped even when from/id
62
+ match), group bot-as-channel accept, wrong-recipient, unsupported-version, unknown
63
+ sender, bot-id mismatch, duplicate, **role-not-allowed-from-source** (notify from echo
64
+ dropped even though notify is a known role), unknown-role.
65
+ - **Cycle-detection**: key never collapses (corr always present); trips within window,
66
+ clears after; no collision on different corr.
67
+ - tsc --noEmit clean.
68
+ - PR 2/3 add the integration + e2e tiers (TelegramAdapter wiring, mentor consumer, the
69
+ bidirectional contract test, the supervised live cycle).
70
+
71
+ ## Migration parity
72
+
73
+ None for this PR — a new unwired module. No agent-installed file changes, no config, no
74
+ routes. PR 2/3 carry the config defaults + migration (file-outbox retirement, dead-config
75
+ removal) per the spec's §Migration parity.