instar 1.3.655 → 1.3.657
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +21 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/LiveTestHarness.d.ts +55 -1
- package/dist/core/LiveTestHarness.d.ts.map +1 -1
- package/dist/core/LiveTestHarness.js +71 -2
- package/dist/core/LiveTestHarness.js.map +1 -1
- package/dist/core/RealChannelDriver.d.ts +15 -0
- package/dist/core/RealChannelDriver.d.ts.map +1 -1
- package/dist/core/RealChannelDriver.js +16 -0
- package/dist/core/RealChannelDriver.js.map +1 -1
- package/dist/core/SlackLiveSender.d.ts +29 -0
- package/dist/core/SlackLiveSender.d.ts.map +1 -1
- package/dist/core/SlackLiveSender.js +68 -3
- package/dist/core/SlackLiveSender.js.map +1 -1
- package/dist/core/TelegramLiveSender.d.ts +18 -0
- package/dist/core/TelegramLiveSender.d.ts.map +1 -1
- package/dist/core/TelegramLiveSender.js +60 -0
- package/dist/core/TelegramLiveSender.js.map +1 -1
- package/dist/core/rateLimitFalsePositiveMatrix.d.ts +28 -0
- package/dist/core/rateLimitFalsePositiveMatrix.d.ts.map +1 -0
- package/dist/core/rateLimitFalsePositiveMatrix.js +74 -0
- package/dist/core/rateLimitFalsePositiveMatrix.js.map +1 -0
- package/dist/monitoring/CompactionSentinel.d.ts +9 -0
- package/dist/monitoring/CompactionSentinel.d.ts.map +1 -1
- package/dist/monitoring/CompactionSentinel.js +8 -0
- package/dist/monitoring/CompactionSentinel.js.map +1 -1
- package/dist/monitoring/QuotaCollector.d.ts.map +1 -1
- package/dist/monitoring/QuotaCollector.js +21 -8
- package/dist/monitoring/QuotaCollector.js.map +1 -1
- package/dist/monitoring/RateLimitSentinel.d.ts +25 -0
- package/dist/monitoring/RateLimitSentinel.d.ts.map +1 -1
- package/dist/monitoring/RateLimitSentinel.js +44 -0
- package/dist/monitoring/RateLimitSentinel.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.656.md +68 -0
- package/upgrades/1.3.657.md +32 -0
- package/upgrades/side-effects/false-ratelimit-recovery-completed-sessions.md +113 -0
- package/upgrades/side-effects/real-channel-absence-collect.md +73 -0
|
@@ -39,6 +39,45 @@ export interface ChannelDriver {
|
|
|
39
39
|
timeoutMs: number;
|
|
40
40
|
afterMessageId?: string;
|
|
41
41
|
}): Promise<ReplyResult | null>;
|
|
42
|
+
/**
|
|
43
|
+
* OPTIONAL — collect EVERY message that lands on the channel within a window
|
|
44
|
+
* (after `afterMessageId`). Backs ABSENCE assertions: "after X, no message
|
|
45
|
+
* matching <pattern> arrives within the window". A single awaitReply can't prove
|
|
46
|
+
* absence (it returns the first reply, and a spurious background nudge may land
|
|
47
|
+
* AFTER a legitimate reply). A driver that does not implement this makes an
|
|
48
|
+
* absence scenario BLOCKED (driver-unsupported), never a silent pass. The fix
|
|
49
|
+
* this enables to test: a finished session must emit ZERO throttle-resume nudges
|
|
50
|
+
* (live incident 2026-06-24).
|
|
51
|
+
*/
|
|
52
|
+
collectMessages?(surface: Surface, channelId: string, opts: {
|
|
53
|
+
windowMs: number;
|
|
54
|
+
afterMessageId?: string;
|
|
55
|
+
}): Promise<ReplyResult[]>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Thrown by a driver whose `collectMessages` exists at the object level but cannot
|
|
59
|
+
* serve a SPECIFIC surface (e.g. RealChannelDriver implements collectMessages, but the
|
|
60
|
+
* sender wired for `dashboard` has no history to collect). The harness maps THIS error
|
|
61
|
+
* to BLOCKED (driver-unsupported) rather than FAIL — an absence assertion the driver
|
|
62
|
+
* genuinely cannot make on this surface is unverifiable, not a failure. Any OTHER throw
|
|
63
|
+
* remains a loud FAIL (a real driver error must not be silently downgraded).
|
|
64
|
+
*/
|
|
65
|
+
export declare class DriverCapabilityError extends Error {
|
|
66
|
+
constructor(message: string);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Thrown by a SENDER's `collectMessages` when it read the channel but CANNOT guarantee
|
|
70
|
+
* it saw every message in the window — a paginated/truncated history read (a full page
|
|
71
|
+
* came back, so more may exist) or a failed read. An absence proof over an incomplete
|
|
72
|
+
* read would silently false-PASS (the spurious nudge could be on the page we never
|
|
73
|
+
* fetched), so the harness maps THIS to BLOCKED, never PASS. Distinct from
|
|
74
|
+
* DriverCapabilityError (which is the capability LAYER saying a surface has no collector
|
|
75
|
+
* at all): senders raise THIS for an incomplete read; only RealChannelDriver raises
|
|
76
|
+
* DriverCapabilityError for a missing method. A real transport error stays a plain Error
|
|
77
|
+
* (→ FAIL).
|
|
78
|
+
*/
|
|
79
|
+
export declare class AbsenceUnverifiableError extends Error {
|
|
80
|
+
constructor(message: string);
|
|
42
81
|
}
|
|
43
82
|
export type Volatility = 'safe' | 'volatile' | 'permission';
|
|
44
83
|
export interface HarnessScenario {
|
|
@@ -50,12 +89,27 @@ export interface HarnessScenario {
|
|
|
50
89
|
channelId: string;
|
|
51
90
|
/** The user message to send. */
|
|
52
91
|
input: string;
|
|
53
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Deterministic expectations checked against the captured reply (or, for an
|
|
94
|
+
* absence scenario, against every message collected over `absenceWindowMs`).
|
|
95
|
+
* - replyContains / replyNotEmpty / responderMachine: assert the reply.
|
|
96
|
+
* - replyMustNotContain: the (first) reply must NOT contain this substring.
|
|
97
|
+
* - noMessageMatching: ABSENCE — paired with `absenceWindowMs`, asserts NO
|
|
98
|
+
* message landing on the channel within the window contains this substring.
|
|
99
|
+
*/
|
|
54
100
|
expect: {
|
|
55
101
|
replyContains?: string;
|
|
56
102
|
replyNotEmpty?: boolean;
|
|
57
103
|
responderMachine?: string;
|
|
104
|
+
replyMustNotContain?: string;
|
|
105
|
+
noMessageMatching?: string;
|
|
58
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* When set, this is an ABSENCE scenario: after sending `input`, the harness
|
|
109
|
+
* collects every message on the channel for this many ms and asserts none match
|
|
110
|
+
* `expect.noMessageMatching`. Requires a driver implementing `collectMessages`.
|
|
111
|
+
*/
|
|
112
|
+
absenceWindowMs?: number;
|
|
59
113
|
timeoutMs?: number;
|
|
60
114
|
}
|
|
61
115
|
export interface HarnessMatrix {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveTestHarness.d.ts","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAAE,qBAAqB,EAAE,YAAY,EAAe,OAAO,EAC5E,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,UAAU;IAAG,SAAS,EAAE,MAAM,CAAA;CAAE;AACjD,MAAM,WAAW,WAAW;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE;AAE7F,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,kFAAkF;IAClF,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5D,+CAA+C;IAC/C,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7E,0DAA0D;IAC1D,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACpI;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd
|
|
1
|
+
{"version":3,"file":"LiveTestHarness.d.ts","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,31 @@
|
|
|
19
19
|
* platform), not a fuzzy judgment. (An optional Tier-1 semantic supervisor for
|
|
20
20
|
* natural-language expectations is a follow-on; the core encodes exact checks.)
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Thrown by a driver whose `collectMessages` exists at the object level but cannot
|
|
24
|
+
* serve a SPECIFIC surface (e.g. RealChannelDriver implements collectMessages, but the
|
|
25
|
+
* sender wired for `dashboard` has no history to collect). The harness maps THIS error
|
|
26
|
+
* to BLOCKED (driver-unsupported) rather than FAIL — an absence assertion the driver
|
|
27
|
+
* genuinely cannot make on this surface is unverifiable, not a failure. Any OTHER throw
|
|
28
|
+
* remains a loud FAIL (a real driver error must not be silently downgraded).
|
|
29
|
+
*/
|
|
30
|
+
export class DriverCapabilityError extends Error {
|
|
31
|
+
constructor(message) { super(message); this.name = 'DriverCapabilityError'; }
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Thrown by a SENDER's `collectMessages` when it read the channel but CANNOT guarantee
|
|
35
|
+
* it saw every message in the window — a paginated/truncated history read (a full page
|
|
36
|
+
* came back, so more may exist) or a failed read. An absence proof over an incomplete
|
|
37
|
+
* read would silently false-PASS (the spurious nudge could be on the page we never
|
|
38
|
+
* fetched), so the harness maps THIS to BLOCKED, never PASS. Distinct from
|
|
39
|
+
* DriverCapabilityError (which is the capability LAYER saying a surface has no collector
|
|
40
|
+
* at all): senders raise THIS for an incomplete read; only RealChannelDriver raises
|
|
41
|
+
* DriverCapabilityError for a missing method. A real transport error stays a plain Error
|
|
42
|
+
* (→ FAIL).
|
|
43
|
+
*/
|
|
44
|
+
export class AbsenceUnverifiableError extends Error {
|
|
45
|
+
constructor(message) { super(message); this.name = 'AbsenceUnverifiableError'; }
|
|
46
|
+
}
|
|
22
47
|
export class HarnessVolatileChannelError extends Error {
|
|
23
48
|
constructor(scenarioId, surface, channelId) {
|
|
24
49
|
super(`refusing volatile/permission scenario "${scenarioId}" on non-demo channel ${surface}:${channelId} (§5.3 — volatile scenarios run only on demo channels)`);
|
|
@@ -40,9 +65,13 @@ export class LiveTestHarness {
|
|
|
40
65
|
*/
|
|
41
66
|
async run(matrix, opts = {}) {
|
|
42
67
|
// §5.3 PRE-FLIGHT: refuse the whole run if a volatile/permission scenario points
|
|
43
|
-
// at a non-demo channel — before a single message is sent.
|
|
68
|
+
// at a non-demo channel — before a single message is sent. An ABSENCE scenario
|
|
69
|
+
// (absenceWindowMs != null) is ALSO demo-only regardless of its `safe` tag: it polls
|
|
70
|
+
// the channel's whole agent-outbound history into the harness/logs, so a `safe` tag
|
|
71
|
+
// must not let it read a live (operator) channel's content (security review FD-2).
|
|
44
72
|
for (const s of matrix.scenarios) {
|
|
45
|
-
|
|
73
|
+
const requiresDemo = s.volatility !== 'safe' || s.absenceWindowMs != null;
|
|
74
|
+
if (requiresDemo && !this.d.driver.isDemoChannel(s.surface, s.channelId)) {
|
|
46
75
|
throw new HarnessVolatileChannelError(s.id, s.surface, s.channelId);
|
|
47
76
|
}
|
|
48
77
|
}
|
|
@@ -67,6 +96,44 @@ export class LiveTestHarness {
|
|
|
67
96
|
const timeoutMs = s.timeoutMs ?? this.d.defaultTimeoutMs ?? 30_000;
|
|
68
97
|
const maxRetries = this.d.maxReplyRetries ?? 2;
|
|
69
98
|
const base = { id: s.id, description: s.description, surface: s.surface, riskCategory: s.riskCategory, verdict: 'FAIL' };
|
|
99
|
+
// ── ABSENCE scenario: assert NO message matching the pattern lands within the
|
|
100
|
+
// window (the structural way to catch a spurious background message — e.g. a
|
|
101
|
+
// throttle-resume nudge fired against a finished session).
|
|
102
|
+
if (s.absenceWindowMs != null) {
|
|
103
|
+
const pattern = s.expect.noMessageMatching;
|
|
104
|
+
if (!pattern) {
|
|
105
|
+
return { ...base, verdict: 'BLOCKED', blockedKind: 'operator-waiver', blockedReason: 'absence scenario missing expect.noMessageMatching' };
|
|
106
|
+
}
|
|
107
|
+
if (!this.d.driver.collectMessages) {
|
|
108
|
+
// Never silently pass — an absence assertion an unsupported driver cannot
|
|
109
|
+
// make is BLOCKED with a named reason, not a PASS.
|
|
110
|
+
return { ...base, verdict: 'BLOCKED', blockedKind: 'platform-error', blockedReason: 'driver does not support collectMessages (absence assertion unverifiable)' };
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const sent = await this.d.driver.send(s.surface, s.channelId, s.input);
|
|
114
|
+
const collected = await this.d.driver.collectMessages(s.surface, s.channelId, { windowMs: s.absenceWindowMs, afterMessageId: sent.messageId });
|
|
115
|
+
const offending = collected.find(m => m.text.includes(pattern));
|
|
116
|
+
const evidence = { channelId: s.channelId, messageIds: [sent.messageId, ...collected.map(m => m.messageId)] };
|
|
117
|
+
if (offending) {
|
|
118
|
+
this.log(`scenario ${s.id} FAIL: spurious message matched "${pattern}": ${JSON.stringify(offending.text.slice(0, 120))}`);
|
|
119
|
+
return { ...base, verdict: 'FAIL', evidence, blockedReason: `spurious message matched "${pattern}"` };
|
|
120
|
+
}
|
|
121
|
+
return { ...base, verdict: 'PASS', evidence };
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
if (err instanceof DriverCapabilityError || err instanceof AbsenceUnverifiableError) {
|
|
125
|
+
// Either the surface has no collector (DriverCapabilityError) or the read could
|
|
126
|
+
// not be proven complete (AbsenceUnverifiableError — truncated/paginated/failed
|
|
127
|
+
// history). Both make the absence assertion unverifiable → BLOCKED (named),
|
|
128
|
+
// never a silent PASS over an incomplete read and never a misleading FAIL.
|
|
129
|
+
return { ...base, verdict: 'BLOCKED', blockedKind: 'platform-error', blockedReason: `absence unverifiable on surface "${s.surface}": ${err.message}` };
|
|
130
|
+
}
|
|
131
|
+
// @silent-fallback-ok: not a silent fallback — a real driver error is SURFACED as
|
|
132
|
+
// a FAIL verdict (with the error in blockedReason) that the LiveTestGate consumes
|
|
133
|
+
// and VETOES on. The error is escalated, not swallowed; this is the loud path.
|
|
134
|
+
return { ...base, verdict: 'FAIL', blockedReason: `driver error: ${err instanceof Error ? err.message : String(err)}` };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
70
137
|
try {
|
|
71
138
|
const sent = await this.d.driver.send(s.surface, s.channelId, s.input);
|
|
72
139
|
let reply = null;
|
|
@@ -85,6 +152,8 @@ export class LiveTestHarness {
|
|
|
85
152
|
failures.push(`reply missing "${s.expect.replyContains}"`);
|
|
86
153
|
if (s.expect.replyNotEmpty && reply.text.trim() === '')
|
|
87
154
|
failures.push('reply empty');
|
|
155
|
+
if (s.expect.replyMustNotContain && reply.text.includes(s.expect.replyMustNotContain))
|
|
156
|
+
failures.push(`reply unexpectedly contained "${s.expect.replyMustNotContain}"`);
|
|
88
157
|
if (s.expect.responderMachine && reply.responderMachineId !== s.expect.responderMachine)
|
|
89
158
|
failures.push(`responder ${reply.responderMachineId ?? 'unknown'} ≠ expected ${s.expect.responderMachine}`);
|
|
90
159
|
if (failures.length) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveTestHarness.js","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"LiveTestHarness.js","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,17 @@ export interface SurfaceSender {
|
|
|
32
32
|
timeoutMs: number;
|
|
33
33
|
afterMessageId?: string;
|
|
34
34
|
}): Promise<Omit<ReplyResult, 'responderMachineId'> | null>;
|
|
35
|
+
/**
|
|
36
|
+
* OPTIONAL — collect EVERY agent-authored message on the channel within `windowMs`
|
|
37
|
+
* after `afterMessageId` (the surface's OUTBOUND, the class a spurious background
|
|
38
|
+
* nudge belongs to). Backs the harness ABSENCE assertion over a REAL channel. A
|
|
39
|
+
* sender that omits this makes its surface unverifiable for absence (the driver
|
|
40
|
+
* raises DriverCapabilityError → harness BLOCKED, never a false PASS).
|
|
41
|
+
*/
|
|
42
|
+
collectMessages?(channelId: string, opts: {
|
|
43
|
+
windowMs: number;
|
|
44
|
+
afterMessageId?: string;
|
|
45
|
+
}): Promise<Array<Omit<ReplyResult, 'responderMachineId'>>>;
|
|
35
46
|
}
|
|
36
47
|
export interface RealChannelDriverDeps {
|
|
37
48
|
/** Per-surface real senders. A surface absent here throws if a scenario targets it. */
|
|
@@ -57,5 +68,9 @@ export declare class RealChannelDriver implements ChannelDriver {
|
|
|
57
68
|
timeoutMs: number;
|
|
58
69
|
afterMessageId?: string;
|
|
59
70
|
}): Promise<ReplyResult | null>;
|
|
71
|
+
collectMessages(surface: Surface, channelId: string, opts: {
|
|
72
|
+
windowMs: number;
|
|
73
|
+
afterMessageId?: string;
|
|
74
|
+
}): Promise<ReplyResult[]>;
|
|
60
75
|
}
|
|
61
76
|
//# sourceMappingURL=RealChannelDriver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RealChannelDriver.d.ts","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;
|
|
1
|
+
{"version":3,"file":"RealChannelDriver.d.ts","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,kGAAkG;AAClG,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3D,kHAAkH;IAClH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7I;;;;;;OAMG;IACH,eAAe,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;CACnJ;AAED,MAAM,WAAW,qBAAqB;IACpC,uFAAuF;IACvF,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;IACjD,mCAAmC;IACnC,YAAY,EAAE,IAAI,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAAC;IACzD;;;;OAIG;IACH,uBAAuB,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzF,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAwB;gBAC9B,IAAI,EAAE,qBAAqB;IAEvC,OAAO,CAAC,GAAG;IAEX,OAAO,CAAC,SAAS;IAUjB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAIrD,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAI5E,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAgBlI,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAexI"}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* failures degrade to `undefined` (the harness then simply can't assert on responder)
|
|
21
21
|
* rather than throwing the whole scenario — the SEND/REPLY evidence is still recorded.
|
|
22
22
|
*/
|
|
23
|
+
import { DriverCapabilityError } from './LiveTestHarness.js';
|
|
23
24
|
export class RealChannelDriver {
|
|
24
25
|
d;
|
|
25
26
|
constructor(deps) { this.d = deps; }
|
|
@@ -56,5 +57,20 @@ export class RealChannelDriver {
|
|
|
56
57
|
}
|
|
57
58
|
return { ...reply, responderMachineId };
|
|
58
59
|
}
|
|
60
|
+
async collectMessages(surface, channelId, opts) {
|
|
61
|
+
const sender = this.senderFor(surface);
|
|
62
|
+
if (!sender.collectMessages) {
|
|
63
|
+
// The surface is wired, but its sender can't collect history — the absence
|
|
64
|
+
// assertion is unverifiable HERE. Raise the typed capability error so the harness
|
|
65
|
+
// records BLOCKED (driver-unsupported), never a false PASS and never a FAIL that
|
|
66
|
+
// would wrongly read as "the agent sent a spurious message".
|
|
67
|
+
throw new DriverCapabilityError(`sender for surface "${surface}" does not implement collectMessages`);
|
|
68
|
+
}
|
|
69
|
+
const msgs = await sender.collectMessages(channelId, opts);
|
|
70
|
+
// The absence assertion checks message TEXT only; responder-machine attribution is
|
|
71
|
+
// unnecessary here (and would cost one placement read per message). Return the
|
|
72
|
+
// agent-authored messages as ReplyResult with responderMachineId left undefined.
|
|
73
|
+
return msgs.map(m => ({ ...m }));
|
|
74
|
+
}
|
|
59
75
|
}
|
|
60
76
|
//# sourceMappingURL=RealChannelDriver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RealChannelDriver.js","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;
|
|
1
|
+
{"version":3,"file":"RealChannelDriver.js","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAmC7D,MAAM,OAAO,iBAAiB;IACX,CAAC,CAAwB;IAC1C,YAAY,IAA2B,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAEnD,GAAG,CAAC,CAAS,IAAU,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvE,SAAS,CAAC,OAAgB;QAChC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,8EAA8E;YAC9E,kEAAkE;YAClE,MAAM,IAAI,KAAK,CAAC,0CAA0C,OAAO,qBAAqB,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,aAAa,CAAC,OAAgB,EAAE,SAAiB;QAC/C,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAgB,EAAE,SAAiB,EAAE,IAAY;QAC1D,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAAgB,EAAE,SAAiB,EAAE,IAAoD;QACxG,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACxE,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,kBAAsC,CAAC;QAC3C,IAAI,CAAC;YACH,kBAAkB,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,uBAAuB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC;QAC/F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gFAAgF;YAChF,2EAA2E;YAC3E,6DAA6D;YAC7D,IAAI,CAAC,GAAG,CAAC,wCAAwC,OAAO,IAAI,SAAS,kCAAkC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3J,kBAAkB,GAAG,SAAS,CAAC;QACjC,CAAC;QACD,OAAO,EAAE,GAAG,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,OAAgB,EAAE,SAAiB,EAAE,IAAmD;QAC5G,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YAC5B,2EAA2E;YAC3E,kFAAkF;YAClF,iFAAiF;YACjF,6DAA6D;YAC7D,MAAM,IAAI,qBAAqB,CAAC,uBAAuB,OAAO,sCAAsC,CAAC,CAAC;QACxG,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC3D,mFAAmF;QACnF,+EAA+E;QAC/E,iFAAiF;QACjF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;CACF"}
|
|
@@ -41,6 +41,14 @@ export interface SlackLiveSenderDeps {
|
|
|
41
41
|
api: SlackCaller;
|
|
42
42
|
/** The agent's (Echo's) Slack bot user id — awaitReply waits for a reply authored by THIS id. */
|
|
43
43
|
agentBotUserId: string;
|
|
44
|
+
/**
|
|
45
|
+
* OPTIONAL — the agent's Slack app `bot_id`. A message posted by the agent's BACKGROUND
|
|
46
|
+
* path (incoming-webhook / app post) can carry `bot_id` with NO `user` field; matching
|
|
47
|
+
* on `agentBotUserId` alone would then SKIP it → a spurious background nudge would be
|
|
48
|
+
* missed by the absence proof (false PASS). When set, a message authored by EITHER the
|
|
49
|
+
* agent's user id OR this bot_id counts as agent-outbound. (Lessons-review FD-3.)
|
|
50
|
+
*/
|
|
51
|
+
agentBotId?: string;
|
|
44
52
|
/** Poll cadence while awaiting a reply. Default 2000ms. */
|
|
45
53
|
pollIntervalMs?: number;
|
|
46
54
|
/** Injected for tests; defaults to real timers / clock. */
|
|
@@ -59,6 +67,27 @@ export declare class SlackLiveSender implements SurfaceSender {
|
|
|
59
67
|
timeoutMs: number;
|
|
60
68
|
afterMessageId?: string;
|
|
61
69
|
}): Promise<Omit<ReplyResult, 'responderMachineId'> | null>;
|
|
70
|
+
/** A message is agent-outbound if it is from the agent's user id OR its app bot_id. */
|
|
71
|
+
private isAgentAuthored;
|
|
72
|
+
/**
|
|
73
|
+
* Collect EVERY agent-authored message strictly after `after`, polling across the
|
|
74
|
+
* window so a nudge landing LATE (after a legitimate reply) is still seen. Returned
|
|
75
|
+
* oldest-first. Backs the absence assertion over a real Slack channel.
|
|
76
|
+
*
|
|
77
|
+
* Soundness for an ABSENCE proof (an under-collection here is a silent false-PASS):
|
|
78
|
+
* - **Failed read:** `ok === false` (auth revoked / not_in_channel) throws — never a
|
|
79
|
+
* vacuous PASS over a read that returned nothing because it FAILED.
|
|
80
|
+
* - **Truncation guard:** a `next_cursor` (more pages exist) or a full page
|
|
81
|
+
* (>= HISTORY_LIMIT) means the read may be incomplete → AbsenceUnverifiableError →
|
|
82
|
+
* harness BLOCKED, never a false PASS over an unread page.
|
|
83
|
+
* - **Anti-laundering:** ALL text VERSIONS per ts are kept (an edit can't launder a
|
|
84
|
+
* nudge out). **Identity:** user id OR app bot_id (a background nudge may have only
|
|
85
|
+
* bot_id). **Bounded window:** clamped to MAX_WINDOW_MS.
|
|
86
|
+
*/
|
|
87
|
+
collectMessages(channelId: string, opts: {
|
|
88
|
+
windowMs: number;
|
|
89
|
+
afterMessageId?: string;
|
|
90
|
+
}): Promise<Array<Omit<ReplyResult, 'responderMachineId'>>>;
|
|
62
91
|
/** Find the FIRST agent-authored message strictly after `after` (oldest-first). */
|
|
63
92
|
private findAgentReply;
|
|
64
93
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SlackLiveSender.d.ts","sourceRoot":"","sources":["../../src/core/SlackLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"SlackLiveSender.d.ts","sourceRoot":"","sources":["../../src/core/SlackLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMpE,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAC9D,EAAE,CAAC,EAAE,OAAO,CAAC;QACb,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClG,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,mBAAmB;IAClC,6FAA6F;IAC7F,GAAG,EAAE,WAAW,CAAC;IACjB,iGAAiG;IACjG,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,eAAgB,YAAW,aAAa;IACnD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAsB;gBAC5B,IAAI,EAAE,mBAAmB;IAErC,OAAO,CAAC,GAAG;YACG,KAAK;IAGnB,OAAO,CAAC,GAAG;IAEL,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAU1D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,GAAG,IAAI,CAAC;IAelJ,uFAAuF;IACvF,OAAO,CAAC,eAAe;IAMvB;;;;;;;;;;;;;;OAcG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAmCtJ,mFAAmF;YACrE,cAAc;CAkB7B"}
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
* the RealChannelDriver's injected placement reader; this sender only returns the
|
|
20
20
|
* reply text + id.
|
|
21
21
|
*/
|
|
22
|
+
import { AbsenceUnverifiableError } from './LiveTestHarness.js';
|
|
23
|
+
/** History page size + the absolute ceiling on an absence-collection window. */
|
|
24
|
+
const HISTORY_LIMIT = 100;
|
|
25
|
+
const MAX_WINDOW_MS = 300_000;
|
|
22
26
|
export class SlackLiveSender {
|
|
23
27
|
d;
|
|
24
28
|
constructor(deps) { this.d = deps; }
|
|
@@ -52,12 +56,73 @@ export class SlackLiveSender {
|
|
|
52
56
|
this.log(`no agent reply in ${channelId} within ${opts.timeoutMs}ms`);
|
|
53
57
|
return null;
|
|
54
58
|
}
|
|
59
|
+
/** A message is agent-outbound if it is from the agent's user id OR its app bot_id. */
|
|
60
|
+
isAgentAuthored(m) {
|
|
61
|
+
if (m.user === this.d.agentBotUserId)
|
|
62
|
+
return true;
|
|
63
|
+
if (this.d.agentBotId && m.bot_id === this.d.agentBotId)
|
|
64
|
+
return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Collect EVERY agent-authored message strictly after `after`, polling across the
|
|
69
|
+
* window so a nudge landing LATE (after a legitimate reply) is still seen. Returned
|
|
70
|
+
* oldest-first. Backs the absence assertion over a real Slack channel.
|
|
71
|
+
*
|
|
72
|
+
* Soundness for an ABSENCE proof (an under-collection here is a silent false-PASS):
|
|
73
|
+
* - **Failed read:** `ok === false` (auth revoked / not_in_channel) throws — never a
|
|
74
|
+
* vacuous PASS over a read that returned nothing because it FAILED.
|
|
75
|
+
* - **Truncation guard:** a `next_cursor` (more pages exist) or a full page
|
|
76
|
+
* (>= HISTORY_LIMIT) means the read may be incomplete → AbsenceUnverifiableError →
|
|
77
|
+
* harness BLOCKED, never a false PASS over an unread page.
|
|
78
|
+
* - **Anti-laundering:** ALL text VERSIONS per ts are kept (an edit can't launder a
|
|
79
|
+
* nudge out). **Identity:** user id OR app bot_id (a background nudge may have only
|
|
80
|
+
* bot_id). **Bounded window:** clamped to MAX_WINDOW_MS.
|
|
81
|
+
*/
|
|
82
|
+
async collectMessages(channelId, opts) {
|
|
83
|
+
const after = opts.afterMessageId;
|
|
84
|
+
const deadline = this.now() + Math.min(Math.max(0, opts.windowMs), MAX_WINDOW_MS);
|
|
85
|
+
const pollMs = this.d.pollIntervalMs ?? 2000;
|
|
86
|
+
const seen = new Map(); // ts → every text version observed
|
|
87
|
+
for (let first = true; first || this.now() < deadline; first = false) {
|
|
88
|
+
const res = await this.d.api.call('conversations.history', {
|
|
89
|
+
channel: channelId,
|
|
90
|
+
...(after ? { oldest: after, inclusive: false } : {}),
|
|
91
|
+
limit: HISTORY_LIMIT,
|
|
92
|
+
});
|
|
93
|
+
if (res.ok === false) {
|
|
94
|
+
throw new AbsenceUnverifiableError(`conversations.history failed for ${channelId} (ok=false) — cannot prove absence over a failed read`);
|
|
95
|
+
}
|
|
96
|
+
const messages = res.messages ?? [];
|
|
97
|
+
const nextCursor = res.response_metadata?.next_cursor;
|
|
98
|
+
if ((nextCursor && nextCursor.length > 0) || messages.length >= HISTORY_LIMIT) {
|
|
99
|
+
throw new AbsenceUnverifiableError(`${channelId} history read is paginated/truncated (cursor or full ${HISTORY_LIMIT}-page) — cannot prove completeness`);
|
|
100
|
+
}
|
|
101
|
+
for (const m of messages) {
|
|
102
|
+
if (!m.ts)
|
|
103
|
+
continue; // skip a malformed entry
|
|
104
|
+
if (after && !(m.ts > after))
|
|
105
|
+
continue; // strictly after the prompt
|
|
106
|
+
if (!this.isAgentAuthored(m))
|
|
107
|
+
continue; // agent OUTBOUND only (user id OR bot_id)
|
|
108
|
+
const set = seen.get(m.ts) ?? new Set();
|
|
109
|
+
set.add(m.text ?? '');
|
|
110
|
+
seen.set(m.ts, set);
|
|
111
|
+
}
|
|
112
|
+
if (this.now() >= deadline)
|
|
113
|
+
break;
|
|
114
|
+
await this.sleep(pollMs);
|
|
115
|
+
}
|
|
116
|
+
return [...seen.entries()]
|
|
117
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
118
|
+
.flatMap(([ts, texts]) => [...texts].map(text => ({ messageId: ts, text })));
|
|
119
|
+
}
|
|
55
120
|
/** Find the FIRST agent-authored message strictly after `after` (oldest-first). */
|
|
56
121
|
async findAgentReply(channelId, after) {
|
|
57
122
|
const res = await this.d.api.call('conversations.history', {
|
|
58
123
|
channel: channelId,
|
|
59
124
|
...(after ? { oldest: after, inclusive: false } : {}),
|
|
60
|
-
limit:
|
|
125
|
+
limit: HISTORY_LIMIT,
|
|
61
126
|
});
|
|
62
127
|
const messages = res.messages ?? [];
|
|
63
128
|
// conversations.history returns newest-first; scan oldest-first so we return the
|
|
@@ -66,8 +131,8 @@ export class SlackLiveSender {
|
|
|
66
131
|
for (const m of oldestFirst) {
|
|
67
132
|
if (after && !(m.ts > after))
|
|
68
133
|
continue; // strictly-after guard (belt + suspenders vs `oldest`)
|
|
69
|
-
if (
|
|
70
|
-
continue; // only the AGENT's reply
|
|
134
|
+
if (!this.isAgentAuthored(m))
|
|
135
|
+
continue; // only the AGENT's reply (user id OR bot_id)
|
|
71
136
|
const text = m.text ?? '';
|
|
72
137
|
return { text, messageId: m.ts };
|
|
73
138
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SlackLiveSender.js","sourceRoot":"","sources":["../../src/core/SlackLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"SlackLiveSender.js","sourceRoot":"","sources":["../../src/core/SlackLiveSender.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,24 @@ export declare class TelegramLiveSender implements SurfaceSender {
|
|
|
48
48
|
timeoutMs: number;
|
|
49
49
|
afterMessageId?: string;
|
|
50
50
|
}): Promise<Omit<ReplyResult, 'responderMachineId'> | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Collect EVERY agent-authored message in the topic strictly after `afterMessageId`,
|
|
53
|
+
* polling across the window so a nudge that lands LATE (after a legitimate reply) is
|
|
54
|
+
* still seen. Returned oldest-first. Backs the absence assertion.
|
|
55
|
+
*
|
|
56
|
+
* Soundness for an ABSENCE proof (an under-collection here is a silent false-PASS):
|
|
57
|
+
* - **Anti-laundering:** ALL distinct text VERSIONS of a messageId are kept (not
|
|
58
|
+
* last-write-wins), so an edit that rewrites a spurious nudge to benign text cannot
|
|
59
|
+
* launder it out of the proof — every observed version is returned and matched.
|
|
60
|
+
* - **Truncation guard:** a poll returning a FULL page (>= HISTORY_LIMIT) means the
|
|
61
|
+
* read may be truncated (a nudge could be on an unread page) → AbsenceUnverifiableError
|
|
62
|
+
* → the harness records BLOCKED, never a false PASS over an incomplete read.
|
|
63
|
+
* - **Bounded window:** windowMs is clamped to MAX_WINDOW_MS (caps real-API poll count).
|
|
64
|
+
*/
|
|
65
|
+
collectMessages(channelId: string, opts: {
|
|
66
|
+
windowMs: number;
|
|
67
|
+
afterMessageId?: string;
|
|
68
|
+
}): Promise<Array<Omit<ReplyResult, 'responderMachineId'>>>;
|
|
51
69
|
private findAgentReply;
|
|
52
70
|
}
|
|
53
71
|
//# sourceMappingURL=TelegramLiveSender.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelegramLiveSender.d.ts","sourceRoot":"","sources":["../../src/core/TelegramLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"TelegramLiveSender.d.ts","sourceRoot":"","sources":["../../src/core/TelegramLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMpE,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,uGAAuG;IACvG,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,4FAA4F;IAC5F,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,oBAAoB,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAC1G,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAyB;gBAC/B,IAAI,EAAE,sBAAsB;IAExC,OAAO,CAAC,GAAG;YACG,KAAK;IAGnB,OAAO,CAAC,GAAG;IAEX,OAAO,CAAC,QAAQ;IAMV,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAQ1D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,GAAG,IAAI,CAAC;IAelJ;;;;;;;;;;;;;OAaG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC,CAAC;YAuCxI,cAAc;CAW7B"}
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* sent. Null on timeout. The responder-MACHINE attribution (the cross-machine proof) is
|
|
16
16
|
* the RealChannelDriver's injected placement reader, not this sender.
|
|
17
17
|
*/
|
|
18
|
+
import { AbsenceUnverifiableError } from './LiveTestHarness.js';
|
|
19
|
+
/** History page size + the absolute ceiling on an absence-collection window. */
|
|
20
|
+
const HISTORY_LIMIT = 100;
|
|
21
|
+
const MAX_WINDOW_MS = 300_000;
|
|
18
22
|
export class TelegramLiveSender {
|
|
19
23
|
d;
|
|
20
24
|
constructor(deps) { this.d = deps; }
|
|
@@ -52,6 +56,62 @@ export class TelegramLiveSender {
|
|
|
52
56
|
this.log(`no agent reply in topic ${topic} within ${opts.timeoutMs}ms`);
|
|
53
57
|
return null;
|
|
54
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Collect EVERY agent-authored message in the topic strictly after `afterMessageId`,
|
|
61
|
+
* polling across the window so a nudge that lands LATE (after a legitimate reply) is
|
|
62
|
+
* still seen. Returned oldest-first. Backs the absence assertion.
|
|
63
|
+
*
|
|
64
|
+
* Soundness for an ABSENCE proof (an under-collection here is a silent false-PASS):
|
|
65
|
+
* - **Anti-laundering:** ALL distinct text VERSIONS of a messageId are kept (not
|
|
66
|
+
* last-write-wins), so an edit that rewrites a spurious nudge to benign text cannot
|
|
67
|
+
* launder it out of the proof — every observed version is returned and matched.
|
|
68
|
+
* - **Truncation guard:** a poll returning a FULL page (>= HISTORY_LIMIT) means the
|
|
69
|
+
* read may be truncated (a nudge could be on an unread page) → AbsenceUnverifiableError
|
|
70
|
+
* → the harness records BLOCKED, never a false PASS over an incomplete read.
|
|
71
|
+
* - **Bounded window:** windowMs is clamped to MAX_WINDOW_MS (caps real-API poll count).
|
|
72
|
+
*/
|
|
73
|
+
async collectMessages(channelId, opts) {
|
|
74
|
+
const topic = this.topicNum(channelId);
|
|
75
|
+
const afterId = opts.afterMessageId != null ? Number(opts.afterMessageId) : -Infinity;
|
|
76
|
+
const deadline = this.now() + Math.min(Math.max(0, opts.windowMs), MAX_WINDOW_MS);
|
|
77
|
+
const pollMs = this.d.pollIntervalMs ?? 2000;
|
|
78
|
+
const seen = new Map(); // messageId → every text version observed
|
|
79
|
+
for (let first = true; first || this.now() < deadline; first = false) {
|
|
80
|
+
const entries = await this.d.getHistory(topic, HISTORY_LIMIT);
|
|
81
|
+
// Telegram getTopicHistory returns the most-recent HISTORY_LIMIT entries of the
|
|
82
|
+
// topic's WHOLE lifetime (a tail, NOT bounded to the marker like Slack's `oldest`).
|
|
83
|
+
// So a full page only means the read is TRUNCATED when its OLDEST entry is still
|
|
84
|
+
// AFTER the marker — i.e. the marker scrolled off the page, so post-marker messages
|
|
85
|
+
// between the marker and the page's oldest may be unread. If the oldest in-page
|
|
86
|
+
// entry is <= the marker, the marker (or older) is in-page → every post-marker
|
|
87
|
+
// message is present → complete, even on a long-lived demo topic with >100 lifetime
|
|
88
|
+
// messages. (Round-2 review: a bare `length >= LIMIT` wrongly blocked any reused topic.)
|
|
89
|
+
if (entries.length >= HISTORY_LIMIT) {
|
|
90
|
+
const ids = entries.map(e => e.messageId).filter(n => Number.isFinite(n));
|
|
91
|
+
const oldestInPage = ids.length ? Math.min(...ids) : -Infinity;
|
|
92
|
+
if (oldestInPage > afterId) {
|
|
93
|
+
throw new AbsenceUnverifiableError(`topic ${topic} full ${HISTORY_LIMIT}-entry page is entirely after the marker — the marker scrolled off, post-marker messages may be unread (cannot prove completeness)`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const e of entries) {
|
|
97
|
+
if (!Number.isFinite(e.messageId))
|
|
98
|
+
continue; // skip a malformed entry, never a junk key
|
|
99
|
+
if (e.messageId <= afterId)
|
|
100
|
+
continue; // strictly after the prompt we sent
|
|
101
|
+
if (e.fromUser)
|
|
102
|
+
continue; // agent OUTBOUND only (the background-nudge class)
|
|
103
|
+
const set = seen.get(e.messageId) ?? new Set();
|
|
104
|
+
set.add(e.text ?? '');
|
|
105
|
+
seen.set(e.messageId, set);
|
|
106
|
+
}
|
|
107
|
+
if (this.now() >= deadline)
|
|
108
|
+
break;
|
|
109
|
+
await this.sleep(pollMs);
|
|
110
|
+
}
|
|
111
|
+
return [...seen.entries()]
|
|
112
|
+
.sort((a, b) => a[0] - b[0])
|
|
113
|
+
.flatMap(([messageId, texts]) => [...texts].map(text => ({ messageId: String(messageId), text })));
|
|
114
|
+
}
|
|
55
115
|
async findAgentReply(topicId, afterId) {
|
|
56
116
|
const entries = await this.d.getHistory(topicId, 100);
|
|
57
117
|
// Oldest-first by messageId so we return the EARLIEST agent reply after the prompt.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelegramLiveSender.js","sourceRoot":"","sources":["../../src/core/TelegramLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"TelegramLiveSender.js","sourceRoot":"","sources":["../../src/core/TelegramLiveSender.ts"],"names":[],"mappings":"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"}
|