@animalabs/agent-framework 0.28.0 → 0.29.0
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/README.md +88 -25
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +4 -1
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +3 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +7 -3
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +2 -0
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +5 -2
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/framework.d.ts +164 -36
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1120 -479
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +34 -10
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +118 -48
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/types.d.ts +12 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.js +34 -8
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +170 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +561 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +131 -53
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +3 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +3 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +20 -20
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +18 -17
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +17 -16
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +8 -26
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +16 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +14 -25
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +180 -50
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +28 -12
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +6 -7
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +6 -7
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.js +8 -6
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +5 -1
- package/dist/src/modules/workspace/answers.d.ts.map +1 -1
- package/dist/src/modules/workspace/answers.js +16 -1
- package/dist/src/modules/workspace/answers.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +6 -4
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +50 -40
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +1 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +66 -36
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +51 -20
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +120 -82
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +133 -37
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +79 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +83 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
- package/dist/src/reasoning-loss-notice.js +5 -3
- package/dist/src/reasoning-loss-notice.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +1 -53
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +56 -3
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/supplied-text.d.ts +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +9 -8
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +3 -2
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +24 -26
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +23 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +43 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +22 -11
- package/dist/src/types/module.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/agent.ts +4 -1
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +7 -3
- package/src/auto-continue.ts +8 -2
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/framework.ts +1132 -492
- package/src/gate/event-gate.ts +128 -47
- package/src/gate/index.ts +1 -1
- package/src/gate/types.ts +12 -2
- package/src/heartbeat/heartbeat.ts +48 -9
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +2 -2
- package/src/line-reader.ts +58 -0
- package/src/mcpl/channel-registry.ts +137 -52
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +3 -1
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +20 -18
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tool-presence.ts +18 -17
- package/src/mcpl/tool-signature.ts +17 -16
- package/src/mcpl/toolset-host.ts +18 -6
- package/src/mcpl/toolsets-module.ts +142 -50
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +29 -12
- package/src/module-registry.ts +6 -7
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +8 -6
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +20 -2
- package/src/modules/workspace/direct-write.ts +4 -3
- package/src/modules/workspace/effects.ts +50 -40
- package/src/modules/workspace/index.ts +58 -34
- package/src/modules/workspace/observe.ts +139 -88
- package/src/modules/workspace/reconcile.ts +159 -36
- package/src/notice-tags.ts +84 -0
- package/src/reasoning-loss-notice.ts +5 -3
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/tool-args.ts +9 -8
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tune-out/coordinator.ts +25 -27
- package/src/types/agent.ts +23 -9
- package/src/types/framework.ts +44 -0
- package/src/types/module.ts +23 -11
package/src/gate/event-gate.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* inference tokens, not whether the agent should know about the event.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { NOTICE_TAG } from '../notice-tags.js';
|
|
15
|
+
import { cutToFit, suppliedValue } from '../supplied-text.js';
|
|
14
16
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
15
17
|
import { basename, dirname, join } from 'node:path';
|
|
16
18
|
import { GateScript } from './gate-script.js';
|
|
@@ -343,7 +345,7 @@ function referenceLine(count: number, channel: string, oldest: number, now: numb
|
|
|
343
345
|
/** A handed batched-wake line's text (WakeLine), as it reads when written at
|
|
344
346
|
* `now`: the gate's words, its message's age counted to then. */
|
|
345
347
|
export function wakeLineText(line: WakeLine, now: number): string {
|
|
346
|
-
return `[
|
|
348
|
+
return `[${NOTICE_TAG.gate}: 1 event matched]\n\n${referenceLine(1, line.shown, line.at, now)}`;
|
|
347
349
|
}
|
|
348
350
|
|
|
349
351
|
/** The event's own stored entry, as the framework is asked about it. */
|
|
@@ -1825,30 +1827,72 @@ function globAtLeastAsBroad(earlier: string | undefined, later: string | undefin
|
|
|
1825
1827
|
return earlier === later;
|
|
1826
1828
|
}
|
|
1827
1829
|
|
|
1830
|
+
/** The event types the gate can see from an MCPL server: its pushes and its
|
|
1831
|
+
* channels' messages. */
|
|
1832
|
+
export const MCPL_GATE_EVENT_TYPES: readonly string[] = ['mcpl:push-event', 'mcpl:channel-incoming'];
|
|
1833
|
+
|
|
1834
|
+
/** The event types a host knows a source sends (EventGate's
|
|
1835
|
+
* `eventTypesFrom`), or undefined when it could send any. */
|
|
1836
|
+
export type EventTypesFrom = (source: string) => readonly string[] | undefined;
|
|
1837
|
+
|
|
1838
|
+
/** The event types `rule` can match: its scope's, narrowed to those its
|
|
1839
|
+
* source is known to send; 'all' when neither says. */
|
|
1840
|
+
function typesSeenBy(rule: GatePolicy, eventTypesFrom?: EventTypesFrom): { types: string[] | 'all'; known: boolean } {
|
|
1841
|
+
const scope = rule.match.scope && rule.match.scope.length > 0 ? rule.match.scope : undefined;
|
|
1842
|
+
const source = rule.match.source;
|
|
1843
|
+
let sent: readonly string[] | undefined;
|
|
1844
|
+
if (eventTypesFrom && source !== undefined && !source.includes('*')) {
|
|
1845
|
+
try {
|
|
1846
|
+
sent = eventTypesFrom(source);
|
|
1847
|
+
} catch {
|
|
1848
|
+
sent = undefined;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
if (scope && sent) return { types: scope.filter((t) => sent!.includes(t)), known: true };
|
|
1852
|
+
if (scope) return { types: scope, known: true };
|
|
1853
|
+
if (sent) return { types: [...sent], known: true };
|
|
1854
|
+
return { types: 'all', known: false };
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1828
1857
|
/**
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1858
|
+
* Whether `earlier` provably matches events `later` would match, so that
|
|
1859
|
+
* `later` doesn't see them when `earlier` sits before it and consumes its
|
|
1860
|
+
* matches: the event types concerned, whether that's every event `later`
|
|
1861
|
+
* would match, and whether `later` is known to see those types. Returns null
|
|
1862
|
+
* when it can't be proven (which is not proof of safety — the lint is
|
|
1863
|
+
* deliberately one-sided so every note it emits is true).
|
|
1834
1864
|
*/
|
|
1835
|
-
function subsumedEventTypes(
|
|
1865
|
+
function subsumedEventTypes(
|
|
1866
|
+
earlier: GatePolicy,
|
|
1867
|
+
later: GatePolicy,
|
|
1868
|
+
eventTypesFrom?: EventTypesFrom,
|
|
1869
|
+
): Omit<ShadowWarning, 'earlier' | 'later'> | null {
|
|
1836
1870
|
const e = earlier.match;
|
|
1837
1871
|
const l = later.match;
|
|
1838
1872
|
|
|
1839
|
-
//
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1873
|
+
// Event types: which of those `later` can match does `earlier` cover?
|
|
1874
|
+
const seen = typesSeenBy(later, eventTypesFrom);
|
|
1875
|
+
const earlierTypes = e.scope && e.scope.length > 0 ? e.scope : 'all';
|
|
1876
|
+
let eventTypes: string[] | 'all';
|
|
1877
|
+
let whole: boolean;
|
|
1878
|
+
let known = seen.known;
|
|
1879
|
+
if (earlierTypes === 'all') {
|
|
1880
|
+
eventTypes = seen.types;
|
|
1881
|
+
whole = true;
|
|
1882
|
+
} else if (seen.types === 'all') {
|
|
1883
|
+
// `later` matches every type; `earlier` only consumes its own scope, so
|
|
1884
|
+
// the overlap is partial — exactly those types, which may never reach
|
|
1885
|
+
// `later`.
|
|
1886
|
+
eventTypes = earlierTypes;
|
|
1887
|
+
whole = false;
|
|
1847
1888
|
} else {
|
|
1848
|
-
const overlap =
|
|
1889
|
+
const overlap = seen.types.filter((t) => earlierTypes.includes(t));
|
|
1849
1890
|
if (overlap.length === 0) return null;
|
|
1850
|
-
|
|
1891
|
+
eventTypes = overlap;
|
|
1892
|
+
whole = overlap.length === seen.types.length;
|
|
1893
|
+
known = true;
|
|
1851
1894
|
}
|
|
1895
|
+
if (Array.isArray(eventTypes) && eventTypes.length === 0) return null;
|
|
1852
1896
|
|
|
1853
1897
|
// Every remaining discriminator on `earlier` must be at least as broad as
|
|
1854
1898
|
// `later`'s, otherwise some `later` events slip past `earlier`.
|
|
@@ -1876,38 +1920,55 @@ function subsumedEventTypes(earlier: GatePolicy, later: GatePolicy): string[] |
|
|
|
1876
1920
|
if (e.tagsAll && !subsetOf(e.tagsAll, l.tagsAll ?? [])) return null;
|
|
1877
1921
|
if (e.tagsNone && !subsetOf(e.tagsNone, l.tagsNone ?? [])) return null;
|
|
1878
1922
|
|
|
1879
|
-
return
|
|
1923
|
+
return { eventTypes, whole, known };
|
|
1880
1924
|
}
|
|
1881
1925
|
|
|
1882
1926
|
/**
|
|
1883
|
-
* Find rules
|
|
1927
|
+
* Find rules an earlier, broader rule keeps from applying (first match wins),
|
|
1928
|
+
* in whole or for some event types.
|
|
1884
1929
|
* The 2026-08-08 Mythos incident is the canonical catch: a prepended
|
|
1885
1930
|
* `{scope:[mcpl:channel-incoming]}` sampler silently made the
|
|
1886
1931
|
* direct-address and mention rules dead for the whole incoming lane.
|
|
1887
1932
|
* `passthrough` observers shadow nothing — non-firing matches fall through.
|
|
1933
|
+
* `eventTypesFrom` leaves out types a rule's source is known never to send:
|
|
1934
|
+
* a rule for an MCPL server's events isn't told about another rule that
|
|
1935
|
+
* takes every `external-message`.
|
|
1888
1936
|
*/
|
|
1889
|
-
export function findShadowedPolicies(policies: GatePolicy[]): ShadowWarning[] {
|
|
1937
|
+
export function findShadowedPolicies(policies: GatePolicy[], eventTypesFrom?: EventTypesFrom): ShadowWarning[] {
|
|
1890
1938
|
const out: ShadowWarning[] = [];
|
|
1891
1939
|
for (let i = 0; i < policies.length; i++) {
|
|
1892
1940
|
const earlier = policies[i];
|
|
1893
1941
|
if (earlier.passthrough) continue;
|
|
1894
1942
|
for (let j = i + 1; j < policies.length; j++) {
|
|
1895
|
-
const
|
|
1896
|
-
if (
|
|
1897
|
-
out.push({ earlier: earlier.name, later: policies[j].name,
|
|
1943
|
+
const found = subsumedEventTypes(earlier, policies[j], eventTypesFrom);
|
|
1944
|
+
if (found) {
|
|
1945
|
+
out.push({ earlier: earlier.name, later: policies[j].name, ...found });
|
|
1898
1946
|
}
|
|
1899
1947
|
}
|
|
1900
1948
|
}
|
|
1901
1949
|
return out;
|
|
1902
1950
|
}
|
|
1903
1951
|
|
|
1904
|
-
/**
|
|
1905
|
-
*
|
|
1906
|
-
*
|
|
1952
|
+
/** A ShadowWarning in words, used in tool results, traces, and gate_status
|
|
1953
|
+
* alike so the same note reads the same way everywhere. It says which of
|
|
1954
|
+
* three it is (the rule never applies; it misses some of its events; it
|
|
1955
|
+
* may not be meant for those events at all) and what to do. */
|
|
1907
1956
|
export function formatShadowWarning(w: ShadowWarning): string {
|
|
1908
|
-
const
|
|
1909
|
-
|
|
1910
|
-
|
|
1957
|
+
const later = `"${w.later}"`;
|
|
1958
|
+
const earlier = `"${w.earlier}"`;
|
|
1959
|
+
const move = `move ${later} above ${earlier} (wake_add_rule with the whole rule and insertBefore: "${w.earlier}")`;
|
|
1960
|
+
if (w.whole) {
|
|
1961
|
+
return `${later} never applies: ${earlier} comes first and matches every event ${later} would. `
|
|
1962
|
+
+ `If ${later} should apply, ${move}, or narrow ${earlier}.`;
|
|
1963
|
+
}
|
|
1964
|
+
const types = w.eventTypes === 'all' ? 'all' : w.eventTypes.join(', ');
|
|
1965
|
+
if (w.known) {
|
|
1966
|
+
return `${earlier} comes first and takes ${later}'s ${types} events, so ${later} applies only to its others. `
|
|
1967
|
+
+ `If it should apply to those too, ${move}.`;
|
|
1968
|
+
}
|
|
1969
|
+
return `${earlier} comes first and takes every ${types} event, so ${later} sees none. `
|
|
1970
|
+
+ `Nothing to do unless ${later} is meant for ${types} events; then ${move}. `
|
|
1971
|
+
+ `A scope on ${later} naming the event types it's for clears this note.`;
|
|
1911
1972
|
}
|
|
1912
1973
|
|
|
1913
1974
|
/** The text of a thrown value, for a log line. It never throws itself, whatever was thrown. */
|
|
@@ -2035,10 +2096,12 @@ export class EventGate {
|
|
|
2035
2096
|
// Per-policy stats
|
|
2036
2097
|
private stats = new Map<string, PolicyStats>();
|
|
2037
2098
|
|
|
2038
|
-
//
|
|
2039
|
-
// config change
|
|
2040
|
-
|
|
2099
|
+
// The shadow lint's last findings (see refreshShadowWarnings), recomputed
|
|
2100
|
+
// on every config change and fingerprinted so an unchanged set doesn't
|
|
2101
|
+
// re-trace.
|
|
2041
2102
|
private shadowWarningsFingerprint = '[]';
|
|
2103
|
+
/** See the constructor's `eventTypesFrom`. */
|
|
2104
|
+
private eventTypesFromFn: EventTypesFrom | undefined;
|
|
2042
2105
|
|
|
2043
2106
|
// Observability counters for events that didn't match any policy
|
|
2044
2107
|
private totalEvaluations = 0;
|
|
@@ -2153,6 +2216,11 @@ export class EventGate {
|
|
|
2153
2216
|
* (WakeProvenance.line), for a fresh start of the wake's turn to decide
|
|
2154
2217
|
* (WakeLine). Absent: every batched wake writes its line, as before. */
|
|
2155
2218
|
isNewestEntry?: (entry: GateEntryRef) => boolean;
|
|
2219
|
+
/** The event types the gate can see from a source, when the host knows
|
|
2220
|
+
* them (an MCPL server's id: MCPL_GATE_EVENT_TYPES), else undefined.
|
|
2221
|
+
* The rule-order notes (findShadowedPolicies) leave out types a rule's
|
|
2222
|
+
* source never sends. Absent: any source can send any type. */
|
|
2223
|
+
eventTypesFrom?: EventTypesFrom;
|
|
2156
2224
|
}) {
|
|
2157
2225
|
this.configPath = opts.configPath;
|
|
2158
2226
|
this.privilegedUsersPath = opts.privilegedUsersPath;
|
|
@@ -2163,6 +2231,7 @@ export class EventGate {
|
|
|
2163
2231
|
this.resolveRouteChannelFn = opts.resolveRouteChannel ?? defaultRouteChannel;
|
|
2164
2232
|
this.channelShownFn = opts.channelShown;
|
|
2165
2233
|
this.isNewestEntryFn = opts.isNewestEntry;
|
|
2234
|
+
this.eventTypesFromFn = opts.eventTypesFrom;
|
|
2166
2235
|
this.now = opts.now ?? (() => Date.now());
|
|
2167
2236
|
this.judgeFn = opts.judge;
|
|
2168
2237
|
this.changesJudgedContextFn = opts.changesJudgedContext;
|
|
@@ -3448,22 +3517,32 @@ export class EventGate {
|
|
|
3448
3517
|
/** Current shadow-lint findings for the live policy order. Fresh compute —
|
|
3449
3518
|
* cheap (policy lists are tens of entries at most). */
|
|
3450
3519
|
lintShadows(): ShadowWarning[] {
|
|
3451
|
-
return findShadowedPolicies(this.config.policies);
|
|
3520
|
+
return findShadowedPolicies(this.config.policies, this.eventTypesFromFn);
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
/**
|
|
3524
|
+
* The sources the host knows changed (eventTypesFrom answers differently),
|
|
3525
|
+
* as when an MCPL server connects or is removed: the rule-order notes are
|
|
3526
|
+
* made again, and traced if they changed, as a change to the rules would.
|
|
3527
|
+
*/
|
|
3528
|
+
sourcesChanged(): void {
|
|
3529
|
+
this.refreshShadowWarnings('sources-changed');
|
|
3452
3530
|
}
|
|
3453
3531
|
|
|
3454
3532
|
/**
|
|
3455
|
-
* Recompute shadow warnings and, when the set changed
|
|
3456
|
-
*
|
|
3457
|
-
*
|
|
3458
|
-
*
|
|
3533
|
+
* Recompute shadow warnings and, when the set changed, emit a
|
|
3534
|
+
* `gate:shadow-warnings` trace so the hazard lands in ops observability at
|
|
3535
|
+
* the moment the order changed — not at the next missed DM — and, with an
|
|
3536
|
+
* empty list, the moment the last of them went. Called from writeConfig,
|
|
3537
|
+
* reloadIfChanged and sourcesChanged.
|
|
3459
3538
|
*/
|
|
3460
3539
|
private refreshShadowWarnings(reason: string): void {
|
|
3461
3540
|
const warnings = this.lintShadows();
|
|
3462
3541
|
const fingerprint = JSON.stringify(warnings);
|
|
3463
3542
|
if (fingerprint === this.shadowWarningsFingerprint) return;
|
|
3543
|
+
const hadSome = this.shadowWarningsFingerprint !== '[]';
|
|
3464
3544
|
this.shadowWarningsFingerprint = fingerprint;
|
|
3465
|
-
|
|
3466
|
-
if (warnings.length > 0) {
|
|
3545
|
+
if (warnings.length > 0 || hadSome) {
|
|
3467
3546
|
this.emitTrace({
|
|
3468
3547
|
type: 'gate:shadow-warnings',
|
|
3469
3548
|
reason,
|
|
@@ -3621,7 +3700,7 @@ export class EventGate {
|
|
|
3621
3700
|
const quoted = events.filter(e => !e.inContext);
|
|
3622
3701
|
const referenced = events.filter(e => e.inContext);
|
|
3623
3702
|
|
|
3624
|
-
const lines: string[] = quoted.map(e => `- [${e.policyName}] (${e.eventType}): ${e.content}`);
|
|
3703
|
+
const lines: string[] = quoted.map(e => `- [${e.policyName}] (${e.eventType}): ${suppliedValue(e.content)}`);
|
|
3625
3704
|
// How each referenced channel is named in the line, in order.
|
|
3626
3705
|
const shown: string[] = [];
|
|
3627
3706
|
if (referenced.length > 0) {
|
|
@@ -3640,7 +3719,7 @@ export class EventGate {
|
|
|
3640
3719
|
? '(unknown channel)'
|
|
3641
3720
|
: e.routeChannelId !== undefined && e.serverId !== undefined && this.channelShownFn
|
|
3642
3721
|
? this.channelShownFn({ serverId: e.serverId, channelId: e.routeChannelId })
|
|
3643
|
-
: id;
|
|
3722
|
+
: suppliedValue(id);
|
|
3644
3723
|
const key = JSON.stringify([e.serverId ?? null, id ?? null]);
|
|
3645
3724
|
const entry = byChannel.get(key) ?? { name, count: 0, oldest: e.timestamp, label: e.channelLabel };
|
|
3646
3725
|
entry.count += 1;
|
|
@@ -3653,7 +3732,7 @@ export class EventGate {
|
|
|
3653
3732
|
// agent can still address tools that want the composite id. A name
|
|
3654
3733
|
// may carry its own `#`, so one is added only where the name lacks
|
|
3655
3734
|
// it, as at the other places that add a `#`.
|
|
3656
|
-
const channel = label ? `${label.startsWith('#') ?
|
|
3735
|
+
const channel = label ? `${label.startsWith('#') ? '' : '#'}${suppliedValue(label)} (${name})` : name;
|
|
3657
3736
|
shown.push(channel);
|
|
3658
3737
|
lines.push(referenceLine(count, channel, oldest, Date.now()));
|
|
3659
3738
|
}
|
|
@@ -3666,7 +3745,7 @@ export class EventGate {
|
|
|
3666
3745
|
// gate hands the line to the wake (WakeProvenance.line) and a fresh start
|
|
3667
3746
|
// of the wake's turn decides it (WakeLine). Several messages, one with
|
|
3668
3747
|
// anything after it, or content the line quotes, keep the line now.
|
|
3669
|
-
const text = `[
|
|
3748
|
+
const text = `[${NOTICE_TAG.gate}: ${events.length} event${events.length > 1 ? 's' : ''} matched]\n\n${lines.join('\n')}`;
|
|
3670
3749
|
const only = events.length === 1 && events[0].inContext ? events[0] : undefined;
|
|
3671
3750
|
let line: WakeLine | undefined;
|
|
3672
3751
|
if (only && this.newestEntry(only)) {
|
|
@@ -3911,7 +3990,7 @@ export class EventGate {
|
|
|
3911
3990
|
record.last = {
|
|
3912
3991
|
at: Date.now(),
|
|
3913
3992
|
verdict: outcome,
|
|
3914
|
-
reason:
|
|
3993
|
+
reason: cutToFit(suppliedValue(reason), 300),
|
|
3915
3994
|
eventCount: state.events.length,
|
|
3916
3995
|
durationMs,
|
|
3917
3996
|
...(!failed && typeof verdict.inputTokens === 'number' ? { inputTokens: verdict.inputTokens } : {}),
|
|
@@ -3919,7 +3998,7 @@ export class EventGate {
|
|
|
3919
3998
|
if (this.config.policies.some((p) => p.name === policyName)) this.judgeRecords.set(policyName, record);
|
|
3920
3999
|
if (failed) {
|
|
3921
4000
|
this.judgeFailStreak++;
|
|
3922
|
-
this.judgeLastFailure =
|
|
4001
|
+
this.judgeLastFailure = cutToFit(suppliedValue(reason), 200);
|
|
3923
4002
|
} else {
|
|
3924
4003
|
this.judgeFailStreak = 0;
|
|
3925
4004
|
}
|
|
@@ -4246,7 +4325,7 @@ export class EventGate {
|
|
|
4246
4325
|
const placement: WritePlacement = {};
|
|
4247
4326
|
this.addMessageFn('user', [{
|
|
4248
4327
|
type: 'text',
|
|
4249
|
-
text: `[
|
|
4328
|
+
text: `[${NOTICE_TAG.selfWake}] your ${source} timer (${Math.round(ms / 1000)}s) elapsed — now ${nowIso}`,
|
|
4250
4329
|
}], { source: 'gate:self-wake' }, agentName, placement);
|
|
4251
4330
|
this.requestInferenceFn(
|
|
4252
4331
|
agentName,
|
|
@@ -4497,7 +4576,9 @@ export class EventGate {
|
|
|
4497
4576
|
this.judgeUnwiredNotice(), this.judgeFailingNotice(),
|
|
4498
4577
|
]
|
|
4499
4578
|
.filter((e): e is string => e !== null),
|
|
4500
|
-
|
|
4579
|
+
// Fresh, not the cache: the sources the host knows (eventTypesFrom)
|
|
4580
|
+
// change as servers connect, with no change to the rules.
|
|
4581
|
+
shadowWarnings: this.lintShadows().map(formatShadowWarning),
|
|
4501
4582
|
totalEvaluations: this.totalEvaluations,
|
|
4502
4583
|
defaultDecisions: {
|
|
4503
4584
|
triggered: this.defaultTriggered,
|
package/src/gate/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { EventGate, wakeProvenance, wakeLineText, type WakeProvenance, type WritePlacement } from './event-gate.js';
|
|
1
|
+
export { EventGate, MCPL_GATE_EVENT_TYPES, wakeProvenance, wakeLineText, type EventTypesFrom, type WakeProvenance, type WritePlacement } from './event-gate.js';
|
|
2
2
|
export type {
|
|
3
3
|
GateConfig,
|
|
4
4
|
GateOptions,
|
package/src/gate/types.ts
CHANGED
|
@@ -359,10 +359,20 @@ export interface GateDecision {
|
|
|
359
359
|
export interface ShadowWarning {
|
|
360
360
|
/** Name of the earlier (consuming) policy. */
|
|
361
361
|
earlier: string;
|
|
362
|
-
/** Name of the later policy it
|
|
362
|
+
/** Name of the later policy whose events it takes. */
|
|
363
363
|
later: string;
|
|
364
|
-
/** Event types affected
|
|
364
|
+
/** Event types affected: those of `later`'s (its scope's, narrowed to the
|
|
365
|
+
* ones its source is known to send) that `earlier` covers. 'all' only when
|
|
366
|
+
* neither rule has a scope and `later`'s source could send any type. */
|
|
365
367
|
eventTypes: string[] | 'all';
|
|
368
|
+
/** Whether `earlier` takes every event `later` would match, so `later`
|
|
369
|
+
* never applies. */
|
|
370
|
+
whole: boolean;
|
|
371
|
+
/** Whether `later` is known to see these types: its scope names them, or
|
|
372
|
+
* its source is one whose event types the host knows (an MCPL server's).
|
|
373
|
+
* False when `later` names no scope and its source could send anything,
|
|
374
|
+
* so the types may never reach it. */
|
|
375
|
+
known: boolean;
|
|
366
376
|
}
|
|
367
377
|
|
|
368
378
|
/** One row of the dry-run probe table (see EventGate.probeTable). */
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* missed.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
import { NOTICE_TAG } from '../notice-tags.js';
|
|
31
32
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
32
33
|
import { dirname } from 'node:path';
|
|
33
34
|
import { checkToolInput, shownValue } from '../gate/event-gate.js';
|
|
@@ -81,7 +82,7 @@ export const DEFAULT_HEARTBEAT: Readonly<HeartbeatSettings> = {
|
|
|
81
82
|
intervalSeconds: 4 * 60 * 60,
|
|
82
83
|
paused: false,
|
|
83
84
|
deliveryMode: 'message',
|
|
84
|
-
message:
|
|
85
|
+
message: `[${NOTICE_TAG.heartbeat}] Periodic self-check-in. Review anything pending — reminders, schedules, follow-ups — and act if needed; otherwise a brief note to yourself is fine.`,
|
|
85
86
|
};
|
|
86
87
|
|
|
87
88
|
/** A scheduled reminder. `everySeconds` null: one-off, dropped once it fires. */
|
|
@@ -112,6 +113,17 @@ export interface HeartbeatOptions {
|
|
|
112
113
|
importFrom?: { configPath?: string; remindersPath?: string };
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
/** One of the heartbeat's scheduled wakes: its next tick or a pending reminder. */
|
|
117
|
+
export interface HeartbeatScheduledWake extends UpcomingWake {
|
|
118
|
+
kind: 'heartbeat' | 'reminder';
|
|
119
|
+
at: number;
|
|
120
|
+
/** Whether it's a silent tick, delivered as one (HeartbeatWake's `silent`). */
|
|
121
|
+
silent: boolean;
|
|
122
|
+
/** The text it brings, as of its due time: so the gate can be asked about
|
|
123
|
+
* the event it will judge. */
|
|
124
|
+
text: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
115
127
|
/** A wake the heartbeat asks the framework to deliver. */
|
|
116
128
|
export type HeartbeatWake =
|
|
117
129
|
| { kind: 'heartbeat'; reason: 'schedule' | 'manual'; silent: boolean; text: string; eventId: string }
|
|
@@ -223,11 +235,16 @@ function callAt(dueAt: number, now: () => number, onDue: () => void): () => void
|
|
|
223
235
|
return () => clearTimeout(timer);
|
|
224
236
|
}
|
|
225
237
|
|
|
238
|
+
/** The text a tick brings: the time, then its message. */
|
|
239
|
+
function tickText(now: number, message: string, timeZone: string): string {
|
|
240
|
+
return `[${NOTICE_TAG.currentTime}: ${formatHeartbeatTime(now, timeZone)}] ${message}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
226
243
|
/** The text a reminder brings: a header naming it, then its message. */
|
|
227
244
|
export function reminderText(rem: Reminder, now: number, timeZone: string): string {
|
|
228
245
|
let schedule = rem.everySeconds !== null ? `, repeats every ${rem.everySeconds}s` : `, set ${formatHeartbeatTime(rem.createdAt, timeZone)}`;
|
|
229
246
|
if (now - rem.fireAt >= LATE_MS) schedule += `, due ${formatHeartbeatTime(rem.fireAt, timeZone)}`;
|
|
230
|
-
return `[reminder ${rem.id}${schedule} · current time: ${formatHeartbeatTime(now, timeZone)}] ${rem.message}`;
|
|
247
|
+
return `[${NOTICE_TAG.reminder} ${rem.id}${schedule} · current time: ${formatHeartbeatTime(now, timeZone)}] ${rem.message}`;
|
|
231
248
|
}
|
|
232
249
|
|
|
233
250
|
/** A length of time as the agent is shown it. */
|
|
@@ -318,24 +335,46 @@ export class Heartbeat {
|
|
|
318
335
|
|
|
319
336
|
/** What the heartbeat will wake the agent with, soonest first, for the reply judge (UpcomingWake). */
|
|
320
337
|
upcomingWakes(): UpcomingWake[] {
|
|
321
|
-
|
|
338
|
+
return this.scheduledWakes().map(({ at, certain, what }) => ({ at, certain, what }));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* The heartbeat's next tick and its pending reminders, soonest first, each
|
|
343
|
+
* with its kind and the event it will bring, so the gate can be asked about
|
|
344
|
+
* it. `whose` words each for the reply judge ("its heartbeat", as
|
|
345
|
+
* upcomingWakes) or for the agent ("your heartbeat", as skip_reply's
|
|
346
|
+
* confirmation names one due before its self-wake).
|
|
347
|
+
*/
|
|
348
|
+
scheduledWakes(whose: 'its' | 'your' = 'its'): HeartbeatScheduledWake[] {
|
|
349
|
+
const wakes: HeartbeatScheduledWake[] = [];
|
|
322
350
|
const s = this.state;
|
|
323
351
|
if (!s.settings.paused && s.nextFireAt !== null) {
|
|
352
|
+
const at = Math.max(s.nextFireAt, this.notBefore);
|
|
353
|
+
const silent = s.settings.deliveryMode === 'silent';
|
|
324
354
|
wakes.push({
|
|
325
|
-
|
|
355
|
+
kind: 'heartbeat',
|
|
356
|
+
at,
|
|
326
357
|
certain: true,
|
|
327
|
-
what:
|
|
358
|
+
what: `${whose} heartbeat (${silent ? 'a silent tick' : 'a check-in prompt'}, every ${shownLength(s.settings.intervalSeconds)})`,
|
|
359
|
+
silent,
|
|
360
|
+
// A host's interval can put the tick past the last time a date
|
|
361
|
+
// shows (readSettings has no ceiling), and a time can't be shown there.
|
|
362
|
+
text: canHoldTime(at) ? tickText(at, s.settings.message, this.timeZone) : s.settings.message,
|
|
328
363
|
});
|
|
329
364
|
}
|
|
330
365
|
for (const r of s.reminders) {
|
|
331
366
|
const message = r.message.length > 80 ? `${r.message.slice(0, 77)}...` : r.message;
|
|
367
|
+
const at = Math.max(dueAt(r), this.notBefore);
|
|
332
368
|
wakes.push({
|
|
333
|
-
|
|
369
|
+
kind: 'reminder',
|
|
370
|
+
at,
|
|
334
371
|
certain: true,
|
|
335
|
-
what:
|
|
372
|
+
what: `${whose} reminder ${JSON.stringify(message)}${r.everySeconds !== null ? ` (repeats every ${shownLength(r.everySeconds)})` : ''}`,
|
|
373
|
+
silent: false,
|
|
374
|
+
text: reminderText(r, at, this.timeZone),
|
|
336
375
|
});
|
|
337
376
|
}
|
|
338
|
-
return wakes.sort((a, b) =>
|
|
377
|
+
return wakes.sort((a, b) => a.at - b.at);
|
|
339
378
|
}
|
|
340
379
|
|
|
341
380
|
/**
|
|
@@ -549,7 +588,7 @@ export class Heartbeat {
|
|
|
549
588
|
kind: 'heartbeat',
|
|
550
589
|
reason,
|
|
551
590
|
silent,
|
|
552
|
-
text:
|
|
591
|
+
text: tickText(now, s.message, this.timeZone),
|
|
553
592
|
eventId: `heartbeat_${now}_${Math.random().toString(36).slice(2, 8)}`,
|
|
554
593
|
});
|
|
555
594
|
log(`fired heartbeat (${reason}${silent ? ', silent' : ''})`);
|
package/src/held-writes.ts
CHANGED
|
@@ -62,9 +62,9 @@ function valueAt(value: unknown, path: string): unknown {
|
|
|
62
62
|
* A row as a module's context answers it: marked `heldFromResidents` when it
|
|
63
63
|
* carries the stamp a tune-out puts on a message it holds from the residents
|
|
64
64
|
* (`metadata.tuneOut`). Every resident's viewFilter leaves a stamped row out
|
|
65
|
-
* of its compiled view (framework.ts, createAgent), and a tool
|
|
66
|
-
* stores a stamped write without injecting it into the live turn, so
|
|
67
|
-
* marked row is one the store keeps and no resident is shown
|
|
65
|
+
* of its compiled view (framework.ts, createAgent), and a resident's tool
|
|
66
|
+
* boundary stores a stamped write without injecting it into the live turn, so
|
|
67
|
+
* a marked row is one the store keeps and no resident is shown
|
|
68
68
|
* (ModuleMessage.heldFromResidents says where its text can still reach
|
|
69
69
|
* one). Any other row comes back as it is.
|
|
70
70
|
*/
|