@gajae-code/coding-agent 0.15.2 → 0.15.4
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/CHANGELOG.md +43 -1
- package/dist/types/config/autorouting-tier-map.d.ts +0 -9
- package/dist/types/config/file-lock.d.ts +7 -1
- package/dist/types/config/keybindings.d.ts +13 -3
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/model-resolver.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +65 -1
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +42 -2
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
- package/dist/types/gjc-runtime/workflow-placeholder.d.ts +6 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/modes/action-registry.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +16 -1
- package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +42 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -0
- package/dist/types/modes/shared/oauth-url-copy.d.ts +9 -0
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/ui-language.d.ts +2 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/broker/session-index.d.ts +18 -0
- package/dist/types/sdk/bus/index.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/cli/session-cli.d.ts +1 -1
- package/dist/types/sdk/lifecycle/service.d.ts +32 -5
- package/dist/types/sdk/session.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/session/fallback-chain-controller.d.ts +8 -0
- package/dist/types/slash-commands/helpers/aside.d.ts +54 -0
- package/dist/types/tools/ask.d.ts +9 -0
- package/dist/types/utils/clipboard.d.ts +9 -1
- package/package.json +7 -7
- package/scripts/generate-sdk-adapter-parity-manifest.ts +5 -2
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/src/commands/sdk.ts +2 -2
- package/src/config/autorouting-tier-map.ts +0 -9
- package/src/config/file-lock.ts +15 -1
- package/src/config/keybindings.ts +13 -3
- package/src/config/model-registry.ts +132 -21
- package/src/config/model-resolver.ts +2 -1
- package/src/config/models-config-schema.ts +2 -2
- package/src/config/settings-schema.ts +66 -1
- package/src/config/settings.ts +1 -1
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +235 -4
- package/src/coordinator-mcp/server.ts +402 -2
- package/src/dap/client.ts +19 -8
- package/src/deep-interview/render-middleware.ts +6 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/gjc-runtime/deep-interview-stage.ts +20 -1
- package/src/gjc-runtime/deep-interview-state.ts +3 -1
- package/src/gjc-runtime/ralplan-runtime.ts +6 -1
- package/src/gjc-runtime/session-state-lock.ts +11 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +5 -0
- package/src/gjc-runtime/workflow-placeholder.ts +33 -0
- package/src/internal-urls/docs-index.generated.ts +11 -9
- package/src/lsp/client.ts +39 -4
- package/src/main.ts +42 -11
- package/src/modes/action-registry.ts +7 -0
- package/src/modes/components/hook-selector.ts +2 -1
- package/src/modes/components/iterm-pet-transport.ts +4 -2
- package/src/modes/components/runtime-mcp-add-wizard.ts +38 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/tool-status-header.ts +149 -41
- package/src/modes/components/welcome.ts +8 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/goal-mode-controller.ts +7 -2
- package/src/modes/controllers/input-controller.ts +141 -7
- package/src/modes/controllers/plan-mode-controller.ts +12 -4
- package/src/modes/controllers/runtime-mcp-command-controller.ts +164 -20
- package/src/modes/controllers/selector-controller.ts +11 -2
- package/src/modes/interactive-mode.ts +88 -1
- package/src/modes/shared/oauth-url-copy.ts +33 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/ui-language.ts +44 -3
- package/src/runtime-mcp/oauth-flow.ts +19 -8
- package/src/runtime-mcp/transports/stdio.ts +27 -13
- package/src/sdk/acp/adapter.ts +29 -2
- package/src/sdk/broker/broker.ts +26 -6
- package/src/sdk/broker/lifecycle-ledger.ts +28 -1
- package/src/sdk/broker/lifecycle.ts +664 -5
- package/src/sdk/broker/session-index.ts +246 -0
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/index.ts +135 -38
- package/src/sdk/bus/natives-augment.d.ts +20 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +3 -0
- package/src/sdk/cli/session-cli.ts +34 -7
- package/src/sdk/lifecycle/service.ts +166 -11
- package/src/sdk/mcp/server.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +40 -0
- package/src/sdk/protocol/operation-registry.ts +2 -1
- package/src/sdk/session.ts +91 -7
- package/src/session/agent-session.ts +473 -66
- package/src/session/fallback-chain-controller.ts +17 -0
- package/src/setup/frictionless-onboarding.ts +4 -4
- package/src/slash-commands/aside-command.test.ts +366 -0
- package/src/slash-commands/builtin-registry.ts +19 -0
- package/src/slash-commands/helpers/aside.ts +299 -0
- package/src/tools/ask-contract.ts +54 -3
- package/src/tools/ask.ts +54 -6
- package/src/utils/clipboard.ts +37 -28
|
@@ -915,6 +915,91 @@ interface SessionIndexOpenGroup {
|
|
|
915
915
|
promise: Promise<void>;
|
|
916
916
|
closed: boolean;
|
|
917
917
|
}
|
|
918
|
+
|
|
919
|
+
type SessionIdentityExpectation = {
|
|
920
|
+
sessionId: string;
|
|
921
|
+
stateRoot: string;
|
|
922
|
+
endpointGeneration: number;
|
|
923
|
+
pid: number;
|
|
924
|
+
processIncarnation?: string;
|
|
925
|
+
hostIncarnation?: string;
|
|
926
|
+
endpointMtimeMs?: number;
|
|
927
|
+
lifecycleRequestId?: string;
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
function sameSessionTuple(
|
|
931
|
+
event: SessionIndexEvent,
|
|
932
|
+
expected: SessionIdentityExpectation,
|
|
933
|
+
expectedRoot: string,
|
|
934
|
+
): boolean {
|
|
935
|
+
return (
|
|
936
|
+
event.sessionId === expected.sessionId &&
|
|
937
|
+
event.endpointGeneration === expected.endpointGeneration &&
|
|
938
|
+
resolveEquivalentPath(event.locator.stateRoot) === expectedRoot
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function sameSessionIdentity(
|
|
943
|
+
event: SessionIndexEvent,
|
|
944
|
+
expected: SessionIdentityExpectation,
|
|
945
|
+
expectedRoot: string,
|
|
946
|
+
): boolean {
|
|
947
|
+
return (
|
|
948
|
+
sameSessionTuple(event, expected, expectedRoot) &&
|
|
949
|
+
event.pid === expected.pid &&
|
|
950
|
+
event.processIncarnation === expected.processIncarnation &&
|
|
951
|
+
event.hostIncarnation === expected.hostIncarnation &&
|
|
952
|
+
event.endpointMtimeMs === expected.endpointMtimeMs &&
|
|
953
|
+
event.lifecycleRequestId === expected.lifecycleRequestId
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function supersededAtForIdentity(
|
|
958
|
+
events: readonly SessionIndexEvent[],
|
|
959
|
+
expected: SessionIdentityExpectation,
|
|
960
|
+
): number | undefined {
|
|
961
|
+
const expectedRoot = resolveEquivalentPath(expected.stateRoot);
|
|
962
|
+
const targetRegistration = events.findLast(
|
|
963
|
+
event => event.type === "host_registered" && sameSessionIdentity(event, expected, expectedRoot),
|
|
964
|
+
);
|
|
965
|
+
const targetAnchor =
|
|
966
|
+
targetRegistration?.indexSeq ??
|
|
967
|
+
events.find(event => sameSessionIdentity(event, expected, expectedRoot))?.indexSeq;
|
|
968
|
+
if (targetAnchor === undefined) return undefined;
|
|
969
|
+
return events
|
|
970
|
+
.filter(
|
|
971
|
+
event =>
|
|
972
|
+
event.type === "host_registered" &&
|
|
973
|
+
sameSessionTuple(event, expected, expectedRoot) &&
|
|
974
|
+
!sameSessionIdentity(event, expected, expectedRoot) &&
|
|
975
|
+
event.indexSeq > targetAnchor,
|
|
976
|
+
)
|
|
977
|
+
.map(event => event.indexSeq)
|
|
978
|
+
.sort((a, b) => a - b)[0];
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function followsApplicableTombstone(
|
|
982
|
+
events: readonly SessionIndexEvent[],
|
|
983
|
+
candidate: SessionIndexEvent,
|
|
984
|
+
expected: SessionIdentityExpectation,
|
|
985
|
+
expectedRoot: string,
|
|
986
|
+
): boolean {
|
|
987
|
+
const tombstone = events.findLast(
|
|
988
|
+
event =>
|
|
989
|
+
event.type === "session_deleted" &&
|
|
990
|
+
event.indexSeq < candidate.indexSeq &&
|
|
991
|
+
sameSessionIdentity(event, expected, expectedRoot),
|
|
992
|
+
);
|
|
993
|
+
if (tombstone === undefined) return false;
|
|
994
|
+
const reRegistration = events.findLast(
|
|
995
|
+
event =>
|
|
996
|
+
event.type === "host_registered" &&
|
|
997
|
+
event.indexSeq < candidate.indexSeq &&
|
|
998
|
+
sameSessionIdentity(event, expected, expectedRoot),
|
|
999
|
+
);
|
|
1000
|
+
return reRegistration === undefined || reRegistration.indexSeq < tombstone.indexSeq;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
918
1003
|
export class SessionIndex {
|
|
919
1004
|
static #operations = new Map<string, Promise<void>>();
|
|
920
1005
|
static #openGroups = new Map<string, SessionIndexOpenGroup>();
|
|
@@ -1774,6 +1859,167 @@ export class SessionIndex {
|
|
|
1774
1859
|
return reduceEvents(this.#events, this.#policy.clock(), this.#agentDir, probedIncarnations).identities;
|
|
1775
1860
|
}
|
|
1776
1861
|
|
|
1862
|
+
/** Reconstructs a receipt-bound identity even when admission superseded it. */
|
|
1863
|
+
findHistoricalSessionIdentity(expected: {
|
|
1864
|
+
sessionId: string;
|
|
1865
|
+
stateRoot: string;
|
|
1866
|
+
endpointGeneration: number;
|
|
1867
|
+
pid: number;
|
|
1868
|
+
processIncarnation?: string;
|
|
1869
|
+
hostIncarnation?: string;
|
|
1870
|
+
endpointMtimeMs?: number;
|
|
1871
|
+
lifecycleRequestId?: string;
|
|
1872
|
+
}): IndexedSession | undefined {
|
|
1873
|
+
const expectedRoot = resolveEquivalentPath(expected.stateRoot);
|
|
1874
|
+
const supersededAt = supersededAtForIdentity(this.#events, expected);
|
|
1875
|
+
const matching = this.#events.filter(
|
|
1876
|
+
event =>
|
|
1877
|
+
(supersededAt === undefined || event.indexSeq < supersededAt) &&
|
|
1878
|
+
sameSessionIdentity(event, expected, expectedRoot),
|
|
1879
|
+
);
|
|
1880
|
+
const latest = matching.findLast(event => event.type !== "host_heartbeat");
|
|
1881
|
+
if (latest === undefined) return undefined;
|
|
1882
|
+
return projectIdentity(
|
|
1883
|
+
{
|
|
1884
|
+
identity: identityKey(latest),
|
|
1885
|
+
latest,
|
|
1886
|
+
heartbeat: matching.findLast(event => event.type === "host_heartbeat"),
|
|
1887
|
+
},
|
|
1888
|
+
false,
|
|
1889
|
+
this.#policy.clock(),
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/** Returns the latest identity-bound terminal event, if retained. */
|
|
1894
|
+
findSessionTerminalEvidence(
|
|
1895
|
+
expected: Pick<
|
|
1896
|
+
IndexedSession,
|
|
1897
|
+
| "sessionId"
|
|
1898
|
+
| "locator"
|
|
1899
|
+
| "endpointGeneration"
|
|
1900
|
+
| "pid"
|
|
1901
|
+
| "processIncarnation"
|
|
1902
|
+
| "hostIncarnation"
|
|
1903
|
+
| "endpointMtimeMs"
|
|
1904
|
+
| "lifecycleRequestId"
|
|
1905
|
+
>,
|
|
1906
|
+
): { type: "host_unregistered" | "session_closed" | "session_deleted"; indexSeq: number } | undefined {
|
|
1907
|
+
const expectedRoot = resolveEquivalentPath(expected.locator.stateRoot);
|
|
1908
|
+
const expectedIncarnation = expected.hostIncarnation ?? expected.processIncarnation;
|
|
1909
|
+
const supersededAt = supersededAtForIdentity(this.#events, {
|
|
1910
|
+
sessionId: expected.sessionId,
|
|
1911
|
+
stateRoot: expected.locator.stateRoot,
|
|
1912
|
+
endpointGeneration: expected.endpointGeneration,
|
|
1913
|
+
pid: expected.pid,
|
|
1914
|
+
processIncarnation: expected.processIncarnation,
|
|
1915
|
+
hostIncarnation: expected.hostIncarnation,
|
|
1916
|
+
endpointMtimeMs: expected.endpointMtimeMs,
|
|
1917
|
+
lifecycleRequestId: expected.lifecycleRequestId,
|
|
1918
|
+
});
|
|
1919
|
+
const event = this.#events.findLast(
|
|
1920
|
+
event =>
|
|
1921
|
+
(event.type === "host_unregistered" ||
|
|
1922
|
+
event.type === "session_closed" ||
|
|
1923
|
+
event.type === "session_deleted") &&
|
|
1924
|
+
(event.hostIncarnation ?? event.processIncarnation) === expectedIncarnation &&
|
|
1925
|
+
(supersededAt === undefined || event.indexSeq < supersededAt) &&
|
|
1926
|
+
!followsApplicableTombstone(
|
|
1927
|
+
this.#events,
|
|
1928
|
+
event,
|
|
1929
|
+
{
|
|
1930
|
+
sessionId: expected.sessionId,
|
|
1931
|
+
stateRoot: expected.locator.stateRoot,
|
|
1932
|
+
endpointGeneration: expected.endpointGeneration,
|
|
1933
|
+
pid: expected.pid,
|
|
1934
|
+
processIncarnation: expected.processIncarnation,
|
|
1935
|
+
hostIncarnation: expected.hostIncarnation,
|
|
1936
|
+
endpointMtimeMs: expected.endpointMtimeMs,
|
|
1937
|
+
lifecycleRequestId: expected.lifecycleRequestId,
|
|
1938
|
+
},
|
|
1939
|
+
expectedRoot,
|
|
1940
|
+
) &&
|
|
1941
|
+
sameSessionIdentity(
|
|
1942
|
+
event,
|
|
1943
|
+
{
|
|
1944
|
+
sessionId: expected.sessionId,
|
|
1945
|
+
stateRoot: expected.locator.stateRoot,
|
|
1946
|
+
endpointGeneration: expected.endpointGeneration,
|
|
1947
|
+
pid: expected.pid,
|
|
1948
|
+
processIncarnation: expected.processIncarnation,
|
|
1949
|
+
hostIncarnation: expected.hostIncarnation,
|
|
1950
|
+
endpointMtimeMs: expected.endpointMtimeMs,
|
|
1951
|
+
lifecycleRequestId: expected.lifecycleRequestId,
|
|
1952
|
+
},
|
|
1953
|
+
expectedRoot,
|
|
1954
|
+
),
|
|
1955
|
+
);
|
|
1956
|
+
return event &&
|
|
1957
|
+
(event.type === "host_unregistered" || event.type === "session_closed" || event.type === "session_deleted")
|
|
1958
|
+
? { type: event.type, indexSeq: event.indexSeq }
|
|
1959
|
+
: undefined;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/** Returns the latest identity-bound session_closed evidence, even after a tombstone. */
|
|
1963
|
+
findSessionClosedEvidence(
|
|
1964
|
+
expected: Pick<
|
|
1965
|
+
IndexedSession,
|
|
1966
|
+
| "sessionId"
|
|
1967
|
+
| "locator"
|
|
1968
|
+
| "endpointGeneration"
|
|
1969
|
+
| "pid"
|
|
1970
|
+
| "processIncarnation"
|
|
1971
|
+
| "hostIncarnation"
|
|
1972
|
+
| "endpointMtimeMs"
|
|
1973
|
+
| "lifecycleRequestId"
|
|
1974
|
+
>,
|
|
1975
|
+
): number | undefined {
|
|
1976
|
+
const expectedRoot = resolveEquivalentPath(expected.locator.stateRoot);
|
|
1977
|
+
const supersededAt = supersededAtForIdentity(this.#events, {
|
|
1978
|
+
sessionId: expected.sessionId,
|
|
1979
|
+
stateRoot: expected.locator.stateRoot,
|
|
1980
|
+
endpointGeneration: expected.endpointGeneration,
|
|
1981
|
+
pid: expected.pid,
|
|
1982
|
+
processIncarnation: expected.processIncarnation,
|
|
1983
|
+
hostIncarnation: expected.hostIncarnation,
|
|
1984
|
+
endpointMtimeMs: expected.endpointMtimeMs,
|
|
1985
|
+
lifecycleRequestId: expected.lifecycleRequestId,
|
|
1986
|
+
});
|
|
1987
|
+
return this.#events.findLast(
|
|
1988
|
+
event =>
|
|
1989
|
+
event.type === "session_closed" &&
|
|
1990
|
+
(supersededAt === undefined || event.indexSeq < supersededAt) &&
|
|
1991
|
+
!followsApplicableTombstone(
|
|
1992
|
+
this.#events,
|
|
1993
|
+
event,
|
|
1994
|
+
{
|
|
1995
|
+
sessionId: expected.sessionId,
|
|
1996
|
+
stateRoot: expected.locator.stateRoot,
|
|
1997
|
+
endpointGeneration: expected.endpointGeneration,
|
|
1998
|
+
pid: expected.pid,
|
|
1999
|
+
processIncarnation: expected.processIncarnation,
|
|
2000
|
+
hostIncarnation: expected.hostIncarnation,
|
|
2001
|
+
endpointMtimeMs: expected.endpointMtimeMs,
|
|
2002
|
+
lifecycleRequestId: expected.lifecycleRequestId,
|
|
2003
|
+
},
|
|
2004
|
+
expectedRoot,
|
|
2005
|
+
) &&
|
|
2006
|
+
sameSessionIdentity(
|
|
2007
|
+
event,
|
|
2008
|
+
{
|
|
2009
|
+
sessionId: expected.sessionId,
|
|
2010
|
+
stateRoot: expected.locator.stateRoot,
|
|
2011
|
+
endpointGeneration: expected.endpointGeneration,
|
|
2012
|
+
pid: expected.pid,
|
|
2013
|
+
processIncarnation: expected.processIncarnation,
|
|
2014
|
+
hostIncarnation: expected.hostIncarnation,
|
|
2015
|
+
endpointMtimeMs: expected.endpointMtimeMs,
|
|
2016
|
+
lifecycleRequestId: expected.lifecycleRequestId,
|
|
2017
|
+
},
|
|
2018
|
+
expectedRoot,
|
|
2019
|
+
),
|
|
2020
|
+
)?.indexSeq;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1777
2023
|
/**
|
|
1778
2024
|
* Production coalesced heartbeat checkpoint pass (C2): appends one
|
|
1779
2025
|
* `host_heartbeat` per session at most once per {@link SESSION_HEARTBEAT_INTERVAL_MS}.
|
package/src/sdk/bus/index.ts
CHANGED
|
@@ -166,6 +166,7 @@ import {
|
|
|
166
166
|
ASK_SELECTED_ACK_CAPABILITY,
|
|
167
167
|
type EnsureDaemonResult,
|
|
168
168
|
ensureTelegramDaemonRunningDetailed,
|
|
169
|
+
POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY,
|
|
169
170
|
} from "./telegram-daemon";
|
|
170
171
|
|
|
171
172
|
export type {
|
|
@@ -1176,7 +1177,10 @@ interface SessionRuntime {
|
|
|
1176
1177
|
host: SessionSdkHost;
|
|
1177
1178
|
/** Delivers one ring-positioned event envelope to every attached subscriber
|
|
1178
1179
|
* connection, applying the same capability gate as event replay. */
|
|
1179
|
-
broadcastEventFrame: (event: SdkFrame) =>
|
|
1180
|
+
broadcastEventFrame: (event: SdkFrame) => string[];
|
|
1181
|
+
/** Delivers one positioned event and returns opaque receipts only for the
|
|
1182
|
+
* notification-effect generations that accepted it. */
|
|
1183
|
+
broadcastEventFrameWithReceipts: (event: SdkFrame) => string[];
|
|
1180
1184
|
/** Owns stateRoot-backed revisions and removes their spills on terminal shutdown. */
|
|
1181
1185
|
revisions: RevisionStore;
|
|
1182
1186
|
/** Releases all snapshot pins before the revision store is closed. */
|
|
@@ -1403,26 +1407,39 @@ function emitSessionEvent(
|
|
|
1403
1407
|
runtime: Pick<SessionRuntime, "host" | "broadcastEventFrame">,
|
|
1404
1408
|
frame: { type: string; [key: string]: unknown },
|
|
1405
1409
|
payload: Record<string, unknown> = frame,
|
|
1406
|
-
):
|
|
1407
|
-
runtime.broadcastEventFrame(runtime.host.emitEvent({ kind: frame.type, payload }));
|
|
1410
|
+
): string[] {
|
|
1411
|
+
return runtime.broadcastEventFrame(runtime.host.emitEvent({ kind: frame.type, payload }));
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function emitSessionEventWithReceipts(
|
|
1415
|
+
runtime: Pick<SessionRuntime, "host" | "broadcastEventFrameWithReceipts">,
|
|
1416
|
+
frame: { type: string; [key: string]: unknown },
|
|
1417
|
+
): string[] {
|
|
1418
|
+
return runtime.broadcastEventFrameWithReceipts(runtime.host.emitEvent({ kind: frame.type, payload: frame }));
|
|
1408
1419
|
}
|
|
1409
1420
|
|
|
1410
1421
|
function pushSessionFrame(
|
|
1411
1422
|
runtime: Pick<SessionRuntime, "server" | "host" | "broadcastEventFrame">,
|
|
1412
1423
|
frame: { type: string; [key: string]: unknown },
|
|
1413
|
-
):
|
|
1414
|
-
emitSessionEvent(runtime, frame);
|
|
1424
|
+
): boolean {
|
|
1425
|
+
const positionedRecipients = emitSessionEvent(runtime, frame);
|
|
1415
1426
|
if (frame.type === "turn_stream") {
|
|
1416
|
-
runtime.server.pushTurnStreamUnchecked(
|
|
1427
|
+
const rawAccepted = runtime.server.pushTurnStreamUnchecked(
|
|
1417
1428
|
String(frame.sessionId),
|
|
1418
1429
|
frame.phase === "live" ? "live" : "finalized",
|
|
1419
1430
|
String(frame.text),
|
|
1420
1431
|
typeof frame.finalAnswer === "boolean" ? frame.finalAnswer : undefined,
|
|
1421
1432
|
typeof frame.messageRef === "string" ? frame.messageRef : undefined,
|
|
1433
|
+
positionedRecipients,
|
|
1422
1434
|
);
|
|
1423
|
-
|
|
1435
|
+
// A retained lean settlement may be consumed only after either the
|
|
1436
|
+
// positioned or legacy raw transport accepts the lead-in. Older native addons
|
|
1437
|
+
// return undefined here, which deliberately fails closed unless the
|
|
1438
|
+
// positioned leg has already proved acceptance.
|
|
1439
|
+
return positionedRecipients.length > 0 || rawAccepted === true;
|
|
1424
1440
|
}
|
|
1425
|
-
runtime.server.pushFrame(JSON.stringify(frame));
|
|
1441
|
+
runtime.server.pushFrame(JSON.stringify(frame), positionedRecipients);
|
|
1442
|
+
return positionedRecipients.length > 0;
|
|
1426
1443
|
}
|
|
1427
1444
|
|
|
1428
1445
|
async function pushTerminalSessionFrame(
|
|
@@ -1430,6 +1447,9 @@ async function pushTerminalSessionFrame(
|
|
|
1430
1447
|
frame: { type: "session_closed"; sessionId: string },
|
|
1431
1448
|
): Promise<boolean> {
|
|
1432
1449
|
emitSessionEvent(runtime, frame);
|
|
1450
|
+
// Terminal shutdown still uses the acknowledged native leg: the positioned
|
|
1451
|
+
// send is best-effort and the server must not stop before a socket writer has
|
|
1452
|
+
// settled this final frame.
|
|
1433
1453
|
return await runtime.server.pushFrameAndWait(JSON.stringify(frame), 1_000);
|
|
1434
1454
|
}
|
|
1435
1455
|
|
|
@@ -1438,8 +1458,15 @@ function pushFileAttachment(
|
|
|
1438
1458
|
frame: { type: "file_attachment"; sessionId: string; name: string; mime?: string; caption?: string },
|
|
1439
1459
|
data: Buffer,
|
|
1440
1460
|
): void {
|
|
1441
|
-
emitSessionEvent(runtime, frame, { ...frame, data: data.toString("base64") });
|
|
1442
|
-
runtime.server.pushFileAttachmentUnchecked(
|
|
1461
|
+
const positionedRecipients = emitSessionEvent(runtime, frame, { ...frame, data: data.toString("base64") });
|
|
1462
|
+
runtime.server.pushFileAttachmentUnchecked(
|
|
1463
|
+
frame.sessionId,
|
|
1464
|
+
frame.name,
|
|
1465
|
+
frame.mime,
|
|
1466
|
+
data,
|
|
1467
|
+
frame.caption,
|
|
1468
|
+
positionedRecipients,
|
|
1469
|
+
);
|
|
1443
1470
|
}
|
|
1444
1471
|
|
|
1445
1472
|
/** Agent lifecycle is SDK session truth, independent of optional chat delivery. */
|
|
@@ -1449,8 +1476,8 @@ function emitAgentLifecycle(
|
|
|
1449
1476
|
): void {
|
|
1450
1477
|
try {
|
|
1451
1478
|
const json = JSON.stringify(frame);
|
|
1452
|
-
emitSessionEvent(runtime, frame);
|
|
1453
|
-
runtime.server.pushFrame(json);
|
|
1479
|
+
const positionedRecipients = emitSessionEvent(runtime, frame);
|
|
1480
|
+
runtime.server.pushFrame(json, positionedRecipients);
|
|
1454
1481
|
} catch (error) {
|
|
1455
1482
|
logger.warn(`sdk: lifecycle delivery failed: ${String(error)}`);
|
|
1456
1483
|
}
|
|
@@ -4743,20 +4770,41 @@ export function createNotificationsExtension(
|
|
|
4743
4770
|
* or an event replay, which is exactly the attached-subscriber set. Fenced
|
|
4744
4771
|
* connections are excluded like their inbound frames, and capability-gated
|
|
4745
4772
|
* kinds follow the same gate replay applies, so live and replay delivery
|
|
4746
|
-
* stay one truth per connection.
|
|
4773
|
+
* stay one truth per connection. Only notification adapters that explicitly
|
|
4774
|
+
* negotiate positioned-only effects are returned for matching raw exclusion;
|
|
4775
|
+
* ordinary direct SDK subscribers retain both public surfaces from #4570.
|
|
4747
4776
|
*/
|
|
4748
|
-
const broadcastEventFrame = (event: SdkFrame):
|
|
4777
|
+
const broadcastEventFrame = (event: SdkFrame): string[] => {
|
|
4749
4778
|
const gated = CAP_GATED_FRAME_KINDS.has(String(event.kind));
|
|
4750
4779
|
const json = JSON.stringify(event);
|
|
4780
|
+
const recipients: string[] = [];
|
|
4751
4781
|
for (const [connectionId, capabilities] of hostCapCache) {
|
|
4752
4782
|
if (fencedConnections.has(connectionId)) continue;
|
|
4753
4783
|
if (gated && !capabilities.has(TOOL_ACTIVITY_CAPABILITY)) continue;
|
|
4754
4784
|
try {
|
|
4755
4785
|
server.sendTo(connectionId, json);
|
|
4786
|
+
if (capabilities.has(POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY)) recipients.push(connectionId);
|
|
4756
4787
|
} catch {
|
|
4757
4788
|
// Broadcasts are best effort; directed responses surface send failures.
|
|
4758
4789
|
}
|
|
4759
4790
|
}
|
|
4791
|
+
return recipients;
|
|
4792
|
+
};
|
|
4793
|
+
const broadcastEventFrameWithReceipts = (event: SdkFrame): string[] => {
|
|
4794
|
+
const gated = CAP_GATED_FRAME_KINDS.has(String(event.kind));
|
|
4795
|
+
const json = JSON.stringify(event);
|
|
4796
|
+
const receipts: string[] = [];
|
|
4797
|
+
for (const [connectionId, capabilities] of hostCapCache) {
|
|
4798
|
+
if (fencedConnections.has(connectionId)) continue;
|
|
4799
|
+
if (gated && !capabilities.has(TOOL_ACTIVITY_CAPABILITY)) continue;
|
|
4800
|
+
try {
|
|
4801
|
+
const receipt = server.sendToWithReceipt(connectionId, json);
|
|
4802
|
+
if (capabilities.has(POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY)) receipts.push(receipt);
|
|
4803
|
+
} catch {
|
|
4804
|
+
// Rejected positioned sends remain eligible for the atomic raw fallback.
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
return receipts;
|
|
4760
4808
|
};
|
|
4761
4809
|
let cancelPreflightsForConnection: ((connectionId: string) => Promise<void>) | undefined;
|
|
4762
4810
|
const promptTerminalTombstones = new Map<string, { connectionId: string; expiresAt: number }>();
|
|
@@ -6836,6 +6884,7 @@ export function createNotificationsExtension(
|
|
|
6836
6884
|
server,
|
|
6837
6885
|
host,
|
|
6838
6886
|
broadcastEventFrame,
|
|
6887
|
+
broadcastEventFrameWithReceipts,
|
|
6839
6888
|
revisions,
|
|
6840
6889
|
cursors,
|
|
6841
6890
|
id,
|
|
@@ -7109,6 +7158,19 @@ export function createNotificationsExtension(
|
|
|
7109
7158
|
"@gajae-code/natives is out of date: missing onNegotiatedCapabilities. Rebuild the native addon (bun --cwd=packages/natives run build).",
|
|
7110
7159
|
);
|
|
7111
7160
|
}
|
|
7161
|
+
if (
|
|
7162
|
+
typeof server.supportsPositionedRawExclusion !== "function" ||
|
|
7163
|
+
server.supportsPositionedRawExclusion() !== true
|
|
7164
|
+
) {
|
|
7165
|
+
throw new Error(
|
|
7166
|
+
"@gajae-code/natives is out of date: missing positioned raw-fan-out exclusion. Rebuild the native addon (bun --cwd=packages/natives run build).",
|
|
7167
|
+
);
|
|
7168
|
+
}
|
|
7169
|
+
if (typeof server.sendToWithReceipt !== "function" || typeof server.queueIdleAfterDirected !== "function") {
|
|
7170
|
+
throw new Error(
|
|
7171
|
+
"@gajae-code/natives is out of date: missing recipient-bound dependent delivery. Rebuild the native addon (bun --cwd=packages/natives run build).",
|
|
7172
|
+
);
|
|
7173
|
+
}
|
|
7112
7174
|
server.onNegotiatedCapabilities((_err, connectionId, capabilities) => {
|
|
7113
7175
|
if (connectionId) hostCapCache.set(connectionId, new Set(capabilities));
|
|
7114
7176
|
});
|
|
@@ -7670,8 +7732,8 @@ export function createNotificationsExtension(
|
|
|
7670
7732
|
sessionId: id,
|
|
7671
7733
|
...buildIdentity(ctx.cwd, sessionName, telegramTopicsEnabled()),
|
|
7672
7734
|
};
|
|
7673
|
-
emitSessionEvent(initializedRuntime, identity);
|
|
7674
|
-
server.pushFrame(JSON.stringify(identity));
|
|
7735
|
+
const positionedRecipients = emitSessionEvent(initializedRuntime, identity);
|
|
7736
|
+
server.pushFrame(JSON.stringify(identity), positionedRecipients);
|
|
7675
7737
|
}, 250);
|
|
7676
7738
|
sessionNameObserver.unref?.();
|
|
7677
7739
|
runtime.stopSessionNameObserver = () => clearInterval(sessionNameObserver);
|
|
@@ -7684,8 +7746,8 @@ export function createNotificationsExtension(
|
|
|
7684
7746
|
sessionId: id,
|
|
7685
7747
|
...buildIdentity(ctx.cwd, sessionNameAfterStartup, telegramTopicsEnabled()),
|
|
7686
7748
|
};
|
|
7687
|
-
emitSessionEvent(initializedRuntime, identity);
|
|
7688
|
-
server.pushFrame(JSON.stringify(identity));
|
|
7749
|
+
const positionedRecipients = emitSessionEvent(initializedRuntime, identity);
|
|
7750
|
+
server.pushFrame(JSON.stringify(identity), positionedRecipients);
|
|
7689
7751
|
}
|
|
7690
7752
|
const agentDir = lifecycleAgentDir ?? settings?.getAgentDir?.();
|
|
7691
7753
|
if (lifecycleRequired && !agentDir) throw new Error("Lifecycle SDK host requires an agent directory.");
|
|
@@ -8463,6 +8525,20 @@ export function createNotificationsExtension(
|
|
|
8463
8525
|
};
|
|
8464
8526
|
|
|
8465
8527
|
const hasUserSettlementReceipt = (rt: SessionRuntime): boolean => rt.pendingSettled?.receipts[0]?.origin === "user";
|
|
8528
|
+
const composeSettlementText = (receipts: ReadonlyArray<{ text: string }>): string =>
|
|
8529
|
+
receipts.map(receipt => receipt.text).join("\n\n");
|
|
8530
|
+
|
|
8531
|
+
const consumePublishedSettlement = (rt: SessionRuntime, text: string, window: number): void => {
|
|
8532
|
+
const pending = rt.pendingSettled;
|
|
8533
|
+
if (!pending || pending.window !== window) return;
|
|
8534
|
+
if (composeSettlementText(pending.receipts) === text) {
|
|
8535
|
+
rt.pendingSettled = undefined;
|
|
8536
|
+
return;
|
|
8537
|
+
}
|
|
8538
|
+
const receipts = pending.receipts.filter(receipt => receipt.text !== text);
|
|
8539
|
+
if (receipts.length === pending.receipts.length) return;
|
|
8540
|
+
rt.pendingSettled = receipts.length ? { ...pending, receipts } : undefined;
|
|
8541
|
+
};
|
|
8466
8542
|
|
|
8467
8543
|
const flushPendingFinal = (rt: SessionRuntime, id: string): void => {
|
|
8468
8544
|
const pending = rt.pendingFinal;
|
|
@@ -8479,7 +8555,7 @@ export function createNotificationsExtension(
|
|
|
8479
8555
|
for (const receipt of pending.receipts)
|
|
8480
8556
|
deferLeanReceipt(rt, receipt.text, receipt.messageRef, pending.window, receipt.origin);
|
|
8481
8557
|
} else {
|
|
8482
|
-
const text = pending.receipts
|
|
8558
|
+
const text = composeSettlementText(pending.receipts);
|
|
8483
8559
|
const messageRef = pending.receipts.length === 1 ? pending.receipts[0]?.messageRef : undefined;
|
|
8484
8560
|
try {
|
|
8485
8561
|
pushSessionFrame(rt, {
|
|
@@ -8502,15 +8578,14 @@ export function createNotificationsExtension(
|
|
|
8502
8578
|
const flushPendingSettled = (rt: SessionRuntime, id: string): void => {
|
|
8503
8579
|
const settled = rt.pendingSettled;
|
|
8504
8580
|
if (!settled?.receipts.length || !rt.notificationsActive || rt.redact || rt.policySuspended) return;
|
|
8505
|
-
|
|
8506
|
-
const text = settled.receipts.map(receipt => receipt.text).join("\n\n");
|
|
8581
|
+
const text = composeSettlementText(settled.receipts);
|
|
8507
8582
|
// A composition represents multiple finalized turns. It must be a fresh
|
|
8508
8583
|
// terminal message rather than editing either constituent turn's stream.
|
|
8509
8584
|
const messageRef = settled.receipts.length === 1 ? settled.receipts[0]?.messageRef : undefined;
|
|
8510
8585
|
const previousLiveRef = rt.liveRef;
|
|
8511
8586
|
if (messageRef) rt.liveRef = messageRef;
|
|
8512
8587
|
try {
|
|
8513
|
-
flushTurnText(rt, id, text, true);
|
|
8588
|
+
if (flushTurnText(rt, id, text, true)) rt.pendingSettled = undefined;
|
|
8514
8589
|
} finally {
|
|
8515
8590
|
if (!messageRef) rt.liveRef = previousLiveRef;
|
|
8516
8591
|
}
|
|
@@ -8704,16 +8779,20 @@ export function createNotificationsExtension(
|
|
|
8704
8779
|
const seq = rt.idleSeq++;
|
|
8705
8780
|
// Re-assert the identity header so the daemon renames the topic once the
|
|
8706
8781
|
// session title has been auto-generated ("{repo}/{branch} - {title}"). The
|
|
8707
|
-
// daemon only renames when the title actually changed.
|
|
8782
|
+
// daemon only renames when the title actually changed. Bind the dependent
|
|
8783
|
+
// idle to the exact connection generations that accepted this positioned
|
|
8784
|
+
// identity; native raw fallbacks enqueue identity and idle as one bounded
|
|
8785
|
+
// writer command for the remaining snapshot cohort.
|
|
8708
8786
|
try {
|
|
8709
|
-
|
|
8787
|
+
const identity = {
|
|
8710
8788
|
type: "identity_header",
|
|
8711
8789
|
sessionId: id,
|
|
8712
8790
|
...buildIdentity(ctx.cwd, ctx.sessionManager.getSessionName(), telegramTopicsEnabled()),
|
|
8713
|
-
}
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8791
|
+
} as const;
|
|
8792
|
+
const receipts = emitSessionEventWithReceipts(rt, identity);
|
|
8793
|
+
const outcome = rt.server.queueIdleAfterDirected(
|
|
8794
|
+
JSON.stringify(identity),
|
|
8795
|
+
receipts,
|
|
8717
8796
|
JSON.stringify(
|
|
8718
8797
|
notificationActionPayload(
|
|
8719
8798
|
{
|
|
@@ -8726,8 +8805,13 @@ export function createNotificationsExtension(
|
|
|
8726
8805
|
),
|
|
8727
8806
|
),
|
|
8728
8807
|
);
|
|
8729
|
-
|
|
8730
|
-
|
|
8808
|
+
if (outcome.status === "rejected" || outcome.status === "partial") {
|
|
8809
|
+
logger.warn(
|
|
8810
|
+
`notifications: dependent idle ${outcome.status} (${outcome.queuedCount}/${outcome.recipientCount} recipients queued)`,
|
|
8811
|
+
);
|
|
8812
|
+
}
|
|
8813
|
+
} catch (error) {
|
|
8814
|
+
logger.warn(`notifications: recipient-bound identity/idle delivery failed: ${String(error)}`);
|
|
8731
8815
|
}
|
|
8732
8816
|
|
|
8733
8817
|
// Lean: emit the latest deferred assistant answer exactly once at idle.
|
|
@@ -8778,13 +8862,10 @@ export function createNotificationsExtension(
|
|
|
8778
8862
|
// The daemon coalesces/throttles these via its shared rate-limit pool.
|
|
8779
8863
|
// Push the in-flight turn's assistant text as a finalized turn_stream, deduped
|
|
8780
8864
|
// against what was already flushed for this turn (the pre-ask lead-in).
|
|
8781
|
-
const flushTurnText = (rt: SessionRuntime, id: string, text: string | undefined, finalAnswer: boolean):
|
|
8782
|
-
if (!text || text === rt.preAskFlushedText || !rt.notificationsActive || rt.policySuspended) return;
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
// so agent_end does not re-emit stale intermediate narration (#2863 review).
|
|
8786
|
-
if (!finalAnswer && rt.currentTurnSettlementOrigin !== "autonomous" && !hasUserSettlementReceipt(rt))
|
|
8787
|
-
rt.pendingSettled = undefined;
|
|
8865
|
+
const flushTurnText = (rt: SessionRuntime, id: string, text: string | undefined, finalAnswer: boolean): boolean => {
|
|
8866
|
+
if (!text || text === rt.preAskFlushedText || !rt.notificationsActive || rt.policySuspended) return false;
|
|
8867
|
+
const hadUserSettlementReceipt = hasUserSettlementReceipt(rt);
|
|
8868
|
+
const settlementWindow = rt.currentTurnSettlementWindow ?? rt.settlementWindow;
|
|
8788
8869
|
// Decision A: a stream-enabled turn must finalize as an in-place edit of ONE
|
|
8789
8870
|
// live message, never a fresh (rich-promotable) send. If live frames were
|
|
8790
8871
|
// async-queued and none landed before this flush, reuse the per-turn ref
|
|
@@ -8794,7 +8875,7 @@ export function createNotificationsExtension(
|
|
|
8794
8875
|
rt.liveRef = String(rt.turnSeq);
|
|
8795
8876
|
}
|
|
8796
8877
|
try {
|
|
8797
|
-
pushSessionFrame(rt, {
|
|
8878
|
+
const published = pushSessionFrame(rt, {
|
|
8798
8879
|
type: "turn_stream",
|
|
8799
8880
|
sessionId: id,
|
|
8800
8881
|
phase: "finalized",
|
|
@@ -8802,8 +8883,24 @@ export function createNotificationsExtension(
|
|
|
8802
8883
|
text,
|
|
8803
8884
|
...(rt.liveRef ? { messageRef: rt.liveRef } : {}),
|
|
8804
8885
|
});
|
|
8886
|
+
if (!published) return false;
|
|
8887
|
+
rt.preAskFlushedText = text;
|
|
8888
|
+
if (!finalAnswer) {
|
|
8889
|
+
// A successfully published ask lead-in settles only exact matching
|
|
8890
|
+
// receipts from the same user-request window. Distinct retained user
|
|
8891
|
+
// receipts remain eligible for the idle summary (#4458).
|
|
8892
|
+
consumePublishedSettlement(rt, text, settlementWindow);
|
|
8893
|
+
// Ask lead-ins supersede deferred lean narration from earlier turns so
|
|
8894
|
+
// agent_end does not re-emit stale intermediate text (#2863 review).
|
|
8895
|
+
// Preserve the pre-publish user-receipt decision even when the exact
|
|
8896
|
+
// match above consumed that receipt.
|
|
8897
|
+
if (rt.currentTurnSettlementOrigin !== "autonomous" && !hadUserSettlementReceipt)
|
|
8898
|
+
rt.pendingSettled = undefined;
|
|
8899
|
+
}
|
|
8900
|
+
return true;
|
|
8805
8901
|
} catch (e) {
|
|
8806
8902
|
logger.warn(`notifications: pushFrame (turn) failed: ${String(e)}`);
|
|
8903
|
+
return false;
|
|
8807
8904
|
}
|
|
8808
8905
|
};
|
|
8809
8906
|
|
|
@@ -21,7 +21,14 @@ declare module "@gajae-code/natives" {
|
|
|
21
21
|
interface RetireIfUnclaimedResult {
|
|
22
22
|
status: "retired" | "already_terminal" | "claimed" | "stale";
|
|
23
23
|
}
|
|
24
|
+
interface DependentIdleDeliveryResult {
|
|
25
|
+
status: "queued" | "no_recipients" | "rejected" | "partial";
|
|
26
|
+
recipientCount: number;
|
|
27
|
+
queuedCount: number;
|
|
28
|
+
}
|
|
24
29
|
interface NotificationServer {
|
|
30
|
+
/** Broadcast a validated frame, omitting subscribers that received its positioned event envelope. */
|
|
31
|
+
pushFrame(json: string, excludedConnectionIds?: string[]): void;
|
|
25
32
|
/** Register the raw v3 SDK frame callback. Must be called before start. */
|
|
26
33
|
onSdkFrame(callback: (err: null | Error, frame: SdkFrameEvent) => void): void;
|
|
27
34
|
/** Register the connection-close callback. Must be called before start. */
|
|
@@ -32,6 +39,14 @@ declare module "@gajae-code/natives" {
|
|
|
32
39
|
): void;
|
|
33
40
|
/** Directed delivery of a validated, bounded JSON v3 envelope to one connection. */
|
|
34
41
|
sendTo(connectionId: string, json: string): void;
|
|
42
|
+
/** Directed delivery with an opaque exact-generation receipt. */
|
|
43
|
+
sendToWithReceipt(connectionId: string, json: string): string;
|
|
44
|
+
/** Queue raw identity fallback and dependent idle on one exact bounded cohort. */
|
|
45
|
+
queueIdleAfterDirected(
|
|
46
|
+
prerequisiteJson: string,
|
|
47
|
+
positionedReceipts: string[],
|
|
48
|
+
neededJson: string,
|
|
49
|
+
): DependentIdleDeliveryResult;
|
|
35
50
|
/** Register a correlated workflow-gate action_needed frame. */
|
|
36
51
|
registerWorkflowGateAsk(workflowJson: string, repliable: boolean): void;
|
|
37
52
|
/** Atomically register the single active arbitrated action and return its private lease. */
|
|
@@ -45,7 +60,8 @@ declare module "@gajae-code/natives" {
|
|
|
45
60
|
text: string,
|
|
46
61
|
finalAnswer?: boolean,
|
|
47
62
|
messageRef?: string,
|
|
48
|
-
|
|
63
|
+
excludedConnectionIds?: string[],
|
|
64
|
+
): boolean;
|
|
49
65
|
/** Broadcast raw file bytes; Rust encodes the unchanged base64 wire field. */
|
|
50
66
|
pushFileAttachmentUnchecked(
|
|
51
67
|
sessionId: string,
|
|
@@ -53,11 +69,14 @@ declare module "@gajae-code/natives" {
|
|
|
53
69
|
mime: string | undefined,
|
|
54
70
|
data: Buffer,
|
|
55
71
|
caption: string | undefined,
|
|
72
|
+
excludedConnectionIds?: string[],
|
|
56
73
|
): void;
|
|
57
74
|
/** Counters for known-good N-API frame crossings. */
|
|
58
75
|
knownGoodFrameStats(): {
|
|
59
76
|
knownGoodTurnStreamFrames: number;
|
|
60
77
|
turnStreamSerdeValidationParses: number;
|
|
61
78
|
};
|
|
79
|
+
/** Proves that optional raw fan-out exclusions are enforced by the loaded addon. */
|
|
80
|
+
supportsPositionedRawExclusion(): boolean;
|
|
62
81
|
}
|
|
63
82
|
}
|
|
@@ -280,8 +280,11 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
|
|
|
280
280
|
* still running the pre-#4689 router keeps the old hot polling loop and its
|
|
281
281
|
* lock contention, so an already-running owner must be replaced rather than
|
|
282
282
|
* retained across this upgrade.
|
|
283
|
+
* Generation 177 consumes an exact same-window lean settlement receipt only
|
|
284
|
+
* after its autonomous ask lead-in publishes successfully, preventing idle
|
|
285
|
+
* replay without changing the serving protocol.
|
|
283
286
|
*/
|
|
284
|
-
export const DAEMON_GENERATION =
|
|
287
|
+
export const DAEMON_GENERATION = 178;
|
|
285
288
|
|
|
286
289
|
/**
|
|
287
290
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
|