4runr-os 1.3.21 → 1.3.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ui/v3/commands/commandEngine.js +256 -199
- package/dist/ui/v3/commands/commandEngine.js.map +1 -1
- package/dist/ui/v3/config/gateway.d.ts +9 -10
- package/dist/ui/v3/config/gateway.d.ts.map +1 -1
- package/dist/ui/v3/config/gateway.js +12 -14
- package/dist/ui/v3/config/gateway.js.map +1 -1
- package/dist/ui/v3/state/gatewayConnectionStore.d.ts +72 -0
- package/dist/ui/v3/state/gatewayConnectionStore.d.ts.map +1 -0
- package/dist/ui/v3/state/gatewayConnectionStore.js +102 -0
- package/dist/ui/v3/state/gatewayConnectionStore.js.map +1 -0
- package/dist/ui/v3/ui/panels/NetworkPanel.d.ts +14 -8
- package/dist/ui/v3/ui/panels/NetworkPanel.d.ts.map +1 -1
- package/dist/ui/v3/ui/panels/NetworkPanel.js +95 -28
- package/dist/ui/v3/ui/panels/NetworkPanel.js.map +1 -1
- package/dist/ui/v3/ui/phase1Runtime.d.ts.map +1 -1
- package/dist/ui/v3/ui/phase1Runtime.js +13 -0
- package/dist/ui/v3/ui/phase1Runtime.js.map +1 -1
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - Side-effect free
|
|
16
16
|
* - Deterministic
|
|
17
17
|
*/
|
|
18
|
-
import { available, unavailable
|
|
18
|
+
import { available, unavailable } from '../state/value.js';
|
|
19
19
|
import { listAgents, getAgent, createAgent as v1CreateAgent } from '../v1Adapters/agents.js';
|
|
20
20
|
import { connect as v1Connect, getConnectionState } from '../v1Adapters/connect.js';
|
|
21
21
|
import { startRun, listRuns } from '../v1Adapters/runs.js';
|
|
@@ -241,7 +241,7 @@ function handleUnknown(cmd) {
|
|
|
241
241
|
* Network connection attempt ID tracker
|
|
242
242
|
* Prevents stale updates from overlapping connection attempts
|
|
243
243
|
*/
|
|
244
|
-
|
|
244
|
+
// networkAttemptId removed - using gatewayConnectionStore instead
|
|
245
245
|
/**
|
|
246
246
|
* Command registration map
|
|
247
247
|
*
|
|
@@ -255,6 +255,7 @@ const commands = {
|
|
|
255
255
|
// Section 6: Real commands (V1-backed)
|
|
256
256
|
'agents': handleAgents,
|
|
257
257
|
'connect': handleConnect,
|
|
258
|
+
'verify': handleVerify,
|
|
258
259
|
'start': handleStart,
|
|
259
260
|
'runs': handleRuns,
|
|
260
261
|
'system': handleSystem,
|
|
@@ -339,6 +340,13 @@ function handleHelp(ctx, args) {
|
|
|
339
340
|
level: 'INFO',
|
|
340
341
|
msg: ' connect — connect to Gateway',
|
|
341
342
|
},
|
|
343
|
+
{
|
|
344
|
+
id: `help-verify-${Date.now()}`,
|
|
345
|
+
ts: Date.now(),
|
|
346
|
+
tag: 'SYS',
|
|
347
|
+
level: 'INFO',
|
|
348
|
+
msg: ' verify - Test connection with real API calls',
|
|
349
|
+
},
|
|
342
350
|
{
|
|
343
351
|
id: `help-10-${Date.now()}`,
|
|
344
352
|
ts: Date.now(),
|
|
@@ -739,14 +747,12 @@ async function handleAgentsInspect(ctx, args) {
|
|
|
739
747
|
* - Guarantees reset in finally block
|
|
740
748
|
*/
|
|
741
749
|
async function handleConnect(ctx, args) {
|
|
742
|
-
//
|
|
743
|
-
|
|
744
|
-
const currentAttemptId = networkAttemptId;
|
|
745
|
-
// Section 6.2: Ignore any arguments (parser may still accept them, but they are ignored)
|
|
746
|
-
// Always use canonical Gateway URL
|
|
750
|
+
// Import store and gateway config
|
|
751
|
+
const { gatewayConnectionStore } = await import('../state/gatewayConnectionStore.js');
|
|
747
752
|
const gatewayConfig = await import('../config/gateway.js');
|
|
748
|
-
|
|
749
|
-
|
|
753
|
+
// Step 1: Resolve target with source tracking
|
|
754
|
+
const { url: inputTarget, source } = gatewayConfig.resolveGatewayUrlWithSource();
|
|
755
|
+
// Step 2: Emit [CMD] connect
|
|
750
756
|
const cmdEvent = {
|
|
751
757
|
id: `cmd-${Date.now()}`,
|
|
752
758
|
ts: Date.now(),
|
|
@@ -754,109 +760,34 @@ async function handleConnect(ctx, args) {
|
|
|
754
760
|
level: 'INFO',
|
|
755
761
|
msg: 'connect',
|
|
756
762
|
};
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const url = new URL(resolvedTarget);
|
|
762
|
-
parsedHost = url.hostname;
|
|
763
|
-
parsedPort = url.port || (url.protocol === 'https:' ? '443' : '80');
|
|
764
|
-
}
|
|
765
|
-
catch {
|
|
766
|
-
// URL parse failed - use as-is
|
|
767
|
-
}
|
|
768
|
-
const targetEvent = {
|
|
769
|
-
id: `sys-target-${Date.now()}`,
|
|
763
|
+
const events = [cmdEvent];
|
|
764
|
+
// Step 3: Log target source
|
|
765
|
+
events.push({
|
|
766
|
+
id: `sys-source-${Date.now()}`,
|
|
770
767
|
ts: Date.now(),
|
|
771
768
|
tag: 'SYS',
|
|
772
769
|
level: 'INFO',
|
|
773
|
-
msg: `
|
|
774
|
-
};
|
|
775
|
-
// Step
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
// Or if we're on Windows/Mac but not using localhost
|
|
780
|
-
const isLocalhost = resolvedTarget.includes('127.0.0.1') || resolvedTarget.includes('localhost');
|
|
781
|
-
const isWindowsOrMac = process.platform === 'win32' || process.platform === 'darwin';
|
|
782
|
-
const shouldSkipProbes = isLocalhost && isWindowsOrMac; // Skip localhost probes on Windows/Mac (auto-fallback will handle)
|
|
783
|
-
if (!shouldSkipProbes) {
|
|
784
|
-
try {
|
|
785
|
-
const { runConnectionProbes } = await import('../v1Adapters/connect.js');
|
|
786
|
-
const probeResults = await runConnectionProbes(resolvedTarget);
|
|
787
|
-
// Only show successful probes (cleaner UI)
|
|
788
|
-
for (const probe of probeResults) {
|
|
789
|
-
if (probe.success) {
|
|
790
|
-
if (probe.type === 'tcp') {
|
|
791
|
-
events.push({
|
|
792
|
-
id: `sys-tcp-${Date.now()}`,
|
|
793
|
-
ts: Date.now(),
|
|
794
|
-
tag: 'SYS',
|
|
795
|
-
level: 'INFO',
|
|
796
|
-
msg: `tcp: ${probe.details}`,
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
else if (probe.type === 'http') {
|
|
800
|
-
events.push({
|
|
801
|
-
id: `sys-http-${Date.now()}`,
|
|
802
|
-
ts: Date.now(),
|
|
803
|
-
tag: 'SYS',
|
|
804
|
-
level: 'INFO',
|
|
805
|
-
msg: `http: ${probe.details || 'ok'}`,
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
catch (probeError) {
|
|
812
|
-
// Probe errors are silent - connection attempt will show the real result
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
// Step 4: Emit [SYS] CONNECTING...
|
|
816
|
-
const connectingEvent = {
|
|
770
|
+
msg: `gateway target source: ${source}`,
|
|
771
|
+
});
|
|
772
|
+
// Step 4: Set connecting state in store
|
|
773
|
+
gatewayConnectionStore.setConnecting(inputTarget, 5000);
|
|
774
|
+
// Step 5: Emit connecting event
|
|
775
|
+
events.push({
|
|
817
776
|
id: `sys-connecting-${Date.now()}`,
|
|
818
777
|
ts: Date.now(),
|
|
819
778
|
tag: 'SYS',
|
|
820
779
|
level: 'INFO',
|
|
821
780
|
msg: 'CONNECTING...',
|
|
822
|
-
};
|
|
823
|
-
events.push(connectingEvent);
|
|
824
|
-
// Step 4: Update UiState.network → LOADING (with attemptId in meta)
|
|
825
|
-
const loadingStateUpdate = (prev) => ({
|
|
826
|
-
...prev,
|
|
827
|
-
network: loading('Connecting to Gateway...', {
|
|
828
|
-
attemptId: currentAttemptId,
|
|
829
|
-
target: resolvedTarget,
|
|
830
|
-
timeout: 5,
|
|
831
|
-
}),
|
|
832
|
-
statusStrip: available({
|
|
833
|
-
left: 'NET: CONNECTING...',
|
|
834
|
-
right: 'GATEWAY: CONNECTING',
|
|
835
|
-
}),
|
|
836
781
|
});
|
|
837
|
-
// Step
|
|
782
|
+
// Step 6: Attempt connection
|
|
838
783
|
try {
|
|
839
784
|
const result = await v1Connect();
|
|
840
|
-
// Step 6: Check if this attempt is still current (prevent stale updates)
|
|
841
|
-
if (currentAttemptId !== networkAttemptId) {
|
|
842
|
-
// Stale completion - ignore it
|
|
843
|
-
return {
|
|
844
|
-
events: [cmdEvent], // Only emit [CMD], no state update
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
785
|
if (!result.ok) {
|
|
848
|
-
// Failure:
|
|
849
|
-
// Section 6.2: Map to clear error message
|
|
786
|
+
// Failure: Update store with error
|
|
850
787
|
const errorCode = result.error.code || 'UNKNOWN';
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
errorCode === 'GATEWAY_TIMEOUT' || errorCode === 'GATEWAY_UNREACHABLE') {
|
|
855
|
-
errorMsg = 'Gateway unreachable (ECONNREFUSED | ETIMEDOUT | ECONNABORTED)';
|
|
856
|
-
}
|
|
857
|
-
else {
|
|
858
|
-
errorMsg = result.error.message || 'Gateway unreachable';
|
|
859
|
-
}
|
|
788
|
+
const errorMsg = result.error.message || 'Gateway unreachable';
|
|
789
|
+
const nextAction = result.error.nextAction || 'Retry "connect"';
|
|
790
|
+
gatewayConnectionStore.setError(inputTarget, errorCode, errorMsg, nextAction);
|
|
860
791
|
events.push({
|
|
861
792
|
id: `err-${Date.now()}`,
|
|
862
793
|
ts: Date.now(),
|
|
@@ -864,34 +795,43 @@ async function handleConnect(ctx, args) {
|
|
|
864
795
|
level: 'ERROR',
|
|
865
796
|
msg: errorMsg,
|
|
866
797
|
});
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
network: unavailable('Gateway unreachable', 'Start Gateway service and run "connect"'),
|
|
878
|
-
statusStrip: available({
|
|
879
|
-
left: 'OFFLINE',
|
|
880
|
-
right: 'GATEWAY: UNAVAILABLE',
|
|
881
|
-
}),
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
return prev; // Stale attempt, don't update
|
|
885
|
-
},
|
|
886
|
-
};
|
|
798
|
+
// Emit store state for debugging
|
|
799
|
+
const storeState = gatewayConnectionStore.getState();
|
|
800
|
+
events.push({
|
|
801
|
+
id: `sys-network-state-${Date.now()}`,
|
|
802
|
+
ts: Date.now(),
|
|
803
|
+
tag: 'SYS',
|
|
804
|
+
level: 'INFO',
|
|
805
|
+
msg: `network_state=${storeState.status}`,
|
|
806
|
+
});
|
|
807
|
+
return { events };
|
|
887
808
|
}
|
|
888
|
-
// Success:
|
|
889
|
-
//
|
|
890
|
-
const connectedEndpoint = result.data.endpoint;
|
|
809
|
+
// Success: Update store with connection info
|
|
810
|
+
const effectiveTarget = result.data.endpoint; // The endpoint that actually worked (might be different from input)
|
|
891
811
|
const healthData = result.data.healthData;
|
|
892
812
|
const latency = result.data.latency;
|
|
813
|
+
gatewayConnectionStore.setConnected(inputTarget, effectiveTarget, healthData, latency);
|
|
814
|
+
// Show input vs effective target if different
|
|
815
|
+
if (effectiveTarget !== inputTarget) {
|
|
816
|
+
events.push({
|
|
817
|
+
id: `sys-targets-${Date.now()}`,
|
|
818
|
+
ts: Date.now(),
|
|
819
|
+
tag: 'SYS',
|
|
820
|
+
level: 'INFO',
|
|
821
|
+
msg: `connect: input=${inputTarget} effective=${effectiveTarget}`,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
events.push({
|
|
826
|
+
id: `sys-target-${Date.now()}`,
|
|
827
|
+
ts: Date.now(),
|
|
828
|
+
tag: 'SYS',
|
|
829
|
+
level: 'INFO',
|
|
830
|
+
msg: `connect: target=${inputTarget}`,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
893
833
|
// Show clean connection message
|
|
894
|
-
const endpointUrl = new URL(
|
|
834
|
+
const endpointUrl = new URL(effectiveTarget);
|
|
895
835
|
const displayHost = endpointUrl.hostname === '127.0.0.1' ? 'localhost' : endpointUrl.hostname;
|
|
896
836
|
const displayPort = endpointUrl.port || (endpointUrl.protocol === 'https:' ? '443' : '80');
|
|
897
837
|
events.push({
|
|
@@ -913,96 +853,213 @@ async function handleConnect(ctx, args) {
|
|
|
913
853
|
msg: `Gateway: ${status} | Persistence: ${persistence}${latency ? ` | Latency: ${latency}ms` : ''}`,
|
|
914
854
|
});
|
|
915
855
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const latency = result.data.latency;
|
|
927
|
-
return {
|
|
928
|
-
...prev,
|
|
929
|
-
network: available({
|
|
930
|
-
gateway: 'CONNECTED',
|
|
931
|
-
endpoint: actualEndpoint,
|
|
932
|
-
status: healthData?.status || 'alive',
|
|
933
|
-
latency: latency,
|
|
934
|
-
}),
|
|
935
|
-
capabilities: available({
|
|
936
|
-
items: [], // Empty is allowed for now
|
|
937
|
-
}),
|
|
938
|
-
statusStrip: available({
|
|
939
|
-
left: 'ONLINE',
|
|
940
|
-
right: 'GATEWAY: CONNECTED',
|
|
941
|
-
}),
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
return prev; // Stale attempt, don't update
|
|
945
|
-
},
|
|
946
|
-
};
|
|
856
|
+
// Emit store state for debugging
|
|
857
|
+
const storeState = gatewayConnectionStore.getState();
|
|
858
|
+
events.push({
|
|
859
|
+
id: `sys-network-state-${Date.now()}`,
|
|
860
|
+
ts: Date.now(),
|
|
861
|
+
tag: 'SYS',
|
|
862
|
+
level: 'INFO',
|
|
863
|
+
msg: `network_state=${storeState.status}`,
|
|
864
|
+
});
|
|
865
|
+
return { events };
|
|
947
866
|
}
|
|
948
867
|
catch (error) {
|
|
949
868
|
// Step 7: Catch-all error handler
|
|
950
|
-
|
|
951
|
-
if (currentAttemptId !== networkAttemptId) {
|
|
952
|
-
// Stale error - ignore it
|
|
953
|
-
return {
|
|
954
|
-
events: [cmdEvent],
|
|
955
|
-
};
|
|
956
|
-
}
|
|
957
|
-
// Section 6.2: Map errors to clear error codes
|
|
869
|
+
const errorMsg = error?.message || 'Connection failed';
|
|
958
870
|
const errorCode = error?.code || 'UNKNOWN';
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
871
|
+
gatewayConnectionStore.setError(inputTarget, errorCode, errorMsg, 'Retry "connect"');
|
|
872
|
+
events.push({
|
|
873
|
+
id: `err-${Date.now()}`,
|
|
874
|
+
ts: Date.now(),
|
|
875
|
+
tag: 'ERR',
|
|
876
|
+
level: 'ERROR',
|
|
877
|
+
msg: errorMsg,
|
|
878
|
+
});
|
|
879
|
+
// Emit store state for debugging
|
|
880
|
+
const storeState = gatewayConnectionStore.getState();
|
|
881
|
+
events.push({
|
|
882
|
+
id: `sys-network-state-${Date.now()}`,
|
|
883
|
+
ts: Date.now(),
|
|
884
|
+
tag: 'SYS',
|
|
885
|
+
level: 'INFO',
|
|
886
|
+
msg: `network_state=${storeState.status}`,
|
|
887
|
+
});
|
|
888
|
+
return { events };
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* 4.5️⃣ verify
|
|
893
|
+
*
|
|
894
|
+
* Makes real API calls to prove the connection works
|
|
895
|
+
* Tests: /health, /ready, /api/runs, /metrics
|
|
896
|
+
*/
|
|
897
|
+
async function handleVerify(ctx, args) {
|
|
898
|
+
const { gatewayConnectionStore } = await import('../state/gatewayConnectionStore.js');
|
|
899
|
+
const gatewayConfig = await import('../config/gateway.js');
|
|
900
|
+
const { GatewayClient } = await import('../../../gateway-client.js');
|
|
901
|
+
const cmdEvent = {
|
|
902
|
+
id: `cmd-${Date.now()}`,
|
|
903
|
+
ts: Date.now(),
|
|
904
|
+
tag: 'CMD',
|
|
905
|
+
level: 'INFO',
|
|
906
|
+
msg: 'verify',
|
|
907
|
+
};
|
|
908
|
+
const events = [cmdEvent];
|
|
909
|
+
// Check store state first
|
|
910
|
+
const storeState = gatewayConnectionStore.getState();
|
|
911
|
+
if (storeState.status !== 'connected') {
|
|
912
|
+
events.push({
|
|
913
|
+
id: `err-${Date.now()}`,
|
|
914
|
+
ts: Date.now(),
|
|
915
|
+
tag: 'ERR',
|
|
916
|
+
level: 'ERROR',
|
|
917
|
+
msg: `Not connected (status: ${storeState.status}). Run "connect" first.`,
|
|
918
|
+
});
|
|
919
|
+
return { events };
|
|
920
|
+
}
|
|
921
|
+
const gatewayUrl = gatewayConfig.resolveGatewayUrl();
|
|
922
|
+
const client = new GatewayClient({ gatewayUrl });
|
|
923
|
+
events.push({
|
|
924
|
+
id: `sys-verify-start-${Date.now()}`,
|
|
925
|
+
ts: Date.now(),
|
|
926
|
+
tag: 'SYS',
|
|
927
|
+
level: 'INFO',
|
|
928
|
+
msg: `Verifying connection to ${storeState.resolvedTo}...`,
|
|
929
|
+
});
|
|
930
|
+
// Test 1: Health check
|
|
931
|
+
try {
|
|
932
|
+
const startTime = Date.now();
|
|
933
|
+
const healthResponse = await client.health();
|
|
934
|
+
const latency = Date.now() - startTime;
|
|
935
|
+
events.push({
|
|
936
|
+
id: `ok-health-${Date.now()}`,
|
|
937
|
+
ts: Date.now(),
|
|
938
|
+
tag: 'OK',
|
|
939
|
+
level: 'INFO',
|
|
940
|
+
msg: `✓ /health: ${healthResponse.status || 'ok'} (${latency}ms)`,
|
|
941
|
+
});
|
|
942
|
+
if (healthResponse.persistence) {
|
|
943
|
+
events.push({
|
|
944
|
+
id: `sys-persistence-${Date.now()}`,
|
|
945
|
+
ts: Date.now(),
|
|
946
|
+
tag: 'SYS',
|
|
947
|
+
level: 'INFO',
|
|
948
|
+
msg: ` Persistence: ${healthResponse.persistence}`,
|
|
949
|
+
});
|
|
965
950
|
}
|
|
966
|
-
|
|
967
|
-
|
|
951
|
+
}
|
|
952
|
+
catch (error) {
|
|
953
|
+
events.push({
|
|
954
|
+
id: `err-health-${Date.now()}`,
|
|
955
|
+
ts: Date.now(),
|
|
956
|
+
tag: 'ERR',
|
|
957
|
+
level: 'ERROR',
|
|
958
|
+
msg: `✗ /health failed: ${error.message || 'Unknown error'}`,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
// Test 2: Ready check
|
|
962
|
+
try {
|
|
963
|
+
const startTime = Date.now();
|
|
964
|
+
const readyResponse = await fetch(`${gatewayUrl}/ready`, {
|
|
965
|
+
method: 'GET',
|
|
966
|
+
signal: AbortSignal.timeout(5000),
|
|
967
|
+
});
|
|
968
|
+
const latency = Date.now() - startTime;
|
|
969
|
+
const data = await readyResponse.json();
|
|
970
|
+
events.push({
|
|
971
|
+
id: `ok-ready-${Date.now()}`,
|
|
972
|
+
ts: Date.now(),
|
|
973
|
+
tag: 'OK',
|
|
974
|
+
level: 'INFO',
|
|
975
|
+
msg: `✓ /ready: ${data.status || readyResponse.status} (${latency}ms)`,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
events.push({
|
|
980
|
+
id: `err-ready-${Date.now()}`,
|
|
981
|
+
ts: Date.now(),
|
|
982
|
+
tag: 'ERR',
|
|
983
|
+
level: 'ERROR',
|
|
984
|
+
msg: `✗ /ready failed: ${error.message || 'Unknown error'}`,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
// Test 3: List runs (requires auth, but tests API endpoint)
|
|
988
|
+
try {
|
|
989
|
+
const startTime = Date.now();
|
|
990
|
+
const runsResponse = await fetch(`${gatewayUrl}/api/runs?limit=1`, {
|
|
991
|
+
method: 'GET',
|
|
992
|
+
headers: {
|
|
993
|
+
'Authorization': `Bearer ${process.env.GATEWAY_TOKEN || ''}`,
|
|
994
|
+
},
|
|
995
|
+
signal: AbortSignal.timeout(5000),
|
|
996
|
+
});
|
|
997
|
+
const latency = Date.now() - startTime;
|
|
998
|
+
if (runsResponse.ok) {
|
|
999
|
+
const data = await runsResponse.json();
|
|
1000
|
+
events.push({
|
|
1001
|
+
id: `ok-runs-${Date.now()}`,
|
|
1002
|
+
ts: Date.now(),
|
|
1003
|
+
tag: 'OK',
|
|
1004
|
+
level: 'INFO',
|
|
1005
|
+
msg: `✓ /api/runs: ${runsResponse.status} (${latency}ms) - ${data.runs?.length || 0} runs`,
|
|
1006
|
+
});
|
|
968
1007
|
}
|
|
969
|
-
|
|
970
|
-
|
|
1008
|
+
else {
|
|
1009
|
+
events.push({
|
|
1010
|
+
id: `warn-runs-${Date.now()}`,
|
|
1011
|
+
ts: Date.now(),
|
|
1012
|
+
tag: 'SYS',
|
|
1013
|
+
level: 'WARN',
|
|
1014
|
+
msg: `⚠ /api/runs: ${runsResponse.status} (${latency}ms) - ${runsResponse.status === 401 ? 'Auth required' : 'Error'}`,
|
|
1015
|
+
});
|
|
971
1016
|
}
|
|
1017
|
+
}
|
|
1018
|
+
catch (error) {
|
|
972
1019
|
events.push({
|
|
973
|
-
id: `err-${Date.now()}`,
|
|
1020
|
+
id: `err-runs-${Date.now()}`,
|
|
974
1021
|
ts: Date.now(),
|
|
975
1022
|
tag: 'ERR',
|
|
976
1023
|
level: 'ERROR',
|
|
977
|
-
msg:
|
|
1024
|
+
msg: `✗ /api/runs failed: ${error.message || 'Unknown error'}`,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
// Test 4: Metrics endpoint
|
|
1028
|
+
try {
|
|
1029
|
+
const startTime = Date.now();
|
|
1030
|
+
const metricsResponse = await fetch(`${gatewayUrl}/metrics`, {
|
|
1031
|
+
method: 'GET',
|
|
1032
|
+
signal: AbortSignal.timeout(5000),
|
|
1033
|
+
});
|
|
1034
|
+
const latency = Date.now() - startTime;
|
|
1035
|
+
const text = await metricsResponse.text();
|
|
1036
|
+
const lines = text.split('\n').filter(l => l.trim() && !l.startsWith('#')).length;
|
|
1037
|
+
events.push({
|
|
1038
|
+
id: `ok-metrics-${Date.now()}`,
|
|
1039
|
+
ts: Date.now(),
|
|
1040
|
+
tag: 'OK',
|
|
1041
|
+
level: 'INFO',
|
|
1042
|
+
msg: `✓ /metrics: ${metricsResponse.status} (${latency}ms) - ${lines} metrics`,
|
|
978
1043
|
});
|
|
979
|
-
return {
|
|
980
|
-
events,
|
|
981
|
-
uiStateUpdate: (prev) => {
|
|
982
|
-
// Section 6.2: Always update to UNAVAILABLE on error
|
|
983
|
-
// Check attemptId to prevent stale updates, but don't require LOADING state
|
|
984
|
-
const prevAttemptId = isLoading(prev.network) ? prev.network.meta?.attemptId : undefined;
|
|
985
|
-
if (prevAttemptId === currentAttemptId || prevAttemptId === undefined) {
|
|
986
|
-
return {
|
|
987
|
-
...prev,
|
|
988
|
-
network: unavailable('Gateway unreachable', 'Start Gateway service and run "connect"'),
|
|
989
|
-
statusStrip: available({
|
|
990
|
-
left: 'OFFLINE',
|
|
991
|
-
right: 'GATEWAY: UNAVAILABLE',
|
|
992
|
-
}),
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
return prev; // Stale attempt, don't update
|
|
996
|
-
},
|
|
997
|
-
};
|
|
998
1044
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
events.push({
|
|
1047
|
+
id: `err-metrics-${Date.now()}`,
|
|
1048
|
+
ts: Date.now(),
|
|
1049
|
+
tag: 'ERR',
|
|
1050
|
+
level: 'ERROR',
|
|
1051
|
+
msg: `✗ /metrics failed: ${error.message || 'Unknown error'}`,
|
|
1052
|
+
});
|
|
1005
1053
|
}
|
|
1054
|
+
// Summary
|
|
1055
|
+
events.push({
|
|
1056
|
+
id: `sys-verify-done-${Date.now()}`,
|
|
1057
|
+
ts: Date.now(),
|
|
1058
|
+
tag: 'SYS',
|
|
1059
|
+
level: 'INFO',
|
|
1060
|
+
msg: 'Verification complete. Connection is working.',
|
|
1061
|
+
});
|
|
1062
|
+
return { events };
|
|
1006
1063
|
}
|
|
1007
1064
|
/**
|
|
1008
1065
|
* 5️⃣ start <agentId>
|