@skillcap/gdh 0.13.3 → 0.15.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 +158 -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/dist/guidance.d.ts.map +1 -1
- package/node_modules/@gdh/docs/dist/guidance.js +1 -0
- package/node_modules/@gdh/docs/dist/guidance.js.map +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 +232 -6
- 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 +427 -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 +12 -12
|
@@ -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
|
}
|
|
@@ -1998,11 +2022,53 @@ function mergeRuntimeBridgeEntries(entries) {
|
|
|
1998
2022
|
return merged;
|
|
1999
2023
|
}
|
|
2000
2024
|
async function invokeSyntheticBridgeEntry(session, entryId, inputValue) {
|
|
2025
|
+
if (entryId === "screenshot.capture") {
|
|
2026
|
+
return invokeScreenshotCaptureEntry(session, inputValue);
|
|
2027
|
+
}
|
|
2001
2028
|
if (entryId === "state.node_property.await") {
|
|
2002
2029
|
return invokeNodePropertyAwaitEntry(session, inputValue);
|
|
2003
2030
|
}
|
|
2031
|
+
if (entryId === "state.node_presence.await") {
|
|
2032
|
+
return invokeNodePresenceAwaitEntry(session, inputValue);
|
|
2033
|
+
}
|
|
2034
|
+
if (entryId === "state.signal.await") {
|
|
2035
|
+
return invokeSignalAwaitEntry(session, inputValue);
|
|
2036
|
+
}
|
|
2004
2037
|
return null;
|
|
2005
2038
|
}
|
|
2039
|
+
async function invokeScreenshotCaptureEntry(session, inputValue) {
|
|
2040
|
+
const input = toJsonRecord(inputValue);
|
|
2041
|
+
const label = typeof input["label"] === "string" ? input["label"] : "screenshot";
|
|
2042
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
2043
|
+
const basename = `${stamp}-${sanitizeScreenshotLabel(label)}`;
|
|
2044
|
+
const screenshotDirectory = path.join(session.artifactDirectory, "screenshots");
|
|
2045
|
+
const imagePath = path.join(screenshotDirectory, `${basename}.png`);
|
|
2046
|
+
const metadataPath = path.join(screenshotDirectory, `${basename}.json`);
|
|
2047
|
+
await fs.mkdir(path.dirname(imagePath), { recursive: true });
|
|
2048
|
+
await fs.mkdir(path.dirname(metadataPath), { recursive: true });
|
|
2049
|
+
recordBridgeEvent(session, "screenshot_capture_requested", {
|
|
2050
|
+
imagePath,
|
|
2051
|
+
metadataPath,
|
|
2052
|
+
});
|
|
2053
|
+
const response = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "screenshot.capture", {
|
|
2054
|
+
...input,
|
|
2055
|
+
imagePath,
|
|
2056
|
+
metadataPath,
|
|
2057
|
+
});
|
|
2058
|
+
recordBridgeEvent(session, "screenshot_capture_response", {
|
|
2059
|
+
state: response.state,
|
|
2060
|
+
result: response.result,
|
|
2061
|
+
error: response.error,
|
|
2062
|
+
imagePath,
|
|
2063
|
+
metadataPath,
|
|
2064
|
+
});
|
|
2065
|
+
return {
|
|
2066
|
+
state: response.state,
|
|
2067
|
+
result: response.result,
|
|
2068
|
+
error: response.error,
|
|
2069
|
+
waiterEvidence: null,
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2006
2072
|
async function invokeNodePropertyAwaitEntry(session, inputValue) {
|
|
2007
2073
|
const input = toJsonRecord(inputValue);
|
|
2008
2074
|
const nodePath = typeof input["nodePath"] === "string" ? input["nodePath"] : "";
|
|
@@ -2016,6 +2082,7 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
|
|
|
2016
2082
|
state: "failed",
|
|
2017
2083
|
result: null,
|
|
2018
2084
|
error: "state.node_property.await requires nodePath and property.",
|
|
2085
|
+
waiterEvidence: null,
|
|
2019
2086
|
};
|
|
2020
2087
|
}
|
|
2021
2088
|
if (!hasExpected) {
|
|
@@ -2023,12 +2090,20 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
|
|
|
2023
2090
|
state: "failed",
|
|
2024
2091
|
result: null,
|
|
2025
2092
|
error: "state.node_property.await requires an expected value.",
|
|
2093
|
+
waiterEvidence: null,
|
|
2026
2094
|
};
|
|
2027
2095
|
}
|
|
2028
|
-
const
|
|
2096
|
+
const target = {
|
|
2097
|
+
nodePath,
|
|
2098
|
+
property,
|
|
2099
|
+
expected,
|
|
2100
|
+
};
|
|
2101
|
+
const startedAt = new Date().toISOString();
|
|
2102
|
+
const startedAtMs = Date.now();
|
|
2103
|
+
recordWaiterStarted(session, "state.node_property.await", target, timeoutMs, pollIntervalMs);
|
|
2029
2104
|
let attempts = 0;
|
|
2030
2105
|
let lastValue = null;
|
|
2031
|
-
while (Date.now() -
|
|
2106
|
+
while (Date.now() - startedAtMs <= timeoutMs) {
|
|
2032
2107
|
attempts += 1;
|
|
2033
2108
|
recordBridgeEvent(session, "invoke_expanded_leaf_step", {
|
|
2034
2109
|
parentEntryId: "state.node_property.await",
|
|
@@ -2056,37 +2131,326 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
|
|
|
2056
2131
|
state: response.state,
|
|
2057
2132
|
result: response.result,
|
|
2058
2133
|
error: response.error,
|
|
2134
|
+
waiterEvidence: null,
|
|
2059
2135
|
};
|
|
2060
2136
|
}
|
|
2061
2137
|
const payload = toJsonRecord(response.result);
|
|
2062
2138
|
lastValue = (payload["value"] ?? null);
|
|
2063
2139
|
if (jsonValuesEqual(lastValue, expected)) {
|
|
2140
|
+
const result = {
|
|
2141
|
+
nodePath,
|
|
2142
|
+
property,
|
|
2143
|
+
expected,
|
|
2144
|
+
value: lastValue,
|
|
2145
|
+
attempts,
|
|
2146
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2147
|
+
};
|
|
2148
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2149
|
+
waiterId: "state.node_property.await",
|
|
2150
|
+
outcome: "satisfied",
|
|
2151
|
+
startedAt,
|
|
2152
|
+
timeoutMs,
|
|
2153
|
+
pollCount: attempts,
|
|
2154
|
+
target,
|
|
2155
|
+
result,
|
|
2156
|
+
});
|
|
2157
|
+
recordWaiterSatisfied(session, "state.node_property.await", target, attempts, result);
|
|
2064
2158
|
return {
|
|
2065
2159
|
state: "ok",
|
|
2066
|
-
result
|
|
2067
|
-
nodePath,
|
|
2068
|
-
property,
|
|
2069
|
-
expected,
|
|
2070
|
-
value: lastValue,
|
|
2071
|
-
attempts,
|
|
2072
|
-
elapsedMs: Date.now() - startedAt,
|
|
2073
|
-
},
|
|
2160
|
+
result,
|
|
2074
2161
|
error: null,
|
|
2162
|
+
waiterEvidence,
|
|
2075
2163
|
};
|
|
2076
2164
|
}
|
|
2077
2165
|
await waitMs(pollIntervalMs);
|
|
2078
2166
|
}
|
|
2167
|
+
const result = {
|
|
2168
|
+
nodePath,
|
|
2169
|
+
property,
|
|
2170
|
+
expected,
|
|
2171
|
+
value: lastValue,
|
|
2172
|
+
attempts,
|
|
2173
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2174
|
+
};
|
|
2175
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2176
|
+
waiterId: "state.node_property.await",
|
|
2177
|
+
outcome: "timed_out",
|
|
2178
|
+
startedAt,
|
|
2179
|
+
timeoutMs,
|
|
2180
|
+
pollCount: attempts,
|
|
2181
|
+
target,
|
|
2182
|
+
result,
|
|
2183
|
+
});
|
|
2184
|
+
recordWaiterTimedOut(session, "state.node_property.await", target, attempts, result);
|
|
2079
2185
|
return {
|
|
2080
2186
|
state: "unavailable",
|
|
2081
|
-
result
|
|
2187
|
+
result,
|
|
2188
|
+
error: "Timed out waiting for the requested node property value.",
|
|
2189
|
+
waiterEvidence,
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
async function invokeNodePresenceAwaitEntry(session, inputValue) {
|
|
2193
|
+
const input = toJsonRecord(inputValue);
|
|
2194
|
+
const nodePath = typeof input["nodePath"] === "string" ? input["nodePath"] : "";
|
|
2195
|
+
const expected = typeof input["expected"] === "string" ? input["expected"] : "";
|
|
2196
|
+
const timeoutMs = clampNumber(input["timeoutMs"], 1500, 100, 5000);
|
|
2197
|
+
const pollIntervalMs = clampNumber(input["pollIntervalMs"], 50, 25, 250);
|
|
2198
|
+
if (nodePath.length === 0) {
|
|
2199
|
+
return {
|
|
2200
|
+
state: "failed",
|
|
2201
|
+
result: null,
|
|
2202
|
+
error: "state.node_presence.await requires nodePath.",
|
|
2203
|
+
waiterEvidence: null,
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
if (expected !== "present" && expected !== "absent") {
|
|
2207
|
+
return {
|
|
2208
|
+
state: "failed",
|
|
2209
|
+
result: null,
|
|
2210
|
+
error: 'state.node_presence.await requires expected = "present" or "absent".',
|
|
2211
|
+
waiterEvidence: null,
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
const target = {
|
|
2215
|
+
nodePath,
|
|
2216
|
+
expected,
|
|
2217
|
+
};
|
|
2218
|
+
recordWaiterStarted(session, "state.node_presence.await", target, timeoutMs, pollIntervalMs);
|
|
2219
|
+
const startedAt = new Date().toISOString();
|
|
2220
|
+
const startedAtMs = Date.now();
|
|
2221
|
+
let attempts = 0;
|
|
2222
|
+
let lastSnapshot = null;
|
|
2223
|
+
while (Date.now() - startedAtMs <= timeoutMs) {
|
|
2224
|
+
attempts += 1;
|
|
2225
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_step", {
|
|
2226
|
+
parentEntryId: "state.node_presence.await",
|
|
2227
|
+
leafEntryId: "state.node_presence.get",
|
|
2228
|
+
attempt: attempts,
|
|
2229
|
+
input: {
|
|
2230
|
+
nodePath,
|
|
2231
|
+
},
|
|
2232
|
+
});
|
|
2233
|
+
const response = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.node_presence.get", {
|
|
2082
2234
|
nodePath,
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2235
|
+
});
|
|
2236
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_response", {
|
|
2237
|
+
parentEntryId: "state.node_presence.await",
|
|
2238
|
+
leafEntryId: "state.node_presence.get",
|
|
2239
|
+
attempt: attempts,
|
|
2240
|
+
state: response.state,
|
|
2241
|
+
result: response.result,
|
|
2242
|
+
error: response.error,
|
|
2243
|
+
});
|
|
2244
|
+
if (response.state !== "ok") {
|
|
2245
|
+
return {
|
|
2246
|
+
state: response.state,
|
|
2247
|
+
result: response.result,
|
|
2248
|
+
error: response.error,
|
|
2249
|
+
waiterEvidence: null,
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
const payload = toJsonRecord(response.result);
|
|
2253
|
+
lastSnapshot = payload;
|
|
2254
|
+
const present = payload["present"] === true;
|
|
2255
|
+
const matches = expected === "present" ? present : !present;
|
|
2256
|
+
if (matches) {
|
|
2257
|
+
const result = {
|
|
2258
|
+
nodePath,
|
|
2259
|
+
expected,
|
|
2260
|
+
present,
|
|
2261
|
+
name: (payload["name"] ?? null),
|
|
2262
|
+
className: (payload["className"] ?? null),
|
|
2263
|
+
attempts,
|
|
2264
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2265
|
+
};
|
|
2266
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2267
|
+
waiterId: "state.node_presence.await",
|
|
2268
|
+
outcome: "satisfied",
|
|
2269
|
+
startedAt,
|
|
2270
|
+
timeoutMs,
|
|
2271
|
+
pollCount: attempts,
|
|
2272
|
+
target,
|
|
2273
|
+
result,
|
|
2274
|
+
});
|
|
2275
|
+
recordWaiterSatisfied(session, "state.node_presence.await", target, attempts, result);
|
|
2276
|
+
return {
|
|
2277
|
+
state: "ok",
|
|
2278
|
+
result,
|
|
2279
|
+
error: null,
|
|
2280
|
+
waiterEvidence,
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
await waitMs(pollIntervalMs);
|
|
2284
|
+
}
|
|
2285
|
+
const result = {
|
|
2286
|
+
nodePath,
|
|
2287
|
+
expected,
|
|
2288
|
+
present: lastSnapshot?.["present"] === true,
|
|
2289
|
+
name: (lastSnapshot?.["name"] ?? null),
|
|
2290
|
+
className: (lastSnapshot?.["className"] ?? null),
|
|
2291
|
+
attempts,
|
|
2292
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2293
|
+
};
|
|
2294
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2295
|
+
waiterId: "state.node_presence.await",
|
|
2296
|
+
outcome: "timed_out",
|
|
2297
|
+
startedAt,
|
|
2298
|
+
timeoutMs,
|
|
2299
|
+
pollCount: attempts,
|
|
2300
|
+
target,
|
|
2301
|
+
result,
|
|
2302
|
+
});
|
|
2303
|
+
recordWaiterTimedOut(session, "state.node_presence.await", target, attempts, result);
|
|
2304
|
+
return {
|
|
2305
|
+
state: "unavailable",
|
|
2306
|
+
result,
|
|
2307
|
+
error: "Timed out waiting for the requested node presence state.",
|
|
2308
|
+
waiterEvidence,
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
async function invokeSignalAwaitEntry(session, inputValue) {
|
|
2312
|
+
const input = toJsonRecord(inputValue);
|
|
2313
|
+
const nodePath = typeof input["nodePath"] === "string" ? input["nodePath"] : "";
|
|
2314
|
+
const signalName = typeof input["signalName"] === "string" ? input["signalName"] : "";
|
|
2315
|
+
const afterCount = clampCount(input["afterCount"], 0);
|
|
2316
|
+
const timeoutMs = clampNumber(input["timeoutMs"], 1500, 100, 5000);
|
|
2317
|
+
const pollIntervalMs = clampNumber(input["pollIntervalMs"], 50, 25, 250);
|
|
2318
|
+
if (nodePath.length === 0 || signalName.length === 0) {
|
|
2319
|
+
return {
|
|
2320
|
+
state: "failed",
|
|
2321
|
+
result: null,
|
|
2322
|
+
error: "state.signal.await requires nodePath and signalName.",
|
|
2323
|
+
waiterEvidence: null,
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
const target = {
|
|
2327
|
+
nodePath,
|
|
2328
|
+
signalName,
|
|
2329
|
+
afterCount,
|
|
2330
|
+
};
|
|
2331
|
+
recordWaiterStarted(session, "state.signal.await", target, timeoutMs, pollIntervalMs);
|
|
2332
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_step", {
|
|
2333
|
+
parentEntryId: "state.signal.await",
|
|
2334
|
+
leafEntryId: "state.signal_observation.start",
|
|
2335
|
+
attempt: 1,
|
|
2336
|
+
input: {
|
|
2337
|
+
nodePath,
|
|
2338
|
+
signalName,
|
|
2088
2339
|
},
|
|
2089
|
-
|
|
2340
|
+
});
|
|
2341
|
+
const startResponse = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.signal_observation.start", {
|
|
2342
|
+
nodePath,
|
|
2343
|
+
signalName,
|
|
2344
|
+
});
|
|
2345
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_response", {
|
|
2346
|
+
parentEntryId: "state.signal.await",
|
|
2347
|
+
leafEntryId: "state.signal_observation.start",
|
|
2348
|
+
attempt: 1,
|
|
2349
|
+
state: startResponse.state,
|
|
2350
|
+
result: startResponse.result,
|
|
2351
|
+
error: startResponse.error,
|
|
2352
|
+
});
|
|
2353
|
+
if (startResponse.state !== "ok") {
|
|
2354
|
+
return {
|
|
2355
|
+
state: startResponse.state,
|
|
2356
|
+
result: startResponse.result,
|
|
2357
|
+
error: startResponse.error,
|
|
2358
|
+
waiterEvidence: null,
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
const startedAt = new Date().toISOString();
|
|
2362
|
+
const startedAtMs = Date.now();
|
|
2363
|
+
let attempts = 0;
|
|
2364
|
+
let lastSnapshot = null;
|
|
2365
|
+
while (Date.now() - startedAtMs <= timeoutMs) {
|
|
2366
|
+
attempts += 1;
|
|
2367
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_step", {
|
|
2368
|
+
parentEntryId: "state.signal.await",
|
|
2369
|
+
leafEntryId: "state.signal_observation.get",
|
|
2370
|
+
attempt: attempts,
|
|
2371
|
+
input: {
|
|
2372
|
+
nodePath,
|
|
2373
|
+
signalName,
|
|
2374
|
+
},
|
|
2375
|
+
});
|
|
2376
|
+
const response = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.signal_observation.get", {
|
|
2377
|
+
nodePath,
|
|
2378
|
+
signalName,
|
|
2379
|
+
});
|
|
2380
|
+
recordBridgeEvent(session, "invoke_expanded_leaf_response", {
|
|
2381
|
+
parentEntryId: "state.signal.await",
|
|
2382
|
+
leafEntryId: "state.signal_observation.get",
|
|
2383
|
+
attempt: attempts,
|
|
2384
|
+
state: response.state,
|
|
2385
|
+
result: response.result,
|
|
2386
|
+
error: response.error,
|
|
2387
|
+
});
|
|
2388
|
+
if (response.state !== "ok") {
|
|
2389
|
+
return {
|
|
2390
|
+
state: response.state,
|
|
2391
|
+
result: response.result,
|
|
2392
|
+
error: response.error,
|
|
2393
|
+
waiterEvidence: null,
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
const payload = toJsonRecord(response.result);
|
|
2397
|
+
lastSnapshot = payload;
|
|
2398
|
+
const count = clampCount(payload["count"], 0);
|
|
2399
|
+
if (count > afterCount) {
|
|
2400
|
+
const result = {
|
|
2401
|
+
nodePath,
|
|
2402
|
+
signalName,
|
|
2403
|
+
afterCount,
|
|
2404
|
+
count,
|
|
2405
|
+
lastObservedAt: (payload["lastObservedAt"] ?? null),
|
|
2406
|
+
lastPayload: (payload["lastPayload"] ?? null),
|
|
2407
|
+
attempts,
|
|
2408
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2409
|
+
};
|
|
2410
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2411
|
+
waiterId: "state.signal.await",
|
|
2412
|
+
outcome: "satisfied",
|
|
2413
|
+
startedAt,
|
|
2414
|
+
timeoutMs,
|
|
2415
|
+
pollCount: attempts,
|
|
2416
|
+
target,
|
|
2417
|
+
result,
|
|
2418
|
+
});
|
|
2419
|
+
recordWaiterSatisfied(session, "state.signal.await", target, attempts, result);
|
|
2420
|
+
return {
|
|
2421
|
+
state: "ok",
|
|
2422
|
+
result,
|
|
2423
|
+
error: null,
|
|
2424
|
+
waiterEvidence,
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
await waitMs(pollIntervalMs);
|
|
2428
|
+
}
|
|
2429
|
+
const result = {
|
|
2430
|
+
nodePath,
|
|
2431
|
+
signalName,
|
|
2432
|
+
afterCount,
|
|
2433
|
+
count: clampCount(lastSnapshot?.["count"], 0),
|
|
2434
|
+
lastObservedAt: (lastSnapshot?.["lastObservedAt"] ?? null),
|
|
2435
|
+
lastPayload: (lastSnapshot?.["lastPayload"] ?? null),
|
|
2436
|
+
attempts,
|
|
2437
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
2438
|
+
};
|
|
2439
|
+
const waiterEvidence = buildWaiterEvidence({
|
|
2440
|
+
waiterId: "state.signal.await",
|
|
2441
|
+
outcome: "timed_out",
|
|
2442
|
+
startedAt,
|
|
2443
|
+
timeoutMs,
|
|
2444
|
+
pollCount: attempts,
|
|
2445
|
+
target,
|
|
2446
|
+
result,
|
|
2447
|
+
});
|
|
2448
|
+
recordWaiterTimedOut(session, "state.signal.await", target, attempts, result);
|
|
2449
|
+
return {
|
|
2450
|
+
state: "unavailable",
|
|
2451
|
+
result,
|
|
2452
|
+
error: "Timed out waiting for the requested signal emission.",
|
|
2453
|
+
waiterEvidence,
|
|
2090
2454
|
};
|
|
2091
2455
|
}
|
|
2092
2456
|
function toJsonRecord(value) {
|
|
@@ -2101,6 +2465,16 @@ function clampNumber(value, fallback, minimum, maximum) {
|
|
|
2101
2465
|
}
|
|
2102
2466
|
return Math.max(minimum, Math.min(maximum, Math.trunc(value)));
|
|
2103
2467
|
}
|
|
2468
|
+
function clampCount(value, fallback) {
|
|
2469
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2470
|
+
return fallback;
|
|
2471
|
+
}
|
|
2472
|
+
return Math.max(0, Math.trunc(value));
|
|
2473
|
+
}
|
|
2474
|
+
function sanitizeScreenshotLabel(value) {
|
|
2475
|
+
const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2476
|
+
return (sanitized || "screenshot").slice(0, 80);
|
|
2477
|
+
}
|
|
2104
2478
|
function jsonValuesEqual(left, right) {
|
|
2105
2479
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
2106
2480
|
}
|
|
@@ -2125,6 +2499,42 @@ function recordBridgeEvent(session, event, payload) {
|
|
|
2125
2499
|
...payload,
|
|
2126
2500
|
});
|
|
2127
2501
|
}
|
|
2502
|
+
function recordWaiterStarted(session, entryId, target, timeoutMs, pollIntervalMs) {
|
|
2503
|
+
recordBridgeEvent(session, "invoke_waiter_started", {
|
|
2504
|
+
entryId,
|
|
2505
|
+
target,
|
|
2506
|
+
timeoutMs,
|
|
2507
|
+
pollIntervalMs,
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
function recordWaiterSatisfied(session, entryId, target, attempts, result) {
|
|
2511
|
+
recordBridgeEvent(session, "invoke_waiter_satisfied", {
|
|
2512
|
+
entryId,
|
|
2513
|
+
target,
|
|
2514
|
+
attempts,
|
|
2515
|
+
result,
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
function recordWaiterTimedOut(session, entryId, target, attempts, result) {
|
|
2519
|
+
recordBridgeEvent(session, "invoke_waiter_timed_out", {
|
|
2520
|
+
entryId,
|
|
2521
|
+
target,
|
|
2522
|
+
attempts,
|
|
2523
|
+
result,
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
function buildWaiterEvidence(input) {
|
|
2527
|
+
return {
|
|
2528
|
+
waiterId: input.waiterId,
|
|
2529
|
+
outcome: input.outcome,
|
|
2530
|
+
startedAt: input.startedAt,
|
|
2531
|
+
finishedAt: new Date().toISOString(),
|
|
2532
|
+
timeoutMs: input.timeoutMs,
|
|
2533
|
+
pollCount: input.pollCount,
|
|
2534
|
+
target: input.target,
|
|
2535
|
+
result: input.result,
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2128
2538
|
function bridgeMissing(reason) {
|
|
2129
2539
|
return {
|
|
2130
2540
|
required: true,
|