@skillcap/gdh 0.13.3 → 0.14.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/INSTALL-BUNDLE.json +1 -1
- package/README.md +4 -4
- package/RELEASE-SPAN-UPDATE-CONTRACTS.json +79 -0
- package/node_modules/@gdh/adapters/package.json +8 -8
- package/node_modules/@gdh/authoring/package.json +2 -2
- package/node_modules/@gdh/cli/package.json +10 -10
- package/node_modules/@gdh/core/dist/index.d.ts +37 -2
- package/node_modules/@gdh/core/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/core/dist/index.js +2 -2
- package/node_modules/@gdh/core/dist/index.js.map +1 -1
- package/node_modules/@gdh/core/package.json +1 -1
- package/node_modules/@gdh/docs/package.json +2 -2
- package/node_modules/@gdh/mcp/package.json +8 -8
- package/node_modules/@gdh/observability/dist/runtime-bundles.d.ts.map +1 -1
- package/node_modules/@gdh/observability/dist/runtime-bundles.js +28 -2
- package/node_modules/@gdh/observability/dist/runtime-bundles.js.map +1 -1
- package/node_modules/@gdh/observability/package.json +2 -2
- package/node_modules/@gdh/runtime/dist/bridge-surface.js +173 -0
- package/node_modules/@gdh/runtime/dist/bridge-surface.js.map +1 -1
- package/node_modules/@gdh/runtime/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/runtime/dist/index.js +387 -17
- package/node_modules/@gdh/runtime/dist/index.js.map +1 -1
- package/node_modules/@gdh/runtime/package.json +2 -2
- package/node_modules/@gdh/scan/package.json +3 -3
- package/node_modules/@gdh/verify/dist/scenarios.d.ts +3 -1
- package/node_modules/@gdh/verify/dist/scenarios.d.ts.map +1 -1
- package/node_modules/@gdh/verify/dist/scenarios.js +425 -36
- package/node_modules/@gdh/verify/dist/scenarios.js.map +1 -1
- package/node_modules/@gdh/verify/package.json +7 -7
- package/package.json +11 -11
|
@@ -1552,6 +1552,24 @@ const HOST_RUNTIME_BRIDGE_ENTRIES = [
|
|
|
1552
1552
|
executionRoute: null,
|
|
1553
1553
|
safetyLevel: "read_only",
|
|
1554
1554
|
},
|
|
1555
|
+
{
|
|
1556
|
+
id: "state.node_presence.await",
|
|
1557
|
+
summary: "Wait for one exact node path to become present or absent through bounded polling.",
|
|
1558
|
+
kind: "waiter",
|
|
1559
|
+
shape: "composite",
|
|
1560
|
+
source: "core",
|
|
1561
|
+
executionRoute: null,
|
|
1562
|
+
safetyLevel: "read_only",
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
id: "state.signal.await",
|
|
1566
|
+
summary: "Wait for one observed signal emission to occur after an optional observed-count baseline.",
|
|
1567
|
+
kind: "waiter",
|
|
1568
|
+
shape: "composite",
|
|
1569
|
+
source: "core",
|
|
1570
|
+
executionRoute: null,
|
|
1571
|
+
safetyLevel: "read_only",
|
|
1572
|
+
},
|
|
1555
1573
|
];
|
|
1556
1574
|
export function createRuntimeBridgeManager() {
|
|
1557
1575
|
const sessions = new Map();
|
|
@@ -1816,6 +1834,7 @@ export function createRuntimeBridgeManager() {
|
|
|
1816
1834
|
summary: `Runtime bridge session "${sessionId}" was not found.`,
|
|
1817
1835
|
reasons: ["bridge_session_not_found"],
|
|
1818
1836
|
result: null,
|
|
1837
|
+
waiterEvidence: null,
|
|
1819
1838
|
transcriptPath: null,
|
|
1820
1839
|
};
|
|
1821
1840
|
}
|
|
@@ -1827,6 +1846,7 @@ export function createRuntimeBridgeManager() {
|
|
|
1827
1846
|
summary: `Runtime bridge session "${sessionId}" is no longer active.`,
|
|
1828
1847
|
reasons: ["bridge_session_not_active"],
|
|
1829
1848
|
result: null,
|
|
1849
|
+
waiterEvidence: null,
|
|
1830
1850
|
transcriptPath: session.transcriptPath,
|
|
1831
1851
|
};
|
|
1832
1852
|
}
|
|
@@ -1838,6 +1858,7 @@ export function createRuntimeBridgeManager() {
|
|
|
1838
1858
|
summary: `Runtime bridge session "${sessionId}" is already handling another invocation.`,
|
|
1839
1859
|
reasons: ["bridge_session_busy"],
|
|
1840
1860
|
result: null,
|
|
1861
|
+
waiterEvidence: null,
|
|
1841
1862
|
transcriptPath: session.transcriptPath,
|
|
1842
1863
|
};
|
|
1843
1864
|
}
|
|
@@ -1864,6 +1885,7 @@ export function createRuntimeBridgeManager() {
|
|
|
1864
1885
|
: `Bridge entry "${entryId}" returned state "${syntheticResponse.state}".`,
|
|
1865
1886
|
reasons: syntheticResponse.error ? [syntheticResponse.error] : [],
|
|
1866
1887
|
result: syntheticResponse.result,
|
|
1888
|
+
waiterEvidence: syntheticResponse.waiterEvidence,
|
|
1867
1889
|
transcriptPath: session.transcriptPath,
|
|
1868
1890
|
};
|
|
1869
1891
|
}
|
|
@@ -1883,6 +1905,7 @@ export function createRuntimeBridgeManager() {
|
|
|
1883
1905
|
: `Bridge entry "${entryId}" returned state "${response.state}".`,
|
|
1884
1906
|
reasons: response.error ? [response.error] : [],
|
|
1885
1907
|
result: response.result,
|
|
1908
|
+
waiterEvidence: null,
|
|
1886
1909
|
transcriptPath: session.transcriptPath,
|
|
1887
1910
|
};
|
|
1888
1911
|
}
|
|
@@ -1898,6 +1921,7 @@ export function createRuntimeBridgeManager() {
|
|
|
1898
1921
|
summary: `Bridge entry "${entryId}" failed: ${formatBridgeError(error)}.`,
|
|
1899
1922
|
reasons: ["bridge_invoke_failed"],
|
|
1900
1923
|
result: null,
|
|
1924
|
+
waiterEvidence: null,
|
|
1901
1925
|
transcriptPath: session.transcriptPath,
|
|
1902
1926
|
};
|
|
1903
1927
|
}
|
|
@@ -2001,6 +2025,12 @@ async function invokeSyntheticBridgeEntry(session, entryId, inputValue) {
|
|
|
2001
2025
|
if (entryId === "state.node_property.await") {
|
|
2002
2026
|
return invokeNodePropertyAwaitEntry(session, inputValue);
|
|
2003
2027
|
}
|
|
2028
|
+
if (entryId === "state.node_presence.await") {
|
|
2029
|
+
return invokeNodePresenceAwaitEntry(session, inputValue);
|
|
2030
|
+
}
|
|
2031
|
+
if (entryId === "state.signal.await") {
|
|
2032
|
+
return invokeSignalAwaitEntry(session, inputValue);
|
|
2033
|
+
}
|
|
2004
2034
|
return null;
|
|
2005
2035
|
}
|
|
2006
2036
|
async function invokeNodePropertyAwaitEntry(session, inputValue) {
|
|
@@ -2016,6 +2046,7 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
|
|
|
2016
2046
|
state: "failed",
|
|
2017
2047
|
result: null,
|
|
2018
2048
|
error: "state.node_property.await requires nodePath and property.",
|
|
2049
|
+
waiterEvidence: null,
|
|
2019
2050
|
};
|
|
2020
2051
|
}
|
|
2021
2052
|
if (!hasExpected) {
|
|
@@ -2023,12 +2054,20 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
|
|
|
2023
2054
|
state: "failed",
|
|
2024
2055
|
result: null,
|
|
2025
2056
|
error: "state.node_property.await requires an expected value.",
|
|
2057
|
+
waiterEvidence: null,
|
|
2026
2058
|
};
|
|
2027
2059
|
}
|
|
2028
|
-
const
|
|
2060
|
+
const target = {
|
|
2061
|
+
nodePath,
|
|
2062
|
+
property,
|
|
2063
|
+
expected,
|
|
2064
|
+
};
|
|
2065
|
+
const startedAt = new Date().toISOString();
|
|
2066
|
+
const startedAtMs = Date.now();
|
|
2067
|
+
recordWaiterStarted(session, "state.node_property.await", target, timeoutMs, pollIntervalMs);
|
|
2029
2068
|
let attempts = 0;
|
|
2030
2069
|
let lastValue = null;
|
|
2031
|
-
while (Date.now() -
|
|
2070
|
+
while (Date.now() - startedAtMs <= timeoutMs) {
|
|
2032
2071
|
attempts += 1;
|
|
2033
2072
|
recordBridgeEvent(session, "invoke_expanded_leaf_step", {
|
|
2034
2073
|
parentEntryId: "state.node_property.await",
|
|
@@ -2056,37 +2095,326 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
|
|
|
2056
2095
|
state: response.state,
|
|
2057
2096
|
result: response.result,
|
|
2058
2097
|
error: response.error,
|
|
2098
|
+
waiterEvidence: null,
|
|
2059
2099
|
};
|
|
2060
2100
|
}
|
|
2061
2101
|
const payload = toJsonRecord(response.result);
|
|
2062
2102
|
lastValue = (payload["value"] ?? null);
|
|
2063
2103
|
if (jsonValuesEqual(lastValue, expected)) {
|
|
2104
|
+
const result = {
|
|
2105
|
+
nodePath,
|
|
2106
|
+
property,
|
|
2107
|
+
expected,
|
|
2108
|
+
value: lastValue,
|
|
2109
|
+
attempts,
|
|
2110
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2111
|
+
};
|
|
2112
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2113
|
+
waiterId: "state.node_property.await",
|
|
2114
|
+
outcome: "satisfied",
|
|
2115
|
+
startedAt,
|
|
2116
|
+
timeoutMs,
|
|
2117
|
+
pollCount: attempts,
|
|
2118
|
+
target,
|
|
2119
|
+
result,
|
|
2120
|
+
});
|
|
2121
|
+
recordWaiterSatisfied(session, "state.node_property.await", target, attempts, result);
|
|
2064
2122
|
return {
|
|
2065
2123
|
state: "ok",
|
|
2066
|
-
result
|
|
2067
|
-
nodePath,
|
|
2068
|
-
property,
|
|
2069
|
-
expected,
|
|
2070
|
-
value: lastValue,
|
|
2071
|
-
attempts,
|
|
2072
|
-
elapsedMs: Date.now() - startedAt,
|
|
2073
|
-
},
|
|
2124
|
+
result,
|
|
2074
2125
|
error: null,
|
|
2126
|
+
waiterEvidence,
|
|
2075
2127
|
};
|
|
2076
2128
|
}
|
|
2077
2129
|
await waitMs(pollIntervalMs);
|
|
2078
2130
|
}
|
|
2131
|
+
const result = {
|
|
2132
|
+
nodePath,
|
|
2133
|
+
property,
|
|
2134
|
+
expected,
|
|
2135
|
+
value: lastValue,
|
|
2136
|
+
attempts,
|
|
2137
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2138
|
+
};
|
|
2139
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2140
|
+
waiterId: "state.node_property.await",
|
|
2141
|
+
outcome: "timed_out",
|
|
2142
|
+
startedAt,
|
|
2143
|
+
timeoutMs,
|
|
2144
|
+
pollCount: attempts,
|
|
2145
|
+
target,
|
|
2146
|
+
result,
|
|
2147
|
+
});
|
|
2148
|
+
recordWaiterTimedOut(session, "state.node_property.await", target, attempts, result);
|
|
2079
2149
|
return {
|
|
2080
2150
|
state: "unavailable",
|
|
2081
|
-
result
|
|
2151
|
+
result,
|
|
2152
|
+
error: "Timed out waiting for the requested node property value.",
|
|
2153
|
+
waiterEvidence,
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
async function invokeNodePresenceAwaitEntry(session, inputValue) {
|
|
2157
|
+
const input = toJsonRecord(inputValue);
|
|
2158
|
+
const nodePath = typeof input["nodePath"] === "string" ? input["nodePath"] : "";
|
|
2159
|
+
const expected = typeof input["expected"] === "string" ? input["expected"] : "";
|
|
2160
|
+
const timeoutMs = clampNumber(input["timeoutMs"], 1500, 100, 5000);
|
|
2161
|
+
const pollIntervalMs = clampNumber(input["pollIntervalMs"], 50, 25, 250);
|
|
2162
|
+
if (nodePath.length === 0) {
|
|
2163
|
+
return {
|
|
2164
|
+
state: "failed",
|
|
2165
|
+
result: null,
|
|
2166
|
+
error: "state.node_presence.await requires nodePath.",
|
|
2167
|
+
waiterEvidence: null,
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
if (expected !== "present" && expected !== "absent") {
|
|
2171
|
+
return {
|
|
2172
|
+
state: "failed",
|
|
2173
|
+
result: null,
|
|
2174
|
+
error: 'state.node_presence.await requires expected = "present" or "absent".',
|
|
2175
|
+
waiterEvidence: null,
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
const target = {
|
|
2179
|
+
nodePath,
|
|
2180
|
+
expected,
|
|
2181
|
+
};
|
|
2182
|
+
recordWaiterStarted(session, "state.node_presence.await", target, timeoutMs, pollIntervalMs);
|
|
2183
|
+
const startedAt = new Date().toISOString();
|
|
2184
|
+
const startedAtMs = Date.now();
|
|
2185
|
+
let attempts = 0;
|
|
2186
|
+
let lastSnapshot = null;
|
|
2187
|
+
while (Date.now() - startedAtMs <= timeoutMs) {
|
|
2188
|
+
attempts += 1;
|
|
2189
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_step", {
|
|
2190
|
+
parentEntryId: "state.node_presence.await",
|
|
2191
|
+
leafEntryId: "state.node_presence.get",
|
|
2192
|
+
attempt: attempts,
|
|
2193
|
+
input: {
|
|
2194
|
+
nodePath,
|
|
2195
|
+
},
|
|
2196
|
+
});
|
|
2197
|
+
const response = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.node_presence.get", {
|
|
2082
2198
|
nodePath,
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2199
|
+
});
|
|
2200
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_response", {
|
|
2201
|
+
parentEntryId: "state.node_presence.await",
|
|
2202
|
+
leafEntryId: "state.node_presence.get",
|
|
2203
|
+
attempt: attempts,
|
|
2204
|
+
state: response.state,
|
|
2205
|
+
result: response.result,
|
|
2206
|
+
error: response.error,
|
|
2207
|
+
});
|
|
2208
|
+
if (response.state !== "ok") {
|
|
2209
|
+
return {
|
|
2210
|
+
state: response.state,
|
|
2211
|
+
result: response.result,
|
|
2212
|
+
error: response.error,
|
|
2213
|
+
waiterEvidence: null,
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
const payload = toJsonRecord(response.result);
|
|
2217
|
+
lastSnapshot = payload;
|
|
2218
|
+
const present = payload["present"] === true;
|
|
2219
|
+
const matches = expected === "present" ? present : !present;
|
|
2220
|
+
if (matches) {
|
|
2221
|
+
const result = {
|
|
2222
|
+
nodePath,
|
|
2223
|
+
expected,
|
|
2224
|
+
present,
|
|
2225
|
+
name: (payload["name"] ?? null),
|
|
2226
|
+
className: (payload["className"] ?? null),
|
|
2227
|
+
attempts,
|
|
2228
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2229
|
+
};
|
|
2230
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2231
|
+
waiterId: "state.node_presence.await",
|
|
2232
|
+
outcome: "satisfied",
|
|
2233
|
+
startedAt,
|
|
2234
|
+
timeoutMs,
|
|
2235
|
+
pollCount: attempts,
|
|
2236
|
+
target,
|
|
2237
|
+
result,
|
|
2238
|
+
});
|
|
2239
|
+
recordWaiterSatisfied(session, "state.node_presence.await", target, attempts, result);
|
|
2240
|
+
return {
|
|
2241
|
+
state: "ok",
|
|
2242
|
+
result,
|
|
2243
|
+
error: null,
|
|
2244
|
+
waiterEvidence,
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
await waitMs(pollIntervalMs);
|
|
2248
|
+
}
|
|
2249
|
+
const result = {
|
|
2250
|
+
nodePath,
|
|
2251
|
+
expected,
|
|
2252
|
+
present: lastSnapshot?.["present"] === true,
|
|
2253
|
+
name: (lastSnapshot?.["name"] ?? null),
|
|
2254
|
+
className: (lastSnapshot?.["className"] ?? null),
|
|
2255
|
+
attempts,
|
|
2256
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2257
|
+
};
|
|
2258
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2259
|
+
waiterId: "state.node_presence.await",
|
|
2260
|
+
outcome: "timed_out",
|
|
2261
|
+
startedAt,
|
|
2262
|
+
timeoutMs,
|
|
2263
|
+
pollCount: attempts,
|
|
2264
|
+
target,
|
|
2265
|
+
result,
|
|
2266
|
+
});
|
|
2267
|
+
recordWaiterTimedOut(session, "state.node_presence.await", target, attempts, result);
|
|
2268
|
+
return {
|
|
2269
|
+
state: "unavailable",
|
|
2270
|
+
result,
|
|
2271
|
+
error: "Timed out waiting for the requested node presence state.",
|
|
2272
|
+
waiterEvidence,
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
async function invokeSignalAwaitEntry(session, inputValue) {
|
|
2276
|
+
const input = toJsonRecord(inputValue);
|
|
2277
|
+
const nodePath = typeof input["nodePath"] === "string" ? input["nodePath"] : "";
|
|
2278
|
+
const signalName = typeof input["signalName"] === "string" ? input["signalName"] : "";
|
|
2279
|
+
const afterCount = clampCount(input["afterCount"], 0);
|
|
2280
|
+
const timeoutMs = clampNumber(input["timeoutMs"], 1500, 100, 5000);
|
|
2281
|
+
const pollIntervalMs = clampNumber(input["pollIntervalMs"], 50, 25, 250);
|
|
2282
|
+
if (nodePath.length === 0 || signalName.length === 0) {
|
|
2283
|
+
return {
|
|
2284
|
+
state: "failed",
|
|
2285
|
+
result: null,
|
|
2286
|
+
error: "state.signal.await requires nodePath and signalName.",
|
|
2287
|
+
waiterEvidence: null,
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
const target = {
|
|
2291
|
+
nodePath,
|
|
2292
|
+
signalName,
|
|
2293
|
+
afterCount,
|
|
2294
|
+
};
|
|
2295
|
+
recordWaiterStarted(session, "state.signal.await", target, timeoutMs, pollIntervalMs);
|
|
2296
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_step", {
|
|
2297
|
+
parentEntryId: "state.signal.await",
|
|
2298
|
+
leafEntryId: "state.signal_observation.start",
|
|
2299
|
+
attempt: 1,
|
|
2300
|
+
input: {
|
|
2301
|
+
nodePath,
|
|
2302
|
+
signalName,
|
|
2088
2303
|
},
|
|
2089
|
-
|
|
2304
|
+
});
|
|
2305
|
+
const startResponse = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.signal_observation.start", {
|
|
2306
|
+
nodePath,
|
|
2307
|
+
signalName,
|
|
2308
|
+
});
|
|
2309
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_response", {
|
|
2310
|
+
parentEntryId: "state.signal.await",
|
|
2311
|
+
leafEntryId: "state.signal_observation.start",
|
|
2312
|
+
attempt: 1,
|
|
2313
|
+
state: startResponse.state,
|
|
2314
|
+
result: startResponse.result,
|
|
2315
|
+
error: startResponse.error,
|
|
2316
|
+
});
|
|
2317
|
+
if (startResponse.state !== "ok") {
|
|
2318
|
+
return {
|
|
2319
|
+
state: startResponse.state,
|
|
2320
|
+
result: startResponse.result,
|
|
2321
|
+
error: startResponse.error,
|
|
2322
|
+
waiterEvidence: null,
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
const startedAt = new Date().toISOString();
|
|
2326
|
+
const startedAtMs = Date.now();
|
|
2327
|
+
let attempts = 0;
|
|
2328
|
+
let lastSnapshot = null;
|
|
2329
|
+
while (Date.now() - startedAtMs <= timeoutMs) {
|
|
2330
|
+
attempts += 1;
|
|
2331
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_step", {
|
|
2332
|
+
parentEntryId: "state.signal.await",
|
|
2333
|
+
leafEntryId: "state.signal_observation.get",
|
|
2334
|
+
attempt: attempts,
|
|
2335
|
+
input: {
|
|
2336
|
+
nodePath,
|
|
2337
|
+
signalName,
|
|
2338
|
+
},
|
|
2339
|
+
});
|
|
2340
|
+
const response = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.signal_observation.get", {
|
|
2341
|
+
nodePath,
|
|
2342
|
+
signalName,
|
|
2343
|
+
});
|
|
2344
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_response", {
|
|
2345
|
+
parentEntryId: "state.signal.await",
|
|
2346
|
+
leafEntryId: "state.signal_observation.get",
|
|
2347
|
+
attempt: attempts,
|
|
2348
|
+
state: response.state,
|
|
2349
|
+
result: response.result,
|
|
2350
|
+
error: response.error,
|
|
2351
|
+
});
|
|
2352
|
+
if (response.state !== "ok") {
|
|
2353
|
+
return {
|
|
2354
|
+
state: response.state,
|
|
2355
|
+
result: response.result,
|
|
2356
|
+
error: response.error,
|
|
2357
|
+
waiterEvidence: null,
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
const payload = toJsonRecord(response.result);
|
|
2361
|
+
lastSnapshot = payload;
|
|
2362
|
+
const count = clampCount(payload["count"], 0);
|
|
2363
|
+
if (count > afterCount) {
|
|
2364
|
+
const result = {
|
|
2365
|
+
nodePath,
|
|
2366
|
+
signalName,
|
|
2367
|
+
afterCount,
|
|
2368
|
+
count,
|
|
2369
|
+
lastObservedAt: (payload["lastObservedAt"] ?? null),
|
|
2370
|
+
lastPayload: (payload["lastPayload"] ?? null),
|
|
2371
|
+
attempts,
|
|
2372
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2373
|
+
};
|
|
2374
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2375
|
+
waiterId: "state.signal.await",
|
|
2376
|
+
outcome: "satisfied",
|
|
2377
|
+
startedAt,
|
|
2378
|
+
timeoutMs,
|
|
2379
|
+
pollCount: attempts,
|
|
2380
|
+
target,
|
|
2381
|
+
result,
|
|
2382
|
+
});
|
|
2383
|
+
recordWaiterSatisfied(session, "state.signal.await", target, attempts, result);
|
|
2384
|
+
return {
|
|
2385
|
+
state: "ok",
|
|
2386
|
+
result,
|
|
2387
|
+
error: null,
|
|
2388
|
+
waiterEvidence,
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
await waitMs(pollIntervalMs);
|
|
2392
|
+
}
|
|
2393
|
+
const result = {
|
|
2394
|
+
nodePath,
|
|
2395
|
+
signalName,
|
|
2396
|
+
afterCount,
|
|
2397
|
+
count: clampCount(lastSnapshot?.["count"], 0),
|
|
2398
|
+
lastObservedAt: (lastSnapshot?.["lastObservedAt"] ?? null),
|
|
2399
|
+
lastPayload: (lastSnapshot?.["lastPayload"] ?? null),
|
|
2400
|
+
attempts,
|
|
2401
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2402
|
+
};
|
|
2403
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2404
|
+
waiterId: "state.signal.await",
|
|
2405
|
+
outcome: "timed_out",
|
|
2406
|
+
startedAt,
|
|
2407
|
+
timeoutMs,
|
|
2408
|
+
pollCount: attempts,
|
|
2409
|
+
target,
|
|
2410
|
+
result,
|
|
2411
|
+
});
|
|
2412
|
+
recordWaiterTimedOut(session, "state.signal.await", target, attempts, result);
|
|
2413
|
+
return {
|
|
2414
|
+
state: "unavailable",
|
|
2415
|
+
result,
|
|
2416
|
+
error: "Timed out waiting for the requested signal emission.",
|
|
2417
|
+
waiterEvidence,
|
|
2090
2418
|
};
|
|
2091
2419
|
}
|
|
2092
2420
|
function toJsonRecord(value) {
|
|
@@ -2101,6 +2429,12 @@ function clampNumber(value, fallback, minimum, maximum) {
|
|
|
2101
2429
|
}
|
|
2102
2430
|
return Math.max(minimum, Math.min(maximum, Math.trunc(value)));
|
|
2103
2431
|
}
|
|
2432
|
+
function clampCount(value, fallback) {
|
|
2433
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2434
|
+
return fallback;
|
|
2435
|
+
}
|
|
2436
|
+
return Math.max(0, Math.trunc(value));
|
|
2437
|
+
}
|
|
2104
2438
|
function jsonValuesEqual(left, right) {
|
|
2105
2439
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
2106
2440
|
}
|
|
@@ -2125,6 +2459,42 @@ function recordBridgeEvent(session, event, payload) {
|
|
|
2125
2459
|
...payload,
|
|
2126
2460
|
});
|
|
2127
2461
|
}
|
|
2462
|
+
function recordWaiterStarted(session, entryId, target, timeoutMs, pollIntervalMs) {
|
|
2463
|
+
recordBridgeEvent(session, "invoke_waiter_started", {
|
|
2464
|
+
entryId,
|
|
2465
|
+
target,
|
|
2466
|
+
timeoutMs,
|
|
2467
|
+
pollIntervalMs,
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
function recordWaiterSatisfied(session, entryId, target, attempts, result) {
|
|
2471
|
+
recordBridgeEvent(session, "invoke_waiter_satisfied", {
|
|
2472
|
+
entryId,
|
|
2473
|
+
target,
|
|
2474
|
+
attempts,
|
|
2475
|
+
result,
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
function recordWaiterTimedOut(session, entryId, target, attempts, result) {
|
|
2479
|
+
recordBridgeEvent(session, "invoke_waiter_timed_out", {
|
|
2480
|
+
entryId,
|
|
2481
|
+
target,
|
|
2482
|
+
attempts,
|
|
2483
|
+
result,
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
function buildWaiterEvidence(input) {
|
|
2487
|
+
return {
|
|
2488
|
+
waiterId: input.waiterId,
|
|
2489
|
+
outcome: input.outcome,
|
|
2490
|
+
startedAt: input.startedAt,
|
|
2491
|
+
finishedAt: new Date().toISOString(),
|
|
2492
|
+
timeoutMs: input.timeoutMs,
|
|
2493
|
+
pollCount: input.pollCount,
|
|
2494
|
+
target: input.target,
|
|
2495
|
+
result: input.result,
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2128
2498
|
function bridgeMissing(reason) {
|
|
2129
2499
|
return {
|
|
2130
2500
|
required: true,
|