instar 1.3.474 → 1.3.475
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/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +7 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/types.d.ts +13 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/GrowthDigestPublisher.d.ts +168 -0
- package/dist/monitoring/GrowthDigestPublisher.d.ts.map +1 -0
- package/dist/monitoring/GrowthDigestPublisher.js +477 -0
- package/dist/monitoring/GrowthDigestPublisher.js.map +1 -0
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +67 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +5 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +95 -71
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/1.3.475.md +66 -0
- package/upgrades/side-effects/proactive-growth-digest-publisher-slice2.md +201 -0
package/dist/server/routes.js
CHANGED
|
@@ -728,60 +728,7 @@ export function createRoutes(ctx) {
|
|
|
728
728
|
// reply isn't re-sent across a restart (finding_cross_restart_duplicate_replies,
|
|
729
729
|
// topic 21816). Fail-open: a backing hiccup degrades to in-memory-only.
|
|
730
730
|
new SqliteOutboundDedupStore(SqliteOutboundDedupStore.defaultPath(ctx.config.stateDir)));
|
|
731
|
-
|
|
732
|
-
//
|
|
733
|
-
// Invoked before forwarding agent-authored messages to a user. Runs the
|
|
734
|
-
// ConversationalToneReviewer (single Haiku-class call, ~500ms) to catch CLI
|
|
735
|
-
// commands, file paths, config keys, and other technical leakage that the
|
|
736
|
-
// agent's internal memory discipline missed.
|
|
737
|
-
//
|
|
738
|
-
// Returns true if the message was blocked (response already sent as 422).
|
|
739
|
-
// Returns false if safe to proceed (pass, fail-open, or gate unavailable).
|
|
740
|
-
/**
|
|
741
|
-
* Outbound-message gate — SINGLE authority for agent→user message delivery.
|
|
742
|
-
*
|
|
743
|
-
* Combines structured signals from upstream detectors (junk-payload matcher,
|
|
744
|
-
* outbound dedup gate) with conversational context, and makes the block/allow
|
|
745
|
-
* decision in one LLM call via MessagingToneGate.
|
|
746
|
-
*
|
|
747
|
-
* This is the reshaping called for by docs/signal-vs-authority.md: detectors
|
|
748
|
-
* emit signals, the authority decides. No detector holds independent block
|
|
749
|
-
* power — "test" is junk sometimes and legitimate other times; a near-
|
|
750
|
-
* duplicate is respawn-race sometimes and a legitimate re-ask other times.
|
|
751
|
-
* Only the authority has the conversational context to tell them apart.
|
|
752
|
-
*
|
|
753
|
-
* Bypass flags preserved:
|
|
754
|
-
* metadata.isProxy = true → skip all gating (system-generated msgs)
|
|
755
|
-
* metadata.allowDebugText = true → junk signal suppressed
|
|
756
|
-
* metadata.allowDuplicate = true → duplicate signal suppressed
|
|
757
|
-
*
|
|
758
|
-
* Returns true if blocked (response already sent as 422). False if safe.
|
|
759
|
-
*/
|
|
760
|
-
async function checkOutboundMessage(text, channel, res, options) {
|
|
761
|
-
// ── Self-Violation Signal (OBSERVE-ONLY) ──────────────────────────
|
|
762
|
-
// Record — but NEVER act on — the case where this finalized outbound
|
|
763
|
-
// message contradicts a stored preference. This runs as a fire-and-forget
|
|
764
|
-
// VOID call that is structurally independent of the tone-gate verdict and
|
|
765
|
-
// of this function's return value: it cannot block, delay, or rewrite the
|
|
766
|
-
// message. It runs FIRST (before the gate-availability early-return below)
|
|
767
|
-
// so the observation happens regardless of whether the tone gate exists or
|
|
768
|
-
// what it decides. Dark by default (gated inside on enabled+selfViolationSignal).
|
|
769
|
-
void observeSelfViolation(text, options.topicId).catch(() => {
|
|
770
|
-
/* @silent-fallback-ok — observe-only; a detector error never affects delivery */
|
|
771
|
-
});
|
|
772
|
-
// ── Principal-Coherence Signal (OBSERVE-ONLY) ─────────────────────
|
|
773
|
-
// Record — but NEVER act on — the case where this finalized outbound
|
|
774
|
-
// message credits an operator-ROLE decision (approval / mandate / credential
|
|
775
|
-
// / lock / acting-for) to a principal who is NOT the topic's VERIFIED
|
|
776
|
-
// operator. That is the "Caroline" identity-bleed failure, caught in the
|
|
777
|
-
// agent's OWN output where no inbound gate watches. Same fire-and-forget
|
|
778
|
-
// VOID contract as observeSelfViolation: structurally independent of the
|
|
779
|
-
// tone-gate verdict and of this function's return value — it cannot block,
|
|
780
|
-
// delay, or rewrite the message. Dark by default (gated inside on
|
|
781
|
-
// monitoring.principalCoherence.enabled + a wired TopicOperatorStore).
|
|
782
|
-
void observePrincipalCoherence(text, options.topicId).catch(() => {
|
|
783
|
-
/* @silent-fallback-ok — observe-only; a detector error never affects delivery */
|
|
784
|
-
});
|
|
731
|
+
async function evaluateOutbound(text, channel, options) {
|
|
785
732
|
// ── Localhost-link guard (operator-mandated HARD rule, 2026-06-05) ──
|
|
786
733
|
// A clickable localhost/loopback link must never reach a user — the user
|
|
787
734
|
// is almost never on the machine this server runs on. This is a
|
|
@@ -793,18 +740,22 @@ export function createRoutes(ctx) {
|
|
|
793
740
|
if (!options.allowLocalhostLink) {
|
|
794
741
|
const localLink = detectLocalhostLink(text);
|
|
795
742
|
if (localLink.detected) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
743
|
+
return {
|
|
744
|
+
ok: false,
|
|
745
|
+
status: 422,
|
|
746
|
+
reason: 'localhost-link-guard',
|
|
747
|
+
body: {
|
|
748
|
+
error: `Message blocked: contains a machine-local link (${localLink.match}) that the user cannot open from their device. ` +
|
|
749
|
+
`Replace it with the public tunnel URL (GET /tunnel → url + path), or omit the link and say it will follow. ` +
|
|
750
|
+
`If the operator explicitly asked for the raw local URL, resend with metadata.allowLocalhostLink: true.`,
|
|
751
|
+
blockedBy: 'localhost-link-guard',
|
|
752
|
+
match: localLink.match,
|
|
753
|
+
},
|
|
754
|
+
};
|
|
804
755
|
}
|
|
805
756
|
}
|
|
806
757
|
if (!ctx.messagingToneGate)
|
|
807
|
-
return
|
|
758
|
+
return { ok: true }; // No authority configured — pass through
|
|
808
759
|
try {
|
|
809
760
|
// ── Collect signals from upstream detectors ──
|
|
810
761
|
const signals = {};
|
|
@@ -916,21 +867,94 @@ export function createRoutes(ctx) {
|
|
|
916
867
|
result,
|
|
917
868
|
});
|
|
918
869
|
if (!result.pass) {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
870
|
+
return {
|
|
871
|
+
ok: false,
|
|
872
|
+
status: 422,
|
|
873
|
+
reason: 'tone-gate-blocked',
|
|
874
|
+
body: {
|
|
875
|
+
error: 'tone-gate-blocked',
|
|
876
|
+
rule: result.rule,
|
|
877
|
+
issue: result.issue,
|
|
878
|
+
suggestion: result.suggestion,
|
|
879
|
+
latencyMs: result.latencyMs,
|
|
880
|
+
},
|
|
881
|
+
};
|
|
927
882
|
}
|
|
928
883
|
}
|
|
929
884
|
catch {
|
|
930
885
|
// Fail-open — any error short of a clean block lets the message through.
|
|
931
886
|
}
|
|
887
|
+
return { ok: true };
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Route adapter — fires the observe-only observers (route-side) then delegates
|
|
891
|
+
* the block/allow decision to the shared res-free `evaluateOutbound`. Returns
|
|
892
|
+
* true if blocked (the mapped status/body already written). Behavior is
|
|
893
|
+
* byte-identical to the pre-extraction function for every existing caller.
|
|
894
|
+
*/
|
|
895
|
+
async function checkOutboundMessage(text, channel, res, options) {
|
|
896
|
+
// ── Self-Violation Signal (OBSERVE-ONLY) ──────────────────────────
|
|
897
|
+
// Record — but NEVER act on — the case where this finalized outbound message
|
|
898
|
+
// contradicts a stored preference. Fire-and-forget VOID, structurally
|
|
899
|
+
// independent of the decision and of this function's return value: it cannot
|
|
900
|
+
// block, delay, or rewrite the message. Stays route-side (NOT in
|
|
901
|
+
// evaluateOutbound) — there is nothing for it to catch on the proactive-digest
|
|
902
|
+
// path, and keeping it here preserves byte-identical behavior for callers.
|
|
903
|
+
void observeSelfViolation(text, options.topicId).catch(() => {
|
|
904
|
+
/* @silent-fallback-ok — observe-only; a detector error never affects delivery */
|
|
905
|
+
});
|
|
906
|
+
// ── Principal-Coherence Signal (OBSERVE-ONLY) ─────────────────────
|
|
907
|
+
// Same fire-and-forget VOID contract for the operator-role / principal
|
|
908
|
+
// identity-bleed ("Caroline") case. Route-side for the same reason.
|
|
909
|
+
void observePrincipalCoherence(text, options.topicId).catch(() => {
|
|
910
|
+
/* @silent-fallback-ok — observe-only; a detector error never affects delivery */
|
|
911
|
+
});
|
|
912
|
+
const decision = await evaluateOutbound(text, channel, options);
|
|
913
|
+
if (!decision.ok) {
|
|
914
|
+
res.status(decision.status).json(decision.body);
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
932
917
|
return false;
|
|
933
918
|
}
|
|
919
|
+
/**
|
|
920
|
+
* Shared guarded delivery to the Agent Updates topic — the proactive growth
|
|
921
|
+
* digest publisher's `send`. Resolves the Updates topic server-side (never a
|
|
922
|
+
* fallback topic, mirroring the /telegram/post-update 400), runs the SAME
|
|
923
|
+
* `evaluateOutbound` chokepoint as the route, then sends. Returns a flat
|
|
924
|
+
* DeliveryResult and never throws — a guard block is a normal, non-error
|
|
925
|
+
* outcome that the publisher records and never re-acts on.
|
|
926
|
+
*/
|
|
927
|
+
async function postToUpdatesTopic(text, opts) {
|
|
928
|
+
if (!ctx.telegram)
|
|
929
|
+
return { ok: false, reason: 'telegram-not-configured' };
|
|
930
|
+
const updatesTopicId = ctx.state.get('agent-updates-topic');
|
|
931
|
+
if (!updatesTopicId || typeof updatesTopicId !== 'number') {
|
|
932
|
+
return { ok: false, reason: 'no-updates-topic' };
|
|
933
|
+
}
|
|
934
|
+
if (typeof text !== 'string' || text.length === 0)
|
|
935
|
+
return { ok: false, reason: 'empty-text' };
|
|
936
|
+
if (text.length > 4096)
|
|
937
|
+
return { ok: false, reason: 'too-long' };
|
|
938
|
+
const decision = await evaluateOutbound(text, 'telegram', {
|
|
939
|
+
topicId: updatesTopicId,
|
|
940
|
+
allowDebugText: opts?.allowDebugText === true,
|
|
941
|
+
allowDuplicate: opts?.allowDuplicate === true,
|
|
942
|
+
});
|
|
943
|
+
if (!decision.ok)
|
|
944
|
+
return { ok: false, reason: decision.reason };
|
|
945
|
+
try {
|
|
946
|
+
await ctx.telegram.sendToTopic(updatesTopicId, text);
|
|
947
|
+
return { ok: true };
|
|
948
|
+
}
|
|
949
|
+
catch (err) {
|
|
950
|
+
return { ok: false, reason: err instanceof Error ? err.message : 'send-error' };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
// Attach the shared guarded sender to the growth-digest publisher (if wired).
|
|
954
|
+
// Runs once at route registration — the publisher was constructed + started in
|
|
955
|
+
// AgentServer; this is the §3.3 single-funnel hookup, so the publisher can never
|
|
956
|
+
// reach sendToTopic without passing through the identical evaluateOutbound.
|
|
957
|
+
ctx.growthDigestPublisher?.attachSender((text) => postToUpdatesTopic(text));
|
|
934
958
|
/**
|
|
935
959
|
* Self-Violation Signal — OBSERVE-ONLY (Correction & Preference Learning
|
|
936
960
|
* Sentinel extension).
|